Edit: this is unnecessary for what I need (see post 13).
Original post:
After some trial and error, I think I managed to do it. The more robust version should include additional checks to verify things like checking that the correct peer made an rpc call, and maybe even encrypting the message on the unencrypted connection (when the certificate is being sent). However, for simplicity, I’ll post just the basic implementation. I decided to make two AutoLoads, for the two connections. They are called MultiplayerAutoLoad1 and MultiplayerAutoLoad2. If a player is acting as a server, then peers in both of those AutoLoads will be servers, and if it is a client, then both will be clients connecting to corresponding servers. Connections on MultiplayerAutoLoad1 use dtls, while connections on the other don’t. Clients would first connect to the second (MultiplayerAutoLoad2) server, where the connection doesn’t use dtls.
Let’s name servers server1 and server2, and clients client1 and client2. A player will either have both server1 and server2, or both client1 and client2. Here is the sequence when a client tries to connect to the server:
- Client2 tries to connect to server2 (no dtls).
- When client2 receives connected_to_server signal, it requests the certificate for server1.
- Server2 gets that request, and sends the certificate to client2.
- When client2 receives the certificate, it sends it to MultiplayerAutoLoad1 and tells it to start client1.
- Client1 tries to connect to server1 (using dtls).
- When client1 receives connected_to_server signal, it tells client2 to disconnect from server2.
The only connection left is between server1 and client1, which uses dtls. Here is the code:
MultiplayerAutoLoad1
extends Node
var crypto := Crypto.new()
var key := CryptoKey.new()
var cert := X509Certificate.new()
var peer1 := NetworkedMultiplayerENet.new()
var ip := "127.0.0.1"
var port1 := 9876
var max_players := 10
func _ready():
# Connecting signals; err can be used to verify that everything is connected correctly.
var err := get_tree().connect("network_peer_connected", self, "playerConnected1")
err += get_tree().connect("network_peer_disconnected", self, "playerDisconnected1")
err += get_tree().connect("connected_to_server", self, "connectedOk1")
err += get_tree().connect("connection_failed", self, "connectedFail1")
err += get_tree().connect("server_disconnected", self, "serverDisconnected1")
print(err)
func createServer():
key = crypto.generate_rsa(4096)
cert = crypto.generate_self_signed_certificate(key)
peer1.dtls_verify = false
peer1.use_dtls = true
peer1.set_dtls_key(key)
peer1.set_dtls_certificate(cert)
var err := peer1.create_server(port1, max_players)
print(err)
get_tree().set_network_peer(peer1)
func connectToServer1(server_cert: X509Certificate):
peer1.dtls_verify = false
peer1.use_dtls = true
peer1.set_dtls_certificate(server_cert)
var err := peer1.create_client(ip, port1)
print(err)
get_tree().set_network_peer(peer1)
func getCertificate() -> X509Certificate:
return cert
func playerConnected1(id: int):
print("Client %d connected to server 1." % id)
func playerDisconnected1(id: int):
print("Client %d disconnected from server 1." % id)
func connectedOk1():
print("Connected to server 1.")
MultiplayerAutoLoad2.disconnectFromServer2()
func connectedFail1():
print("Failed to connect to server 1.")
func serverDisconnected1():
print("Server 1 disconnected.")
And MultiplayerAutoLoad2
extends Node
var peer2 := NetworkedMultiplayerENet.new()
var ip := "127.0.0.1"
var port2 := 9877
var max_players := 10
var is_server := false
func _ready():
# Creating new MultiplayerAPI to use for another connection
custom_multiplayer = MultiplayerAPI.new()
custom_multiplayer.set_root_node(self)
var err := custom_multiplayer.connect("network_peer_connected", self, "playerConnected2")
err += custom_multiplayer.connect("network_peer_disconnected", self, "playerDisconnected2")
err += custom_multiplayer.connect("connected_to_server", self, "connectedOk2")
err += custom_multiplayer.connect("connection_failed", self, "connectedFail2")
err += custom_multiplayer.connect("server_disconnected", self, "serverDisconnected2")
print(err)
# This is necessary; otherwise, custom multiplayer won't work.
func _process(_delta):
if custom_multiplayer != null && custom_multiplayer.has_network_peer():
custom_multiplayer.poll()
func createServer():
var err := peer2.create_server(port2, max_players)
print(err)
custom_multiplayer.network_peer = peer2
is_server = true
func connectToServer2():
var err := peer2.create_client(ip, port2)
print(err)
custom_multiplayer.network_peer = peer2
is_server = false
func disconnectFromServer2():
if (!is_server):
custom_multiplayer.network_peer = null
remote func requestCertificate():
rpc_id(custom_multiplayer.get_rpc_sender_id(), "server1Certificate", var2bytes(MultiplayerAutoLoad1.getCertificate(), true))
remote func server1Certificate(cert_bytes: PoolByteArray):
if (custom_multiplayer.get_rpc_sender_id() != 1):
return
var server_cert: X509Certificate = bytes2var(cert_bytes, true) as X509Certificate
if (server_cert != null):
print("Certificate received.")
MultiplayerAutoLoad1.connectToServer1(server_cert)
func playerConnected2(id: int):
print("Client %d connected to server 2." % id)
func playerDisconnected2(id: int):
print("Client %d disconnected from server 2." % id)
func connectedOk2():
print("Connected to server 2.")
rpc_id(1, "requestCertificate")
func connectedFail2():
print("Failed to connect to server 2.")
func serverDisconnected2():
print("Server 2 disconnected.")
Thank you to pennyloafers for helping me decide what approach to take.