using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Permissions; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CommunityPatchExtras.Common; using CommunityPatchExtras.Patches; using HarmonyLib; using Jotunn; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using SimpleJson; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("CommunityPatchExtras")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CommunityPatchExtras")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")] [assembly: AssemblyFileVersion("0.1.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.1.0")] namespace CommunityPatchExtras { internal static class ConfigFileWatcher { private class WatchEntry { internal DateTime LastWriteUTC; internal long FileLength; internal Action Callback; } internal class ConfigFileWatcherBehaviour : MonoBehaviour { private float nextPollTime; public void Update() { if (!(Time.unscaledTime < nextPollTime)) { nextPollTime = Time.unscaledTime + PollInterval(); Poll(); } } private static float PollInterval() { if (ValConfig.ConfigPollIntervalSeconds == null) { return 30f; } return ValConfig.ConfigPollIntervalSeconds.Value; } private static void Poll() { if (WatchedFiles.Count == 0) { return; } string[] array = WatchedFiles.Keys.ToArray(); foreach (string text in array) { if (!WatchedFiles.TryGetValue(text, out var value) || !File.Exists(text)) { continue; } FileInfo fileInfo = new FileInfo(text); DateTime lastWriteTimeUtc = fileInfo.LastWriteTimeUtc; long length = fileInfo.Length; if (!(lastWriteTimeUtc == value.LastWriteUTC) || length != value.FileLength) { value.LastWriteUTC = lastWriteTimeUtc; value.FileLength = length; try { value.Callback?.Invoke(text); } catch (Exception ex) { Logger.LogWarning("ConfigFileWatcher callback for " + text + " threw: " + ex.Message); } } } } } private const float FallbackPollSeconds = 30f; private static readonly Dictionary WatchedFiles = new Dictionary(StringComparer.OrdinalIgnoreCase); private static ConfigFileWatcherBehaviour watchProcess; internal static void Initialize() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)watchProcess != (Object)null)) { GameObject val = new GameObject("ValheimCommunityPatchExtras_ConfigFileWatcher"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; watchProcess = val.AddComponent(); Logger.LogDebug("ConfigFileWatcher initialized."); } } internal static void Register(string fullPath, Action onChanged) { if (!string.IsNullOrEmpty(fullPath)) { DateTime lastWriteUTC = DateTime.MinValue; long fileLength = 0L; if (File.Exists(fullPath)) { FileInfo fileInfo = new FileInfo(fullPath); lastWriteUTC = fileInfo.LastWriteTimeUtc; fileLength = fileInfo.Length; } WatchedFiles[fullPath] = new WatchEntry { LastWriteUTC = lastWriteUTC, FileLength = fileLength, Callback = onChanged }; Logger.LogDebug("ConfigFileWatcher watching " + fullPath); } } internal static void RefreshStamp(string fullPath) { if (string.IsNullOrEmpty(fullPath) || !WatchedFiles.TryGetValue(fullPath, out var value)) { return; } try { FileInfo fileInfo = new FileInfo(fullPath); value.LastWriteUTC = fileInfo.LastWriteTimeUtc; value.FileLength = fileInfo.Length; } catch (Exception) { value.LastWriteUTC = DateTime.MinValue; value.FileLength = 0L; } } } internal static class ConfigNetwork { private static bool initialized; private static Harmony harmony; private static readonly HashSet usedRpcNames = new HashSet(StringComparer.OrdinalIgnoreCase); private const byte EditProtocolVersion = 1; internal static bool ServerConfigsSynced { get; private set; } internal static event Action EditResult; internal static void Init() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown if (initialized) { return; } initialized = true; SynchronizationManager.OnConfigurationSynchronized += OnConfigurationSynchronized; try { harmony = new Harmony("MidnightsFX.ValheimCommunityPatchExtras.config"); harmony.Patch((MethodBase)AccessTools.Method(typeof(ZNet), "Shutdown", (Type[])null, (Type[])null), new HarmonyMethod(typeof(ConfigNetwork), "ResetOnWorldUnload", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Logger.LogWarning("Could not patch ZNet.Shutdown for config sync teardown: " + ex.Message); } } internal static void RegisterFile(YamlConfigFile file) { //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Expected O, but got Unknown //IL_00ea: Expected O, but got Unknown //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Expected O, but got Unknown //IL_01a5: Expected O, but got Unknown if (file == null || file.Sync == ConfigSyncMode.LocalOnly) { return; } if (string.IsNullOrEmpty(file.RpcName)) { file.RpcName = "ValheimCommunityPatchExtras_" + Path.GetFileNameWithoutExtension(file.FileName); } if (!usedRpcNames.Add(file.RpcName)) { Logger.LogError("Config RPC name '" + file.RpcName + "' is already in use; " + file.FileName + " will not be synced. Give it an explicit RpcName."); return; } file.Rpc = NetworkManager.Instance.AddRPC(file.RpcName, (CoroutineHandler)((long sender, ZPackage package) => OnServerReceive(file, sender, package)), (CoroutineHandler)((long sender, ZPackage package) => OnClientReceive(file, sender, package))); SynchronizationManager.Instance.AddInitialSynchronization(file.Rpc, (Func)(() => SendFileAsZPackage(file))); if (!file.AllowAdminEdit) { return; } string text = file.RpcName + "_Edit"; if (!usedRpcNames.Add(text)) { Logger.LogError("Config RPC name '" + text + "' is already in use; " + file.FileName + " will not accept admin edits."); } else { file.EditRpc = NetworkManager.Instance.AddRPC(text, (CoroutineHandler)((long sender, ZPackage package) => OnServerReceiveEdit(file, sender, package)), (CoroutineHandler)((long sender, ZPackage package) => OnClientReceiveEditResult(file, sender, package))); } } internal static bool RequestEdit(YamlConfigFile file, string yaml, out string refusal) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown refusal = ""; if (file == null || file.EditRpc == null) { refusal = "this config cannot be edited remotely."; return false; } if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { refusal = "not connected to a server as a client."; return false; } if (SynchronizationManager.Instance != null && !SynchronizationManager.Instance.PlayerIsAdmin) { refusal = "only server admins can change this."; return false; } ZPackage val = new ZPackage(); val.Write((byte)1); val.Write(yaml); file.EditRpc.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), val); return true; } private static IEnumerator OnServerReceiveEdit(YamlConfigFile file, long sender, ZPackage package) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { yield break; } byte b = package.ReadByte(); if (b != 1) { SendEditResult(sender, file, accepted: false, $"This server expects edit protocol v{(byte)1}, " + $"the sender used v{b}. Update so both sides match."); yield break; } string yaml = package.ReadString(); string message; if (!SenderIsAdmin(sender)) { Logger.LogWarning($"Rejecting an edit of {file.FileName} from non-admin peer {sender}."); SendEditResult(sender, file, accepted: false, "Only server admins can change " + file.FileName + "."); } else if (!YamlConfigManager.ApplyEdited(file, yaml, out message)) { Logger.LogWarning($"Admin peer {sender} sent a {file.FileName} that was rejected: {message}"); SendEditResult(sender, file, accepted: false, message); } else { Logger.LogInfo($"{file.FileName} was replaced by admin peer {sender}."); SendEditResult(sender, file, accepted: true, message); yield return null; } } private static IEnumerator OnClientReceiveEditResult(YamlConfigFile file, long sender, ZPackage package) { if (package.ReadByte() != 1) { ConfigNetwork.EditResult?.Invoke(file, arg2: false, "The server answered with an edit protocol this build does not understand."); yield break; } bool arg = package.ReadBool(); string arg2 = package.ReadString(); ConfigNetwork.EditResult?.Invoke(file, arg, arg2); yield return null; } private static void SendEditResult(long peer, YamlConfigFile file, bool accepted, string message) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown if (file.EditRpc != null) { ZPackage val = new ZPackage(); val.Write((byte)1); val.Write(accepted); val.Write(message ?? ""); file.EditRpc.SendPackage(peer, val); } } internal static void Broadcast(YamlConfigFile file) { if (file != null && file.Rpc != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { file.Rpc.SendPackage(ZNet.instance.m_peers, SendFileAsZPackage(file)); } } internal static void ResetServerSyncState() { ServerConfigsSynced = false; } private static void OnConfigurationSynchronized(object sender, EventArgs e) { ServerConfigsSynced = true; } private static void ResetOnWorldUnload() { ResetServerSyncState(); } private static IEnumerator OnServerReceive(YamlConfigFile file, long sender, ZPackage package) { Logger.LogDebug($"Peer {sender} sent {file.FileName}; this config is server-authoritative, ignoring."); yield break; } private static IEnumerator OnClientReceive(YamlConfigFile file, long sender, ZPackage package) { string text = package.ReadString(); file.LoadFrom(text, ConfigOrigin.ServerSync); if (file.ClientWritesToDisk) { YamlConfigManager.WriteRawToDisk(file, text); } yield return null; } private static ZPackage SendFileAsZPackage(YamlConfigFile file) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); try { val.Write(File.Exists(file.Path) ? File.ReadAllText(file.Path) : file.SerializeCurrent()); } catch (Exception ex) { Logger.LogError("Could not read " + file.FileName + " to send to peers: " + ex.Message); val.Write(""); } return val; } internal static bool SenderIsAdmin(long sender) { ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(sender) : null); if (val == null || val.m_socket == null) { return false; } return ZNet.instance.IsAdmin(val.m_socket.GetHostName()); } } internal class ValidationReport { internal readonly List Warnings = new List(); internal readonly List Errors = new List(); internal bool HasErrors => Errors.Count > 0; internal ValidationReport Warn(string message) { Warnings.Add(message); return this; } internal ValidationReport Error(string message) { Errors.Add(message); return this; } internal ValidationReport Absorb(ValidationReport other) { if (other == null) { return this; } Warnings.AddRange(other.Warnings); Errors.AddRange(other.Errors); return this; } } internal static class ConfigValidation { internal static float Prefer(float bepInExValue, float yamlValue, float sentinel = 0f) { if (bepInExValue != sentinel) { return bepInExValue; } return yamlValue; } internal static string SuggestKey(string unknownKey, IEnumerable knownKeys) { if (string.IsNullOrEmpty(unknownKey) || knownKeys == null) { return ""; } int num = Math.Max(1, unknownKey.Length / 4); string text = null; int num2 = int.MaxValue; foreach (string knownKey in knownKeys) { if (!string.IsNullOrEmpty(knownKey)) { int num3 = Distance(unknownKey, knownKey); if (num3 < num2) { num2 = num3; text = knownKey; } } } if (text == null || num2 > num) { return ""; } return " Did you mean '" + text + "'?"; } private static int Distance(string a, string b) { a = a.ToLowerInvariant(); b = b.ToLowerInvariant(); if (a == b) { return 0; } if (a.Length == 0) { return b.Length; } if (b.Length == 0) { return a.Length; } int[] array = new int[b.Length + 1]; int[] array2 = new int[b.Length + 1]; for (int i = 0; i <= b.Length; i++) { array[i] = i; } for (int j = 1; j <= a.Length; j++) { array2[0] = j; for (int k = 1; k <= b.Length; k++) { int num = ((a[j - 1] != b[k - 1]) ? 1 : 0); array2[k] = Math.Min(Math.Min(array2[k - 1] + 1, array[k] + 1), array[k - 1] + num); } int[] array3 = array; array = array2; array2 = array3; } return array[b.Length]; } } internal static class YamlConfigManager { internal static YamlConfigFile ExampleFile; internal static YamlConfigFile> ExampleSavedDataFile; private const string ExampleHeader = "#################################################\n# CommunityPatchExtras - Example settings\n#\n# Entries is a map of : . The key is the identity used elsewhere in this mod, so\n# renaming one is a breaking change; DisplayName is only a label and is safe to change.\n#\n# DisplayName string Shown to the player.\n# Multiplier float Scales the thing. 1.0 is unchanged. Range 0.1 - 10.\n# Mode enum Off | Add | Multiply\n# Prefabs list Prefab names this entry applies to. Unknown names are warned about\n# and skipped, they do not break the file.\n#\n# A typo in a key or an enum value costs you that one setting and logs a warning naming the\n# line; the rest of the file still loads.\n#################################################"; private static bool initialized; private static readonly List Files = new List(); private static readonly Dictionary ByPath = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static IEnumerable All => Files; private static void RegisterConfigFiles() { RegisterExampleConfigs(); } private static void RegisterExampleConfigs() { ExampleFile = Register(new YamlConfigFile("ExampleSettings.yaml") { Header = "#################################################\n# CommunityPatchExtras - Example settings\n#\n# Entries is a map of : . The key is the identity used elsewhere in this mod, so\n# renaming one is a breaking change; DisplayName is only a label and is safe to change.\n#\n# DisplayName string Shown to the player.\n# Multiplier float Scales the thing. 1.0 is unchanged. Range 0.1 - 10.\n# Mode enum Off | Add | Multiply\n# Prefabs list Prefab names this entry applies to. Unknown names are warned about\n# and skipped, they do not break the file.\n#\n# A typo in a key or an enum value costs you that one setting and logs a warning naming the\n# line; the rest of the file still loads.\n#################################################", Defaults = () => ExampleData.BuildDefaults(), Apply = delegate(ExampleSettings parsed) { ExampleData.Current = parsed; }, Validate = ExampleData.Validate, NeedsPrefabs = true, SchemaVersion = 1, GetSchemaVersion = (ExampleSettings settings) => settings.Version, SetSchemaVersion = delegate(ExampleSettings settings, int version) { settings.Version = version; } }); ExampleSavedDataFile = Register(new YamlConfigFile>("ExampleSavedData.yaml") { SubFolder = "SavedData", Header = "# Save data written by this mod. Edit it with the game closed.", Defaults = () => new Dictionary(), Apply = delegate(Dictionary parsed) { ExampleData.SavedCounters = parsed; }, Sync = ConfigSyncMode.LocalOnly, Watch = false }); Register(new YamlConfigFile>("ExampleLegacyFormat.yaml") { Header = "# A config kept in its original camelCase form for backwards compatibility.", Format = YamlFormat.CamelCase, Defaults = () => new Dictionary(), Apply = delegate(Dictionary parsed) { ExampleData.LegacyEntries = parsed; }, ClientWritesToDisk = true }); } internal static void Init() { if (!initialized) { initialized = true; ConfigNetwork.Init(); YamlFormat.AddTypeConverter((IYamlTypeConverter)(object)new TolerantEnumConverter()); RegisterConfigFiles(); ConfigFileWatcher.Initialize(); Logger.LogDebug($"Registered {Files.Count} yaml config files."); } } internal static TFile Register(TFile file) where TFile : YamlConfigFile { if (file == null) { return null; } Files.Add(file); if (initialized) { Prepare(file); } return file; } internal static YamlConfigFile Find(string fileNameOrPath) { if (string.IsNullOrEmpty(fileNameOrPath)) { return null; } if (ByPath.TryGetValue(fileNameOrPath, out var value)) { return value; } for (int i = 0; i < Files.Count; i++) { if (string.Equals(Files[i].FileName, fileNameOrPath, StringComparison.OrdinalIgnoreCase)) { return Files[i]; } } return null; } internal static string ConfigDirectory(string subFolder = null) { string text = Path.Combine(Paths.ConfigPath, ValConfig.cfgFolder); if (!string.IsNullOrEmpty(subFolder)) { text = Path.Combine(text, subFolder); } return Directory.CreateDirectory(text).FullName; } internal static void ReloadFromDisk(YamlConfigFile file, bool broadcast = true) { if (file == null) { return; } try { if (!File.Exists(file.Path)) { Logger.LogWarning(file.FileName + " is no longer on disk; rewriting it with this mod's built-in defaults."); RestoreDefaults(file); } if (file.LoadFrom(File.ReadAllText(file.Path), ConfigOrigin.LocalFile) && broadcast) { ConfigNetwork.Broadcast(file); } } catch (Exception ex) { Logger.LogError("Could not reload " + file.FileName + ": " + ex.Message); } } internal static bool ApplyEdited(YamlConfigFile file, string yaml, out string message) { message = ""; if (file == null) { message = "no config file was named"; return false; } if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()) { message = file.FileName + " belongs to the server; changes have to be sent to it."; return false; } string parseError; ValidationReport validationReport = file.DryRun(yaml, out parseError); if (parseError != null) { message = file.FileName + " was rejected because " + parseError + "."; return false; } if (validationReport.HasErrors) { message = string.Join(" ", validationReport.Errors.ToArray()); return false; } if (!file.LoadFrom(yaml, ConfigOrigin.Api)) { message = file.LastError ?? (file.FileName + " could not be applied."); return false; } WriteRawToDisk(file, yaml); ConfigNetwork.Broadcast(file); message = ((validationReport.Warnings.Count == 0) ? "" : string.Join(" ", validationReport.Warnings.ToArray())); return true; } internal static string SerializeForEdit(YamlConfigFile file, T value) where T : class { if (file == null || value == null) { return ""; } return file.EffectiveFormat.Serializer.Serialize((object)value); } internal static void RestoreDefaults(YamlConfigFile file) { WriteRawToDisk(file, file?.SerializeDefaults()); } internal static void WriteCurrentToDisk(YamlConfigFile file) { WriteRawToDisk(file, file?.SerializeCurrent()); } internal static void WriteRawToDisk(YamlConfigFile file, string serializedYaml) { if (file == null || string.IsNullOrEmpty(file.Path)) { return; } try { Directory.CreateDirectory(Path.GetDirectoryName(file.Path)); using (StreamWriter streamWriter = new StreamWriter(file.Path)) { if (!string.IsNullOrEmpty(file.Header)) { streamWriter.WriteLine(file.Header); } streamWriter.WriteLine(serializedYaml); } ConfigFileWatcher.RefreshStamp(file.Path); } catch (Exception ex) { Logger.LogError("Could not write " + file.FileName + ": " + ex.Message); } } internal static void RevalidateAll() { for (int i = 0; i < Files.Count; i++) { try { Files[i].Revalidate(); } catch (Exception ex) { Logger.LogError("Revalidating " + Files[i].FileName + " threw: " + ex.Message); } } } internal static bool HasNoUsableConfig(string yamlText) { if (string.IsNullOrWhiteSpace(yamlText)) { return true; } string[] array = yamlText.Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0 && !text.StartsWith("#") && !(text == "---") && !(text == "...")) { return false; } } return true; } private static void Prepare(YamlConfigFile file) { try { file.Path = Path.Combine(ConfigDirectory(file.SubFolder), file.FileName); ByPath[file.Path] = file; if (!File.Exists(file.Path)) { Logger.LogDebug(file.FileName + " missing, writing this mod's built-in defaults."); RestoreDefaults(file); } else if (HasNoUsableConfig(File.ReadAllText(file.Path))) { Logger.LogWarning(file.FileName + " was empty and has been overwritten with this mod's built-in defaults. File: " + file.Path); RestoreDefaults(file); } file.LoadFrom(File.Exists(file.Path) ? File.ReadAllText(file.Path) : "", ConfigOrigin.Startup); ConfigNetwork.RegisterFile(file); if (file.Watch) { ConfigFileWatcher.Register(file.Path, OnWatchedFileChanged); } } catch (Exception arg) { Logger.LogError($"Could not prepare {file.FileName}: {arg}"); } } private static void OnWatchedFileChanged(string path) { if (ByPath.TryGetValue(path, out var file)) { ConfigChangeDebouncer.Schedule(file, delegate { ReloadFromDisk(file); }); } } } public enum ExampleMode { Off, Add, Multiply } public class ExampleEntry { public string DisplayName { get; set; } [DefaultValue(1f)] public float Multiplier { get; set; } = 1f; public ExampleMode Mode { get; set; } public List Prefabs { get; set; } = new List(); } public class ExampleSettings { public int Version { get; set; } = 1; public Dictionary Entries { get; set; } = new Dictionary(); } internal static class ExampleData { internal static ExampleSettings Current = new ExampleSettings(); internal static Dictionary SavedCounters = new Dictionary(); internal static Dictionary LegacyEntries = new Dictionary(); internal static ExampleSettings BuildDefaults() { return new ExampleSettings { Version = 1, Entries = new Dictionary { { "Example", new ExampleEntry { DisplayName = "An example", Multiplier = 1.5f, Mode = ExampleMode.Multiply } } } }; } internal static ValidationReport Validate(ExampleSettings next, ExampleSettings previous) { ValidationReport validationReport = new ValidationReport(); if (next.Entries == null || next.Entries.Count == 0) { return validationReport.Error("it defines no entries"); } foreach (KeyValuePair entry in next.Entries) { if (entry.Value == null) { validationReport.Error("entry '" + entry.Key + "' has no settings under it"); continue; } if (entry.Value.Multiplier < 0.1f || entry.Value.Multiplier > 10f) { validationReport.Warn($"entry '{entry.Key}' has Multiplier {entry.Value.Multiplier}, outside the " + "supported range of 0.1 - 10. It will be used as written."); } if (entry.Value.Prefabs == null) { continue; } foreach (string prefab in entry.Value.Prefabs) { if (PrefabManager.Instance != null && (Object)(object)PrefabManager.Instance.GetPrefab(prefab) == (Object)null) { validationReport.Warn("entry '" + entry.Key + "' names prefab '" + prefab + "', which does not exist. That prefab will be skipped."); } } } if (previous != null && previous.Entries != null) { foreach (string key in previous.Entries.Keys) { if (!next.Entries.ContainsKey(key)) { validationReport.Warn("entry '" + key + "' was removed. Anything still referring to it will fall back."); } } } return validationReport; } } internal class TolerantEnumConverter : IYamlTypeConverter { private static readonly Dictionary fallbacks = new Dictionary(); internal static void SetFallback(Type enumType, object fallback) { if (!(enumType == null) && enumType.IsEnum) { fallbacks[enumType] = fallback; } } public bool Accepts(Type type) { if (type != null) { return type.IsEnum; } return false; } public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) { //IL_003e: 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) Scalar val = ParserExtensions.Consume(parser); string value = val.Value; if (!string.IsNullOrWhiteSpace(value)) { try { return Enum.Parse(type, value.Trim(), ignoreCase: true); } catch (Exception) { } } object obj = FallbackFor(type); object[] array = new object[4]; Mark start = ((ParsingEvent)val).Start; array[0] = ((Mark)(ref start)).Line; array[1] = value; array[2] = type.Name; array[3] = obj; Logger.LogWarning(string.Format("line {0}: '{1}' is not a valid {2}. Using {3}. ", array) + "Valid values: " + string.Join(", ", Enum.GetNames(type)) + "."); return obj; } public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown emitter.Emit((ParsingEvent)new Scalar((value == null) ? "" : value.ToString())); } private static object FallbackFor(Type type) { if (fallbacks.TryGetValue(type, out var value)) { return value; } return Activator.CreateInstance(type); } } internal static class ConfigUI { internal class ConfigUIInputGuard : MonoBehaviour { private bool held; internal void Hold() { if (!held) { held = true; PushInputBlock(); } } public void OnDestroy() { if (held) { held = false; PopInputBlock(); } } } internal const float RowHeight = 34f; internal const float SubRowHeight = 26f; internal const float RowGap = 4f; internal const float CloseXSize = 28f; private static int inputBlockDepth; internal static void PushInputBlock() { if (!((Object)(object)Player.m_localPlayer == (Object)null)) { inputBlockDepth++; if (inputBlockDepth == 1) { GUIManager.BlockInput(true); } } } internal static void PopInputBlock() { if (inputBlockDepth > 0) { inputBlockDepth--; if (inputBlockDepth == 0) { GUIManager.BlockInput(false); } } } internal static GameObject NewUI(string name, Transform parent, params Type[] components) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown GameObject val = new GameObject(name, components) { layer = 5 }; if ((Object)(object)val.GetComponent() == (Object)null) { val.AddComponent(); } val.transform.SetParent(parent, false); return val; } internal static GameObject NewRect(string name, Transform parent, float x, float y, float w, float h) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) GameObject obj = NewUI(name, parent, typeof(RectTransform)); RectTransform val = (RectTransform)obj.transform; val.anchorMin = new Vector2(0f, 1f); val.anchorMax = new Vector2(0f, 1f); val.pivot = new Vector2(0f, 1f); val.sizeDelta = new Vector2(w, h); val.anchoredPosition = new Vector2(x, 0f - y); return obj; } internal static GameObject NewRow(Transform parent, float width, float height) { return NewRect("Row", parent, 0f, 0f, width, height); } internal static GameObject NewLayoutRow(Transform content, float width, float height) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) GameObject obj = NewUI("LayoutRow", content, typeof(RectTransform), typeof(LayoutElement)); RectTransform val = (RectTransform)obj.transform; val.anchorMin = new Vector2(0f, 1f); val.anchorMax = new Vector2(0f, 1f); val.pivot = new Vector2(0f, 1f); val.sizeDelta = new Vector2(width, height); LayoutElement component = obj.GetComponent(); component.minHeight = height; component.preferredHeight = height; component.minWidth = width; component.preferredWidth = width; return obj; } internal static void LayoutColumn(List rows, float x, float startY, float gap = 4f) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) float num = startY; foreach (GameObject row in rows) { if (!((Object)(object)row == (Object)null) && row.activeSelf) { RectTransform val = (RectTransform)row.transform; val.anchoredPosition = new Vector2(x, 0f - num); num += val.sizeDelta.y + gap; } } } internal static void PositionRow(GameObject row, float x, float y) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)row == (Object)null)) { ((RectTransform)row.transform).anchoredPosition = new Vector2(x, 0f - y); } } internal static GameObject CreatePanel(string title, float w, float h, out Transform body) { //IL_0019: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) GameObject val = GUIManager.Instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 0f), w, h, true); val.AddComponent().Hold(); AddText(val.transform, 0f, 16f, w, 34f, title, 22, (TextAnchor)4, GUIManager.Instance.ValheimYellow); body = val.transform; return val; } internal static GameObject CreateScroll(Transform parent, float x, float y, float w, float h, out Transform content, out float contentWidth) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) GameObject val = NewRect("ScrollHolder", parent, x, y, w, h); GameObject val2 = GUIManager.Instance.CreateScrollView(val.transform, false, true, 8f, 4f, GUIManager.Instance.ValheimScrollbarHandleColorBlock, new Color(0f, 0f, 0f, 0.5f), w, h); content = val2.transform.Find("Scroll View/Viewport/Content"); contentWidth = w - 16f; ScrollRect componentInChildren = val2.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.scrollSensitivity = 200f; } return val2; } internal static string L(string text) { if (string.IsNullOrEmpty(text)) { return ""; } if (Localization.instance == null) { return text; } return Localization.instance.Localize(text); } internal static Text AddText(Transform parent, float x, float y, float w, float h, string text, int fontSize, TextAnchor anchor, Color? color = null) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) GameObject obj = GUIManager.Instance.CreateText(L(text), parent, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(x, 0f - y), GUIManager.Instance.AveriaSerifBold, fontSize, (Color)(((??)color) ?? GUIManager.Instance.ValheimBeige), true, Color.black, w, h, false); RectTransform val = (RectTransform)obj.transform; val.pivot = new Vector2(0f, 1f); val.anchoredPosition = new Vector2(x, 0f - y); Text component = obj.GetComponent(); component.alignment = anchor; component.horizontalOverflow = (HorizontalWrapMode)0; component.verticalOverflow = (VerticalWrapMode)0; return component; } internal static GameObject AddHeaderRow(Transform parent, float colWidth, string text, TextAnchor anchor = (TextAnchor)3) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) GameObject obj = NewRow(parent, colWidth, 34f); AddText(obj.transform, 0f, 0f, colWidth, 34f, text, 18, anchor, GUIManager.Instance.ValheimYellow); return obj; } internal static GameObject AddTextRow(Transform parent, float colWidth, float height, string text, int fontSize, Color color, TextAnchor anchor = (TextAnchor)0) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) GameObject obj = NewRow(parent, colWidth, height); AddText(obj.transform, 0f, 0f, colWidth, height, text, fontSize, anchor, color); return obj; } internal static GameObject AddSpacerRow(Transform parent, float colWidth, float height) { return NewRow(parent, colWidth, height); } internal static GameObject AddDividerRow(Transform parent, float colWidth, float height = 12f) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) GameObject val = NewRow(parent, colWidth, height); GameObject obj = NewUI("Divider", val.transform, typeof(Image)); RectTransform val2 = (RectTransform)obj.transform; val2.anchorMin = new Vector2(0f, 1f); val2.anchorMax = new Vector2(0f, 1f); val2.pivot = new Vector2(0f, 1f); val2.sizeDelta = new Vector2(colWidth, 2f); val2.anchoredPosition = new Vector2(0f, 0f - height * 0.5f); Image component = obj.GetComponent(); ((Graphic)component).color = new Color(0.6f, 0.5f, 0.35f, 0.6f); ((Graphic)component).raycastTarget = false; return val; } internal static void SetMessages(Text target, IList errors, IList warnings) { if ((Object)(object)target == (Object)null) { return; } List list = new List(); if (errors != null) { foreach (string error in errors) { list.Add("" + error + ""); } } if (warnings != null) { foreach (string warning in warnings) { list.Add("" + warning + ""); } } target.text = string.Join("\n", list.ToArray()); } internal static GameObject AddButton(Transform parent, float x, float y, float w, string text, UnityAction onClick, float h = 40f) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) GameObject val = GUIManager.Instance.CreateButton(L(text), parent, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(x, 0f - y), w, h); RectTransform val2 = (RectTransform)val.transform; val2.pivot = new Vector2(0f, 1f); val2.anchoredPosition = new Vector2(x, 0f - y); if (onClick != null) { ((UnityEvent)val.GetComponent