Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Drop That v3.1.5
Valheim.DropThat.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using CreatureLevelControl; using DropThat.Caches; using DropThat.Commands; using DropThat.Configuration; using DropThat.Configuration.Sync; using DropThat.Core; using DropThat.Core.Configuration; using DropThat.Core.Patches; using DropThat.Creature; using DropThat.Creature.DamageRecords; using DropThat.Creature.StatusRecords; using DropThat.Debugging; using DropThat.Drop; using DropThat.Drop.CharacterDropSystem; using DropThat.Drop.CharacterDropSystem.Caches; using DropThat.Drop.CharacterDropSystem.Conditions; using DropThat.Drop.CharacterDropSystem.Conditions.ModSpecific.CLLC; using DropThat.Drop.CharacterDropSystem.Conditions.ModSpecific.SpawnThat; using DropThat.Drop.CharacterDropSystem.Configuration; using DropThat.Drop.CharacterDropSystem.Configuration.Toml; using DropThat.Drop.CharacterDropSystem.Debug; using DropThat.Drop.CharacterDropSystem.Managers; using DropThat.Drop.CharacterDropSystem.Models; using DropThat.Drop.CharacterDropSystem.Patches; using DropThat.Drop.CharacterDropSystem.Services; using DropThat.Drop.CharacterDropSystem.Sync; using DropThat.Drop.DropTableSystem; using DropThat.Drop.DropTableSystem.Conditions; using DropThat.Drop.DropTableSystem.Conditions.ModSpecific.CLLC; using DropThat.Drop.DropTableSystem.Conditions.ModSpecific.SpawnThat; using DropThat.Drop.DropTableSystem.Configuration; using DropThat.Drop.DropTableSystem.Configuration.Toml; using DropThat.Drop.DropTableSystem.Debug; using DropThat.Drop.DropTableSystem.Managers; using DropThat.Drop.DropTableSystem.Models; using DropThat.Drop.DropTableSystem.Patches; using DropThat.Drop.DropTableSystem.Services; using DropThat.Drop.DropTableSystem.Sync; using DropThat.Drop.Options; using DropThat.Drop.Options.Modifiers; using DropThat.Drop.Options.Modifiers.ModEpicLoot; using DropThat.Integrations; using DropThat.Integrations.CllcIntegration; using DropThat.Integrations.EpicLootIntegration; using DropThat.Locations; using DropThat.Locations.Sync; using DropThat.Utilities; using DropThat.Utilities.Valheim; using EpicLoot; using EpicLoot.Data; using EpicLoot.LegendarySystem; using HarmonyLib; using Microsoft.CodeAnalysis; using ThatCore.Cache; using ThatCore.Config; using ThatCore.Config.Toml; using ThatCore.Config.Toml.Mapping; using ThatCore.Config.Toml.Parsers; using ThatCore.Config.Toml.Schema; using ThatCore.Config.Toml.Writers; using ThatCore.Extensions; using ThatCore.Lifecycle; using ThatCore.Lifecycle.Patches; using ThatCore.Logging; using ThatCore.Models; using ThatCore.Network; using ThatCore.Utilities.Files; using ThatCore.Utilities.Valheim; using UnityEngine; using Valheim.DropThat.Drop.DropTableSystem.Patches; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/ASharpPen/Valheim.SpawnThat")] [assembly: AssemblyTitle("Valheim.DropThat")] [assembly: AssemblyProduct("Valheim.DropThat")] [assembly: AssemblyInformationalVersion("3.1.5+8ce08c9a114ca5eb28ba0cf56d13eaa54d5b048b")] [assembly: AssemblyFileVersion("3.1.5.0")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCompany("A Sharp Pen")] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: InternalsVisibleTo("Valheim.DropThat.Tests")] [assembly: AssemblyDescription("Valheim mod and tool for configuring loot drops.")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("3.1.5.0")] [module: RefSafetyRules(11)] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [<296f7d6c-da35-4841-bb06-68137b2ba19b>Embedded] internal sealed class <296f7d6c-da35-4841-bb06-68137b2ba19b>EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [<296f7d6c-da35-4841-bb06-68137b2ba19b>Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] [CompilerGenerated] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ThatCore.Models { public struct Optional<T> { public T Value { get; private set; } public bool IsSet { get; private set; } public Optional() { IsSet = false; Value = default(T); } public Optional(T value) { IsSet = false; Value = default(T); Set(value); } public void Set(T value) { IsSet = true; Value = value; } public static implicit operator Optional<T>(T value) { return new Optional<T>(value); } public static implicit operator T(Optional<T> optional) { return optional.Value; } public void DoIfSet(Action<T> action) { if (IsSet) { action(Value); } } public void AssignIfSet(ref T dest) { if (IsSet) { dest = Value; } } } public class TypeSet<TKey> : IEnumerable<TKey>, IEnumerable, ICollection<TKey> { private Dictionary<Type, TKey> _dic = new Dictionary<Type, TKey>(); public TKey this[TKey item] { get { return _dic[item.GetType()]; } set { _dic[item.GetType()] = item; } } public int Count => _dic.Count; public bool IsReadOnly { get; } public TypeSet() { } public TypeSet(TypeSet<TKey> input) { _dic = new Dictionary<Type, TKey>(input._dic); } public T GetOrCreate<T>() where T : TKey, new() { if (_dic.TryGetValue(typeof(T), out var value) && value is T) { return (T)(object)value; } T val = new T(); _dic[typeof(T)] = (TKey)(object)val; return val; } public T GetOrDefault<T>() where T : TKey { if (_dic.TryGetValue(typeof(T), out var value) && value is T) { return (T)(object)value; } return default(T); } public void Set(TKey item) { _dic[item.GetType()] = item; } public bool Remove<T>() where T : TKey { return _dic.Remove(typeof(T)); } public bool Remove(Type type) { return _dic.Remove(type); } public TypeSet<TKey> Clone() { return new TypeSet<TKey> { _dic = new Dictionary<Type, TKey>(_dic) }; } public IEnumerator<TKey> GetEnumerator() { return _dic.Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _dic.Values.GetEnumerator(); } public void Add(TKey item) { _dic.Add(item.GetType(), item); } public void Clear() { _dic.Clear(); } public bool Contains(TKey item) { return _dic.ContainsKey(item.GetType()); } public void CopyTo(TKey[] array, int arrayIndex) { _dic.Values.CopyTo(array, arrayIndex); } public bool Remove(TKey item) { return _dic.Remove(item.GetType()); } } } namespace ThatCore.Lifecycle { internal enum GameState { Invalid, Singleplayer, Multiplayer, DedicatedServer } internal static class LifecycleManager { private static HashSet<Action> OnWorldInitActions = new HashSet<Action>(); public static GameState GameState { get; private set; } public static event Action OnWorldInit; public static event Action OnLateInit; public static event Action OnSinglePlayerInit; public static event Action OnMultiplayerInit; public static event Action OnDedicatedServerInit; public static event Action OnFindSpawnPointFirstTime; public static event Action<ZNetPeer> OnNewConnection; public static event Action OnZnetUpdate; public static void SubscribeToWorldInit(Action onInit) { OnWorldInitActions.Add(onInit); } private static void WorldInit() { Log.Debug?.Log("Running world init actions"); LifecycleManager.OnWorldInit.Raise("Error during world init event"); foreach (Action onWorldInitAction in OnWorldInitActions) { onWorldInitAction(); } } internal static void InitSingleplayer() { GameState = GameState.Singleplayer; WorldInit(); Log.Debug?.Log("Running singleplayer init actions"); LifecycleManager.OnSinglePlayerInit.Raise("Error during singleplayer init event"); LifecycleManager.OnLateInit.Raise("Error during singleplayer late init event"); } internal static void InitMultiplayer() { GameState = GameState.Multiplayer; WorldInit(); Log.Debug?.Log("Running multiplayer init actions"); LifecycleManager.OnMultiplayerInit.Raise("Error during multiplayer init event"); LifecycleManager.OnLateInit.Raise("Error during multiplayer late init event"); } internal static void InitDedicated() { GameState = GameState.DedicatedServer; WorldInit(); Log.Debug?.Log("Running dedicated server init actions"); LifecycleManager.OnDedicatedServerInit.Raise("Error during dedicated server init event"); LifecycleManager.OnLateInit.Raise("Error during dedicated server late init event"); } internal static void InitFindSpawnPointFirstTime() { LifecycleManager.OnFindSpawnPointFirstTime.Raise("Error during OnFindSpawnPointFirstTime event"); } internal static void PeerConnected(ZNetPeer peer) { Log.Trace?.Log($"Running OnNewConnection actions for new peer '{peer.m_playerName}:{peer.m_uid}'"); LifecycleManager.OnNewConnection.Raise(peer); } internal static void ZnetUpdate() { LifecycleManager.OnZnetUpdate.Raise(); } } internal static class PatchManager { public static void ApplyPatches(this Harmony harmony) { harmony.PatchAll(typeof(FejdStartup_TriggerLifecycle_Patch)); harmony.PatchAll(typeof(Game_FindSpawnPoint_TriggerLifecycle_Patch)); harmony.PatchAll(typeof(ZNet_OnNewConnection_TriggerSync_Patch)); harmony.PatchAll(typeof(ZNet_Update_Patch)); } } } namespace ThatCore.Lifecycle.Patches { [HarmonyPatch] internal static class FejdStartup_TriggerLifecycle_Patch { [HarmonyPatch(typeof(FejdStartup), "OnWorldStart")] [HarmonyPrefix] private static void InitSingleplayer() { LifecycleManager.InitSingleplayer(); } [HarmonyPatch(typeof(FejdStartup), "JoinServer")] [HarmonyPrefix] private static void InitMultiplayer() { LifecycleManager.InitMultiplayer(); } [HarmonyPatch(typeof(FejdStartup), "ParseServerArguments")] [HarmonyPrefix] private static void InitDedicated() { LifecycleManager.InitDedicated(); } } [HarmonyPatch(typeof(Game))] internal static class Game_FindSpawnPoint_TriggerLifecycle_Patch { private static bool FirstTime; static Game_FindSpawnPoint_TriggerLifecycle_Patch() { FirstTime = true; LifecycleManager.SubscribeToWorldInit(delegate { FirstTime = true; }); } [HarmonyPostfix] [HarmonyPatch("FindSpawnPoint")] private static void TriggerLifecycle() { if (FirstTime) { FirstTime = false; LifecycleManager.InitFindSpawnPointFirstTime(); } } } [HarmonyPatch(typeof(ZNet))] internal static class ZNet_OnNewConnection_TriggerSync_Patch { [HarmonyPatch("OnNewConnection")] [HarmonyPostfix] private static void TriggerLifecycle(ZNetPeer peer) { LifecycleManager.PeerConnected(peer); } } [HarmonyPatch] internal static class ZNet_Update_Patch { [HarmonyPatch(typeof(ZNet), "Update")] [HarmonyPostfix] private static void HookZnetUpdate() { try { LifecycleManager.ZnetUpdate(); } catch (Exception e) { Log.Error?.Log("Error during ZnetUpdate for ZNet.Update", e); } } } } namespace ThatCore.Utilities.Valheim { internal static class HeightmapBiomeExtensions { public static List<Biome> Split(this Biome biomeMask) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) List<Biome> list = new List<Biome>(); foreach (Biome value in Enum.GetValues(typeof(Biome))) { if ((int)value == 895) { if ((int)biomeMask == 895) { list.Add(value); } } else if ((biomeMask & value) > 0) { list.Add(value); } } return list; } public static Biome ToBitmask(this IList<Biome> biomes) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) Biome val = (Biome)0; for (int i = 0; i < biomes.Count; i++) { val |= biomes[i]; } return val; } public static Biome ToBitmask(this IEnumerable<Biome> biomes) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) Biome val = (Biome)0; foreach (Biome biome in biomes) { val |= biome; } return val; } } internal static class HitDataDamageTypeExtensions { public static DamageType ToBitmask(this IList<DamageType> damageTypes) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) DamageType val = (DamageType)0; for (int i = 0; i < damageTypes.Count; i++) { val |= damageTypes[i]; } return val; } public static DamageType ToBitmask(this IEnumerable<DamageType> damageTypes) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) DamageType val = (DamageType)0; foreach (DamageType damageType in damageTypes) { val |= damageType; } return val; } } internal static class ZdoExtensions { private static int NoiseHash = StringExtensionMethods.GetStableHashCode("noise"); private static int TamedHash = StringExtensionMethods.GetStableHashCode("tamed"); private static int EventCreatureHash = StringExtensionMethods.GetStableHashCode("EventCreature"); private static int HuntPlayerHash = StringExtensionMethods.GetStableHashCode("huntplayer"); private static int FactionHash = StringExtensionMethods.GetStableHashCode("faction"); public static float GetNoise(this ZDO zdo) { return zdo.GetFloat(NoiseHash, 0f); } public static bool GetTamed(this ZDO zdo) { return zdo.GetBool(TamedHash, false); } public static bool GetEventCreature(this ZDO zdo) { return zdo.GetBool(EventCreatureHash, false); } public static Faction? GetFaction(this ZDO zdo) { int num = zdo.GetInt(FactionHash, -1); if (num < 0) { return null; } return (Faction)num; } public static void SetFaction(this ZDO zdo, Faction faction) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected I4, but got Unknown zdo.Set(FactionHash, (int)faction, false); } public static bool GetHuntPlayer(this ZDO zdo) { return zdo.GetBool(HuntPlayerHash, false); } public static void SetHuntPlayer(this ZDO zdo, bool huntPlayer) { zdo.Set(HuntPlayerHash, huntPlayer); } } } namespace ThatCore.Utilities.Files { internal static class FileUtils { public static void EnsureDirectoryExistsForFile(string filePath) { string directoryName = Path.GetDirectoryName(filePath); if (!Directory.Exists(directoryName)) { Log.Trace?.Log("Creating missing folders in path."); Directory.CreateDirectory(directoryName); } } } } namespace ThatCore.Network { internal interface IMessage { void Initialize(); void AfterUnpack(); } internal class Serializer { private class TypeResolver : INodeTypeResolver { public HashSet<Type> WhitelistedTypes { get; } public TypeResolver(HashSet<Type> whitelistedTypes) { WhitelistedTypes = whitelistedTypes; } public bool Resolve(NodeEvent nodeEvent, ref Type currentType) { if (nodeEvent.Tag.IsEmpty || string.IsNullOrWhiteSpace(nodeEvent.Tag.Value)) { return false; } Type type = Type.GetType(nodeEvent.Tag.Value.Substring(1)); if ((object)type == null) { return false; } if (WhitelistedTypes.Any((Type x) => x.IsAssignableFrom(type))) { currentType = type; return true; } return false; } } private HashSet<Type> RegisteredTypes { get; } = new HashSet<Type>(); private ISerializer CachedSerializer { get; set; } private IDeserializer CachedDeserializer { get; set; } private bool RefreshSerializerCache { get; set; } = true; private bool RefreshDeserializerCache { get; set; } = true; public Serializer RegisterType(Type type) { if (RegisteredTypes.Add(type)) { RefreshSerializerCache = true; } return this; } public Serializer RegisterTypes(IEnumerable<Type> types) { foreach (Type type in types) { RegisterType(type); } return this; } public ISerializer GetSerializer() { if (RefreshSerializerCache) { CachedSerializer = ConfigureSerializer(); RefreshSerializerCache = false; } return CachedSerializer; } public IDeserializer GetDeserializer() { if (RefreshDeserializerCache) { CachedDeserializer = ConfigureDeserializer(); RefreshDeserializerCache = false; } return CachedDeserializer; } private ISerializer ConfigureSerializer() { SerializerBuilder serializerBuilder = new SerializerBuilder(); if (RegisteredTypes != null) { foreach (Type registeredType in RegisteredTypes) { serializerBuilder.WithTagMapping("!" + registeredType.AssemblyQualifiedName, registeredType); } } return serializerBuilder.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults | DefaultValuesHandling.OmitEmptyCollections).WithTypeConverter(new YamlEnumWriter()).Build(); } public IDeserializer ConfigureDeserializer() { return new DeserializerBuilder().WithNodeTypeResolver(new TypeResolver(RegisteredTypes)).Build(); } public byte[] SerializeAndCompress(object obj) { string s = GetSerializer().Serialize(obj); using MemoryStream memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(s)); using MemoryStream memoryStream2 = new MemoryStream(); using (GZipStream destination = new GZipStream(memoryStream2, CompressionLevel.Optimal)) { memoryStream.CopyTo(destination); } return memoryStream2.ToArray(); } public object DeserializeCompressed(Type type, Stream stream) { using MemoryStream memoryStream = new MemoryStream(); using (GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress, leaveOpen: true)) { gZipStream.CopyTo(memoryStream); } memoryStream.Position = 0L; using TextReader input = new StreamReader(memoryStream, Encoding.Unicode); return GetDeserializer().Deserialize(input, type); } public object DeserializeCompressed(Type type, byte[] content) { using MemoryStream stream = new MemoryStream(content); using MemoryStream memoryStream = new MemoryStream(); using (GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress, leaveOpen: true)) { gZipStream.CopyTo(memoryStream); } memoryStream.Position = 0L; using TextReader input = new StreamReader(memoryStream, Encoding.Unicode); return GetDeserializer().Deserialize(input, type); } public T DeserializeCompressed<T>(Stream stream) { using MemoryStream memoryStream = new MemoryStream(); using (GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress, leaveOpen: true)) { gZipStream.CopyTo(memoryStream); } memoryStream.Position = 0L; using TextReader input = new StreamReader(memoryStream, Encoding.Unicode); return GetDeserializer().Deserialize<T>(input); } } internal static class SerializerManager { private static Dictionary<Type, Serializer> TypeSerializers { get; } = new Dictionary<Type, Serializer>(); public static Serializer GetSerializer<T>() { return GetSerializer(typeof(T)); } public static Serializer GetSerializer(Type type) { if (TypeSerializers.TryGetValue(type, out var value)) { return value; } return TypeSerializers[type] = new Serializer(); } } [Serializable] internal class SplitMessage { public int SplitIndex { get; set; } public int SplitCount { get; set; } public Guid TransferId { get; set; } public byte[] Content { get; set; } } internal static class SplitMessageManager { internal static int MaxPackageSize = 100000; public static List<SplitMessage> Pack(IMessage message) { byte[] array = SerializerManager.GetSerializer(message.GetType()).SerializeAndCompress(message); int num = Math.Max(1, (int)Math.Ceiling((double)array.Count() / (double)MaxPackageSize)); Guid transferId = Guid.NewGuid(); List<SplitMessage> list = new List<SplitMessage>(num); for (int i = 0; i < num; i++) { int num2 = array.Count() - i * MaxPackageSize; int num3 = Math.Min(num2, MaxPackageSize); int num4 = i * MaxPackageSize; byte[] array2 = new byte[num3]; Array.Copy(array, num4, array2, 0, num3); list.Add(new SplitMessage { SplitIndex = i, SplitCount = num, TransferId = transferId, Content = array2 }); Log.Development?.Log("Created package:" + $"\n\t\ti: {i}" + $"\n\t\tRemaining: {num2}" + $"\n\t\tItems: {num3}" + $"\n\t\tStart: {num4}" + $"\n\t\tSplit Length: {array2.Length}" + $"\n\t\tTransferId: {list.Last().TransferId}"); } Log.Development?.Log($"Split package into {list.Count} pieces"); return list; } public static IMessage CombineAndUnpackSplits(Type type, Dictionary<int, SplitMessage> splits) { using MemoryStream memoryStream = new MemoryStream(splits.OrderBy((KeyValuePair<int, SplitMessage> x) => x.Key).SelectMany((KeyValuePair<int, SplitMessage> x) => x.Value.Content).ToArray()); Log.Development?.Log($"Deserializing '{splits.Count}' split packages with total size of '{memoryStream.Length}' bytes"); Log.Debug?.Log($"Deserializing package size: {memoryStream.Length} bytes"); return SerializerManager.GetSerializer(type).DeserializeCompressed(type, memoryStream) as IMessage; } public static T CombineAndUnpackSplits<T>(Dictionary<int, SplitMessage> splits) { using MemoryStream memoryStream = new MemoryStream(splits.OrderBy((KeyValuePair<int, SplitMessage> x) => x.Key).SelectMany((KeyValuePair<int, SplitMessage> x) => x.Value.Content).ToArray()); Log.Development?.Log($"Deserializing '{splits.Count}' split packages with total size of '{memoryStream.Length}' bytes"); Log.Debug?.Log($"Deserializing package size: {memoryStream.Length} bytes"); return SerializerManager.GetSerializer<T>().DeserializeCompressed<T>(memoryStream); } } internal class YamlEnumWriter : IYamlTypeConverter { public bool Accepts(Type type) { return type.IsEnum; } public object ReadYaml(IParser parser, Type type) { throw new NotImplementedException(); } public void WriteYaml(IEmitter emitter, object value, Type type) { YamlDotNet.Core.Events.Scalar scalar = new YamlDotNet.Core.Events.Scalar(((int)value).ToString()); emitter.Emit(scalar); } } internal class IncomingMessage { public Guid TransferId { get; } public int ExpectedSplits { get; } public DateTimeOffset SessionStarted { get; } = DateTimeOffset.UtcNow; public DateTimeOffset LastSplit { get; set; } = DateTimeOffset.UtcNow; public Dictionary<int, SplitMessage> Splits { get; } public HashSet<int> MissingSplits { get; } public IncomingMessage(SplitMessage initialMessage) { TransferId = initialMessage.TransferId; ExpectedSplits = initialMessage.SplitCount; MissingSplits = Enumerable.Range(0, ExpectedSplits).ToHashSet(); Splits = new Dictionary<int, SplitMessage> { { initialMessage.SplitIndex, initialMessage } }; MissingSplits.Remove(initialMessage.SplitIndex); } public void AddPackage(SplitMessage split) { if (!ValidPackage(split)) { Log.Debug?.Log($"Received unexpected misformed package '{split.TransferId}:{split.SplitIndex}' for session '{split.TransferId}'. Dropping package."); return; } Splits[split.SplitIndex] = split; MissingSplits.Remove(split.SplitIndex); LastSplit = DateTimeOffset.UtcNow; } private bool ValidPackage(SplitMessage split) { if (TransferId == split.TransferId) { return ExpectedSplits == split.SplitCount; } return false; } } internal static class IncomingMessageService { private static Dictionary<Guid, IncomingMessage> MessageCache { get; } private static Dictionary<Guid, DateTimeOffset> RecentlyDroppedTransfers { get; } private static TimeSpan RetentionTime { get; } private static TimeSpan DroppedRetentionTime { get; } private static Queue<IMessage> ReceivedMessages { get; } private static BinaryFormatter BinaryFormatter { get; } static IncomingMessageService() { MessageCache = new Dictionary<Guid, IncomingMessage>(); RecentlyDroppedTransfers = new Dictionary<Guid, DateTimeOffset>(); RetentionTime = TimeSpan.FromMinutes(1.0); DroppedRetentionTime = TimeSpan.FromMinutes(2.0); ReceivedMessages = new Queue<IMessage>(); BinaryFormatter = new BinaryFormatter(); LifecycleManager.OnZnetUpdate += UpdateCache; LifecycleManager.OnZnetUpdate += DispatchMessage; LifecycleManager.SubscribeToWorldInit(delegate { MessageCache.Clear(); RecentlyDroppedTransfers.Clear(); ReceivedMessages.Clear(); }); } private static void UpdateCache() { if (RecentlyDroppedTransfers.Count > 0) { DateTimeOffset dateTimeOffset = DateTimeOffset.UtcNow - DroppedRetentionTime; KeyValuePair<Guid, DateTimeOffset>[] array = RecentlyDroppedTransfers.ToArray(); for (int i = 0; i < array.Length; i++) { KeyValuePair<Guid, DateTimeOffset> keyValuePair = array[i]; if (keyValuePair.Value < dateTimeOffset) { RecentlyDroppedTransfers.Remove(keyValuePair.Key); } } } if (MessageCache.Count <= 0) { return; } DateTimeOffset dateTimeOffset2 = DateTimeOffset.UtcNow - RetentionTime; IncomingMessage[] array2 = MessageCache.Values.ToArray(); foreach (IncomingMessage incomingMessage in array2) { if (incomingMessage.LastSplit < dateTimeOffset2) { Log.Trace?.Log($"Removing dead package session with transfer id '{incomingMessage.TransferId}' and {incomingMessage.Splits.Count}/{incomingMessage.ExpectedSplits} received packages"); MessageCache.Remove(incomingMessage.TransferId); RecentlyDroppedTransfers[incomingMessage.TransferId] = DateTimeOffset.UtcNow; } } } private static void DispatchMessage() { try { IMessage message; lock (ReceivedMessages) { if (ReceivedMessages.Count == 0) { return; } message = ReceivedMessages.Dequeue(); } Log.Development?.Log($"[IncomingMessageService:DispatchMessage] Running followup unpacking of message '{message.GetType()}'"); message?.AfterUnpack(); } catch (Exception e) { Log.Error?.Log("Error while attempting to process received package.", e); } } public static Task ReceiveMessageAsync<T>(ZPackage package) { using MemoryStream serializationStream = new MemoryStream(package.ReadByteArray()); ReceiveMessage<T>(BinaryFormatter.Deserialize(serializationStream) as SplitMessage); return Task.CompletedTask; } public static void ReceiveMessage<T>(SplitMessage split) { ReceiveMessage(typeof(T), split); } public static void ReceiveMessage(Type messageType, SplitMessage splitMessage) { if (RecentlyDroppedTransfers.ContainsKey(splitMessage.TransferId)) { Log.Trace?.Log($"[IncommingMessageService:ReceiveMessage] Received part '{splitMessage.SplitIndex}' of message '{splitMessage.TransferId}' which was recently given up on."); RecentlyDroppedTransfers[splitMessage.TransferId] = DateTimeOffset.UtcNow; return; } if (!MessageCache.TryGetValue(splitMessage.TransferId, out var value)) { value = (MessageCache[splitMessage.TransferId] = new IncomingMessage(splitMessage)); } else { value.AddPackage(splitMessage); } Log.Development?.Log($"[IncommingMessageService:ReceiveMessage] Missing splits {value.MissingSplits.Count} for transfer '{splitMessage.TransferId}'"); if (value.MissingSplits.Count != 0) { return; } IMessage item = SplitMessageManager.CombineAndUnpackSplits(messageType, value.Splits); MessageCache.Remove(value.TransferId); lock (ReceivedMessages) { ReceivedMessages.Enqueue(item); } } } internal class OutgoingMessage { public ZPackage Package { get; set; } public ZRpc ZRpc { get; set; } public string Target { get; set; } public int Retries { get; set; } } internal static class OutgoingMessageService { [HarmonyPatch(typeof(ZNetPeer))] internal static class Cleanup { [HarmonyPatch("Dispose")] [HarmonyPrefix] private static void Dispose(ZNetPeer __instance) { string endPointString = __instance.m_socket.GetEndPointString(); lock (SocketQueues) { SocketQueues.Remove(endPointString); } } } private const int MaxQueueSizeForDispatch = 5000; private static Dictionary<string, Queue<OutgoingMessage>> SocketQueues { get; } private static BinaryFormatter BinaryFormatter { get; } static OutgoingMessageService() { SocketQueues = new Dictionary<string, Queue<OutgoingMessage>>(); BinaryFormatter = new BinaryFormatter(); LifecycleManager.OnZnetUpdate += Dispatch; LifecycleManager.SubscribeToWorldInit(delegate { SocketQueues.Clear(); }); } public static void AddToQueue(IMessage dto, string rpcRoute, ZRpc zrpc) { AddToQueueAsync(dto, rpcRoute, zrpc); } private static Task AddToQueueAsync(IMessage message, string rpcRoute, ZRpc zrpc) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown message.Initialize(); List<SplitMessage> list = SplitMessageManager.Pack(message); List<OutgoingMessage> list2 = new List<OutgoingMessage>(); foreach (SplitMessage item in list) { using MemoryStream memoryStream = new MemoryStream(); BinaryFormatter.Serialize(memoryStream, item); ZPackage val = new ZPackage(); val.Write(memoryStream.ToArray()); list2.Add(new OutgoingMessage { Package = val, ZRpc = zrpc, Target = rpcRoute }); } string endPointString = zrpc.GetSocket().GetEndPointString(); lock (SocketQueues) { if (!SocketQueues.TryGetValue(endPointString, out var value)) { value = (SocketQueues[endPointString] = new Queue<OutgoingMessage>()); } foreach (OutgoingMessage item2 in list2) { value.Enqueue(item2); } } return Task.CompletedTask; } private static void Dispatch() { List<ZNetPeer> peers = ZNet.instance.GetPeers(); int count = peers.Count; for (int i = 0; i < count; i++) { DispatchSocket(peers[i].m_socket); } } private static void DispatchSocket(ISocket socket) { if (socket == null) { return; } string endPointString = socket.GetEndPointString(); Queue<OutgoingMessage> value; lock (SocketQueues) { if (!SocketQueues.TryGetValue(endPointString, out value) || value.Count == 0) { return; } } int sendQueueSize = socket.GetSendQueueSize(); if (sendQueueSize > 5000) { Log.Development?.Log("Package queue size: " + sendQueueSize); return; } OutgoingMessage outgoingMessage; lock (SocketQueues) { outgoingMessage = value.Dequeue(); } try { outgoingMessage.ZRpc.Invoke(outgoingMessage.Target, new object[1] { outgoingMessage.Package }); Log.Trace?.Log($"Sending package with size '{outgoingMessage.Package.Size()}' to '{outgoingMessage.Target}'"); } catch (Exception e) { if (outgoingMessage.Retries > 3) { Log.Warning?.Log("Error while trying to send package. Too many retries, will stop trying.", e); return; } lock (SocketQueues) { outgoingMessage.Retries++; value.Enqueue(outgoingMessage); } } } } } namespace ThatCore.Logging { internal class BepInExLogger : ILogger { private readonly ManualLogSource _logger; public BepInExLogger(ManualLogSource logger) { _logger = logger; } public void LogTrace(string message) { _logger.LogDebug((object)message); } public void LogDebug(string message) { _logger.LogInfo((object)message); } public void LogInfo(string message) { _logger.LogMessage((object)message); } public void LogWarning(string message) { _logger.LogWarning((object)message); } public void LogError(string message) { _logger.LogError((object)message); } public void Log(string message, LogLevel level) { switch (level) { case LogLevel.Error: _logger.LogError((object)message); break; case LogLevel.Warning: _logger.LogWarning((object)message); break; case LogLevel.Info: _logger.LogMessage((object)message); break; case LogLevel.Debug: _logger.LogInfo((object)message); break; case LogLevel.Trace: _logger.LogDebug((object)message); break; } } } internal class ConsoleLogger : ILogger { public void LogTrace(string message) { Console.WriteLine("[Trace : ThatCore] " + message); } public void LogDebug(string message) { Console.WriteLine("[Debug : ThatCore] " + message); } public void LogInfo(string message) { Console.WriteLine("[Info : ThatCore] " + message); } public void LogWarning(string message) { Console.WriteLine("[Warning: ThatCore] " + message); } public void LogError(string message) { Console.WriteLine("[Error : ThatCore] " + message); } public void Log(string message, LogLevel level) { switch (level) { case LogLevel.Error: LogError(message); break; case LogLevel.Warning: LogWarning(message); break; case LogLevel.Info: LogInfo(message); break; case LogLevel.Debug: LogDebug(message); break; case LogLevel.Trace: LogTrace(message); break; } } } internal interface ILogger { void LogTrace(string message); void LogDebug(string message); void LogInfo(string message); void LogWarning(string message); void LogError(string message); void Log(string message, LogLevel logLevel); } internal static class Log { private static ILogger _logger; private static LogWriter _traceLogger; private static LogWriter _debugLogger; private static LogWriter _infoLogger; private static LogWriter _warningLogger; private static LogWriter _errorLogger; public static bool DevelopmentEnabled { get; set; } public static bool TraceEnabled { get; set; } public static bool DebugEnabled { get; set; } public static LogWriter Development { get { if (!DevelopmentEnabled) { return null; } return _debugLogger; } } public static LogWriter Trace { get { if (!TraceEnabled) { return null; } return _traceLogger; } } public static LogWriter Debug { get { if (!DebugEnabled) { return null; } return _debugLogger; } } public static LogWriter Info => _infoLogger; public static LogWriter Warning => _warningLogger; public static LogWriter Error => _errorLogger; public static void SetLogger(ILogger logger) { _logger = logger; _traceLogger = new LogWriter(logger, LogLevel.Trace); _debugLogger = new LogWriter(logger, LogLevel.Debug); _infoLogger = new LogWriter(logger, LogLevel.Info); _warningLogger = new LogWriter(logger, LogLevel.Warning); _errorLogger = new LogWriter(logger, LogLevel.Error); } } internal class LogWriter { private ILogger _logger; private LogLevel _logLevel; public LogWriter(ILogger logger, LogLevel logLevel) { _logger = logger; _logLevel = logLevel; } public void Log(string message) { _logger.Log(message, _logLevel); } public void Log(string message, Exception e) { _logger.Log(message + "\n" + e?.Message + "\n" + e?.StackTrace, _logLevel); } } internal enum LogLevel { Invalid, Error, Warning, Info, Debug, Trace } } namespace ThatCore.Extensions { internal static class CodeInstructionExtensions { public static CodeInstruction GetLdlocFromStLoc(this CodeInstruction instruction) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown if (instruction.opcode == OpCodes.Stloc_0) { return new CodeInstruction(OpCodes.Ldloc_0, (object)null); } if (instruction.opcode == OpCodes.Stloc_1) { return new CodeInstruction(OpCodes.Ldloc_1, (object)null); } if (instruction.opcode == OpCodes.Stloc_2) { return new CodeInstruction(OpCodes.Ldloc_2, (object)null); } if (instruction.opcode == OpCodes.Stloc_3) { return new CodeInstruction(OpCodes.Ldloc_3, (object)null); } if (instruction.opcode == OpCodes.Stloc_S) { return new CodeInstruction(OpCodes.Ldloca_S, instruction.operand); } if (instruction.opcode == OpCodes.Stloc) { return new CodeInstruction(OpCodes.Ldloc, instruction.operand); } throw new ArgumentException($"Unexpected instruction '{instruction}' encountered. Expected an {OpCodes.Stloc}."); } public static CodeInstruction GetStlocFromLdloc(this CodeInstruction instruction) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown if (instruction.opcode == OpCodes.Ldloc_0) { return new CodeInstruction(OpCodes.Stloc_0, (object)null); } if (instruction.opcode == OpCodes.Ldloc_1) { return new CodeInstruction(OpCodes.Stloc_1, (object)null); } if (instruction.opcode == OpCodes.Ldloc_2) { return new CodeInstruction(OpCodes.Stloc_2, (object)null); } if (instruction.opcode == OpCodes.Ldloc_3) { return new CodeInstruction(OpCodes.Stloc_3, (object)null); } if (instruction.opcode == OpCodes.Ldloca_S) { return new CodeInstruction(OpCodes.Stloc_S, instruction.operand); } if (instruction.opcode == OpCodes.Ldloc) { return new CodeInstruction(OpCodes.Stloc, instruction.operand); } throw new ArgumentException($"Unexpected instruction '{instruction}' encountered. Expected an {OpCodes.Ldloc}."); } } internal static class CodeMatcherExtensions { public static CodeMatcher GetPosition(this CodeMatcher codeMatcher, out int position) { position = codeMatcher.Pos; return codeMatcher; } public static CodeMatcher InsertAndAdvance(this CodeMatcher codeMatcher, OpCode opcode, object operand = null) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown codeMatcher.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(opcode, operand) }); return codeMatcher; } public static CodeMatcher InsertAndAdvance(this CodeMatcher codeMatcher, MethodInfo method) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown codeMatcher.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Call, (object)method) }); return codeMatcher; } public static CodeMatcher InsertAndAdvance<T>(this CodeMatcher codeMatcher, string methodName) { return codeMatcher.InsertAndAdvance(AccessTools.Method(typeof(T), methodName, (Type[])null, (Type[])null)); } public static CodeMatcher InsertAndAdvance<T>(this CodeMatcher codeMatcher, T action) where T : Delegate { return codeMatcher.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { Transpilers.EmitDelegate<T>(action) }); } public static CodeMatcher GetOpcode(this CodeMatcher codeMatcher, out OpCode opcode) { opcode = codeMatcher.Opcode; return codeMatcher; } public static CodeMatcher GetInstruction(this CodeMatcher codeMatcher, out CodeInstruction instruction) { instruction = codeMatcher.Instruction; return codeMatcher; } public static CodeMatcher GetOperand(this CodeMatcher codeMatcher, out object operand) { operand = codeMatcher.Operand; return codeMatcher; } public static CodeMatcher Print(this CodeMatcher codeMatcher, int before, int after) { if (!Log.DevelopmentEnabled) { return codeMatcher; } for (int i = -before; i <= after; i++) { int num = i; int num2 = codeMatcher.Pos + num; if (num2 > 0) { if (num2 >= codeMatcher.Length) { break; } try { CodeInstruction val = codeMatcher.InstructionAt(num); Log.Development?.Log($"[{num}] " + ((object)val).ToString()); } catch (Exception ex) { Log.Error?.Log(ex.Message); } } } return codeMatcher; } } internal static class DictionaryExtensions { public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dic, TKey key) where TValue : new() { if (!dic.TryGetValue(key, out var value)) { value = (dic[key] = new TValue()); } return value; } public static TValue GetOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dic, TKey key) { if (dic.TryGetValue(key, out var value)) { return value; } return default(TValue); } } internal static class EnumExtensions { public static List<TEnum> Split<TEnum>(this TEnum enumVal) where TEnum : struct, Enum { int num = Convert.ToInt32(enumVal); List<TEnum> list = new List<TEnum>(); foreach (TEnum value in Enum.GetValues(typeof(TEnum))) { if ((num & Convert.ToInt32(value)) > 0) { list.Add(value); } } return list; } } internal static class EventExtensions { public static void Raise<T>(this Action<T> events, T arg, string messageOnError = null) { if (events == null) { return; } Delegate[] invocationList = events.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action<T> action = (Action<T>)invocationList[i]; try { action(arg); } catch (Exception e) { string text = action.Method.DeclaringType.Assembly.GetName().Name + "." + action.Method.DeclaringType.Name + "." + action.Method.Name; Log.Error?.Log("[" + text + "]: " + (messageOnError ?? string.Empty), e); } } } public static void Raise(this Action events, string messageOnError = null) { if (events == null) { return; } Delegate[] invocationList = events.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(); } catch (Exception e) { string text = action.Method.DeclaringType.Assembly.GetName().Name + "." + action.Method.DeclaringType.Name + "." + action.Method.Name; Log.Error?.Log("[" + text + "]: " + (messageOnError ?? string.Empty), e); } } } } internal static class ExpressionExtensions { public static PropertyInfo GetPropertyInfo<T, K>(this Expression<Func<T, K>> propertySelector) { return (propertySelector.Body as MemberExpression)?.Member as PropertyInfo; } } internal static class ICollectionExtensions { public static void AddNullSafe<T>(this ICollection<T> collection, T newEntry) where T : class { if (collection != null && newEntry != null) { Object val = (Object)(object)((newEntry is Object) ? newEntry : null); if (val == null || Object.op_Implicit(val)) { collection.Add(newEntry); } } } public static string Join(this ICollection<float> list) { return list.Join((float x) => x.ToString(CultureInfo.InvariantCulture)); } public static string Join(this ICollection<double> list) { return list.Join((double x) => x.ToString(CultureInfo.InvariantCulture)); } public static string Join<T>(this ICollection<T> list) { if (list == null || list.Count == 0) { return ""; } return string.Join(", ", list); } public static string Join<T>(this ICollection<T> list, Func<T, string> tostring) { if (list == null || list.Count == 0) { return string.Empty; } return string.Join(", ", list.Select((T x) => tostring(x))); } public static string Join<T, K>(this ICollection<T> list, Func<T, K> selector) { if (list == null || list.Count == 0) { return string.Empty; } return string.Join(", ", list.Select((T x) => selector(x))); } } internal static class IEnumerableExtensions { public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action) { foreach (T item in enumerable) { action(item); } } } internal static class ListExtensions { public static void AddOrReplaceByType<T, TEntry>(this List<T> list, TEntry entry) where T : class where TEntry : class, T { Type typeFromHandle = typeof(TEntry); int num = -1; for (int i = 0; i < list.Count; i++) { if (list[i].GetType() == typeFromHandle) { num = i; break; } } if (num < 0) { list.Add((T)entry); } else { list[num] = (T)entry; } } public static void AddOrConfigureByType<T, TEntry>(this List<T> list, Action<TEntry> configure) where T : class where TEntry : T, new() { TEntry val = list.OfType<TEntry>().FirstOrDefault(); if (val != null) { configure(val); return; } TEntry val2 = new TEntry(); configure(val2); list.Add((T)(object)val2); } } internal enum Separator { Comma, Slash, Dot, Newline } internal static class StringExtensions { private static readonly char[] Comma = new char[1] { ',' }; private static readonly char[] Slash = new char[2] { '/', '\\' }; private static readonly char[] Dot = new char[1] { '.' }; private static readonly char[] Newline = new char[1] { '\n' }; public static List<string> SplitBy(this string value, Separator separator, bool toUpper = false) { return value.SplitBy(separator switch { Separator.Comma => Comma, Separator.Slash => Slash, Separator.Dot => Dot, Separator.Newline => Newline, _ => throw new NotSupportedException("Separator"), }, toUpper).ToList(); } public static List<string> SplitByComma(this string value, bool toUpper = false) { return value.SplitBy(Comma, toUpper).ToList(); } public static string[] SplitBySlash(this string value, bool toUpper = false) { return value.SplitBy(Slash, toUpper).ToArray(); } public static IEnumerable<string> SplitBy(this string value, char[] chars, bool toUpper = false) { if (value == null) { return Enumerable.Empty<string>(); } string[] array = value.Trim().Split(chars, StringSplitOptions.RemoveEmptyEntries); if (array == null || array.Length == 0) { return Enumerable.Empty<string>(); } return array.Select(Clean); string Clean(string x) { string text = x.Trim(); if (toUpper) { return text.ToUpperInvariant(); } return text; } } public static bool IsEmpty(this string s) { return string.IsNullOrWhiteSpace(s); } public static bool IsNotEmpty(this string s) { if (string.IsNullOrWhiteSpace(s)) { return false; } return true; } public static string Normalize(this string s) { return s?.Trim() ?? string.Empty; } } internal static class StringHashExtensions { public static int HashInteger(this string str) { int num = 5381; int num2 = num; for (int i = 0; i < str.Length && str[i] != 0; i += 2) { num = ((num << 5) + num) ^ str[i]; if (i == str.Length - 1 || str[i + 1] == '\0') { break; } num2 = ((num2 << 5) + num2) ^ str[i + 1]; } return num + num2 * 1566083941; } public static long Hash(this string str) { long num = 5381L; long num2 = num; for (int i = 0; i < str.Length && str[i] != 0; i += 2) { num = ((num << 5) + num) ^ str[i]; if (i == str.Length - 1 || str[i + 1] == '\0') { break; } num2 = ((num2 << 5) + num2) ^ str[i + 1]; } return num + num2 * 1566083941; } public static long Hash(this IEnumerable<string> strs) { long num = 0L; foreach (long item in from x in strs.Select((string x) => x.Hash()).Distinct() orderby x select x) { num += item; } return num; } } internal static class UnityObjectExtensions { private static Regex NameRegex = new Regex("^[^$(]*(?=$|[(])", RegexOptions.Compiled); public static bool IsNull(this Object obj) { if (obj == (Object)null || !Object.op_Implicit(obj)) { return true; } return false; } public static bool IsNotNull(this Object obj) { if (obj != (Object)null && Object.op_Implicit(obj)) { return true; } return false; } public static string GetName(this Object obj) { if (!obj.IsNotNull()) { return string.Empty; } return obj.name; } public static string GetCleanedName(this Object obj, bool toUpper = false) { if (obj.IsNull()) { return null; } Match match = NameRegex.Match(obj.name); if (!match.Success) { return null; } string text = match.Value.Trim(); if (toUpper) { text = text?.ToUpperInvariant(); } return text; } } } namespace ThatCore.Config { public interface IFileMappingBuilder { } public interface IFileMappingBuilder<TTarget> : IFileMappingBuilder { IFileMappingBuilder<TTarget> Map<TOption>(string configName, TOption defaultValue, string description, Action<TOption, TTarget> fileToTargetMapping); IFileMappingBuilder<TSubTarget> Using<TSubTarget>(Func<TTarget, TSubTarget> selector, bool skipIfNull = true); } public interface IHaveOptions<TTarget, TSource> { IOptionBuilder<TTarget, TSource> AddOption(); } public interface IOptionBuilder<TTarget, TSource> : IOptionBuilderActions<TTarget, TSource> { IOptionBuilder<TTarget, TSource> AddOption(); } public interface IOptionBuilderActions<TTarget, TSource> { IOptionBuilder<TTarget, TSource> FromFile(Action<IFileMappingBuilder<TTarget>> configure); IOptionBuilder<TTarget, TSource> ToFile(Action<ISourceMappingBuilder<TSource>> configure); } public interface ISourceMappingBuilder { } public interface ISourceMappingBuilder<TSource> : ISourceMappingBuilder { ISourceMappingBuilder<TSource> Map<T>(string configName, Func<TSource, T> selector); ISourceMappingBuilder<TSubSource> Using<TSubSource>(Func<TSource, TSubSource> selector, bool skipIfNull = true); } } namespace ThatCore.Config.Toml { internal interface ITomlSetting { string Name { get; } string Description { get; } Type SettingType { get; } bool IsSet { get; set; } object GetValue(); ITomlSetting Clone(bool includeValue = true); } internal interface ITomlSetting<T> : ITomlSetting { T Value { get; set; } T DefaultValue { get; set; } } internal class TomlConfig { public Dictionary<string, ITomlSetting> Settings = new Dictionary<string, ITomlSetting>(); public Dictionary<TomlPathSegment, TomlConfig> Sections = new Dictionary<TomlPathSegment, TomlConfig>(); public TomlPathSegment PathSegment { get; set; } = TomlPathSegment.Default; public TomlConfig CreateSubsection(TomlPathSegment segment) { if (Sections.TryGetValue(segment, out var value)) { return value; } TomlPathSegment pathSegment = new TomlPathSegment(segment.SegmentType, segment.Name, PathSegment); return Sections[segment] = new TomlConfig { PathSegment = pathSegment }; } public void SetSettings(IReadOnlyDictionary<string, ITomlSetting> settings) { foreach (KeyValuePair<string, ITomlSetting> setting in settings) { Settings[setting.Key] = setting.Value; } } public void SetSetting(string name, ITomlSetting setting) { Settings[name] = setting; } public T GetSettingValue<T>(string name, T defaultValue = default(T)) { if (Settings.TryGetValue(name, out var value) && value is TomlSetting<T> tomlSetting) { return tomlSetting; } return defaultValue; } public TomlSetting<T> GetSetting<T>(string name) { if (Settings.TryGetValue(name, out var value) && value is TomlSetting<T> result) { return result; } return null; } public bool TryGetSetting<T>(string name, out TomlSetting<T> setting) { if (Settings.TryGetValue(name, out var value) && value is TomlSetting<T> tomlSetting) { setting = tomlSetting; return true; } setting = null; return false; } public TomlConfig SetSetting<T>(string name, T value, string description = null) { if (Settings.TryGetValue(name, out var value2) && value2 is TomlSetting<T> tomlSetting) { tomlSetting.Set(value); } else { Settings[name] = new TomlSetting<T>(name, default(T), description) { Value = value }; } return this; } public static TomlConfig FindSection(TomlConfig root, List<TomlPathSegment> path) { TomlConfig value = root; foreach (TomlPathSegment item in path) { if (!value.Sections.TryGetValue(item, out value)) { return null; } } return value; } } internal static class TomlConfigMerger { public static void Merge(TomlConfig source, TomlConfig destination) { MergeSettings(source, destination); foreach (KeyValuePair<TomlPathSegment, TomlConfig> section in source.Sections) { TomlConfig destination2 = destination.CreateSubsection(section.Key); Merge(section.Value, destination2); } } private static void MergeSettings(TomlConfig source, TomlConfig destination) { foreach (KeyValuePair<string, ITomlSetting> setting in source.Settings) { if (setting.Value.IsSet) { destination.SetSetting(setting.Key, setting.Value); } } } } internal static class TomlConfigWriter { public static string WriteToString(TomlConfig config, TomlWriterSettings settings) { StringBuilder stringBuilder = new StringBuilder(); if (!string.IsNullOrWhiteSpace(settings.Header)) { foreach (string item in settings.Header.SplitBy(Separator.Newline)) { stringBuilder.AppendLine(item); } stringBuilder.AppendLine(); } WriteSubsectionsRecursively(stringBuilder, config, settings); return stringBuilder.ToString(); } private static void WriteSubsectionsRecursively(StringBuilder builder, TomlConfig config, TomlWriterSettings settings) { foreach (KeyValuePair<TomlPathSegment, TomlConfig> section in config.Sections) { Dictionary<string, ITomlSetting> settings2 = section.Value.Settings; if (settings2 != null && settings2.Count > 0) { builder.AppendLine("[" + section.Value.PathSegment.GetPath() + "]"); WriteSettings(builder, section.Value, settings); builder.AppendLine(); } WriteSubsectionsRecursively(builder, section.Value, settings); } } private static void WriteSettings(StringBuilder builder, TomlConfig config, TomlWriterSettings options) { foreach (KeyValuePair<string, ITomlSetting> setting in config.Settings) { string key = setting.Key; ITomlSetting value = setting.Value; if (!value.IsSet) { continue; } if (options != null && options.AddComments) { foreach (string item in value.Description.SplitBy(Separator.Newline)) { builder.AppendLine("# " + item); } } try { string text = value.Write(); builder.AppendLine(key + " = " + text); } catch (Exception e) { Log.Warning?.Log("Error while attempting to write setting '" + key + "' to file '" + options.FileName + "'. Skipping setting.", e); } } } } internal class TomlLine { public string FileName { get; set; } public int LineNr { get; set; } public string Value { get; set; } } internal static class TomlPath { public static TomlPathSegment Create(TomlPathSegmentType segmentType, string name = null) { return new TomlPathSegment(segmentType, name); } public static TomlPathSegment Chain(this TomlPathSegment parent, TomlPathSegmentType segmentType, string name = null) { return new TomlPathSegment(segmentType, name, parent); } } internal class TomlPathSegment : IEquatable<TomlPathSegment> { internal static TomlPathSegment Default { get; } = new TomlPathSegment(TomlPathSegmentType.Start); public string Name { get; } public string NormalizedName { get; } public TomlPathSegmentType SegmentType { get; } public TomlPathSegment Parent { get; } public TomlPathSegment(TomlPathSegmentType type, string name = null, TomlPathSegment parent = null) { Name = name; SegmentType = type; Parent = parent; NormalizedName = ThatCore.Extensions.StringExtensions.Normalize(Name); } public string GetPath() { StringBuilder stringBuilder = new StringBuilder(); TomlPathSegment tomlPathSegment = this; do { if (tomlPathSegment.SegmentType != TomlPathSegmentType.Start) { if (stringBuilder.Length > 0) { stringBuilder.Insert(0, "."); } stringBuilder.Insert(0, tomlPathSegment.Name); } tomlPathSegment = tomlPathSegment.Parent; } while (tomlPathSegment != null); return stringBuilder.ToString(); } public bool Equals(TomlPathSegment other) { if (base.Equals(other)) { return true; } if (SegmentType == other.SegmentType) { return NormalizedName == other.NormalizedName; } return false; } public bool Equals(TomlPathSegment x, TomlPathSegment y) { return x.Equals(y); } public override int GetHashCode() { return NormalizedName.GetHashCode() + SegmentType.GetHashCode(); } } internal static class TomlPathSegmentExtensions { public static TomlPathSegment FindClosest(this TomlPathSegment segment, TomlPathSegmentType segmentType) { while (segment != null) { if (segment.SegmentType == segmentType) { return segment; } segment = segment.Parent; } return null; } } internal enum TomlPathSegmentType { Invalid, Start, Named, Collection } internal class TomlSetting<T> : ITomlSetting<T>, ITomlSetting { private T value; public string Name { get; set; } public Type SettingType { get; } = typeof(T); public T Value { get { return value; } set { this.value = value; IsSet = true; } } public T DefaultValue { get; set; } public string Description { get; set; } public bool IsSet { get; set; } internal TomlSetting() { } public TomlSetting(string name, T defaultValue, string description = null) { Name = name; DefaultValue = defaultValue; Description = description; } public object GetValue() { return Value; } public void Set(T value) { Value = value; IsSet = true; } public static implicit operator T(TomlSetting<T> entry) { if (!entry.IsSet) { return entry.DefaultValue; } return entry.Value; } public ITomlSetting Clone(bool includeValue = true) { TomlSetting<T> tomlSetting = new TomlSetting<T>(Name, DefaultValue, Description); if (includeValue && IsSet) { tomlSetting.IsSet = true; tomlSetting.Value = Value; } return tomlSetting; } } } namespace ThatCore.Config.Toml.Writers { internal class BoolWriter : ValueWriter<bool> { protected override string WriteInternal(ITomlSetting<bool> entry) { if (!entry.Value) { return "false"; } return "true"; } } internal class NullableBoolWriter : ValueWriter<bool?> { protected override string WriteInternal(ITomlSetting<bool?> entry) { if (entry.IsSet && entry.Value.HasValue) { if (!entry.Value.Value) { return "false"; } return "true"; } return string.Empty; } } internal class DoubleWriter : ValueWriter<double> { protected override string WriteInternal(ITomlSetting<double> entry) { return entry.Value.ToString(CultureInfo.InvariantCulture); } } internal class NullableDoubleWriter : ValueWriter<double?> { protected override string WriteInternal(ITomlSetting<double?> entry) { if (entry.IsSet && entry.Value.HasValue) { return entry.Value.Value.ToString(CultureInfo.InvariantCulture); } return string.Empty; } } internal class EnumListWriter<T> : ValueWriter<List<T>> where T : struct, Enum { protected override string WriteInternal(ITomlSetting<List<T>> entry) { if (entry.IsSet && entry.Value != null) { return GeneralExtensions.Join<string>(entry.Value.Select((T x) => x.ToString()), (Func<string, string>)null, ", "); } return string.Empty; } } internal class EnumWriter<T> : ValueWriter<T> where T : struct, Enum { protected override string WriteInternal(ITomlSetting<T> entry) { return entry.Value.ToString(); } } internal class NullableEnumWriter<T> : ValueWriter<T?> where T : struct, Enum { protected override string WriteInternal(ITomlSetting<T?> entry) { if (entry.IsSet && entry.Value.HasValue) { return entry.Value.Value.ToString(); } return string.Empty; } } internal class FloatWriter : ValueWriter<float> { protected override string WriteInternal(ITomlSetting<float> entry) { return entry.Value.ToString(CultureInfo.InvariantCulture); } } internal class NullableFloatWriter : ValueWriter<float?> { protected override string WriteInternal(ITomlSetting<float?> entry) { if (entry.IsSet && entry.Value.HasValue) { return entry.Value.Value.ToString(CultureInfo.InvariantCulture); } return string.Empty; } } internal class IntListWriter : ValueWriter<List<int>> { protected override string WriteInternal(ITomlSetting<List<int>> entry) { if (entry.IsSet && entry.Value != null) { return GeneralExtensions.Join<string>(entry.Value.Select((int x) => x.ToString(CultureInfo.InvariantCulture)), (Func<string, string>)null, ", "); } return string.Empty; } } internal class IntWriter : ValueWriter<int> { protected override string WriteInternal(ITomlSetting<int> entry) { return entry.Value.ToString(CultureInfo.InvariantCulture); } } internal class NullableIntWriter : ValueWriter<int?> { protected override string WriteInternal(ITomlSetting<int?> entry) { if (entry.IsSet && entry.Value.HasValue) { return entry.Value.Value.ToString(CultureInfo.InvariantCulture); } return string.Empty; } } internal interface IValueWriter { string Write(ITomlSetting entry); } internal abstract class ValueWriter<T> : IValueWriter { protected Type WriterType { get; } protected ValueWriter() { WriterType = typeof(T); } public string Write(ITomlSetting entry) { if (entry is ITomlSetting<T> entry2) { return WriteInternal(entry2); } throw new InvalidOperationException("Unable to write config entry with type " + typeof(T).Name + " using writer " + GetType().Name + "."); } protected abstract string WriteInternal(ITomlSetting<T> entry); } internal class StringListWriter : ValueWriter<List<string>> { protected override string WriteInternal(ITomlSetting<List<string>> entry) { if (entry.IsSet && entry.Value != null) { return entry.Value.Join(); } return string.Empty; } } internal class StringWriter : ValueWriter<string> { protected override string WriteInternal(ITomlSetting<string> entry) { return entry.Value ?? string.Empty; } } internal static class TomlWriterFactory { private static Dictionary<Type, IValueWriter> writers; private static Type ListType; private static Type EnumListWriterType; private static Type EnumWriterType; private static Type NullableEnumWriterType; static TomlWriterFactory() { writers = new Dictionary<Type, IValueWriter>(); ListType = typeof(List<>); EnumListWriterType = typeof(EnumListWriter<>); EnumWriterType = typeof(EnumWriter<>); NullableEnumWriterType = typeof(NullableEnumWriter<>); writers = new Dictionary<Type, IValueWriter> { { typeof(bool), new BoolWriter() }, { typeof(bool?), new NullableBoolWriter() }, { typeof(double), new DoubleWriter() }, { typeof(double?), new NullableDoubleWriter() }, { typeof(float), new FloatWriter() }, { typeof(float?), new NullableFloatWriter() }, { typeof(int), new IntWriter() }, { typeof(int?), new NullableIntWriter() }, { typeof(string), new StringWriter() }, { typeof(List<int>), new IntListWriter() }, { typeof(List<string>), new StringListWriter() } }; } public static string Write<T>(this T entry) where T : ITomlSetting { if (writers.TryGetValue(entry.SettingType, out var value)) { return value.Write(entry); } if (entry.SettingType.IsGenericType && entry.SettingType.GetGenericTypeDefinition() == ListType) { if (entry.SettingType.GenericTypeArguments.First().IsEnum) { Type type = entry.SettingType.GenericTypeArguments.First(); value = (IValueWriter)Activator.CreateInstance(EnumListWriterType.MakeGenericType(type)); writers[entry.SettingType] = value; return value.Write(entry); } throw new NotSupportedException("Unable to write config entries of type '" + entry.SettingType.Name + "'."); } if (entry.SettingType.IsEnum) { value = (IValueWriter)Activator.CreateInstance(EnumWriterType.MakeGenericType(entry.SettingType)); writers[entry.SettingType] = value; return value.Write(entry); } Type? underlyingType = Nullable.GetUnderlyingType(entry.SettingType); if ((object)underlyingType != null && underlyingType.IsEnum) { Type underlyingType2 = Nullable.GetUnderlyingType(entry.SettingType); value = (IValueWriter)Activator.CreateInstance(NullableEnumWriterType.MakeGenericType(underlyingType2)); writers[entry.SettingType] = value; return value.Write(entry); } throw new NotSupportedException(); } } internal class TomlWriterSettings { public string Header { get; set; } public bool AddComments { get; set; } public string FileName { get; set; } } } namespace ThatCore.Config.Toml.Schema { internal interface ITomlSchemaLayer { ITomlSchemaNode ParentNode { get; } ITomlSchemaCollectionLayer AsIndexed(); ITomlSchemaNamedLayer AsNamed(); } internal interface ITomlSchemaNamedLayer : ITomlSchemaLayer { Dictionary<string, ITomlSchemaNode> Nodes { get; } } internal interface ITomlSchemaCollectionLayer : ITomlSchemaLayer { ITomlSchemaNode Node { get; } } internal interface ITomlSchemaNode { Dictionary<string, ITomlSetting> Settings { get; } ITomlSchemaLayer NextLayer { get; } ITomlSchemaLayer ParentLayer { get; } TomlPathSegment PathSegment { get; } } internal abstract class TomlSchemaLayer : ITomlSchemaLayer { public ITomlSchemaNode ParentNode { get; set; } public ITomlSchemaCollectionLayer AsIndexed() { return this as ITomlSchemaCollectionLayer; } public ITomlSchemaNamedLayer AsNamed() { return this as ITomlSchemaNamedLayer; } } internal class SchemaNamedLayer : TomlSchemaLayer, ITomlSchemaNamedLayer, ITomlSchemaLayer { public Dictionary<string, ITomlSchemaNode> Nodes { get; set; } = new Dictionary<string, ITomlSchemaNode>(); } internal class NamedSchemaNode : ITomlSchemaNode { public string Name { get; set; } public Dictionary<string, ITomlSetting> Settings { get; set; } = new Dictionary<string, ITomlSetting>(); public ITomlSchemaLayer NextLayer { get; set; } public ITomlSchemaLayer ParentLayer { get; set; } public TomlPathSegment PathSegment { get; set; } public NamedSchemaNode(TomlPathSegment segment) { Name = segment.Name; PathSegment = segment; } } internal class SchemaCollectionLayer : TomlSchemaLayer, ITomlSchemaCollectionLayer, ITomlSchemaLayer { public ITomlSchemaNode Node { get; set; } = new SchemaCollectionNode(); } internal class SchemaCollectionNode : ITomlSchemaNode { public Dictionary<string, ITomlSetting> Settings { get; set; } public ITomlSchemaLayer NextLayer { get; set; } public ITomlSchemaLayer ParentLayer { get; set; } public TomlPathSegment PathSegment { get; set; } } internal interface ITomlSchemaBuilder { ITomlSchemaLayerBuilder FirstLayer { get; } ITomlNamedSchemaLayerBuilder SetLayerAsNamed(); ITomlSchemaCollectionLayerBuilder SetLayerAsCollection(); ITomlSchemaLayer Build(); } internal interface ITomlSchemaLayerBuilder { ITomlSchemaLayer Build(ITomlSchemaNode parent); } internal interface ITomlNamedSchemaLayerBuilder : ITomlSchemaLayerBuilder { ITomlSchemaNodeBuilder AddNode(string name); ITomlSchemaNamedLayer BuildNamed(ITomlSchemaNode parent); } internal interface ITomlSchemaCollectionLayerBuilder : ITomlSchemaLayerBuilder { ITomlSchemaNodeBuilder GetNode(); ITomlSchemaCollectionLayer BuildCollection(ITomlSchemaNode parent); } internal interface ITomlSchemaNodeBuilder { ITomlSchemaNodeBuilder AddSetting(ITomlSetting entry); ITomlSchemaNodeBuilder AddSetting<T>(string name, T defaultValue = default(T), string description = null); ITomlNamedSchemaLayerBuilder SetNextLayerAsNamed(); ITomlSchemaCollectionLayerBuilder SetNextLayerAsCollection(); ITomlSchemaNode Build(ITomlSchemaLayer parent); } internal class TomlSchemaBuilder : ITomlSchemaBuilder { private ITomlSchemaLayerBuilder _firstLayer = new TomlNamedSchemaLayerBuilder(); public ITomlSchemaLayerBuilder FirstLayer => _firstLayer; public ITomlNamedSchemaLayerBuilder SetLayerAsNamed() { return (ITomlNamedSchemaLayerBuilder)(_firstLayer = new TomlNamedSchemaLayerBuilder()); } public ITomlSchemaCollectionLayerBuilder SetLayerAsCollection() { return (ITomlSchemaCollectionLayerBuilder)(_firstLayer = new TomlSchemaCollectionLayerBuilder()); } public ITomlSchemaLayer Build() { return _firstLayer.Build(null); } } internal class TomlNamedSchemaLayerBuilder : ITomlNamedSchemaLayerBuilder, ITomlSchemaLayerBuilder { private Dictionary<string, TomlSchemaNodeBuilder> Nodes { get; } = new Dictionary<string, TomlSchemaNodeBuilder>(); public ITomlSchemaNodeBuilder AddNode(string name) { string key = ThatCore.Extensions.StringExtensions.Normalize(name); if (Nodes.TryGetValue(key, out var value)) { return value; } return Nodes[key] = new TomlSchemaNodeBuilder(name); } public ITomlSchemaLayer Build(ITomlSchemaNode parent) { return BuildNamed(parent); } public ITomlSchemaNamedLayer BuildNamed(ITomlSchemaNode parent) { SchemaNamedLayer layer = new SchemaNamedLayer(); layer.ParentNode = parent; layer.Nodes = Nodes.ToDictionary((KeyValuePair<string, TomlSchemaNodeBuilder> x) => ThatCore.Extensions.StringExtensions.Normalize(x.Value.Name), (KeyValuePair<string, TomlSchemaNodeBuilder> x) => x.Value.Build(layer)); return layer; } } internal class TomlSchemaCollectionLayerBuilder : ITomlSchemaCollectionLayerBuilder, ITomlSchemaLayerBuilder { private TomlSchemaNodeBuilder Node { get; } = new TomlSchemaNodeBuilder(); public ITomlSchemaNodeBuilder GetNode() { return Node; } public ITomlSchemaLayer Build(ITomlSchemaNode parent) { return BuildCollection(parent); } public ITomlSchemaCollectionLayer BuildCollection(ITomlSchemaNode parent) { SchemaCollectionLayer schemaCollectionLayer = new SchemaCollectionLayer(); schemaCollectionLayer.ParentNode = parent; schemaCollectionLayer.Node = Node.Build(schemaCollectionLayer); return schemaCollectionLayer; } } internal class TomlSchemaNodeBuilder : ITomlSchemaNodeBuilder { public string Name { get; } public Dictionary<string, ITomlSetting> Settings { get; } = new Dictionary<string, ITomlSetting>(); public ITomlSchemaLayerBuilder NextLayer { get; set; } public TomlSchemaNodeBuilder(string name = null) { Name = name; } public ITomlSchemaNodeBuilder AddSetting(ITomlSetting entry) { Settings[ThatCore.Extensions.StringExtensions.Normalize(entry.Name)] = entry; return this; } public ITomlSchemaNodeBuilder AddSetting<T>(string name, T defaultValue = default(T), string description = null) { TomlSetting<T> tomlSetting = new TomlSetting<T>(name, defaultValue, description) { Value = defaultValue }; Settings[ThatCore.Extensions.StringExtensions.Normalize(tomlSetting.Name)] = tomlSetting; return this; } public ITomlSchemaCollectionLayerBuilder SetNextLayerAsCollection() { return (ITomlSchemaCollectionLayerBuilder)(NextLayer = new TomlSchemaCollectionLayerBuilder()); } public ITomlNamedSchemaLayerBuilder SetNextLayerAsNamed() { return (ITomlNamedSchemaLayerBuilder)(NextLayer = new TomlNamedSchemaLayerBuilder()); } public ITomlSchemaNode Build(ITomlSchemaLayer parent) { if (Name == null) { SchemaCollectionNode schemaCollectionNode = new SchemaCollectionNode(); schemaCollectionNode.Settings = new Dictionary<string, ITomlSetting>(Settings); schemaCollectionNode.ParentLayer = parent; schemaCollectionNode.NextLayer = NextLayer?.Build(schemaCollectionNode); schemaCollectionNode.PathSegment = new TomlPathSegment(TomlPathSegmentType.Collection, null, parent?.ParentNode?.PathSegment); return schemaCollectionNode; } NamedSchemaNode namedSchemaNode = new NamedSchemaNode(new TomlPathSegment(TomlPathSegmentType.Named, Name, parent?.ParentNode?.PathSegment)); namedSchemaNode.Settings = new Dictionary<string, ITomlSetting>(Settings); namedSchemaNode.ParentLayer = parent; namedSchemaNode.NextLayer = NextLayer?.Build(namedSchemaNode); return namedSchemaNode; } } internal static class TomlSchemaFileLoader { private static readonly Regex SectionHeader = new Regex("^\\s*[[](.+)[]]", RegexOptions.Compiled); private static readonly Regex SectionSanitizer = new Regex("[^\\p{L}\\d.\\[\\]_]|\\s", RegexOptions.Compiled); private static readonly Regex SettingIdentifier = new Regex("^\\s*([\\p{L}.\\d]+)\\s*=(.*)", RegexOptions.Compiled); private static readonly Regex CommentIdentifier = new Regex("^\\s*(//|#|--)", RegexOptions.Compiled); public static TomlConfig LoadFile(string path, ITomlSchemaLayer schema) { return Load(File.ReadAllLines(path), Path.GetFileName(path), schema); } public static TomlConfig Load(IList<string> lines, string fileName, ITomlSchemaLayer schema) { TomlConfig tomlConfig = new TomlConfig(); int i = 1; string value = null; TomlConfig tomlConfig2 = tomlConfig; ITomlSchemaNode tomlSchemaNode = null; TomlLine tomlLine = new TomlLine(); for (; i <= lines.Count; i++) { string text = lines[i - 1]; if (string.IsNullOrWhiteSpace(text) || CommentIdentifier.IsMatch(text)) { continue; } tomlLine.FileName = fileName; tomlLine.LineNr = i; tomlLine.Value = text; Match match = SectionHeader.Match(text); if (match.Success) { string value2 = match.Groups[1].Value; string text2 = SectionSanitizer.Replace(value2, ""); if (string.IsNullOrWhiteSpace(text2)) { Log.Warning?.Log($"{fileName}, Line {i}: Section name '{value2}' empty after sanitizing. Unable to load section."); value = null; continue; } value = text2; List<TomlPathSegment> list = ConvertToPath(text2, schema, tomlLine); tomlConfig2 = GetOrCreateSection(tomlConfig, list); tomlSchemaNode = FindNode(schema, list); if (tomlConfig2 == null) { Log.Warning?.Log($"{fileName}, Line {i}: Unable to find valid config for section '{text2}'."); } continue; } Match match2 = SettingIdentifier.Match(text); if (match2.Success) { string value3 = match2.Groups[1].Value; string value4 = match2.Groups[2].Value; ITomlSetting value5; ITomlSetting value6; if (string.IsNullOrWhiteSpace(value)) { Log.Warning?.Log($"{fileName}, Line {i}: Setting '{value3}' was not inside any section. Ignoring setting."); } else if (tomlConfig2 == null) { Log.Warning?.Log($"{fileName}, Line {i}: Skipping setting '{value3}' due to not finding valid config for section."); } else if (tomlConfig2.Settings.TryGetValue(ThatCore.Extensions.StringExtensions.Normalize(value3), out value5)) { ParseSetting(value5, value4, tomlLine); } else if (tomlSchemaNode != null && tomlSchemaNode.Settings.TryGetValue(ThatCore.Extensions.StringExtensions.Normalize(value3), out value6)) { ITomlSetting setting = value6.Clone(includeValue: false); ParseSetting(setting, value4, tomlLine); tomlConfig2.SetSetting(value3, setting); } else { Log.Warning?.Log($"{fileName}, Line {i}: Setting '{value3}' did not match any known setting for section '{tomlConfig2.PathSegment.GetPath()}'. Ignoring setting."); } } else { Log.Warning?.Log($"{fileName}, Line {i}: Unknown text '{text}' did not match any known format. Ignoring setting."); } } return tomlConfig; static void ParseSetting(ITomlSetting entry, string value7, TomlLine fileLine) { try { entry.Read(new TomlLine { FileName = fileLine.FileName, LineNr = fileLine.LineNr, Value = value7 }); } catch (Exception e) { Log.Warning?.Log(fileLine.FileName + ", Line " + fileLine.Value + ": Unexpected error during parsing.", e); } } } private static List<TomlPathSegment> ConvertToPath(string header, ITomlSchemaLayer schema, TomlLine fileLine) { List<string> list = header.SplitBy(Separator.Dot); List<TomlPathSegment> list2 = new List<TomlPathSegment>(list.Count + 1); ITomlSchemaLayer tomlSchemaLayer = schema; TomlPathSegment tomlPathSegment = TomlPathSegment.Default; list2.Add(tomlPathSegment); foreach (string item in list) { if (!(tomlSchemaLayer is ITomlSchemaNamedLayer tomlSchemaNamedLayer)) { if (tomlSchemaLayer is ITomlSchemaCollectionLayer tomlSchemaCollectionLayer) { tomlPathSegment = new TomlPathSegment(TomlPathSegmentType.Collection, item, tomlPathSegment); list2.Add(tomlPathSegment); tomlSchemaLayer = tomlSchemaCollectionLayer.Node.NextLayer; } continue; } if (tomlSchemaNamedLayer.Nodes.TryGetValue(ThatCore.Extensions.StringExtensions.Normalize(item), out var value)) { tomlPathSegment = new TomlPathSegment(TomlPathSegmentType.Named, item, tomlPathSegment); list2.Add(tomlPathSegment); tomlSchemaLayer = value.NextLayer; continue; } Log.Warning?.Log($"{fileLine.FileName}, Line {fileLine.LineNr}: Unable to find valid section with name '{header}'."); return null; } return list2; } private static TomlConfig GetOrCreateSection(TomlConfig configFile, List<TomlPathSegment> path) { TomlConfig tomlConfig = configFile; foreach (TomlPathSegment item in path.Skip(1)) { tomlConfig = tomlConfig.CreateSubsection(item); } return tomlConfig; } private static ITomlSchemaNode FindNode(ITomlSchemaLayer schema, List<TomlPathSegment> sectionParts) { ITomlSchemaLayer tomlSchemaLayer = schema; ITomlSchemaNode result = null; foreach (TomlPathSegment item in sectionParts.Skip(1)) { if (!(tomlSchemaLayer is ITomlSchemaCollectionLayer tomlSchemaCollectionLayer)) { if (!(tomlSchemaLayer is ITomlSchemaNamedLayer tomlSchemaNamedLayer)) { return null; } if (tomlSchemaNamedLayer.Nodes.TryGetValue(item.NormalizedName, out var value)) { tomlSchemaLayer = value.NextLayer; result = value; } } else { tomlSchemaLayer = tomlSchemaCollectionLayer.Node.NextLayer; result = tomlSchemaCollectionLayer.Node; } } return result; } } internal static class TomlSchemaWriter { public static string WriteToString(ITomlSchemaLayer schema, TomlWriterSettings settings) { StringBuilder stringBuilder = new StringBuilder(); if (!string.IsNullOrWhiteSpace(settings.Header)) { foreach (string item in settings.Header.SplitBy(Separator.Newline)) { stringBuilder.AppendLine(item); } stringBuilder.AppendLine(); } WriteSectionsRecursively(stringBuilder, schema, settings, string.Empty); return stringBuilder.ToString(); } private static void WriteSectionsRecursively(StringBuilder builder, ITomlSchemaLayer schema, TomlWriterSettings settings, string currentPath) { if (!(schema is ITomlSchemaNamedLayer tomlSchemaNamedLayer)) { if (schema is ITomlSchemaCollectionLayer tomlSchemaCollectionLayer) { string text = "ID"; ITomlSchemaNode node = tomlSchemaCollectionLayer.Node; string text2 = (string.IsNullOrWhiteSpace(currentPath) ? text : string.Join(".", currentPath, text)); WriteSectionHeader(text2); WriteOptions(builder, node, settings); builder.AppendLine(); if (node.NextLayer != null) { WriteSectionsRecursively(builder, node.NextLayer, settings, text2); } return; } throw new NotSupportedException(schema.GetType().FullName); } foreach (KeyValuePair<string, ITomlSchemaNode> node2 in tomlSchemaNamedLayer.Nodes) { string key = node2.Key; ITomlSchemaNode value = node2.Value; string text3 = (string.IsNullOrWhiteSpace(currentPath) ? key : string.Join(".", currentPath, key)); WriteSectionHeader(text3); WriteOptions(builder, value, settings); builder.AppendLine(); if (value.NextLayer != null) { WriteSectionsRecursively(builder, value.NextLayer, settings, text3); } } void WriteSectionHeader(string sectionName) { builder.AppendLine("[" + sectionName + "]"); } } private static void WriteOptions(StringBuilder builder, ITomlSchemaNode node, TomlWriterSettings settings) { foreach (KeyValuePair<string, ITomlSetting> setting in node.Settings) { string key = setting.Key; ITomlSetting value = setting.Value; if (settings != null && settings.AddComments) { foreach (string item in value.Description.SplitBy(Separator.Newline)) { builder.AppendLine("# " + item); } } try { string text = value.Write(); builder.AppendLine(key + " = " + text); } catch (Exception e) { Log.Warning?.Log("Error while attempting to write setting '" + key + "' to file '" + settings.FileName + "'. Skipping setting.", e); } } } } } namespace ThatCore.Config.Toml.Parsers { internal class BoolParser : ValueParser<bool> { protected override void ParseInternal(ITomlSetting<bool> entry, TomlLine line) { if (bool.TryParse(line.Value, out var result)) { entry.Value = result; entry.IsSet = true; } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{line.Value}' as a boolean. Verify spelling. Valid values are \"true\" and \"false\"."); entry.IsSet = false; } } } internal class NullableBoolParser : ValueParser<bool?> { protected override void ParseInternal(ITomlSetting<bool?> entry, TomlLine line) { if (bool.TryParse(line.Value, out var result)) { entry.Value = result; entry.IsSet = true; } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{line.Value}' as a boolean. Verify spelling. Valid values are \"true\" and \"false\"."); entry.IsSet = false; } } } internal class DoubleParser : ValueParser<double> { protected override void ParseInternal(ITomlSetting<double> entry, TomlLine line) { if (double.TryParse(line.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { entry.Value = result; entry.IsSet = true; } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{line.Value}' as a double. Verify spelling. Valid example values are \"0.5\" and \"123\"."); entry.IsSet = false; } } } internal class NullableDoubleParser : ValueParser<double?> { protected override void ParseInternal(ITomlSetting<double?> entry, TomlLine line) { if (double.TryParse(line.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { entry.Value = result; entry.IsSet = true; } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{line.Value}' as a double. Verify spelling. Valid example values are \"0.5\" and \"123\"."); entry.IsSet = false; } } } internal class EnumListParser<T> : ValueParser<List<T>> where T : struct, Enum { protected override void ParseInternal(ITomlSetting<List<T>> entry, TomlLine line) { List<string> list = ThatCore.Extensions.StringExtensions.SplitByComma(line.Value); List<T> list2 = new List<T>(list.Count); foreach (string item in list) { if (Enum.TryParse<T>(item, ignoreCase: true, out var result)) { list2.Add(result); } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{item}'. Verify spelling."); } } if (list.Count > 0 && list2.Count == 0) { entry.IsSet = false; return; } entry.Value = list2; entry.IsSet = true; } } internal class EnumParser<T> : ValueParser<T> where T : struct, Enum { protected override void ParseInternal(ITomlSetting<T> entry, TomlLine line) { if (Enum.TryParse<T>(line.Value, ignoreCase: true, out var result)) { entry.Value = result; entry.IsSet = true; } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{line.Value}'. Verify spelling."); entry.IsSet = false; } } } internal class NullableEnumParser<T> : ValueParser<T?> where T : struct, Enum { protected override void ParseInternal(ITomlSetting<T?> entry, TomlLine line) { if (Enum.TryParse<T>(line.Value, ignoreCase: true, out var result)) { entry.Value = result; entry.IsSet = true; } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{line.Value}'. Verify spelling."); entry.IsSet = false; } } } internal class FloatParser : ValueParser<float> { protected override void ParseInternal(ITomlSetting<float> entry, TomlLine line) { if (float.TryParse(line.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { entry.Value = result; entry.IsSet = true; } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{line.Value}'. Expected decimal number. Eg., 0.5 or 3."); entry.IsSet = false; } } } internal class NullableFloatParser : ValueParser<float?> { protected override void ParseInternal(ITomlSetting<float?> entry, TomlLine line) { if (float.TryParse(line.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { entry.Value = result; entry.IsSet = true; } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{line.Value}'. Expected decimal number. Eg., 0.5 or 3."); entry.IsSet = false; } } } internal class IntListParser : ValueParser<List<int>> { protected override void ParseInternal(ITomlSetting<List<int>> entry, TomlLine line) { List<string> list = ThatCore.Extensions.StringExtensions.SplitByComma(line.Value); List<int> list2 = (from x in list.Select(delegate(string x) { if (int.TryParse(x, out var result)) { return result; } Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{x}'. Expected integer number. Eg., 0, 1 or 3."); return (int?)null; }) where x.HasValue select x.Value).ToList(); if (list.Count > 0 && list2.Count == 0) { entry.IsSet = false; return; } entry.Value = list2; entry.IsSet = true; } } internal class IntParser : ValueParser<int> { protected override void ParseInternal(ITomlSetting<int> entry, TomlLine line) { if (int.TryParse(line.Value, out var result)) { entry.Value = result; entry.IsSet = true; } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{line.Value}'. Expected integer number. Eg., 0, 1 or 3."); entry.IsSet = false; } } } internal class NullableIntParser : ValueParser<int?> { protected override void ParseInternal(ITomlSetting<int?> entry, TomlLine line) { if (int.TryParse(line.Value, out var result)) { entry.Value = result; entry.IsSet = true; } else { Log.Warning?.Log($"{line.FileName}, Line {line.LineNr}: Unable to parse '{line.Value}'. Expected integer number. Eg., 0, 1 or 3."); entry.IsSet = false; } } } internal interface IValueParser { void Parse(ITomlSetting entry, TomlLine line); } internal abstract class ValueParser<T> : IValueParser { protected Type ParserType { get; } private Type ConfigEntryParserType { get; } protected ValueParser() { ParserType = typeof(T); ConfigEntryParserType = typeof(ITomlSetting<>).GetGenericTypeDefinition().MakeGenericType(ParserType); } public void Parse(ITomlSetting entry, TomlLine line) { if (entry is ITomlSetting<T> tomlSetting) { if (string.IsNullOrWhiteSpace(line.Value)) { tomlSetting.IsSet = true; } else { ParseInternal(tomlSetting, line); } return; } throw new InvalidOperationException("Unable to parse config entry with type " + typeof(T).Name + " using parser " + GetType().Name + "."); } protected abstract void ParseInternal(ITomlSetting<T> entry, TomlLine line); } internal class StringListParser : ValueParser<List<string>> { protected override void ParseInternal(ITomlSetting<List<string>> entry, TomlLine line) { entry.Value = ThatCore.Extensions.StringExtensions.SplitByComma(line.Value); entry.IsSet = true; } } internal class StringParser : ValueParser<string> { protected override void ParseInternal(ITomlSetting<string> entry, TomlLine line) { entry.Value = line.Value.Trim(); entry.IsSet = true; } } internal static class TomlParserFactory { private static Dictionary<Type, IValueParser> parsers; private static Type ListType; private static Type EnumListParserType; private static Type EnumParserType; private static Type NullableEnumParserType; static TomlParserFactory() { parsers = new Dictionary<Type, IValueParser>(); ListType = typeof(List<>); EnumListParserType = typeof(EnumListParser<>); EnumParserType = typeof(EnumParser<>); NullableEnumParserType = typeof(NullableEnumParser<>); parsers = new Dictionary<Type, IValueParser> { { typeof(bool), new BoolParser() }, { typeof(double), new DoubleParser() }, { typeof(float), new FloatParser() }, { typeof(int), new IntParser() }, { typeof(bool?), new NullableBoolParser() }, { typeof(double?), new NullableDoubleParser() }, { typeof(float?), new NullableFloatParser() }, { typeof(int?), new NullableIntParser() }, { typeof(string), new StringParser() }, { typeof(List<string>), new StringListParser() }, { typeof(List<int>), new IntListParser() } }; } public static void Read<T>(this T entry, TomlLine line) where T : ITomlSetting { if (parsers.TryGetValue(entry.SettingType, out var value)) { value.Parse(entry, line); return; } if (entry.SettingType.IsGenericType && entry.SettingType.GetGenericTypeDefinition() == ListType) { if (entry.SettingType.GenericTypeArguments.First().IsEnum) { Type type = entry.SettingType.GenericTypeArguments.First(); value = (IValueParser)Activator.CreateInstance(EnumListParserType.MakeGenericType(type)); parsers[entry.SettingType] = value; value.Parse(entry, line); return; } throw new NotSupportedException("Unable to parse config entries of type '" + entry.SettingType.Name + "'."); } if (entry.SettingType.IsEnum) { value = (IValueParser)Activator.CreateInstance(EnumParserType.MakeGenericType(entry.SettingType)); parsers[entry.SettingType] = value; value.Parse(entry, line); return; } Type? underlyingType = Nullable.GetUnderlyingType(entry.SettingType); if ((object)underlyingType != null && underlyingType.IsEnum) { Type underlyingType2 = Nullable.GetUnderlyingType(entry.SettingType); value = (IValueParser)Activator.CreateInstance(NullableEnumParserType.MakeGenericType(underlyingType2)); parsers[entry.SettingType] = value; value.Parse(entry, line); return; } throw new NotSupportedException(); } } } namespace ThatCore.Config.Toml.Mapping { internal class ConfigToObjectMapper<T> { public List<TomlPathSegment> Path { get; set; } public MappingInstantiation<T> Instantiation { get; set; } public List<IMappingInstantiationForParent<T>> SubInstantiations { get; set; } public T Execute(TomlConfig config) { TomlConfig tomlConfig = config; foreach (TomlPathSegment item in Path) { if (item.SegmentType == TomlPathSegmentType.Collection) { throw new NotSupportedException("Cannot handle enumerated segments in initial path."); } tomlConfig = tomlConfig.Sections[item]; } T val = Instantiation.Execute(tomlConfig); foreach (IMappingInstantiationForParent<T> subInstantiation in SubInstantiations) { subInstantiation.Execute(val, tomlConfig); } return val; } } internal class MappingInstantiation<T> { public Func<TomlConfig, T> Instantiation; public List<Action<TomlConfig, T>> InstanceActions { get; set; } = new List<Action<TomlConfig, T>>(); public T Execute(TomlConfig config) { T val = Instantiation(config); foreach (Action<TomlConfig, T> instanceAction in InstanceActions) { instanceAction(config, val); } return val; } } internal class MappingInstantiation<TParent, T> { public Func<TParent, TomlConfig, T> Instantiation; public List<Action<TomlConfig, T>> InstanceActions { get; set; } = new List<Action<TomlConfig, T>>(); public T Execute(TParent parent, TomlConfig config) { T val = Instantiation(parent, config); foreach (Action<TomlConfig, T> instanceAction in InstanceActions) { instanceAction(config, val); } return val; } } internal interface IMappingInstantiationForParent<TParent> { List<TomlPathSegment> SubPath { get; set; } void Execute(TParent parent, TomlConfig config); } internal class MappingInstantiationForParent<TParent, T> : IMappingInstantiationForParent<TParent> { public MappingInstantiation<TParent, T> Instantiation; public Action<TParent, T, TomlConfig> Action; public List<TomlPathSegment> SubPath { get; set; } = new List<TomlPathSegment>(); public List<IMappingInstantiationForParent<T>> SubInstantiations { get; set; } = new List<IMappingInstantiationForParent<T>>(); public void Execute(TParent parent, TomlConfig config) { RecursiveScan(SubPath, 0, config); void RecursiveScan(List<TomlPathSegment> path, int depth, TomlConfig section) { if (depth >= path.Count) { T val = Instantiation.Execute(parent, section); if (Action != null) { Action(parent, val, section); } { foreach (IMappingInstantiationForParent<T> subInstantiation in SubInstantiations) { subInstantiation.Execute(val, section); } return; } } TomlPathSegment tomlPathSegment = path[depth]; if (tomlPathSegment.SegmentType == TomlPathSegmentType.Collection) { foreach (KeyValuePair<TomlPathSegment, TomlConfig> section in section.Sections) { RecursiveScan(path, ++depth, section.Value); } return; } if (section.Sections.TryGetValue(tomlPathSegment, out var value)) { RecursiveScan(path, ++depth, value); } } } } internal class FileMappingBuilder<TTarget> : IFileMappingBuilder<TTarget>, IFileMappingBuilder, IHaveMapping<TTarget>, IMappingExecuter<TTarget> { public Action<TomlConfig, TTarget> Mapping { get; set; } private ITomlSchemaNodeBuilder NodeBuilder { get; } private List<IHaveMapping<TTarget>> SubBuilders { get; } = new List<IHaveMapping<TTarget>>(); public FileMappingBuilder(ITomlSchemaNodeBuilder nodeBuilder) { NodeBuilder = nodeBuilder; } public IFileMappingBuilder<TTarget> Map<TOption>(string confi