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) {