diff --git a/LobbyCompatibility/Features/LobbyHelper.cs b/LobbyCompatibility/Features/LobbyHelper.cs index f4852e7..24017a9 100644 --- a/LobbyCompatibility/Features/LobbyHelper.cs +++ b/LobbyCompatibility/Features/LobbyHelper.cs @@ -20,10 +20,17 @@ internal static class LobbyHelper /// /// The lobby to get the diff from. /// The from the . - public static LobbyDiff GetLobbyDiff(Lobby lobby) + public static LobbyDiff GetLobbyDiff(Lobby lobby) => GetLobbyDiff(GetLobbyPluginPages(lobby)); + + /// + /// Get a from a . + /// + /// The list of strings to parse. + /// The from the . + internal static LobbyDiff GetLobbyDiff(IEnumerable lobbyPluginStrings) { var lobbyPlugins = PluginHelper - .ParseLobbyPluginsMetadata(lobby.GetData(LobbyMetadata.Plugins)).ToList(); + .ParseLobbyPluginsMetadata(lobbyPluginStrings).ToList(); _clientPlugins ??= PluginHelper.GetAllPluginInfo().ToList(); var pluginDiffs = new List(); @@ -106,4 +113,20 @@ public static string GetCompatibilityHeader(PluginDiffResult pluginDiffResult) _ => "Unknown" }; } + + /// + /// Get the plugin pages in from a . + /// + /// The to get the list from. + /// The from the . + internal static IEnumerable GetLobbyPluginPages(Lobby lobby) + { + string[] lobbyPluginStrings = []; + var i = 0; + + do lobbyPluginStrings[i] = lobby.GetData($"{LobbyMetadata.Plugins}{i}"); + while (lobbyPluginStrings[i++].Contains("@")); + + return lobbyPluginStrings; + } } \ No newline at end of file diff --git a/LobbyCompatibility/Features/PluginHelper.cs b/LobbyCompatibility/Features/PluginHelper.cs index 478dbde..2784047 100644 --- a/LobbyCompatibility/Features/PluginHelper.cs +++ b/LobbyCompatibility/Features/PluginHelper.cs @@ -6,6 +6,7 @@ using LobbyCompatibility.Attributes; using LobbyCompatibility.Enums; using LobbyCompatibility.Models; +using LobbyCompatibility.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -72,9 +73,9 @@ internal static IEnumerable GetAllPluginInfo() /// Creates a json string containing the metadata of all plugins, to add to the lobby. /// /// A json string containing the metadata of all plugins. - public static string GetLobbyPluginsMetadata() + public static string[] GetLobbyPluginsMetadata() { - return JsonConvert.SerializeObject(GetAllPluginInfo().ToList(), new VersionConverter()); + return LobbyCompatibilitySerializer.Serialize(GetAllPluginInfo()); } /// @@ -82,11 +83,11 @@ public static string GetLobbyPluginsMetadata() /// /// The json string to parse. /// A list of plugins in the APIPluginInfo format. - internal static IEnumerable ParseLobbyPluginsMetadata(string json) + internal static IEnumerable ParseLobbyPluginsMetadata(IEnumerable json) { try { - return JsonConvert.DeserializeObject>(json, new VersionConverter()) ?? + return LobbyCompatibilitySerializer.Deserialize(json) ?? new List(); } catch (Exception e) diff --git a/LobbyCompatibility/Patches/LobbyDataIsJoinablePostfix.cs b/LobbyCompatibility/Patches/LobbyDataIsJoinablePostfix.cs index 4dbe51d..0a245d1 100644 --- a/LobbyCompatibility/Patches/LobbyDataIsJoinablePostfix.cs +++ b/LobbyCompatibility/Patches/LobbyDataIsJoinablePostfix.cs @@ -1,3 +1,4 @@ +using System.Linq; using HarmonyLib; using LobbyCompatibility.Enums; using LobbyCompatibility.Features; @@ -34,20 +35,20 @@ private static bool Postfix(bool isJoinable, ref Lobby lobby) return PluginHelper.CanJoinVanillaLobbies() && isJoinable; } - var lobbyPluginString = lobby.GetData(LobbyMetadata.Plugins); - + var lobbyPluginStrings = LobbyHelper.GetLobbyPluginPages(lobby).ToArray(); + // Create lobby diff so LatestLobbyDiff is set - LobbyHelper.GetLobbyDiff(lobby); + LobbyHelper.GetLobbyDiff(lobbyPluginStrings); // If the lobby does not have any plugin information, return original result (since we can't check anything) - if (string.IsNullOrEmpty(lobbyPluginString)) + if (lobbyPluginStrings.Length == 0 || string.IsNullOrEmpty(lobbyPluginStrings[0])) { LobbyCompatibilityPlugin.Logger?.LogWarning("Lobby is modded but does not have any plugin information."); return isJoinable; } var matchesPluginRequirements = - PluginHelper.MatchesTargetRequirements(PluginHelper.ParseLobbyPluginsMetadata(lobbyPluginString)); + PluginHelper.MatchesTargetRequirements(PluginHelper.ParseLobbyPluginsMetadata(lobbyPluginStrings)); if (!matchesPluginRequirements) { diff --git a/LobbyCompatibility/Patches/SteamMatchmakingOnLobbyCreatedPostfix.cs b/LobbyCompatibility/Patches/SteamMatchmakingOnLobbyCreatedPostfix.cs index 075a9d7..e1d6f23 100644 --- a/LobbyCompatibility/Patches/SteamMatchmakingOnLobbyCreatedPostfix.cs +++ b/LobbyCompatibility/Patches/SteamMatchmakingOnLobbyCreatedPostfix.cs @@ -29,8 +29,11 @@ private static void Postfix(Result result, ref Lobby lobby) // Modded is flagged as true, since we're using mods lobby.SetData(LobbyMetadata.Modded, "true"); - // Add plugin metadata to the lobby so clients can check if they have the required plugins - lobby.SetData(LobbyMetadata.Plugins, PluginHelper.GetLobbyPluginsMetadata()); + // Add paginated plugin metadata to the lobby so clients can check if they have the required plugins + var plugins = PluginHelper.GetLobbyPluginsMetadata(); + // Add each page with delimiter if there's another page + for (var i = 0; i < plugins.Length; i++) + lobby.SetData($"{LobbyMetadata.Plugins}{i}", $"{plugins[i]}{(i < plugins.Length - 1 ? "@" : "")}"); // Set the joinable modded metadata to the same value as the original joinable metadata, in case it wasn't originally joinable lobby.SetData(LobbyMetadata.JoinableModded, lobby.GetData(LobbyMetadata.Joinable)); diff --git a/LobbyCompatibility/Serialization/LobbyCompatibilitySerializer.cs b/LobbyCompatibility/Serialization/LobbyCompatibilitySerializer.cs new file mode 100644 index 0000000..0ea0457 --- /dev/null +++ b/LobbyCompatibility/Serialization/LobbyCompatibilitySerializer.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; +using LobbyCompatibility.Enums; +using LobbyCompatibility.Models; + +namespace LobbyCompatibility.Serialization; + +public static class LobbyCompatibilitySerializer +{ + private const char PluginSeparator = '&'; + private const char FieldSeparator = ';'; + + public static string[] Serialize(IEnumerable pluginInfoRecordList) + { + List allSerializedPlugins = []; + List separatedConcatStrings = []; + + allSerializedPlugins.AddRange(pluginInfoRecordList.Select(Serialize)); + + var tempString = ""; + + foreach (var plugin in allSerializedPlugins) + { + if (tempString.Length + plugin.Length + 1 < 8192) + tempString += $"{(tempString.Length == 0 ? PluginSeparator : "")}{plugin}"; + else + { + separatedConcatStrings.Add(tempString); + tempString = ""; + } + } + + return separatedConcatStrings.ToArray(); + } + + private static string Serialize(PluginInfoRecord pluginInfoRecord) + { + List serializedPluginInfo = + [ + Serialize(pluginInfoRecord.GUID), + Serialize(pluginInfoRecord.Version), + Serialize(pluginInfoRecord.CompatibilityLevel), + Serialize(pluginInfoRecord.VersionStrictness) + ]; + + return serializedPluginInfo.Join(delimiter: FieldSeparator.ToString()); + } + + private static string Serialize(string pluginGuid) => pluginGuid; + + private static string Serialize(Version version) => version.ToString(); + + private static string Serialize(CompatibilityLevel? compatibilityLevel) + { + return compatibilityLevel switch + { + CompatibilityLevel.ClientOnly => "c", + CompatibilityLevel.ServerOnly => "s", + CompatibilityLevel.Everyone => "e", + CompatibilityLevel.ClientOptional => "o", + _ => "" + }; + } + + private static string Serialize(VersionStrictness? versionStrictness) + { + return versionStrictness switch + { + VersionStrictness.None => "o", + VersionStrictness.Major => "m", + VersionStrictness.Minor => "n", + VersionStrictness.Patch => "p", + _ => "" + }; + } + + public static IEnumerable Deserialize(IEnumerable paginatedSerializedPlugins) + { + var serializedPluginList = paginatedSerializedPlugins.Join(delimiter: PluginSeparator.ToString()).Split(PluginSeparator); + + return serializedPluginList.Select(DeserializePluginInfoRecord); + } + + private static PluginInfoRecord DeserializePluginInfoRecord(string pluginInfoRecord) + { + var splitRecord = pluginInfoRecord.Split(FieldSeparator); + + return new PluginInfoRecord( + DeserializeGuid(splitRecord[0]), + DeserializeVersion(splitRecord[1]), + DeserializeCompatibilityLevel(splitRecord[2]), + DeserializeVersionStrictness(splitRecord[3]) + ); + } + + private static string DeserializeGuid(string pluginGuid) => pluginGuid; + + private static Version DeserializeVersion(string version) => Version.Parse(version); + + private static CompatibilityLevel? DeserializeCompatibilityLevel(string compatibilityLevel) + { + return compatibilityLevel switch + { + "c" => CompatibilityLevel.ClientOnly, + "s" => CompatibilityLevel.ServerOnly, + "e" => CompatibilityLevel.Everyone, + "o" => CompatibilityLevel.ClientOptional, + _ => null + }; + } + + private static VersionStrictness? DeserializeVersionStrictness(string versionStrictness) + { + return versionStrictness switch + { + "o" => VersionStrictness.None, + "m" => VersionStrictness.Major, + "n" => VersionStrictness.Minor, + "p" => VersionStrictness.Patch, + _ => null + }; + } +} \ No newline at end of file