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

EliteAPI fix, NetworkCompatiblity fix and more #200

Merged
merged 11 commits into from
Aug 21, 2020
Merged
3 changes: 1 addition & 2 deletions R2API/EliteAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ private static void GetOriginalEliteCountHook(ILContext il) {
private static void AddEliteAction(List<EliteDef> eliteDefinitions) {
foreach (var customElite in EliteDefinitions) {
eliteDefinitions.Add(customElite.EliteDef);

var currentEliteTiers = GetCombatDirectorEliteTiers();
if (customElite.EliteTier == 1) {
var index = currentEliteTiers[1].eliteTypes.Length;
Expand All @@ -69,7 +68,7 @@ private static void AddEliteAction(List<EliteDef> eliteDefinitions) {
var eliteTierIndex = customElite.EliteTier + 1;
var eliteTypeIndex = currentEliteTiers[eliteTierIndex].eliteTypes.Length;
Array.Resize(ref currentEliteTiers[eliteTierIndex].eliteTypes, eliteTypeIndex + 1);
currentEliteTiers[1].eliteTypes[eliteTypeIndex] = customElite.EliteDef.eliteIndex;
currentEliteTiers[eliteTierIndex].eliteTypes[eliteTypeIndex] = customElite.EliteDef.eliteIndex;
}
OverrideCombatDirectorEliteTiers(currentEliteTiers);

Expand Down
29 changes: 28 additions & 1 deletion R2API/R2API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,36 @@ static GameNetworkManager.SimpleLocalizedKickReason SwapToStandardMessage(GameNe
{
reason.GetDisplayTokenAndFormatParams(out var token, out _);
return new GameNetworkManager.SimpleLocalizedKickReason(token,
"This information is not yet available, see below the list of all mods the server needs you to have : ",
"",
string.Join("\n", NetworkModCompatibilityHelper.networkModList));
}
c.Index++;
c.EmitDelegate<Func<GameNetworkManager.ModMismatchKickReason, GameNetworkManager.SimpleLocalizedKickReason>>(SwapToStandardMessage);
}
};

// Temporary fix for displaying correctly the mods that the user is missing when trying to connect
On.RoR2.Networking.GameNetworkManager.SimpleLocalizedKickReason.GetDisplayTokenAndFormatParams +=
(On.RoR2.Networking.GameNetworkManager.SimpleLocalizedKickReason.orig_GetDisplayTokenAndFormatParams orig,
GameNetworkManager.SimpleLocalizedKickReason self, out string token, out object[] formatArgs) => {
var baseToken = self.baseToken;
var args = self.formatArgs;
token = baseToken;
if (baseToken != "KICK_REASON_MOD_MISMATCH")
{
token = baseToken;
formatArgs = args;
return;
}
var mods = args[1].Split('\n');
var myMods = NetworkModCompatibilityHelper.networkModList;

var extraMods = string.Join("\n", myMods.Except(mods));
var missingMods = string.Join("\n", mods.Except(myMods));

formatArgs = new object[] { extraMods, missingMods };
};

// Temporary fix until the KVP Foreach properly check for null Value before calling Equals on them
IL.RoR2.SteamworksLobbyDataGenerator.RebuildLobbyData += il => {
var c = new ILCursor(il);
Expand Down Expand Up @@ -227,6 +249,11 @@ private static void CheckR2APIMonomodPatch() {
"Please make sure that a file called:",
"Assembly-CSharp.R2API.mm.dll",
"is present in the Risk of Rain 2\\BepInEx\\monomod\\ folder",
"or",
"You are missing the monomod loader that is normally located in,",
"the Risk of Rain 2\\BepInEx\\patchers\\BepInEx.MonoMod.Loader folder.",
"If you don't have this folder, please download BepInEx again from the",
"thunderstore and make sure to follow the installation instructions."
};
Logger.LogBlockError(message);
}
Expand Down
4 changes: 2 additions & 2 deletions R2API/Utils/APISubmodule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public class APISubmoduleHandler {
private readonly int _build;
private readonly ManualLogSource _logger;
private HashSet<string> _moduleSet;
private HashSet<string> LoadedModules;
private static HashSet<string> LoadedModules;

internal APISubmoduleHandler(int build, ManualLogSource logger = null) {
_build = build;
Expand All @@ -64,7 +64,7 @@ internal APISubmoduleHandler(int build, ManualLogSource logger = null) {
/// Return true if the specified submodule is loaded.
/// </summary>
/// <param name="submodule">nameof the submodule</param>
public bool IsLoaded(string submodule) => LoadedModules.Contains(submodule);
public static bool IsLoaded(string submodule) => LoadedModules.Contains(submodule);

internal HashSet<string> LoadRequested(PluginScanner pluginScanner) {
_moduleSet = new HashSet<string>();
Expand Down
40 changes: 29 additions & 11 deletions R2API/Utils/NetworkCompatibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
using System.Linq;
using BepInEx;
using Mono.Cecil;
using R2API.Networking;
using RoR2;

namespace R2API.Utils {
/// <summary>
/// Enum used for telling whether or not the mod should be needed by everyone in multiplayer games.
/// Also can specify if the mod does not work in multiplayer.
/// </summary>
public enum CompatibilityLevel {
NoNeedForSync,
EveryoneMustHaveMod
EveryoneMustHaveMod,
//BreaksMultiplayer //todo
}

/// <summary>
Expand All @@ -29,7 +32,7 @@ public enum VersionStrictness {
/// you want to specify if the mod should be installed by everyone in multiplayer games or not.
/// If the mod is required to be installed by everyone, you'll need to also specify if the same mod version should be used by everyone or not.
/// By default, it's supposed that everyone needs the mod and the same version.
/// e.g: [NetworkCompatibility(CompatibilityLevel.NoNeedForSync, VersionStrictness.DifferentModVersionsAreOk)]
/// e.g: [NetworkCompatibility(CompatibilityLevel.NoNeedForSync)]
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)]
public class NetworkCompatibility : Attribute {
Expand Down Expand Up @@ -83,8 +86,13 @@ internal void BuildModList(PluginScanner pluginScanner) {
// By default, any plugins that don't have the NetworkCompatibility attribute and
// don't have the ManualNetworkRegistration attribute are added to the networked mod list
if (!haveNetworkCompatAttribute) {
if (bepinPluginAttribute != null && !haveManualRegistrationAttribute) {
modList.Add(modGuid + ModGuidAndModVersionSeparator + modVersion);
if (bepinPluginAttribute != null){
if (!haveManualRegistrationAttribute) {
modList.Add(modGuid + ModGuidAndModVersionSeparator + modVersion);
}
else {
R2API.Logger.LogDebug($"Found {nameof(ManualNetworkRegistrationAttribute)} type. Ignoring.");
}
}
else {
R2API.Logger.LogDebug($"Found {nameof(BaseUnityPlugin)} type but no {nameof(BepInPlugin)} attribute");
Expand Down Expand Up @@ -133,6 +141,9 @@ internal void BuildModList(PluginScanner pluginScanner) {

void CallWhenAssembliesAreScanned() {
if (modList.Count != 0) {
if (IsR2APIAffectingNetwork()) {
modList.Add(R2API.PluginGUID + ModGuidAndModVersionSeparator + R2API.PluginVersion);
}
var sortedModList = modList.ToList();
sortedModList.Sort();
R2API.Logger.LogInfo("[NetworkCompatibility] Adding to the networkModList : ");
Expand All @@ -144,15 +155,22 @@ void CallWhenAssembliesAreScanned() {
}
}

internal static bool IsR2APIAffectingNetwork() {
return APISubmoduleHandler.IsLoaded(nameof(NetworkingAPI));
}

private static void TryGetNetworkCompatibilityArguments(IList<CustomAttributeArgument> attributeArguments,
out CompatibilityLevel compatibilityLevel, out VersionStrictness versionStrictness) {
if (attributeArguments[0].Value is int && attributeArguments[1].Value is int) {
compatibilityLevel = (CompatibilityLevel)attributeArguments[0].Value;
versionStrictness = (VersionStrictness)attributeArguments[1].Value;
}
else {
compatibilityLevel = CompatibilityLevel.EveryoneMustHaveMod;
versionStrictness = VersionStrictness.EveryoneNeedSameModVersion;
compatibilityLevel = CompatibilityLevel.EveryoneMustHaveMod;
versionStrictness = VersionStrictness.EveryoneNeedSameModVersion;

if (attributeArguments != null && attributeArguments.Count > 0) {
if (attributeArguments[0].Value is int) {
compatibilityLevel = (CompatibilityLevel)attributeArguments[0].Value;
}
if (attributeArguments[1].Value is int) {
versionStrictness = (VersionStrictness)attributeArguments[1].Value;
}
}
}
}
Expand Down
89 changes: 70 additions & 19 deletions R2API/Utils/PluginScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ private List<AssemblyDefinition> PluginsAssemblyDefinitions {
var assemblies = new List<AssemblyDefinition>();
var resolver = new DefaultAssemblyResolver();
var gameDirectory = new DirectoryInfo(Paths.GameRootPath);

// todo: make resolver able to resolve embedded assemblies
foreach (var directory in gameDirectory.EnumerateDirectories("*", SearchOption.AllDirectories)) {
resolver.AddSearchDirectory(directory.FullName);
}

R2API.Logger.LogDebug("Adding to the list of assemblies to scan:");
foreach (string dll in Directory.GetFiles(Paths.PluginPath, "*.dll", SearchOption.AllDirectories))
{
var fileName = Path.GetFileName(dll);
Expand All @@ -28,9 +32,10 @@ private List<AssemblyDefinition> PluginsAssemblyDefinitions {
try {
assemblies.Add(AssemblyDefinition.ReadAssembly(dll,
new ReaderParameters { AssemblyResolver = resolver }));
R2API.Logger.LogDebug($"{fileName}");
}
catch (Exception) {
// ignored
R2API.Logger.LogDebug($"Cecil ReadAssembly couldn't read {dll}");
}
}

Expand All @@ -43,25 +48,57 @@ private List<AssemblyDefinition> PluginsAssemblyDefinitions {
}

private static void DetectAndRemoveDuplicateAssemblies(ref List<AssemblyDefinition> assemblies) {
var bepinPluginAttributes = assemblies.SelectMany(assemblyDef =>
assemblyDef.MainModule.Types.SelectMany(typeDef => typeDef.CustomAttributes))
.Where(attribute => attribute.AttributeType.FullName == typeof(BepInPlugin).FullName);

var duplicateOldAssemblies = new HashSet<AssemblyDefinition>();
foreach (var bepinPlugin in bepinPluginAttributes) {
var (modGuid, modVer) = GetBepinPluginInfo(bepinPlugin.ConstructorArguments);
foreach (var bepinPlugin2 in bepinPluginAttributes) {
if (bepinPlugin == bepinPlugin2)
continue;

var (modGuid2, modVer2) = GetBepinPluginInfo(bepinPlugin2.ConstructorArguments);
foreach (var assemblyDef in assemblies) {
if (duplicateOldAssemblies.Contains(assemblyDef))
continue;
var bepinPluginAttributes = assemblyDef.MainModule.Types.SelectMany(typeDef => typeDef.CustomAttributes)
.Where(attribute => attribute.AttributeType.FullName == typeof(BepInPlugin).FullName).ToList();
foreach (var otherAssemblyDef in assemblies) {
if (duplicateOldAssemblies.Contains(otherAssemblyDef))
continue;
if (assemblyDef == otherAssemblyDef)
continue;

if (modGuid == modGuid2) {
var comparedTo = string.Compare(modVer, modVer2, StringComparison.Ordinal);
if (comparedTo >= 0) {
duplicateOldAssemblies.Add(bepinPlugin2.AttributeType.Module.Assembly);
var otherBepinPluginAttributes = otherAssemblyDef.MainModule.Types.SelectMany(typeDef => typeDef.CustomAttributes)
.Where(attribute => attribute.AttributeType.FullName == typeof(BepInPlugin).FullName).ToList();

AssemblyDefinition goodAssembly = null;
string goodAssemblyVer = null;
AssemblyDefinition oldDuplicateAssembly = null;
string oldDuplicateModVer = null;

foreach (var bepinPluginAttribute in bepinPluginAttributes) {
var (modGuid, modVer) = GetBepinPluginInfo(bepinPluginAttribute.ConstructorArguments);

if (modGuid == null)
break;

foreach (var otherBepinPluginAttribute in otherBepinPluginAttributes) {
var (otherModGuid, otherModVer) = GetBepinPluginInfo(otherBepinPluginAttribute.ConstructorArguments);
if (modGuid == otherModGuid) {
var comparedTo = string.Compare(modVer, otherModVer, StringComparison.Ordinal);
var isModVerMoreRecentThanOtherModVer = comparedTo >= 0;
if (isModVerMoreRecentThanOtherModVer) {
goodAssembly = bepinPluginAttribute.AttributeType.Module.Assembly;
goodAssemblyVer = modVer;
oldDuplicateAssembly = otherBepinPluginAttribute.AttributeType.Module.Assembly;
oldDuplicateModVer = otherModVer;
}
}
else {
oldDuplicateAssembly = null;
break;
}
}
}

if (oldDuplicateAssembly != null) {
R2API.Logger.LogDebug($"Removing {oldDuplicateAssembly.MainModule.FileName} (ModVer : {oldDuplicateModVer}) from the list " +
$"because it's a duplicate of {goodAssembly.MainModule.FileName} (ModVer : {goodAssemblyVer}).");
duplicateOldAssemblies.Add(oldDuplicateAssembly);
}
}
}

Expand Down Expand Up @@ -135,8 +172,15 @@ private void ScanAssemblyTypes(IEnumerable<TypeDefinition> types) {
}
}
}
catch (Exception) {
// ignored
catch (Exception ex) {
// AssemblyResolutionException will happen on types that are resolved from
// dynamicaly loaded / soft dependency assemblies
if (!(ex is AssemblyResolutionException)) {
R2API.Logger.LogDebug(
$"Catched ex when handling attribute scan request : {ex}\n" +
$"We were looking for {attributeScanRequest.SearchedTypeFullName} " +
$"in the assembly called {typeDef.Module.FileName}");
}
}
}
}
Expand All @@ -153,8 +197,15 @@ private void ScanAssemblyTypes(IEnumerable<TypeDefinition> types) {
}
}
}
catch (Exception) {
// ignored
catch (Exception ex) {
// AssemblyResolutionException will happen on types that are resolved from
// dynamicaly loaded / soft dependency assemblies
if (!(ex is AssemblyResolutionException)) {
R2API.Logger.LogDebug(
$"Catched ex when handling class scan request : {ex}\n" +
$"We were looking for {classScanRequest.SearchedTypeFullName} " +
$"in the assembly called {typeDef.Module.FileName}");
}
}
}
}
Expand Down