Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions include/game_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ Created By:
#ifndef __QMM2_GAME_API_H__
#define __QMM2_GAME_API_H__

#include <cstddef>
#include <cstdint>
#include <vector>
#include <string>
#include <stdarg.h>
#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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions include/osdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand All @@ -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__

Expand Down
1 change: 1 addition & 0 deletions include/qmmapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions include/qvm.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ Created By:
#ifndef __QMM2_QVM_H__
#define __QMM2_QVM_H__

#include <cstddef>
#include <cstdint>
#include <vector>

// magic number is stored in file as 44 14 72 12
#define QVM_MAGIC 0x12721444

#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,
Expand Down Expand Up @@ -112,12 +112,12 @@ typedef struct {
size_t filesize; // .qvm file size

// memory
std::vector<std::byte> memory; // main block of memory
std::vector<uint8_t> 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
Expand All @@ -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<std::byte>& filemem, vmsyscall_t vmsyscall, unsigned int stacksize, bool verify_data);
bool qvm_load(qvm_t& qvm, const std::vector<uint8_t>& 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);

Expand Down
2 changes: 1 addition & 1 deletion src/game_jk2mp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/game_jk2sp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ Created By:

*/

#include <cstddef> // std::byte (jk2sp headers are still weird with system headers, so include this at the top)
#include <string.h>
#include <jk2sp/game/q_shared.h>
#include <jk2sp/game/g_public.h>
Expand Down
2 changes: 1 addition & 1 deletion src/game_q3a.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/game_sof2mp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/game_stvoyhm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/mod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::byte> filemem;
std::vector<uint8_t> filemem;
int stacksize;
bool verify_data;
bool loaded;
Expand Down
19 changes: 17 additions & 2 deletions src/osdef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ Created By:
#include <string.h>
#include "util.h"

#ifdef _WIN32

#ifdef _WIN32
// return error string for GetLastError()
const char* dlerror() {
static std::string str;
char* buf = nullptr;
Expand All @@ -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;

Expand Down
32 changes: 21 additions & 11 deletions src/qvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ Created By:

*/

#include <cstdint>
#include <string.h>
#include <stdlib.h>
#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<std::byte>& filemem, vmsyscall_t vmsyscall, unsigned int stacksize, bool verify_data) {

bool qvm_load(qvm_t& qvm, const std::vector<uint8_t>& 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;
Expand Down Expand Up @@ -111,9 +115,7 @@ bool qvm_load(qvm_t& qvm, const std::vector<std::byte>& 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:
Expand Down Expand Up @@ -147,10 +149,12 @@ bool qvm_load(qvm_t& qvm, const std::vector<std::byte>& 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;
Expand Down Expand Up @@ -194,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;
}
Expand Down Expand Up @@ -223,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:
Expand Down Expand Up @@ -409,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(>= );
Expand All @@ -422,12 +430,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;
}
Expand Down Expand Up @@ -460,7 +468,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;
Expand Down Expand Up @@ -494,8 +502,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)
Expand Down Expand Up @@ -680,6 +688,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",
Expand Down Expand Up @@ -744,6 +753,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;
Expand Down