Upload from upload_mods.ps1

This commit is contained in:
Nathaniel Cosford
2025-06-04 16:44:53 +09:30
commit f1fbbe67bb
1722 changed files with 165268 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
using JetBrains.Annotations;
using System.Collections;
namespace IntegratedConfigs.Core.Entities
{
public enum ClientFileState
{
None,
Received,
LoadLocal,
}
public class XmlLoadInfo
{
public readonly string XmlName;
public readonly string DirectoryPath;
public readonly string LoadStepLocalizationKey;
public readonly bool LoadAtStartup;
public readonly bool SendToClients;
public readonly bool IgnoreMissingFile;
public readonly bool AllowReloadDuringGame;
public readonly Func<XmlFile, IEnumerator> LoadMethod;
public readonly Action CleanupMethod;
public readonly Func<IEnumerator> ExecuteAfterLoad;
public readonly Action<XmlFile> ReloadDuringGameMethod;
public byte[] CompressedXmlData;
[UsedImplicitly] public bool LoadClientFile;
public ClientFileState WasReceivedFromServer;
public bool XmlFileExists() => File.Exists($"{DirectoryPath}/{XmlName}.xml");
public XmlLoadInfo(
string xmlName,
string directoryPath,
bool loadAtStartup,
bool sendToClients,
Func<XmlFile, IEnumerator> loadMethod,
Action cleanupMethod,
Func<IEnumerator> executeAfterLoad = null,
bool allowReloadDuringGame = false,
Action<XmlFile> reloadDuringGameMethod = null,
bool ignoreMissingFile = false,
string loadStepLocalizationKey = null)
{
XmlName = xmlName;
DirectoryPath = directoryPath;
LoadAtStartup = loadAtStartup;
SendToClients = sendToClients;
LoadMethod = loadMethod;
CleanupMethod = cleanupMethod;
ExecuteAfterLoad = executeAfterLoad;
AllowReloadDuringGame = allowReloadDuringGame;
ReloadDuringGameMethod = reloadDuringGameMethod;
IgnoreMissingFile = ignoreMissingFile;
LoadStepLocalizationKey = loadStepLocalizationKey;
if (LoadMethod == null) throw new ArgumentNullException(nameof(loadMethod));
}
}
}

View File

@@ -0,0 +1,449 @@
using IntegratedConfigs.Core.Entities;
using IntegratedConfigs.Scripts;
using JetBrains.Annotations;
using Noemax.GZip;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace IntegratedConfigs.Harmony
{
public static class Harmony_WorldStaticData
{
internal static readonly List<XmlLoadInfo> XmlsToLoad = new List<XmlLoadInfo>();
internal static void RegisterConfig(XmlLoadInfo xmlLoadInfo)
{
XmlsToLoad.Add(xmlLoadInfo);
}
internal static IEnumerator LoadSingleXml(
XmlLoadInfo _loadInfo,
MemoryStream _memStream,
DeflateOutputStream _zipStream)
{
//Log.Out("IntegratedConfigs.Harmony-LoadSingleXml START");
bool coroutineHadException = false;
XmlFile xmlFile = null;
yield return LoadConfig(_loadInfo, (_file => xmlFile = _file));
if (xmlFile != null)
{
if (ThreadManager.IsMainThread() && Application.isPlaying &&
SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
yield return null;
string path = GameIO.GetSaveGameDir() + "/ConfigsDump/" + _loadInfo.XmlName + ".xml";
string directoryName = Path.GetDirectoryName(path);
if (directoryName == null)
throw new NullReferenceException($"{Globals.LOG_TAG} LoadSingleXml directoryName was null");
if (!Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName);
File.WriteAllText(path, xmlFile.SerializeToString(), Encoding.UTF8);
}
yield return null;
if (SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
yield return ThreadManager.CoroutineWrapperWithExceptionCallback(
CacheSingleXml(_loadInfo, xmlFile, _memStream, _zipStream),
_exception =>
{
Log.Error("XML loader: Compressing XML data for '" + xmlFile.Filename + "' failed");
Log.Exception(_exception);
coroutineHadException = true;
});
if (coroutineHadException)
yield break;
else
yield return null;
}
yield return ThreadManager.CoroutineWrapperWithExceptionCallback(_loadInfo.LoadMethod(xmlFile),
_exception =>
{
Log.Error("XML loader: Loading and parsing '" + xmlFile.Filename + "' failed");
Log.Exception(_exception);
coroutineHadException = true;
});
if (!coroutineHadException)
{
if (_loadInfo.ExecuteAfterLoad != null)
{
yield return null;
yield return ThreadManager.CoroutineWrapperWithExceptionCallback(
_loadInfo.ExecuteAfterLoad(), _exception =>
{
Log.Error("XML loader: Executing post load step on '" + xmlFile.Filename + "' failed");
Log.Exception(_exception);
coroutineHadException = true;
});
if (coroutineHadException)
yield break;
}
Log.Out($"{Globals.LOG_TAG} Loaded (local): {_loadInfo.XmlName}");
}
}
}
private static IEnumerator LoadConfig(
XmlLoadInfo _loadInfo,
Action<XmlFile> _callback)
{
//Log.Out("IntegratedConfigs.Harmony-LoadConfig START");
var configName = _loadInfo.XmlName;
if (!configName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
configName += ".xml";
Exception xmlLoadException = null;
XmlFile xmlFile = new XmlFile(_loadInfo.DirectoryPath, configName, (_exception =>
{
if (_exception == null)
return;
xmlLoadException = _exception;
}));
while (!xmlFile.Loaded && xmlLoadException == null)
yield return null;
if (xmlLoadException != null)
{
Log.Error("XML loader: Loading base XML '" + xmlFile.Filename + "' failed:");
Log.Exception(xmlLoadException);
}
else
{
_callback(xmlFile);
}
}
private static IEnumerator CacheSingleXml(
XmlLoadInfo _loadInfo,
XmlFile _origXml,
MemoryStream _memStream,
DeflateOutputStream _zipStream)
{
//Log.Out("IntegratedConfigs.Harmony-CacheSingleXml START");
XmlFile clonedXml = new XmlFile(_origXml);
yield return null;
clonedXml.RemoveComments();
yield return null;
byte[] binXml = clonedXml.SerializeToBytes();
yield return null;
_memStream.SetLength(0L);
_zipStream.Write(binXml, 0, binXml.Length);
_zipStream.Restart();
yield return null;
_loadInfo.CompressedXmlData = _memStream.ToArray();
_memStream.SetLength(0L);
}
}
[HarmonyPatch(typeof(WorldStaticData))]
[HarmonyPatch("Cleanup", typeof(string))]
public class CleanupPatches
{
[UsedImplicitly]
private static bool Prefix(string _xmlNameContaining)
{
foreach (XmlLoadInfo xmlLoadInfo in Harmony_WorldStaticData.XmlsToLoad)
{
if (string.IsNullOrEmpty(_xmlNameContaining) || xmlLoadInfo.XmlName.ContainsCaseInsensitive(_xmlNameContaining))
{
Action cleanupMethod = xmlLoadInfo.CleanupMethod;
if (cleanupMethod != null)
cleanupMethod();
}
}
return true;
}
}
[HarmonyPatch(typeof(WorldStaticData))]
[HarmonyPatch("Reset")]
public static class ResetPatches
{
[UsedImplicitly]
private static void Postfix(string _xmlNameContaining)
{
//Log.Out("IntegratedConfigs.Harmony-Reset START");
using (MemoryStream memoryStream = new MemoryStream())
{
DeflateOutputStream zipStream = new DeflateOutputStream(memoryStream, 3);
foreach (XmlLoadInfo loadInfo in Harmony_WorldStaticData.XmlsToLoad)
{
if (string.IsNullOrEmpty(_xmlNameContaining) ||
loadInfo.XmlName.ContainsCaseInsensitive(_xmlNameContaining))
{
if (!loadInfo.XmlFileExists())
{
if (!loadInfo.IgnoreMissingFile)
Log.Error("XML loader: XML is missing on reset: " + loadInfo.XmlName);
}
else
{
ThreadManager.RunCoroutineSync(
Harmony_WorldStaticData.LoadSingleXml(loadInfo, memoryStream, zipStream));
}
}
}
}
}
}
[HarmonyPatch(typeof(WorldStaticData))]
[HarmonyPatch("LoadAllXmlsCo")]
public class LoadAllXmlsCoPatches
{
[UsedImplicitly]
private static void Postfix(
bool _isStartup,
WorldStaticData.ProgressDelegate _progressDelegate)
{
//Log.Out("IntegratedConfigs.Harmony-LoadAllXmlsCo START");
Log.Out($"{Globals.LOG_TAG} Loading {Harmony_WorldStaticData.XmlsToLoad.Count.ToString()} custom XMLs");
using (MemoryStream memStream = new MemoryStream())
{
//Log.Out("WorldStaticData-LoadAllXmlsCo 1");
DeflateOutputStream zipStream = new DeflateOutputStream(memStream, 3);
var xmlLoadInfoArray = Harmony_WorldStaticData.XmlsToLoad;
//Log.Out("WorldStaticData-LoadAllXmlsCo xmlLoadInfoArray.Count: " + xmlLoadInfoArray.Count);
for (int index = 0; index < xmlLoadInfoArray.Count; ++index)
{
var loadInfo = xmlLoadInfoArray[index];
//Log.Out("loadInfo.DirectoryPath: " + loadInfo.DirectoryPath);
//Log.Out("loadInfo.XmlName: " + loadInfo.XmlName);
if (!loadInfo.XmlFileExists())
{
if (!loadInfo.IgnoreMissingFile)
Log.Error("XML loader: XML is missing: " + loadInfo.XmlName);
}
else if (!_isStartup || loadInfo.LoadAtStartup)
{
if (_progressDelegate != null && loadInfo.LoadStepLocalizationKey != null)
{
_progressDelegate(Localization.Get(loadInfo.LoadStepLocalizationKey), 0.0f);
}
ThreadManager.RunCoroutineSync(
Harmony_WorldStaticData.LoadSingleXml(loadInfo, memStream, zipStream));
// ReSharper disable once RedundantAssignment
loadInfo = null;
}
}
// ReSharper disable once RedundantAssignment
xmlLoadInfoArray = null;
}
//Log.Out("WorldStaticData-LoadAllXmlsCo END");
}
}
[HarmonyPatch(typeof(WorldStaticData))]
[HarmonyPatch("SendXmlsToClient")]
public static class SendXmlsToClientPatches
{
[UsedImplicitly]
private static void Postfix(ClientInfo _cInfo)
{
//Log.Out("IntegratedConfigs.Harmony-SendXmlsToClient START");
foreach (XmlLoadInfo xmlLoadInfo in Harmony_WorldStaticData.XmlsToLoad)
{
if (xmlLoadInfo.SendToClients && (xmlLoadInfo.LoadClientFile || xmlLoadInfo.CompressedXmlData != null))
_cInfo.SendPackage(NetPackageManager.GetPackage<NetPackageConfigFile>().Setup(xmlLoadInfo.XmlName,
xmlLoadInfo.LoadClientFile ? null : xmlLoadInfo.CompressedXmlData));
}
}
}
[HarmonyPatch(typeof(WorldStaticData))]
[HarmonyPatch("AllConfigsReceivedAndLoaded")]
public static class AllConfigsReceivedAndLoadedPatches
{
[UsedImplicitly]
// ReSharper disable once RedundantAssignment
private static bool Prefix(ref bool __result, ref Coroutine ___receivedConfigsHandlerCoroutine)
{
__result = (___receivedConfigsHandlerCoroutine == null &&
WaitForConfigsFromServerPatches.ReceivedConfigsHandlerCoroutine == null);
return false;
}
}
[HarmonyPatch(typeof(WorldStaticData))]
[HarmonyPatch("WaitForConfigsFromServer")]
public static class WaitForConfigsFromServerPatches
{
public static int HighestReceivedIndex = -1;
public static Coroutine ReceivedConfigsHandlerCoroutine;
[UsedImplicitly]
private static bool Prefix(ref Coroutine ___receivedConfigsHandlerCoroutine)
{
ReceivedConfigsHandlerCoroutine = ___receivedConfigsHandlerCoroutine;
if (ReceivedConfigsHandlerCoroutine != null)
ThreadManager.StopCoroutine(ReceivedConfigsHandlerCoroutine);
ReceivedConfigsHandlerCoroutine = ThreadManager.StartCoroutine(HandleReceivedConfigs());
return true;
}
private static IEnumerator HandleReceivedConfigs()
{
HighestReceivedIndex = -1;
foreach (XmlLoadInfo xmlLoadInfo in Harmony_WorldStaticData.XmlsToLoad)
xmlLoadInfo.WasReceivedFromServer = ClientFileState.None;
while (string.IsNullOrEmpty(GamePrefs.GetString(EnumGamePrefs.GameWorld)))
yield return null;
int waitingFor = 0;
while (waitingFor < Harmony_WorldStaticData.XmlsToLoad.Count)
{
XmlLoadInfo loadInfo = Harmony_WorldStaticData.XmlsToLoad[waitingFor];
if (!loadInfo.SendToClients)
++waitingFor;
else if (loadInfo.WasReceivedFromServer == ClientFileState.None)
{
if (loadInfo.IgnoreMissingFile && HighestReceivedIndex > waitingFor)
++waitingFor;
else
yield return null;
}
else
{
WorldStaticData.Cleanup(loadInfo.XmlName);
if (loadInfo.WasReceivedFromServer == ClientFileState.LoadLocal)
{
yield return Harmony_WorldStaticData.LoadSingleXml(loadInfo, null, null);
}
else
{
byte[] uncompressedData;
using (MemoryStream input = new MemoryStream(loadInfo.CompressedXmlData))
{
using (DeflateInputStream source = new DeflateInputStream(input))
{
using (MemoryStream destination = new MemoryStream())
{
StreamUtils.StreamCopy(source, destination);
uncompressedData = destination.ToArray();
}
}
}
yield return null;
XmlFile xmlFile = new XmlFile(uncompressedData);
yield return null;
bool coroutineHadException = false;
yield return ThreadManager.CoroutineWrapperWithExceptionCallback(
loadInfo.LoadMethod(xmlFile), _exception =>
{
Log.Error("XML loader: Loading and parsing '" + xmlFile.Filename + "' failed");
Log.Exception(_exception);
coroutineHadException = true;
});
if (!coroutineHadException)
{
if (loadInfo.ExecuteAfterLoad != null)
{
yield return null;
yield return ThreadManager.CoroutineWrapperWithExceptionCallback(
loadInfo.ExecuteAfterLoad(), _exception =>
{
Log.Error("XML loader: Executing post load step on '" + xmlFile.Filename +
"' failed");
Log.Exception(_exception);
coroutineHadException = true;
});
if (coroutineHadException)
continue;
}
Log.Out($"{Globals.LOG_TAG} Loaded (received): {loadInfo.XmlName}");
yield return null;
// ReSharper disable once RedundantAssignment
uncompressedData = null;
}
else
continue;
}
++waitingFor;
// ReSharper disable once RedundantAssignment
loadInfo = null;
}
}
ReceivedConfigsHandlerCoroutine = null;
}
}
[HarmonyPatch(typeof(WorldStaticData))]
[HarmonyPatch("ReceivedConfigFile")]
public static class ReceivedConfigFilePatches
{
[UsedImplicitly]
private static bool Prefix(string _name, byte[] _data)
{
if (_data != null)
Log.Out(string.Format("Received config file '{0}' from server. Len: {1}", _name, _data.Length.ToString()));
else
Log.Out("Loading config '" + _name + "' from local files");
int _arrayIndex;
XmlLoadInfo loadInfoForName = GetLoadInfoForName(_name, out _arrayIndex);
if (loadInfoForName != null)
{
Log.Out("XML loader: Received config: " + _name);
loadInfoForName.CompressedXmlData = _data;
loadInfoForName.WasReceivedFromServer = _data != null ? ClientFileState.Received : ClientFileState.LoadLocal;
WaitForConfigsFromServerPatches.HighestReceivedIndex = MathUtils.Max(WaitForConfigsFromServerPatches.HighestReceivedIndex, _arrayIndex);
Log.Out($"{Globals.LOG_TAG} XML Source was {loadInfoForName.WasReceivedFromServer.ToString()}");
// Since we got a config file that this patch handles, don't call vanilla code handling.
return false;
}
return true;
}
private static XmlLoadInfo GetLoadInfoForName(
string _xmlName,
out int _arrayIndex)
{
_arrayIndex = -1;
for (int index = 0; index < Harmony_WorldStaticData.XmlsToLoad.Count; ++index)
{
XmlLoadInfo loadInfoForName = Harmony_WorldStaticData.XmlsToLoad[index];
if (loadInfoForName.XmlName.EqualsCaseInsensitive(_xmlName))
{
_arrayIndex = index;
return loadInfoForName;
}
}
return null;
}
}
/// <summary>
/// Not Implementing [via a20.7]
/// Vanilla uses this to save configs to a folder for viewing. It is triggered by a console command
/// so its value is limited.
/// </summary>
[HarmonyPatch(typeof(WorldStaticData))]
[HarmonyPatch("SaveXmlsToFolder")]
public static class SaveXmlsToFolderPatches { }
/// <summary>
/// Not Implementing [via a20.7]
/// Vanilla does not seem to call this function.
/// </summary>
[HarmonyPatch(typeof(WorldStaticData))]
[HarmonyPatch("ReloadInGameXML")]
public static class ReloadInGameXMLPatches { }
}

View File

@@ -0,0 +1,7 @@
namespace IntegratedConfigs.Scripts
{
internal static class Globals
{
internal const string LOG_TAG = "[IntegratedConfigs] -";
}
}

View File

@@ -0,0 +1,9 @@
using IntegratedConfigs.Core.Entities;
namespace IntegratedConfigs.Scripts
{
public interface IIntegratedConfig
{
XmlLoadInfo RegistrationInfo { get; }
}
}

View File

@@ -0,0 +1,51 @@
using JetBrains.Annotations;
using VersionEnforcer.Scripts;
namespace VersionEnforcer.Harmony
{
[UsedImplicitly]
public class Harmony_GameManager
{
[HarmonyPatch(typeof(GameManager), "ShowMessagePlayerDenied")]
public class GameManager_ShowMessagePlayerDeniedPatch
{
[UsedImplicitly]
public static bool Prefix(GameManager __instance, GameUtils.KickPlayerData _kickData)
{
string text;
string kickReason;
if (_kickData.reason == (GameUtils.EKickReason)500)
{
kickReason = "Mod versions do not match server";
text = string.Format(Localization.Get("auth_ModVersionMismatch"), _kickData.customReason);
}
else
{
kickReason = _kickData.ToString();
text = _kickData.LocalizedMessage();
}
var guiWindowManager = Traverse.Create(__instance).Field("windowManager")?.GetValue() as GUIWindowManager;
if (guiWindowManager == null)
{
Log.Warning($"{Globals.LOG_TAG} Unable to find windowManager");
return true;
}
var messageBoxWindowGroupController =
((XUiWindowGroup)(guiWindowManager).GetWindow(XUiC_MessageBoxWindowGroup.ID)).Controller as
XUiC_MessageBoxWindowGroup;
if (messageBoxWindowGroupController == null)
{
Log.Warning($"{Globals.LOG_TAG} Unable to get window with ID {XUiC_MessageBoxWindowGroup.ID}");
return true;
}
Log.Out($"[NET] Kicked from server: {kickReason}");
messageBoxWindowGroupController.ShowMessage(Localization.Get("auth_messageTitle"), text);
return false;
}
}
}
}

View File

@@ -0,0 +1,147 @@
using JetBrains.Annotations;
using System.Collections.Generic;
using VersionEnforcer.Scripts;
namespace VersionEnforcer.Harmony
{
[UsedImplicitly]
public class Harmony_NetPackagePlayerLogin
{
private const string DELIMITER = ":::";
private static int lastCustomFieldsByteSize;
[HarmonyPatch(typeof(NetPackagePlayerLogin), "read")]
public class NetPackagePlayerLogin_ReadPatch
{
[UsedImplicitly]
private static bool Prefix(NetPackagePlayerLogin __instance, PooledBinaryReader _br,
ref string ___playerName,
ref ValueTuple<PlatformUserIdentifierAbs, string> ___platformUserAndToken,
ref ValueTuple<PlatformUserIdentifierAbs, string> ___crossplatformUserAndToken,
ref string ___version,
ref string ___compVersion
)
{
var instanceTraverse = Traverse.Create(__instance);
Log.Out("NPPL.Read");
// Vanilla Fields
/*___playerName = _br.ReadString();
___platformUserAndToken = new ValueTuple<PlatformUserIdentifierAbs, string>(PlatformUserIdentifierAbs.FromStream(_br, false, true), _br.ReadString());
___crossplatformUserAndToken = new ValueTuple<PlatformUserIdentifierAbs, string>(PlatformUserIdentifierAbs.FromStream(_br, false, true), _br.ReadString());
___version = _br.ReadString();
___compVersion = _br.ReadString();*/
instanceTraverse.Field("playerName")?.SetValue(_br.ReadString());
instanceTraverse.Field("platformUserAndToken")?.SetValue((
PlatformUserIdentifierAbs.FromStream(_br, _inclCustomData: true), _br.ReadString()));
instanceTraverse.Field("crossplatformUserAndToken")?.SetValue((
PlatformUserIdentifierAbs.FromStream(_br, _inclCustomData: true), _br.ReadString()));
instanceTraverse.Field("version")?.SetValue(_br.ReadString());
instanceTraverse.Field("compVersion")?.SetValue(_br.ReadString());
// Modded Fields
var numMods = _br.ReadInt32();
var mods = new List<CustomVersionAuthorizer.ModVersionInfo>();
var strLen = 0;
for (int i = 0; i < numMods; i++)
{
var str = _br.ReadString();
strLen += str.Length;
string[] modInfo = str.Split(new[] { DELIMITER }, StringSplitOptions.None);
if (modInfo.Length < 2) continue;
mods.Add(new CustomVersionAuthorizer.ModVersionInfo { ModName = modInfo[0], ModVersion = modInfo[1] });
}
lastCustomFieldsByteSize = sizeof(Int32) + strLen * 2;
var platformUserAndToken =
// ReSharper disable once PossibleNullReferenceException
((PlatformUserIdentifierAbs userId, string token))instanceTraverse.Field("platformUserAndToken")
?.GetValue();
if (CustomVersionAuthorizer.PlatformUserIdToProvidedCustomVersion.ContainsKey(platformUserAndToken.userId
.ReadablePlatformUserIdentifier))
{
CustomVersionAuthorizer.PlatformUserIdToProvidedCustomVersion[
platformUserAndToken.userId.ReadablePlatformUserIdentifier] = mods;
}
else
{
CustomVersionAuthorizer.PlatformUserIdToProvidedCustomVersion.Add(
platformUserAndToken.userId.ReadablePlatformUserIdentifier, mods);
}
return false;
}
}
[HarmonyPatch(typeof(NetPackagePlayerLogin), "write")]
public class NetPackagePlayerLogin_WritePatch
{
[UsedImplicitly]
private static bool Prefix(NetPackagePlayerLogin __instance, PooledBinaryWriter _bw)
{
var instanceTraverse = Traverse.Create(__instance);
var playerName = instanceTraverse.Field("playerName")?.GetValue() as string;
// ReSharper disable once PossibleNullReferenceException
var platformUserAndToken =
((PlatformUserIdentifierAbs userId, string token))instanceTraverse.Field("platformUserAndToken")
?.GetValue();
// ReSharper disable once PossibleNullReferenceException
var crossplatformUserAndToken =
((PlatformUserIdentifierAbs userId, string token))instanceTraverse
.Field("crossplatformUserAndToken")?.GetValue();
var version = instanceTraverse.Field("version")?.GetValue() as string;
var compVersion = instanceTraverse.Field("compVersion")?.GetValue() as string;
// Vanilla Fields
Log.Out("NPPL.Write");
#region base.write(_bw) equivalent replacement
_bw.Write((byte)__instance.PackageId);
#endregion
// ReSharper disable once AssignNullToNotNullAttribute
_bw.Write(playerName);
platformUserAndToken.userId.ToStream(_bw, true);
_bw.Write(platformUserAndToken.token ?? "");
crossplatformUserAndToken.userId.ToStream(_bw, true);
_bw.Write(crossplatformUserAndToken.token ?? "");
// ReSharper disable once AssignNullToNotNullAttribute
_bw.Write(version);
// ReSharper disable once AssignNullToNotNullAttribute
_bw.Write(compVersion);
// Modded
var loadedMods = ModManager.GetLoadedMods();
var mods = new List<string>();
foreach (var mod in loadedMods)
{
mods.Add($"{mod.Name}{DELIMITER}{mod.VersionString}");
}
_bw.Write(loadedMods.Count);
foreach (var str in mods)
{
_bw.Write(str);
}
return false;
}
}
[HarmonyPatch(typeof(NetPackagePlayerLogin), "GetLength")]
public class NetPackagePlayerLogin_GetLengthPatch
{
[UsedImplicitly]
// ReSharper disable once RedundantAssignment
private static bool Prefix(NetPackagePlayerLogin __instance, ref int __result)
{
__result = __instance.GetLength() + lastCustomFieldsByteSize;
return false;
}
}
}
}

View File

@@ -0,0 +1,117 @@
using JetBrains.Annotations;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace VersionEnforcer.Scripts
{
[UsedImplicitly]
public class CustomVersionAuthorizer : AuthorizerAbs
{
internal static readonly Dictionary<string, List<ModVersionInfo>> PlatformUserIdToProvidedCustomVersion =
new Dictionary<string, List<ModVersionInfo>>();
public struct ModVersionInfo
{
public string ModName;
public string ModVersion;
}
public override int Order => 71;
public override string AuthorizerName => "CustomVersion";
public override string StateLocalizationKey => "authstate_customversion";
private enum ModIssue { Missing, InvalidVersion }
private struct ModIssues
{
public string ModName;
public ModIssue Issue;
public string ServerVersion;
public string ClientVersion;
}
private readonly List<string> ignoreList = new List<string>();
private const int MAX_MESSAGE_LENGTH = 200;
public override void Init(IAuthorizationResponses _authResponsesHandler)
{
base.Init(_authResponsesHandler);
LoadIgnoreList();
}
public override (EAuthorizerSyncResult, GameUtils.KickPlayerData?) Authorize(ClientInfo _clientInfo)
{
PlatformUserIdToProvidedCustomVersion.TryGetValue(
_clientInfo.PlatformId.ReadablePlatformUserIdentifier, out var userProvidedCustomVersion);
PlatformUserIdToProvidedCustomVersion.Remove(_clientInfo.PlatformId
.ReadablePlatformUserIdentifier);
var loadedMods = ModManager.GetLoadedMods();
var issues = new List<ModIssues>();
foreach (var mod in loadedMods)
{
if (ignoreList.Contains(mod.Name)) continue;
var clientMatch = userProvidedCustomVersion?.Where(x => x.ModName == mod.Name)
.Cast<ModVersionInfo?>().FirstOrDefault();
if (clientMatch == null)
{
issues.Add(new ModIssues { ModName = mod.Name, Issue = ModIssue.Missing, ServerVersion = mod.VersionString, ClientVersion = "" });
}
else if (!clientMatch.Value.ModVersion.Equals(mod.VersionString))
{
issues.Add(new ModIssues
{
ModName = mod.Name,
Issue = ModIssue.InvalidVersion,
ServerVersion = mod.VersionString,
ClientVersion = clientMatch.Value.ModVersion
});
}
}
var issuesMessage = "";
foreach (var issue in issues)
{
var appendText = "";
if (issue.Issue == ModIssue.Missing) appendText += $"{issue.ModName}@{issue.ServerVersion}(missing), ";
else appendText += $"{issue.ModName}@{issue.ClientVersion}(needs {issue.ServerVersion}), ";
if (issuesMessage.Length + appendText.Length > MAX_MESSAGE_LENGTH)
{
issuesMessage += "..[more].. ";
break;
}
issuesMessage += appendText;
}
if (issues.Count > 0)
{
issuesMessage = issuesMessage.Remove(issuesMessage.Length - 2);
EAuthorizerSyncResult item = EAuthorizerSyncResult.SyncDeny;
int apiResponseEnum = 0;
return new ValueTuple<EAuthorizerSyncResult, GameUtils.KickPlayerData?>(item,
new GameUtils.KickPlayerData((GameUtils.EKickReason)500, apiResponseEnum, default, issuesMessage));
}
return new ValueTuple<EAuthorizerSyncResult, GameUtils.KickPlayerData?>(EAuthorizerSyncResult.SyncAllow, null);
}
private void LoadIgnoreList()
{
var filename = "IgnoreList.xml";
var xmlFile = new XmlFile(ModManager.GetMod("zzz_REBIRTH__Utils", true).Path, filename);
XElement root = xmlFile.XmlDoc.Root;
if (root == null)
{
Log.Error($"{Globals.LOG_TAG} {filename} not found or no XML root");
return;
}
foreach (XElement elem in root.Elements("mod"))
{
string modName = elem.Attribute("name")?.Value ?? "";
if (modName.Length > 0) ignoreList.Add(modName);
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace VersionEnforcer.Scripts
{
public static class Globals
{
public const string LOG_TAG = "[VersionEnforcer] -";
}
}