diff --git a/Fika.Core/FikaPlugin.cs b/Fika.Core/FikaPlugin.cs index 848ffe38..10a0aae6 100644 --- a/Fika.Core/FikaPlugin.cs +++ b/Fika.Core/FikaPlugin.cs @@ -246,7 +246,7 @@ protected void Awake() private IEnumerator RunModHandler() { yield return new WaitForSeconds(5); - ModHandler.Run(); + ModHandler.VerifyMods(); } private void GetClientConfig() diff --git a/Fika.Core/Networking/Models/ModValidationResponse.cs b/Fika.Core/Networking/Models/ModValidationResponse.cs new file mode 100644 index 00000000..f5fba64c --- /dev/null +++ b/Fika.Core/Networking/Models/ModValidationResponse.cs @@ -0,0 +1,17 @@ +using System.Runtime.Serialization; + +namespace Fika.Core.Networking.Http.Models +{ + [DataContract] + public struct ModValidationResponse + { + [DataMember(Name = "forbidden")] + public string[] Forbidden; + + [DataMember(Name = "missingRequired")] + public string[] MissingRequired; + + [DataMember(Name = "hashMismatch")] + public string[] HashMismatch; + } +} \ No newline at end of file diff --git a/Fika.Core/Utils/FikaModHandler.cs b/Fika.Core/Utils/FikaModHandler.cs index c3480226..65e06c82 100644 --- a/Fika.Core/Utils/FikaModHandler.cs +++ b/Fika.Core/Utils/FikaModHandler.cs @@ -1,70 +1,111 @@ using BepInEx; using BepInEx.Bootstrap; using BepInEx.Logging; -using Fika.Core.Networking.Models; +using Comfort.Common; +using EFT; +using EFT.UI; +using Fika.Core.Networking.Http.Models; using LiteNetLib.Utils; +using Newtonsoft.Json; +using SPT.Common.Http; +using System.Collections; using System.Collections.Generic; using System.IO; +using UnityEngine; +using Logger = BepInEx.Logging.Logger; namespace Fika.Core.Utils { public class FikaModHandler { - private string[] loadedMods; private readonly ManualLogSource logger = Logger.CreateLogSource("FikaModHandler"); public bool QuestingBotsLoaded = false; public bool SAINLoaded = false; - public void Run() + public void VerifyMods() { - // Store all loaded plugins (mods) to improve compatibility - List tempPluginInfos = []; + PluginInfo[] pluginInfos = [.. Chainloader.PluginInfos.Values]; + + // Set capacity to avoid unnecessarily resizing for people who have a lot of mods loaded + Dictionary loadedMods = new Dictionary(pluginInfos.Length); + + foreach (PluginInfo pluginInfo in pluginInfos) + { + string location = pluginInfo.Location; + byte[] fileBytes = File.ReadAllBytes(location); + uint crc32 = CRC32C.Compute(fileBytes, 0, fileBytes.Length); + loadedMods.Add(pluginInfo.Metadata.GUID, crc32); + logger.LogInfo($"Loaded plugin: [{pluginInfo.Metadata.Name}] with GUID [{pluginInfo.Metadata.GUID}] and crc32 [{crc32}]"); + CheckSpecialMods(pluginInfo.Metadata.GUID); + } + + string modValidationRequestJson = JsonConvert.SerializeObject(loadedMods); + logger.LogDebug(modValidationRequestJson); + + string validationJson = RequestHandler.PostJson("/fika/client/check/mods", modValidationRequestJson); + logger.LogDebug(validationJson); - foreach (string key in Chainloader.PluginInfos.Keys) + ModValidationResponse validationResult = + JsonConvert.DeserializeObject(validationJson); + + // If any errors were detected we will print what has happened + bool installationError = + validationResult.Forbidden.Length > 0 || + validationResult.MissingRequired.Length > 0 || + validationResult.HashMismatch.Length > 0; + + if (validationResult.Forbidden.Length > 0) { - logger.LogInfo($"Adding {key}, {Chainloader.PluginInfos[key].Metadata.Name} to loaded mods."); - tempPluginInfos.Add(key); - CheckSpecialMods(key); + logger.LogError($"{validationResult.Forbidden.Length} forbidden mod(s) are loaded, have the server host allow or remove the following mods: {string.Join(", ", validationResult.Forbidden)}"); } - /*if (FikaPlugin.Instance.RequiredMods.Count > 0) + if (validationResult.MissingRequired.Length > 0) { - VerifyMods(); - }*/ + logger.LogError($"{validationResult.MissingRequired.Length} missing required mod(s), verify the following mods are present: {string.Join(", ", validationResult.MissingRequired)}"); + } - loadedMods = [.. tempPluginInfos]; + if (validationResult.HashMismatch.Length > 0) + { + logger.LogWarning($"{validationResult.HashMismatch.Length} mismatched mod(s) are loaded, verify the following mods are up to date with the server host: {string.Join(", ", validationResult.HashMismatch)}"); + } - logger.LogInfo($"Loaded {loadedMods.Length} mods!"); + if (installationError) + { + StaticManager.BeginCoroutine(InformInstallationError()); + } } - private void VerifyMods() + private IEnumerator InformInstallationError() { - PluginInfo[] pluginInfos = [.. Chainloader.PluginInfos.Values]; - Dictionary loadedMods = []; - - foreach (PluginInfo pluginInfo in pluginInfos) + while (!Singleton.Instantiated) { - string location = pluginInfo.Location; - byte[] fileBytes = File.ReadAllBytes(location); - uint crc32 = CRC32C.Compute(fileBytes, 0, fileBytes.Length); - loadedMods.Add(pluginInfo.Metadata.GUID, crc32.ToString()); + yield return null; } - ModValidationRequest modValidationRequest = new(loadedMods); - // Send + string message = "Your client doesn't meet server requirements, check logs for more details"; + + // -1f time makes the message permanent + Singleton.Instance.ShowCriticalErrorScreen("INSTALLATION ERROR", message, + ErrorScreen.EButtonType.QuitButton, -1f, Application.Quit, null); } private void CheckSpecialMods(string key) { - if (key == "com.DanW.QuestingBots") + switch (key) { - QuestingBotsLoaded = true; - } + case "com.DanW.QuestingBots": + { + QuestingBotsLoaded = true; - if (key == "me.sol.sain") - { - SAINLoaded = true; + break; + } + case "me.sol.sain": + { + SAINLoaded = true; + + break; + } } } }