Version Handshaking (without messing with Game's version) - Valheim-Modding/Wiki GitHub Wiki

This will be primarily for those that have server side mod implementation. The code below will preform a version handshake between your mod and the client's installed mods. This will prevent people from joining a server for which your mod lives server side. The client will be disconnected when they have not sent the correct RPC call for your mod, join with a different version than what's installed on the server, or have some sort of error in connection.

First, we need to register the RPC and check the version

[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
    public static class RegisterAndCheckVersion
    {
        private static void Prefix(ZNetPeer peer, ref ZNet __instance)
        {

            // Register version check call
            ZLog.Log("YOURMOD - Registering version RPC handler");
            peer.m_rpc.Register<ZPackage>("YOURMOD_Version", new Action<ZRpc, ZPackage>(RpcHandlers.RPC_YOURMOD_Version));

            // Make calls to check versions
            ZLog.Log("YOURMOD - Invoking version check");
            ZPackage zpackage = new ZPackage();
            zpackage.Write(YOURPLUGIN.version);
            peer.m_rpc.Invoke("YOURMOD_Version", (object)zpackage);
        }
    }

Next, we need to check to see if the client sent the RPC to the server

    [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
    public static class VerifyClient
    {
        private static bool Prefix(ZRpc rpc, ZPackage pkg, ref ZNet __instance)
        {
            if (__instance.IsServer() && !RpcHandlers._validatedPeers.Contains(rpc))
            {
                // Disconnect peer if they didn't send mod version at all
                ZLog.LogWarning("Peer never sent version, disconnecting");
                rpc.Invoke("Error", (object)3);
                return false; // Prevent calling underlying method
            }

            return true;
        }
    }

Create logging and remove peer from validated list if disconnected

    [HarmonyPatch(typeof(ZNet), "Disconnect")]
    public static class RemoveDisconnectedPeerFromVerified
    {
        private static void Prefix(ZNetPeer peer, ref ZNet __instance)
        {
            if (__instance.IsServer())
            {
                // Remove peer from validated list
                ZLog.Log("Peer disconnected, removing from validated list");
                RpcHandlers._validatedPeers.Remove(peer.m_rpc);
            }
        }
    }

RPC Handler to add/remove peer from validated list and read pkg

public static class RpcHandlers
    {
        public static List<ZRpc> _validatedPeers = new List<ZRpc>();

        public static void RPC_YOURMOD_Version(ZRpc rpc, ZPackage pkg)
        {
            var version = pkg.ReadString();
            ZLog.Log("YOURMOD - Version check, server: " + version + ",  mine: " + YOURPLUGIN.version);
            if (version != YOURPLUGIN.version)
            {

                if (ZNet.instance.IsServer())
                {
                    // Different versions - force disconnect client from server
                    ZLog.LogWarning("YOURMOD - Peer has incompatible version, disconnecting");
                    rpc.Invoke("Error", (object)3);
                }
            }
            else
            {
                if (!ZNet.instance.IsServer())
                {
                    // Enable mod on client if versions match
                    ZLog.Log("YOURMOD - Recieved same version from server!");
                }
                else
                {
                    // Add client to validated list
                    ZLog.Log("YOURMOD - Adding peer to validated list");
                    _validatedPeers.Add(rpc);
                }
            }
        }
    }

Final product of version handshake should look something like this

using HarmonyLib;
using System;
using System.Collections.Generic;

namespace YOURMOD.NAMESPACE
{

    [HarmonyPatch(typeof(ZNet), "Awake")]
    public static class EnableOnDedicatedServer
    {
        private static void Prefix(ref ZNet __instance)
        {
            // Enable on dedicated servers
            if (__instance.IsDedicated())
            {
                ZLog.Log("YOURMOD - Enabling for dedicated server");
            }
        }
    }

    [HarmonyPatch(typeof(ZNet), "OnNewConnection")]
    public static class RegisterAndCheckVersion
    {
        private static void Prefix(ZNetPeer peer, ref ZNet __instance)
        {

            // Register version check call
            ZLog.Log("YOURMOD - Registering version RPC handler");
            peer.m_rpc.Register<ZPackage>("YOURMOD_Version", new Action<ZRpc, ZPackage>(RpcHandlers.RPC_YOURMOD_Version));

            // Make calls to check versions
            ZLog.Log("YOURMOD - Invoking version check");
            ZPackage zpackage = new ZPackage();
            zpackage.Write(YOURPLUGIN.version);
            peer.m_rpc.Invoke("YOURMOD_Version", (object)zpackage);
        }
    }

    [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
    public static class VerifyClient
    {
        private static bool Prefix(ZRpc rpc, ZPackage pkg, ref ZNet __instance)
        {
            if (__instance.IsServer() && !RpcHandlers._validatedPeers.Contains(rpc))
            {
                // Disconnect peer if they didn't send mod version at all
                ZLog.LogWarning("YOURMOD - Peer never sent version, disconnecting");
                rpc.Invoke("Error", (object)3);
                return false; // Prevent calling underlying method
            }

            return true;
        }
    }

    [HarmonyPatch(typeof(ZNet), "Disconnect")]
    public static class RemoveDisconnectedPeerFromVerified
    {
        private static void Prefix(ZNetPeer peer, ref ZNet __instance)
        {
            if (__instance.IsServer())
            {
                // Remove peer from validated list
                ZLog.Log("YOURMOD - Peer disconnected, removing from validated list");
                RpcHandlers._validatedPeers.Remove(peer.m_rpc);
            }
        }
    }

    public static class RpcHandlers
    {
        public static List<ZRpc> _validatedPeers = new List<ZRpc>();

        public static void RPC_YOURMOD_Version(ZRpc rpc, ZPackage pkg)
        {
            var version = pkg.ReadString();
            ZLog.Log("Better Wards - Version check, server: " + version + ",  mine: " + YOURPLUGIN.version);
            if (version != YOURPLUGIN.version)
            {

                if (ZNet.instance.IsServer())
                {
                    // Different versions - force disconnect client from server
                    ZLog.LogWarning("YOURMOD - Peer has incompatible version, disconnecting");
                    rpc.Invoke("Error", (object)3);
                }
            }
            else
            {
                if (!ZNet.instance.IsServer())
                {
                    // Enable mod on client if versions match
                    ZLog.Log("YOURMOD - Recieved same version from server!");
                }
                else
                {
                    // Add client to validated list
                    ZLog.Log("YOURMOD - Adding peer to validated list");
                    _validatedPeers.Add(rpc);
                }
            }
        }
    }
}
⚠️ **GitHub.com Fallback** ⚠️