private static IEnumerator LoadFromCache(ZNetPeer peer, string id) { var loadTask = Task.Run <BetterContinentsSettings?>(() => { var package = WorldCache.LoadCacheItem(id); // Recalculate the id again to confirm it really matches string localId = WorldCache.PackageID(package); if (id != localId) { return(null); } return(BetterContinentsSettings.Load(package)); }); try { UI.Add("LoadingFromCache", () => UI.DisplayMessage($"Better Continents: initializing from cached config")); yield return(new WaitUntil(() => loadTask.IsCompleted)); } finally { UI.Remove("LoadingFromCache"); } if (loadTask.IsFaulted || loadTask.Result == null) { LastConnectionError = loadTask.Exception != null ? $"Better Continents: cached world settings failed to load ({loadTask.Exception.Message}), please reconnect to download them again!" : $"Better Continents: cached world settings are corrupted, please reconnect to download them again!"; LogError(LastConnectionError); m_connectionStatus.SetValue(null, ZNet.ConnectionStatus.ErrorConnectFailed); ZNet.instance.Disconnect(peer); WorldCache.DeleteCacheItem(id); yield break; } Settings = loadTask.Result.Value; Settings.Dump(); // We only care about server/client version match when the server sends a world that actually uses the mod if (Settings.EnabledForThisWorld && ServerVersion != ModInfo.Version) { LastConnectionError = $"Better Continents: world has the mod enabled, but server {ServerVersion} and client {ModInfo.Version} versions don't match"; LogError(LastConnectionError); m_connectionStatus.SetValue(null, ZNet.ConnectionStatus.ErrorConnectFailed); ZNet.instance.Disconnect(peer); } else if (!Settings.EnabledForThisWorld) { Log($"Server world does not have Better Continents enabled, skipping version check"); } peer.m_rpc.Invoke("BetterContinentsReady", 0); }
private static IEnumerator SendSettings(ZNet instance, ZRpc rpc, ZPackage pkg) { byte[] ArraySlice(byte[] source, int offset, int length) { var target = new byte[length]; Buffer.BlockCopy(source, offset, target, 0, length); return(target); } var peer = instance.GetPeers().First(p => p.m_rpc == rpc); if (!Settings.EnabledForThisWorld) { Log($"Skipping sending settings to {PeerName(peer)}, as Better Continents is not enabled in this world"); } else { Log($"World is using Better Continents, so client version must match server version {ModInfo.Name}"); if (!ClientInfo.TryGetValue(peer.m_uid, out var bcClientInfo)) { Log($"Client info for {PeerName(peer)} not found, client has an old version of Better Continents, or none!"); rpc.Invoke("Error", ZNet.ConnectionStatus.ErrorConnectFailed); ZNet.instance.Disconnect(peer); yield break; } else if (bcClientInfo.version != ModInfo.Version) { Log($"Client {PeerName(peer)} version {bcClientInfo.version} doesn't match server version {ModInfo.Version}"); peer.m_rpc.Invoke("Error", 69); ZNet.instance.Disconnect(peer); yield break; } else { Log($"Client {PeerName(peer)} version {bcClientInfo.version} matches server version {ModInfo.Version}"); } // This was the initial way that versioning was implemented, before the client->server way, so may // as well leave it in Log($"Sending server version {ModInfo.Version} to client for bi-lateral version agreement"); rpc.Invoke("BetterContinentsVersion", ModInfo.Version); var settingsPackage = new ZPackage(); var cleanSettings = Settings.Clean(); cleanSettings.Serialize(settingsPackage); if (WorldCache.CacheItemExists(settingsPackage, bcClientInfo.worldCache)) { // We send hash and id string cacheId = WorldCache.PackageID(settingsPackage); Log($"Client {PeerName(peer)} already has cached settings for world, instructing it to load those (id {cacheId})"); rpc.Invoke("BetterContinentsConfigLoadFromCache", cacheId); } else { Log($"Client {PeerName(peer)} doesn't have cached settings, sending them now"); cleanSettings.Dump(); var settingsData = settingsPackage.GetArray(); Log($"Sending settings package header for {settingsData.Length} byte stream"); rpc.Invoke("BetterContinentsConfigStart", settingsData.Length, GetHashCode(settingsData)); const int SendChunkSize = 256 * 1024; for (int sentBytes = 0; sentBytes < settingsData.Length;) { int packetSize = Mathf.Min(settingsData.Length - sentBytes, SendChunkSize); var packet = ArraySlice(settingsData, sentBytes, packetSize); rpc.Invoke("BetterContinentsConfigPacket", sentBytes, GetHashCode(packet), new ZPackage(packet)); // Make sure to flush or we will saturate the queue... rpc.GetSocket().Flush(); sentBytes += packetSize; Log($"Sent {sentBytes} of {settingsData.Length} bytes"); float timeout = Time.time + 30; yield return(new WaitUntil(() => rpc.GetSocket().GetSendQueueSize() < SendChunkSize || Time.time > timeout)); if (Time.time > timeout) { Log($"Timed out sending config to client {PeerName(peer)} after 30 seconds, disconnecting them"); peer.m_rpc.Invoke("Error", ZNet.ConnectionStatus.ErrorConnectFailed); ZNet.instance.Disconnect(peer); yield break; } } } yield return(new WaitUntil(() => ClientInfo[peer.m_uid].readyForPeerInfo || !peer.m_socket.IsConnected())); } RPC_PeerInfo(instance, rpc, pkg); }