Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Compact Serializer and Paginated Plugin Metadata #38

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions LobbyCompatibility/Features/LobbyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ internal static class LobbyHelper
/// </summary>
/// <param name="lobby"> The lobby to get the diff from. </param>
/// <returns> The <see cref="LobbyDiff" /> from the <see cref="Lobby" />. </returns>
public static LobbyDiff GetLobbyDiff(Lobby lobby)
public static LobbyDiff GetLobbyDiff(Lobby lobby) => GetLobbyDiff(GetLobbyPluginPages(lobby));

/// <summary>
/// Get a <see cref="LobbyDiff" /> from a <see cref="IEnumerable{String}" />.
/// </summary>
/// <param name="lobbyPluginStrings"> The list of strings to parse. </param>
/// <returns> The <see cref="LobbyDiff" /> from the <see cref="IEnumerable{String}"/>. </returns>
internal static LobbyDiff GetLobbyDiff(IEnumerable<string> lobbyPluginStrings)
{
var lobbyPlugins = PluginHelper
.ParseLobbyPluginsMetadata(lobby.GetData(LobbyMetadata.Plugins)).ToList();
.ParseLobbyPluginsMetadata(lobbyPluginStrings).ToList();
_clientPlugins ??= PluginHelper.GetAllPluginInfo().ToList();

var pluginDiffs = new List<PluginDiff>();
Expand Down Expand Up @@ -106,4 +113,20 @@ public static string GetCompatibilityHeader(PluginDiffResult pluginDiffResult)
_ => "Unknown"
};
}

/// <summary>
/// Get the plugin pages in <see cref="IEnumerable{String}" /> from a <see cref="Lobby" />.
/// </summary>
/// <param name="lobby"> The <see cref="Lobby" /> to get the list from. </param>
/// <returns> The <see cref="IEnumerable{String}" /> from the <see cref="Lobby" />. </returns>
internal static IEnumerable<string> GetLobbyPluginPages(Lobby lobby)
{
string[] lobbyPluginStrings = [];
var i = 0;

do lobbyPluginStrings[i] = lobby.GetData($"{LobbyMetadata.Plugins}{i}");
while (lobbyPluginStrings[i++].Contains("@"));

return lobbyPluginStrings;
}
}
9 changes: 5 additions & 4 deletions LobbyCompatibility/Features/PluginHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using LobbyCompatibility.Attributes;
using LobbyCompatibility.Enums;
using LobbyCompatibility.Models;
using LobbyCompatibility.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

Expand Down Expand Up @@ -72,21 +73,21 @@ internal static IEnumerable<PluginInfoRecord> GetAllPluginInfo()
/// Creates a json string containing the metadata of all plugins, to add to the lobby.
/// </summary>
/// <returns> A json string containing the metadata of all plugins. </returns>
public static string GetLobbyPluginsMetadata()
public static string[] GetLobbyPluginsMetadata()
{
return JsonConvert.SerializeObject(GetAllPluginInfo().ToList(), new VersionConverter());
return LobbyCompatibilitySerializer.Serialize(GetAllPluginInfo());
}

/// <summary>
/// Parses a json string containing the metadata of all plugins.
/// </summary>
/// <param name="json"> The json string to parse. </param>
/// <returns> A list of plugins in the APIPluginInfo format. </returns>
internal static IEnumerable<PluginInfoRecord> ParseLobbyPluginsMetadata(string json)
internal static IEnumerable<PluginInfoRecord> ParseLobbyPluginsMetadata(IEnumerable<string> json)
{
try
{
return JsonConvert.DeserializeObject<List<PluginInfoRecord>>(json, new VersionConverter()) ??
return LobbyCompatibilitySerializer.Deserialize(json) ??
new List<PluginInfoRecord>();
}
catch (Exception e)
Expand Down
11 changes: 6 additions & 5 deletions LobbyCompatibility/Patches/LobbyDataIsJoinablePostfix.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using HarmonyLib;
using LobbyCompatibility.Enums;
using LobbyCompatibility.Features;
Expand Down Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
125 changes: 125 additions & 0 deletions LobbyCompatibility/Serialization/LobbyCompatibilitySerializer.cs
Original file line number Diff line number Diff line change
@@ -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<PluginInfoRecord> pluginInfoRecordList)
{
List<string> allSerializedPlugins = [];
List<string> 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<string> 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<PluginInfoRecord> Deserialize(IEnumerable<string> 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
};
}
}
Loading