Upload from upload_mods.ps1
This commit is contained in:
60
Vandracon/IntegratedConfigs/Core/Entities/XmlLoadInfo.cs
Normal file
60
Vandracon/IntegratedConfigs/Core/Entities/XmlLoadInfo.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
449
Vandracon/IntegratedConfigs/Harmony/Harmony_WorldStaticData.cs
Normal file
449
Vandracon/IntegratedConfigs/Harmony/Harmony_WorldStaticData.cs
Normal 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 { }
|
||||
}
|
||||
7
Vandracon/IntegratedConfigs/Scripts/Globals.cs
Normal file
7
Vandracon/IntegratedConfigs/Scripts/Globals.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace IntegratedConfigs.Scripts
|
||||
{
|
||||
internal static class Globals
|
||||
{
|
||||
internal const string LOG_TAG = "[IntegratedConfigs] -";
|
||||
}
|
||||
}
|
||||
9
Vandracon/IntegratedConfigs/Scripts/IIntegratedConfig.cs
Normal file
9
Vandracon/IntegratedConfigs/Scripts/IIntegratedConfig.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using IntegratedConfigs.Core.Entities;
|
||||
|
||||
namespace IntegratedConfigs.Scripts
|
||||
{
|
||||
public interface IIntegratedConfig
|
||||
{
|
||||
XmlLoadInfo RegistrationInfo { get; }
|
||||
}
|
||||
}
|
||||
51
Vandracon/VersionEnforcer/Harmony/Harmony_GameManager.cs
Normal file
51
Vandracon/VersionEnforcer/Harmony/Harmony_GameManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
Vandracon/VersionEnforcer/Scripts/CustomVersionAuthorizer.cs
Normal file
117
Vandracon/VersionEnforcer/Scripts/CustomVersionAuthorizer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Vandracon/VersionEnforcer/Scripts/Globals.cs
Normal file
7
Vandracon/VersionEnforcer/Scripts/Globals.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace VersionEnforcer.Scripts
|
||||
{
|
||||
public static class Globals
|
||||
{
|
||||
public const string LOG_TAG = "[VersionEnforcer] -";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user