diff --git a/src/common/IPC/Common.h b/src/common/IPC/Common.h
index 14f0e186a2..5d1f44b82d 100644
--- a/src/common/IPC/Common.h
+++ b/src/common/IPC/Common.h
@@ -70,6 +70,19 @@ namespace IPC {
void Close() const;
};
+ // Version of the protocol for detecting what ABI version the VM has upon startup
+ constexpr uint32_t ABI_VERSION_DETECTION_ABI_VERSION = 4;
+
+ // Version for the syscall signatures between engine and VM. This must change if the syscall
+ // IDs, argument types, or return types change, or if serialization procedures change.
+ // Follows Daemon major versions.
+ // This should be updated only by update-version-number.py when a "major" release is indicated
+ constexpr const char* SYSCALL_ABI_VERSION = "0.54.0";
+
+ // This should be manually set to true when starting a 'for-X.Y.Z/sync' branch.
+ // This should be set to false by update-version-number.py when a (major) release is created.
+ constexpr bool DAEMON_HAS_COMPATIBILITY_BREAKING_SYSCALL_CHANGES = true;
+
/*
* The messages sent between the VM and the engine are defined by a numerical
* ID used for dispatch, a list of input types and an optional list of return
diff --git a/src/engine/client/cg_api.h b/src/engine/client/cg_api.h
index 58d371dc24..c51ab0080b 100644
--- a/src/engine/client/cg_api.h
+++ b/src/engine/client/cg_api.h
@@ -34,8 +34,6 @@ along with this program. If not, see .
#include "shared/bg_public.h"
#endif
-#define CGAME_API_VERSION 3
-
#define CMD_BACKUP 64
#define CMD_MASK ( CMD_BACKUP - 1 )
// allow a lot of command backups for very fast systems
diff --git a/src/engine/client/cl_cgame.cpp b/src/engine/client/cl_cgame.cpp
index d6156f079e..14db23500f 100644
--- a/src/engine/client/cl_cgame.cpp
+++ b/src/engine/client/cl_cgame.cpp
@@ -963,10 +963,7 @@ CGameVM::CGameVM(): VM::VMBase("cgame", Cvar::CHEAT), services(nullptr), cmdBuff
void CGameVM::Start()
{
services = std::unique_ptr(new VM::CommonVMServices(*this, "CGame", FS::Owner::CGAME, Cmd::CGAME_VM));
- uint32_t version = this->Create();
- if ( version != CGAME_API_VERSION ) {
- Sys::Drop( "CGame ABI mismatch, expected %d, got %d", CGAME_API_VERSION, version );
- }
+ this->Create();
this->CGameStaticInit();
}
diff --git a/src/engine/framework/VirtualMachine.cpp b/src/engine/framework/VirtualMachine.cpp
index 6e08dd3a30..6a2b5d9b80 100644
--- a/src/engine/framework/VirtualMachine.cpp
+++ b/src/engine/framework/VirtualMachine.cpp
@@ -377,7 +377,7 @@ static IPC::Socket CreateInProcessNativeVM(std::pair p
return std::move(pair.first);
}
-uint32_t VMBase::Create()
+void VMBase::Create()
{
type = static_cast(params.vmType.Get());
@@ -419,11 +419,36 @@ uint32_t VMBase::Create()
if (type != TYPE_NATIVE_DLL && !params.debug.Get())
rootChannel.SetRecvTimeout(std::chrono::seconds(2));
- // Read the ABI version from the root socket.
+ // Read the ABI version detection ABI version from the root socket.
// If this fails, we assume the remote process failed to start
Util::Reader reader = rootChannel.RecvMsg();
- Log::Notice("Loaded VM module in %d msec", Sys::Milliseconds() - loadStartTime);
- return reader.Read();
+
+ // VM version incompatibility detection...
+
+ uint32_t magic = reader.Read();
+ if (magic != IPC::ABI_VERSION_DETECTION_ABI_VERSION) {
+ Sys::Drop("Couldn't load the %s gamelogic module: it is built for %s version of Daemon engine",
+ this->name, magic > IPC::ABI_VERSION_DETECTION_ABI_VERSION ? "a newer" : "an older");
+ }
+
+ std::string vmABI = reader.Read();
+ if (vmABI != IPC::SYSCALL_ABI_VERSION) {
+ Sys::Drop("Couldn't load the %s gamelogic module: it uses ABI version %s but this Daemon engine uses %s",
+ vmABI, IPC::SYSCALL_ABI_VERSION);
+ }
+
+ bool vmCompatBreaking = reader.Read();
+ if (vmCompatBreaking && !IPC::DAEMON_HAS_COMPATIBILITY_BREAKING_SYSCALL_CHANGES) {
+ Sys::Drop("Couldn't load the %s gamelogic module: it has compatibility-breaking ABI changes but Daemon engine uses the vanilla %s ABI",
+ this->name, IPC::SYSCALL_ABI_VERSION);
+ } else if (!vmCompatBreaking && IPC::DAEMON_HAS_COMPATIBILITY_BREAKING_SYSCALL_CHANGES) {
+ Sys::Drop("Couldn't load the %s gamelogic module: Daemon has compatibility-breaking ABI changes but the VM uses the vanilla %s ABI",
+ this->name, IPC::SYSCALL_ABI_VERSION);
+ } else if (IPC::DAEMON_HAS_COMPATIBILITY_BREAKING_SYSCALL_CHANGES) {
+ Log::Notice("^6Using %s VM with unreleased ABI changes", this->name);
+ }
+
+ Log::Notice("Loaded %s VM module in %d msec", this->name, Sys::Milliseconds() - loadStartTime);
}
void VMBase::FreeInProcessVM() {
diff --git a/src/engine/framework/VirtualMachine.h b/src/engine/framework/VirtualMachine.h
index aff0f34f9e..723ae18fcb 100644
--- a/src/engine/framework/VirtualMachine.h
+++ b/src/engine/framework/VirtualMachine.h
@@ -113,9 +113,8 @@ class VMBase {
VMBase(std::string name, int vmTypeCvarFlags)
: processHandle(Sys::INVALID_HANDLE), name(name), type(TYPE_NACL), params(name, vmTypeCvarFlags) {}
- // Create the VM for the named module. Returns the ABI version reported
- // by the module. This will automatically free any existing VM.
- uint32_t Create();
+ // Create the VM for the named module. This will automatically free any existing VM.
+ void Create();
// Free the VM
void Free();
diff --git a/src/engine/server/sg_api.h b/src/engine/server/sg_api.h
index 1ddd6f2c16..071090ebe4 100644
--- a/src/engine/server/sg_api.h
+++ b/src/engine/server/sg_api.h
@@ -25,8 +25,6 @@ along with this program. If not, see .
#include "engine/qcommon/q_shared.h"
-#define GAME_API_VERSION 3
-
#define SVF_NOCLIENT 0x00000001
#define SVF_CLIENTMASK 0x00000002
#define SVF_VISDUMMY 0x00000004
diff --git a/src/engine/server/sv_sgame.cpp b/src/engine/server/sv_sgame.cpp
index 521716724c..9d9bd713cb 100644
--- a/src/engine/server/sv_sgame.cpp
+++ b/src/engine/server/sv_sgame.cpp
@@ -338,11 +338,7 @@ void GameVM::Start()
{
services = std::unique_ptr(new VM::CommonVMServices(*this, "SGame", FS::Owner::SGAME, Cmd::SGAME_VM));
- uint32_t version = this->Create();
- if ( version != GAME_API_VERSION ) {
- Sys::Drop( "SGame ABI mismatch, expected %d, got %d", GAME_API_VERSION, version );
- }
-
+ this->Create();
this->GameStaticInit();
}
diff --git a/src/shared/VMMain.cpp b/src/shared/VMMain.cpp
index eb7e9144f9..34d8b85957 100644
--- a/src/shared/VMMain.cpp
+++ b/src/shared/VMMain.cpp
@@ -51,9 +51,11 @@ static void CommonInit(Sys::OSHandle rootSocket)
{
VM::rootChannel = IPC::Channel(IPC::Socket::FromHandle(rootSocket));
- // Send syscall ABI version, also acts as a sign that the module loaded
+ // Send ABI version information, also acts as a sign that the module loaded
Util::Writer writer;
- writer.Write(VM::VM_API_VERSION);
+ writer.Write(IPC::ABI_VERSION_DETECTION_ABI_VERSION);
+ writer.Write(IPC::SYSCALL_ABI_VERSION);
+ writer.Write(IPC::DAEMON_HAS_COMPATIBILITY_BREAKING_SYSCALL_CHANGES);
VM::rootChannel.SendMsg(writer);
// Start the main loop
diff --git a/src/shared/VMMain.h b/src/shared/VMMain.h
index 54586dd0dc..554b93fc32 100644
--- a/src/shared/VMMain.h
+++ b/src/shared/VMMain.h
@@ -42,7 +42,6 @@ namespace VM {
void VMInit();
void VMHandleSyscall(uint32_t id, Util::Reader reader);
void GetNetcodeTables(NetcodeTable& playerStateTable, int& playerStateSize);
- extern int VM_API_VERSION;
// Send a message to the engine
template void SendMsg(Args&&... args) {