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

Added the mod verification logic #62

Merged
merged 4 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions Fika.Core/FikaPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ namespace Fika.Core
/// Originally by: Paulov <br/>
/// Re-written by: Lacyway
/// </summary>
[BepInPlugin("com.fika.core", "Fika.Core", "0.9.8906")]
[BepInPlugin("com.project-fika.core", "Fika.Core", "0.9.8906")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in a separate PR as this is a breaking change for mods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reverted that commit from this PR.

[BepInProcess("EscapeFromTarkov.exe")]
[BepInDependency("com.SPT.custom", BepInDependency.DependencyFlags.HardDependency)] // This is used so that we guarantee to load after aki-custom, that way we can disable its patches
[BepInDependency("com.SPT.singleplayer", BepInDependency.DependencyFlags.HardDependency)] // This is used so that we guarantee to load after aki-singleplayer, that way we can disable its patches
Expand Down Expand Up @@ -246,7 +246,7 @@ protected void Awake()
private IEnumerator RunModHandler()
{
yield return new WaitForSeconds(5);
ModHandler.Run();
ModHandler.VerifyMods();
}

private void GetClientConfig()
Expand Down
17 changes: 17 additions & 0 deletions Fika.Core/Networking/Models/ModValidationResponse.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
103 changes: 72 additions & 31 deletions Fika.Core/Utils/FikaModHandler.cs
Original file line number Diff line number Diff line change
@@ -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<string> tempPluginInfos = [];
PluginInfo[] pluginInfos = [.. Chainloader.PluginInfos.Values];

// Set capacity to avoid unnecessarily resizing for people who have a lot of mods loaded
Dictionary<string, uint> loadedMods = new Dictionary<string, uint>(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<ModValidationResponse>(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<string, string> loadedMods = [];

foreach (PluginInfo pluginInfo in pluginInfos)
while (!Singleton<PreloaderUI>.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<PreloaderUI>.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;
}
}
}
}
Expand Down
Loading