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 DataForge v1.0.3
DataForge.dll
Decompiled 6 hours 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.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Timers; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; 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: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("DataForge")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("DataForge")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")] [assembly: AssemblyFileVersion("1.0.3")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DataForge { internal static class LocalizationOverrideManager { internal sealed class LocalizationPayload { public Dictionary<string, string> All { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); public Dictionary<string, Dictionary<string, string>> Languages { get; set; } = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); } private const string DomainName = "localization"; private const string DefaultLanguageFileName = "English.yml"; private const string SyncedPayloadKey = "localization"; private const long ReloadDelayTicks = 10000000L; private static readonly object StateLock = new object(); private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); private static readonly ISerializer Serializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).DisableAliases().Build(); private static LocalizationPayload ActivePayload = new LocalizationPayload(); private static CustomSyncedValue<string>? SyncedPayload; private static FileSystemWatcher? Watcher; private static DataForgeFileWatcher.DebouncedAction? ReloadDebouncer; private static string? LastParsedPayload; private static readonly Dictionary<string, Dictionary<string, string?>> OriginalTranslationsByLanguage = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, HashSet<string>> AppliedKeysByLanguage = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase); private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); private static string LocalizationDirectory => Path.Combine(ConfigDirectory, "localization"); internal static void Initialize(ConfigSync configSync) { SyncedPayload = new CustomSyncedValue<string>(configSync, "localization", "", 100); SyncedPayload.ValueChanged += OnSyncedPayloadChanged; } internal static void Dispose() { if (SyncedPayload != null) { SyncedPayload.ValueChanged -= OnSyncedPayloadChanged; } Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } internal static void SetupFileWatcher() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } else { EnsureConfigDirectoryAndDefaultOverride(); Watcher?.Dispose(); ReloadDebouncer?.Dispose(); ReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadYamlValues); Watcher = DataForgeFileWatcher.Create(ConfigDirectory, "*.*", includeSubdirectories: true, ReadYamlValues); } } internal static void ReloadFromDiskAndSync() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { ApplySyncedPayload(SyncedPayload?.Value ?? ""); return; } EnsureConfigDirectoryAndDefaultOverride(); LocalizationPayload localizationPayload = LoadPayloadFromDisk(); string text = SerializePayload(localizationPayload); lock (StateLock) { ActivePayload = localizationPayload; LastParsedPayload = text; } PublishPayload(text); ApplyCurrentLocalization(); } internal static void ApplyCurrentLocalization() { Localization instance = Localization.instance; if (instance != null) { ApplyCurrentLocalization(instance, instance.GetSelectedLanguage()); } } internal static void ApplyCurrentLocalization(Localization localization, string? language) { if (localization == null) { return; } string text = NormalizeLanguage(language); Dictionary<string, string> dictionary; lock (StateLock) { dictionary = BuildTranslationsForLanguage(ActivePayload, text); } RestoreRemovedTranslations(localization, text, dictionary.Keys); foreach (KeyValuePair<string, string> item in dictionary) { ApplyTranslation(localization, text, item.Key, item.Value); } AppliedKeysByLanguage[text] = new HashSet<string>(dictionary.Keys, StringComparer.OrdinalIgnoreCase); } private static void ReadYamlValues(object sender, FileSystemEventArgs e) { if (ShouldReloadForFileEvent(e)) { ReloadDebouncer?.Schedule(); } } private static void ReloadYamlValues() { try { DataForgePlugin.Log.LogDebug((object)"Reloading localization YAML files..."); ReloadFromDiskAndSync(); DataForgePlugin.Log.LogInfo((object)"Localization YAML reload complete."); } catch (Exception arg) { DataForgePlugin.Log.LogError((object)$"Error reloading localization YAML files: {arg}"); } } private static bool ShouldReloadForFileEvent(FileSystemEventArgs e) { if (!DataForgePlugin.UsesLocalAuthorityFiles) { return false; } if (IsLocalizationFile(e.FullPath)) { return true; } if (e is RenamedEventArgs e2) { return IsLocalizationFile(e2.OldFullPath); } return false; } private static void OnSyncedPayloadChanged() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { string payload = SyncedPayload?.Value ?? ""; DataForgeProfiler.Profile(string.Format("{0}.ApplySyncedPayload chars={1}", "localization", payload.Length), delegate { ApplySyncedPayload(payload); }); } } private static void ApplySyncedPayload(string payload) { if (!string.Equals(LastParsedPayload, payload, StringComparison.Ordinal)) { LocalizationPayload activePayload = DeserializePayload(payload, "synced localization payload"); lock (StateLock) { ActivePayload = activePayload; LastParsedPayload = payload; } } ApplyCurrentLocalization(); } private static void PublishPayload(string payload) { DataForgeSync.PublishPayload(SyncedPayload, "localization", payload); } private static LocalizationPayload LoadPayloadFromDisk() { LocalizationPayload localizationPayload = new LocalizationPayload(); if (!Directory.Exists(LocalizationDirectory)) { return localizationPayload; } foreach (string item in Directory.GetFiles(LocalizationDirectory, "*.yml").Concat(Directory.GetFiles(LocalizationDirectory, "*.yaml")).OrderBy<string, string>((string path) => path, StringComparer.OrdinalIgnoreCase)) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item); Dictionary<string, string> dictionary = LoadTranslationMap(item, fileNameWithoutExtension + " localization"); if (dictionary.Count != 0) { if (!localizationPayload.Languages.TryGetValue(fileNameWithoutExtension, out Dictionary<string, string> value)) { value = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); localizationPayload.Languages[fileNameWithoutExtension] = value; } MergeTranslations(value, dictionary); } } return localizationPayload; } private static Dictionary<string, string> LoadTranslationMap(string path, string source) { if (!File.Exists(path)) { return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } string text = File.ReadAllText(path); if (string.IsNullOrWhiteSpace(text)) { return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } try { return NormalizeTranslationMap(Deserializer.Deserialize<Dictionary<string, string>>(text), source); } catch (Exception ex) { DataForgePlugin.Log.LogError((object)("Failed to parse " + source + " from '" + path + "': " + ex.Message)); return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } } private static Dictionary<string, string> NormalizeTranslationMap(Dictionary<string, string?>? map, string source) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (map == null) { return dictionary; } foreach (KeyValuePair<string, string> item in map) { string text = NormalizeToken(item.Key); if (text.Length == 0) { DataForgePlugin.Log.LogWarning((object)("Skipping localization entry with an empty token in " + source + ".")); } else { dictionary[text] = item.Value ?? ""; } } return dictionary; } private static string SerializePayload(LocalizationPayload payload) { return Serializer.Serialize(payload); } private static LocalizationPayload DeserializePayload(string payload, string source) { if (string.IsNullOrWhiteSpace(payload)) { return new LocalizationPayload(); } try { return NormalizePayload(Deserializer.Deserialize<LocalizationPayload>(payload), source); } catch (Exception ex) { DataForgePlugin.Log.LogError((object)("Failed to parse " + source + ": " + ex.Message)); return new LocalizationPayload(); } } private static LocalizationPayload NormalizePayload(LocalizationPayload? payload, string source) { LocalizationPayload localizationPayload = new LocalizationPayload(); if (payload == null) { return localizationPayload; } MergeTranslations(localizationPayload.All, NormalizeStringTranslationMap(payload.All, source + " common")); foreach (KeyValuePair<string, Dictionary<string, string>> language in payload.Languages) { string text = NormalizeLanguage(language.Key); if (text.Length != 0) { localizationPayload.Languages[text] = NormalizeStringTranslationMap(language.Value, source + " " + text); } } return localizationPayload; } private static Dictionary<string, string> NormalizeStringTranslationMap(Dictionary<string, string>? map, string source) { return NormalizeTranslationMap(map?.ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> pair) => pair.Key, (KeyValuePair<string, string> pair) => pair.Value, StringComparer.OrdinalIgnoreCase), source); } private static Dictionary<string, string> BuildTranslationsForLanguage(LocalizationPayload payload, string language) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); MergeTranslations(dictionary, payload.All); if (!language.Equals("English", StringComparison.OrdinalIgnoreCase) && payload.Languages.TryGetValue("English", out Dictionary<string, string> value)) { MergeTranslations(dictionary, value); } if (payload.Languages.TryGetValue(language, out Dictionary<string, string> value2)) { MergeTranslations(dictionary, value2); } return dictionary; } private static void MergeTranslations(Dictionary<string, string> target, Dictionary<string, string> source) { foreach (KeyValuePair<string, string> item in source) { target[NormalizeToken(item.Key)] = item.Value; } } private static void ApplyTranslation(Localization localization, string language, string token, string text) { token = NormalizeToken(token); if (token.Length != 0) { Dictionary<string, string> originalTranslations = GetOriginalTranslations(language); if (!originalTranslations.ContainsKey(token)) { originalTranslations[token] = (localization.m_translations.TryGetValue(token, out var value) ? value : null); } localization.m_translations[token] = text; } } private static void RestoreRemovedTranslations(Localization localization, string language, IEnumerable<string> currentTokens) { HashSet<string> current = new HashSet<string>(currentTokens.Select(NormalizeToken), StringComparer.OrdinalIgnoreCase); if (!AppliedKeysByLanguage.TryGetValue(language, out HashSet<string> value)) { return; } Dictionary<string, string> originalTranslations = GetOriginalTranslations(language); string[] array = value.Where((string token) => !current.Contains(token)).ToArray(); foreach (string key in array) { if (originalTranslations.TryGetValue(key, out var value2)) { if (value2 == null) { localization.m_translations.Remove(key); } else { localization.m_translations[key] = value2; } originalTranslations.Remove(key); } } } private static Dictionary<string, string?> GetOriginalTranslations(string language) { if (!OriginalTranslationsByLanguage.TryGetValue(language, out Dictionary<string, string> value)) { value = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); OriginalTranslationsByLanguage[language] = value; } return value; } private static void EnsureConfigDirectoryAndDefaultOverride() { Directory.CreateDirectory(ConfigDirectory); Directory.CreateDirectory(LocalizationDirectory); string path = Path.Combine(LocalizationDirectory, "English.yml"); if (!File.Exists(path)) { File.WriteAllText(path, DefaultEnglishLocalizationTemplate()); } } private static string DefaultEnglishLocalizationTemplate() { return string.Join(Environment.NewLine, "# DataForge server-synced localization.", "#", "# Put language files in this folder using Valheim language names:", "# English.yml, Korean.yml, Turkish.yml, German.yml, etc.", "#", "# English.yml is the fallback file. If a client uses another language,", "# DataForge first applies English.yml and then applies that client's language file.", "#", "# To use a localization key, put a token like $df_item_meadhealthtest in an override field.", "# To override text directly, put plain text in the field instead of a $ token.", "#", "# Example localization entry:", "# $df_item_meadhealthtest: \"Test item\"", "# $df_item_meadhealthtest_description: \"A test item cloned from major healing mead.\"", "#", "# Example item override:", "# - item: MeadHealthtest", "# cloneFrom: MeadHealthMajor", "# name: $df_item_meadhealthtest", "# description: Direct text override without localization", ""); } private static bool IsLocalizationFile(string path) { string extension = Path.GetExtension(path); if (!extension.Equals(".yml", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".yaml", StringComparison.OrdinalIgnoreCase)) { return false; } string fullPath = Path.GetFullPath(path); string fullPath2 = Path.GetFullPath(LocalizationDirectory); char directorySeparatorChar = Path.DirectorySeparatorChar; return fullPath.StartsWith(fullPath2 + directorySeparatorChar, StringComparison.OrdinalIgnoreCase); } private static string NormalizeToken(string token) { return (token ?? "").Trim().TrimStart(new char[1] { '$' }).Trim(); } private static string NormalizeLanguage(string? language) { string text = language?.Trim() ?? ""; if (text.Length != 0) { return text; } return "English"; } } [HarmonyPatch(typeof(Localization), "SetupLanguage")] internal static class DataForgeLocalizationSetupLanguagePatch { private static void Postfix(Localization __instance, string language) { LocalizationOverrideManager.ApplyCurrentLocalization(__instance, language); } } [BepInPlugin("sighsorry.DataForge", "DataForge", "1.0.3")] public class DataForgePlugin : BaseUnityPlugin { public enum Toggle { On = 1, Off = 0 } private sealed class ConfigurationManagerAttributes { [UsedImplicitly] public int? Order; [UsedImplicitly] public bool? Browsable; [UsedImplicitly] public string? Category; [UsedImplicitly] public Action<ConfigEntryBase>? CustomDrawer; } internal const string ModName = "DataForge"; internal const string ModVersion = "1.0.3"; internal const string Author = "sighsorry"; internal const string ModGUID = "sighsorry.DataForge"; private static readonly string ConfigFileName = "sighsorry.DataForge.cfg"; private static readonly string ConfigFileFullPath = Path.Combine(Paths.ConfigPath, ConfigFileName); private static readonly ConfigSync ConfigSync = new ConfigSync("sighsorry.DataForge") { DisplayName = "DataForge", CurrentVersion = "1.0.3", MinimumRequiredVersion = "1.0.3" }; private readonly Harmony _harmony = new Harmony("sighsorry.DataForge"); private readonly object _reloadLock = new object(); private FileSystemWatcher? _watcher; private DataForgeFileWatcher.DebouncedAction? _configReloadDebouncer; private string? _lastConfigFileText; private static bool _sourceOfTruthFileModeReady; internal static string ConnectionError = ""; internal static readonly ManualLogSource Log = Logger.CreateLogSource("DataForge"); private const long ReloadDelayTicks = 10000000L; private const int MaxStoredFireplaceFuelLimit = 9999; private static ConfigEntry<Toggle> _serverConfigLocked = null; private static ConfigEntry<Toggle> _enableItemOverrides = null; private static ConfigEntry<Toggle> _enableRecipeOverrides = null; private static ConfigEntry<Toggle> _enableStatusEffectOverrides = null; private static ConfigEntry<Toggle> _enablePieceOverrides = null; private static ConfigEntry<int> _stackableStackMultiplier = null; private static ConfigEntry<float> _itemWeightMultiplier = null; private static ConfigEntry<Toggle> _showPieceComfortInHammer = null; private static ConfigEntry<Toggle> _highlightStationExtensionsInHammer = null; private static ConfigEntry<Toggle> _ignoreStationExtensionSpacing = null; private static ConfigEntry<int> _maxStoredFireplaceFuel = null; private static ConfigEntry<Toggle> _logStartupTimings = null; internal static bool IsSourceOfTruth => ConfigSync.IsSourceOfTruth; internal static bool UsesLocalAuthorityFiles { get { if (IsSourceOfTruth) { return !IsRemoteServerClient; } return false; } } internal static bool IsRemoteServerClient { get { try { return ZNet.HasServerHost() && ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()); } catch { return false; } } } internal static bool ItemOverridesEnabled => _enableItemOverrides.Value.IsOn(); internal static bool RecipeOverridesEnabled => _enableRecipeOverrides.Value.IsOn(); internal static bool StatusEffectOverridesEnabled => _enableStatusEffectOverrides.Value.IsOn(); internal static bool PieceOverridesEnabled => _enablePieceOverrides.Value.IsOn(); internal static int StackableStackMultiplier => Math.Min(10, Math.Max(1, _stackableStackMultiplier.Value)); internal static float ItemWeightMultiplier => Math.Min(2f, Math.Max(0f, _itemWeightMultiplier.Value)); internal static bool ShowPieceComfortInHammer => _showPieceComfortInHammer.Value.IsOn(); internal static bool HighlightStationExtensionsInHammer => _highlightStationExtensionsInHammer.Value.IsOn(); internal static bool IgnoreStationExtensionSpacing => _ignoreStationExtensionSpacing.Value.IsOn(); internal static int MaxStoredFireplaceFuel => Math.Min(9999, Math.Max(0, _maxStoredFireplaceFuel.Value)); internal static bool LogStartupTimings => _logStartupTimings.Value.IsOn(); public void Awake() { //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Expected O, but got Unknown //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Expected O, but got Unknown bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; _serverConfigLocked = ConfigEntry("1 - General", "Lock Configuration", Toggle.On, "If on, the configuration is locked and can be changed by server admins only.", synchronizedSetting: true, 1000); ConfigSync.AddLockingConfigEntry<Toggle>(_serverConfigLocked); _enableItemOverrides = ConfigEntry("1 - General", "Enable Item Overrides", Toggle.On, "If on, item YAML overrides are applied to matching ObjectDB item prefabs.", synchronizedSetting: true, 900); _enableItemOverrides.SettingChanged += delegate { ItemOverrideManager.ApplyCurrentConfiguration(); }; _enableRecipeOverrides = ConfigEntry("1 - General", "Enable Recipe Overrides", Toggle.On, "If on, recipe YAML overrides are applied to ObjectDB recipes.", synchronizedSetting: true, 800); _enableRecipeOverrides.SettingChanged += delegate { RecipeOverrideManager.ApplyCurrentConfiguration(); }; _enableStatusEffectOverrides = ConfigEntry("1 - General", "Enable Status Effect Overrides", Toggle.On, "If on, status effect YAML overrides are applied to ObjectDB status effects.", synchronizedSetting: true, 700); _enableStatusEffectOverrides.SettingChanged += delegate { StatusEffectOverrideManager.ApplyCurrentConfiguration(); }; _enablePieceOverrides = ConfigEntry("1 - General", "Enable Piece Overrides", Toggle.On, "If on, piece YAML overrides are applied to matching prefabs and loaded pieces.", synchronizedSetting: true, 600); _enablePieceOverrides.SettingChanged += delegate { PieceOverrideManager.ApplyCurrentConfiguration(); }; _stackableStackMultiplier = ConfigEntry("2 - Misc", "Stackable Stack Multiplier", 1, new ConfigDescription("Integer multiplier applied to baseline max stack size for stackable items unless maxStackSize is explicitly set in item YAML. 1 disables this feature.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()), synchronizedSetting: true, 500); _stackableStackMultiplier.SettingChanged += delegate { ItemOverrideManager.ApplyCurrentConfiguration(); }; _itemWeightMultiplier = ConfigEntry("2 - Misc", "Item Weight Multiplier", 1f, new ConfigDescription("Multiplier applied to baseline item weight for all items unless weight is explicitly set in item YAML. 1 disables this feature; 0 makes affected items weightless.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()), synchronizedSetting: true, 400); _itemWeightMultiplier.SettingChanged += delegate { ItemOverrideManager.ApplyCurrentConfiguration(); }; _showPieceComfortInHammer = ConfigEntry("2 - Misc", "Show Comfort In Hammer", Toggle.On, "If on, hammer build icons show an orange comfort value badge for pieces with comfort 1 or higher.", synchronizedSetting: false, 300); _showPieceComfortInHammer.SettingChanged += delegate { PieceComfortHudBadges.RefreshVisibleHud(); }; _highlightStationExtensionsInHammer = ConfigEntry("2 - Misc", "Highlight Station Extensions In Hammer", Toggle.On, "If on, hovering a crafting station or station extension in the hammer tab highlights related station/extension pieces in pale cyan. This setting is client-side only.", synchronizedSetting: false, 250); _highlightStationExtensionsInHammer.SettingChanged += delegate { PieceComfortHudBadges.RefreshVisibleHud(); }; _ignoreStationExtensionSpacing = ConfigEntry("2 - Misc", "Ignore Station Extension Spacing", Toggle.On, "If on, station extensions ignore the vanilla spacing check against other station extensions, allowing close or overlapping extension placement. Other placement restrictions remain unchanged.", synchronizedSetting: true, 200); _maxStoredFireplaceFuel = ConfigEntry("2 - Misc", "maxStoredFuel", 100, $"Maximum stored fuel allowed in fireplaces without changing each fireplace's displayed max fuel. 0 disables this feature. Values are clamped to 0-{9999}. If this value is not greater than a fireplace's max fuel, that fireplace uses vanilla behavior.", synchronizedSetting: true, 100); _maxStoredFireplaceFuel.SettingChanged += delegate { ClampMaxStoredFireplaceFuel(); }; ClampMaxStoredFireplaceFuel(); _logStartupTimings = ConfigEntry("2 - Misc", "Log Startup Timings", Toggle.Off, "If on, logs a lobby-to-world connection timeline plus DataForge synced payload parsing and world-data apply timings. Use only while diagnosing connection or loading delays.", synchronizedSetting: false, 50); LocalizationOverrideManager.Initialize(ConfigSync); StatusEffectOverrideManager.Initialize(ConfigSync); ItemOverrideManager.Initialize(ConfigSync); RecipeOverrideManager.Initialize(ConfigSync); PieceOverrideManager.Initialize(ConfigSync); ConfigSync.SourceOfTruthChanged += OnSourceOfTruthChanged; DataForgeConsoleCommands.Register(); Assembly executingAssembly = Assembly.GetExecutingAssembly(); _harmony.PatchAll(executingAssembly); SetupWatcher(); ((BaseUnityPlugin)this).Config.Save(); _lastConfigFileText = ReadFileTextIfExists(ConfigFileFullPath); if (saveOnConfigSet) { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } private void OnDestroy() { SaveWithRespectToConfigSet(); _watcher?.Dispose(); _watcher = null; _configReloadDebouncer?.Dispose(); _configReloadDebouncer = null; LocalizationOverrideManager.Dispose(); StatusEffectOverrideManager.Dispose(); ItemOverrideManager.Dispose(); RecipeOverrideManager.Dispose(); PieceOverrideManager.Dispose(); ConfigSync.SourceOfTruthChanged -= OnSourceOfTruthChanged; _harmony.UnpatchSelf(); } private static void OnSourceOfTruthChanged(bool isSourceOfTruth) { if (isSourceOfTruth) { EnsureSourceOfTruthFileMode(); return; } _sourceOfTruthFileModeReady = false; LocalizationOverrideManager.SetupFileWatcher(); StatusEffectOverrideManager.SetupFileWatcher(); ItemOverrideManager.SetupFileWatcher(); RecipeOverrideManager.SetupFileWatcher(); PieceOverrideManager.SetupFileWatcher(); } internal static void EnsureSourceOfTruthFileMode() { if (UsesLocalAuthorityFiles && !_sourceOfTruthFileModeReady) { _sourceOfTruthFileModeReady = true; LocalizationOverrideManager.SetupFileWatcher(); StatusEffectOverrideManager.SetupFileWatcher(); ItemOverrideManager.SetupFileWatcher(); RecipeOverrideManager.SetupFileWatcher(); PieceOverrideManager.SetupFileWatcher(); LocalizationOverrideManager.ReloadFromDiskAndSync(); StatusEffectOverrideManager.ReloadFromDiskAndSync(); ItemOverrideManager.ReloadFromDiskAndSync(); RecipeOverrideManager.ReloadFromDiskAndSync(); PieceOverrideManager.ReloadFromDiskAndSync(); } } private void Update() { VneiPrefabCleanupGuard.TryPatchVneiIndexAll(_harmony); } private static void ClampMaxStoredFireplaceFuel() { int num = Math.Min(9999, Math.Max(0, _maxStoredFireplaceFuel.Value)); if (_maxStoredFireplaceFuel.Value != num) { _maxStoredFireplaceFuel.Value = num; } } private void SetupWatcher() { _watcher?.Dispose(); _configReloadDebouncer?.Dispose(); _configReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadConfigValues); _watcher = DataForgeFileWatcher.Create(Paths.ConfigPath, ConfigFileName, includeSubdirectories: false, ReadConfigValues); } private void ReadConfigValues(object sender, FileSystemEventArgs e) { _configReloadDebouncer?.Schedule(); } private void ReloadConfigValues() { lock (_reloadLock) { if (!File.Exists(ConfigFileFullPath)) { Log.LogWarning((object)"Config file does not exist. Skipping reload."); return; } try { string b = ReadFileTextIfExists(ConfigFileFullPath); if (string.Equals(_lastConfigFileText, b, StringComparison.Ordinal)) { Log.LogDebug((object)"Skipping configuration reload because the config file content did not change."); return; } Log.LogDebug((object)"Reloading configuration..."); SaveWithRespectToConfigSet(reload: true); _lastConfigFileText = ReadFileTextIfExists(ConfigFileFullPath); StatusEffectOverrideManager.ApplyCurrentConfiguration(); ItemOverrideManager.ApplyCurrentConfiguration(); RecipeOverrideManager.ApplyCurrentConfiguration(); PieceOverrideManager.ApplyCurrentConfiguration(); Log.LogInfo((object)"Configuration reload complete."); } catch (Exception arg) { Log.LogError((object)$"Error reloading configuration: {arg}"); } } } private void SaveWithRespectToConfigSet(bool reload = false) { bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; if (reload) { ((BaseUnityPlugin)this).Config.Reload(); } else { ((BaseUnityPlugin)this).Config.Save(); } if (saveOnConfigSet) { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } private static string? ReadFileTextIfExists(string path) { try { return File.Exists(path) ? File.ReadAllText(path) : null; } catch (IOException) { return null; } catch (UnauthorizedAccessException) { return null; } } private ConfigEntry<T> ConfigEntry<T>(string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true, int? order = null) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown object[] array = description.Tags ?? Array.Empty<object>(); if (order.HasValue) { object[] array2 = new object[array.Length + 1]; Array.Copy(array, array2, array.Length); array2[array.Length] = new ConfigurationManagerAttributes { Order = order.Value }; array = array2; } ConfigDescription val = new ConfigDescription(description.Description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"), description.AcceptableValues, array); ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, val); ConfigSync.AddConfigEntry<T>(val2).SynchronizedConfig = synchronizedSetting; return val2; } private ConfigEntry<T> ConfigEntry<T>(string group, string name, T value, string description, bool synchronizedSetting = true, int? order = null) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown return ConfigEntry(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), synchronizedSetting, order); } } public static class ToggleExtensions { public static bool IsOn(this DataForgePlugin.Toggle value) { return value == DataForgePlugin.Toggle.On; } public static bool IsOff(this DataForgePlugin.Toggle value) { return value == DataForgePlugin.Toggle.Off; } } internal static class DataForgeConsoleCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__WriteFullScaffoldFiles; public static ConsoleOptionsFetcher <1>__GetFullTabOptions; } private const string WriteFullCommandName = "dataforge:full"; private static readonly List<string> FullTabOptions = new List<string> { "item", "recipe", "effect", "piece", "all" }; private static bool _registered; internal static void Register() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown if (!_registered) { _registered = true; object obj = <>O.<0>__WriteFullScaffoldFiles; if (obj == null) { ConsoleEvent val = WriteFullScaffoldFiles; <>O.<0>__WriteFullScaffoldFiles = val; obj = (object)val; } object obj2 = <>O.<1>__GetFullTabOptions; if (obj2 == null) { ConsoleOptionsFetcher val2 = GetFullTabOptions; <>O.<1>__GetFullTabOptions = val2; obj2 = (object)val2; } new ConsoleCommand("dataforge:full", "Write DataForge full scaffold YAML files with explicit defaults. Usage: dataforge:full [item|recipe|effect|piece|all]", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false); } } private static List<string> GetFullTabOptions() { return FullTabOptions; } private static void WriteFullScaffoldFiles(ConsoleEventArgs args) { if (!TryParseScope(args, out var includeItem, out var includeRecipe, out var includeEffect, out var includePiece)) { return; } if (includeItem) { if (ItemOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path, out string error)) { Terminal context = args.Context; if (context != null) { context.AddString("Wrote item full scaffold to " + path); } } else { Terminal context2 = args.Context; if (context2 != null) { context2.AddString(error); } } } if (includeRecipe) { if (RecipeOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path2, out string error2)) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("Wrote recipe full scaffold to " + path2); } } else { Terminal context4 = args.Context; if (context4 != null) { context4.AddString(error2); } } } if (includeEffect) { if (StatusEffectOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path3, out string error3)) { Terminal context5 = args.Context; if (context5 != null) { context5.AddString("Wrote effect full scaffold to " + path3); } } else { Terminal context6 = args.Context; if (context6 != null) { context6.AddString(error3); } } } if (!includePiece) { return; } if (PieceOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path4, out string error4)) { Terminal context7 = args.Context; if (context7 != null) { context7.AddString("Wrote piece full scaffold to " + path4); } } else { Terminal context8 = args.Context; if (context8 != null) { context8.AddString(error4); } } } private static bool TryParseScope(ConsoleEventArgs args, out bool includeItem, out bool includeRecipe, out bool includeEffect, out bool includePiece) { string text = ((args.Length >= 2) ? (args[1] ?? "").Trim().ToLowerInvariant() : "all"); if (text.Length == 0) { text = "all"; } switch (text) { case "all": includeItem = true; includeRecipe = true; includeEffect = true; includePiece = true; return true; case "items": case "item": includeItem = true; includeRecipe = false; includeEffect = false; includePiece = false; return true; case "recipe": case "recipes": includeItem = false; includeRecipe = true; includeEffect = false; includePiece = false; return true; case "effect": case "effects": includeItem = false; includeRecipe = false; includeEffect = true; includePiece = false; return true; case "piece": case "pieces": includeItem = false; includeRecipe = false; includeEffect = false; includePiece = true; return true; default: { includeItem = false; includeRecipe = false; includeEffect = false; includePiece = false; Terminal context = args.Context; if (context != null) { context.AddString("Syntax: dataforge:full [item|recipe|effect|piece|all]"); } return false; } } } } internal static class DataForgeLogContext { private sealed class PopWhenDisposed : IDisposable { private readonly string? previous; private bool disposed; internal PopWhenDisposed(string? previous) { this.previous = previous; } public void Dispose() { if (!disposed) { CurrentContext = previous; disposed = true; } } } [ThreadStatic] private static string? CurrentContext; internal static IDisposable Push(string? context) { string currentContext = CurrentContext; CurrentContext = (string.IsNullOrWhiteSpace(context) ? currentContext : context); return new PopWhenDisposed(currentContext); } internal static string FormatSource(string source, int entryIndex) { string text = source?.Trim() ?? ""; string text2 = ((text.Length == 0) ? "unknown source" : Path.GetFileName(text)); if (text2.Length == 0) { text2 = text; } return $"{text2}#{entryIndex}"; } internal static void Warning(string message) { DataForgePlugin.Log.LogWarning((object)WithContext(message)); } private static string WithContext(string message) { if (!string.IsNullOrWhiteSpace(CurrentContext)) { return CurrentContext + ": " + message; } return message; } } internal static class DataForgeReferenceSections { private sealed class GroupedEntry<TSource> { public TSource Entry { get; set; } public string SortKey { get; set; } = ""; public string OwnerName { get; set; } = "Unknown / Untracked"; } internal const string VanillaOwnerName = "Valheim"; internal const string UnknownOwnerName = "Unknown / Untracked"; internal static string SerializeReferenceSections<TSource, TOutput>(IEnumerable<TSource> entries, Func<TSource, string> getSortKey, Func<TSource, string> getOwnerName, Func<TSource, TOutput> getOutput, ISerializer serializer) { List<IGrouping<string, GroupedEntry<TSource>>> list = (from entry in entries.Select(delegate(TSource entry) { string text = (getOwnerName(entry) ?? "").Trim(); return new GroupedEntry<TSource> { Entry = entry, SortKey = (getSortKey(entry) ?? "").Trim(), OwnerName = ((text.Length > 0) ? text : "Unknown / Untracked") }; }) orderby GetOwnerSortBucket(entry.OwnerName) select entry).ThenBy<GroupedEntry<TSource>, string>((GroupedEntry<TSource> entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase).ThenBy<GroupedEntry<TSource>, string>((GroupedEntry<TSource> entry) => entry.SortKey, StringComparer.OrdinalIgnoreCase).GroupBy<GroupedEntry<TSource>, string>((GroupedEntry<TSource> entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase) .ToList(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; foreach (IGrouping<string, GroupedEntry<TSource>> item in list) { if (flag) { stringBuilder.AppendLine(); } AppendSectionHeaderComment(stringBuilder, item.Key); foreach (GroupedEntry<TSource> item2 in item) { string value = CollapseScalarBlockListsToInlineLists(serializer.Serialize(new TOutput[1] { getOutput(item2.Entry) }).TrimEnd('\r', '\n')); stringBuilder.AppendLine(value); } flag = true; } if (!flag) { return "[]" + Environment.NewLine; } return stringBuilder.ToString(); } private static void AppendSectionHeaderComment(StringBuilder builder, string ownerName) { builder.Append("# ===== "); builder.Append(string.IsNullOrWhiteSpace(ownerName) ? "Unknown / Untracked" : ownerName.Trim()); builder.AppendLine(" ====="); } private static int GetOwnerSortBucket(string ownerName) { if (string.Equals(ownerName, "Valheim", StringComparison.OrdinalIgnoreCase)) { return 0; } if (!string.Equals(ownerName, "Unknown / Untracked", StringComparison.OrdinalIgnoreCase)) { return 1; } return 2; } private static string CollapseScalarBlockListsToInlineLists(string yaml) { if (string.IsNullOrWhiteSpace(yaml) || yaml.IndexOf("- ", StringComparison.Ordinal) < 0) { return yaml; } string[] array = yaml.Replace("\r\n", "\n").Split(new char[1] { '\n' }); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < array.Length; i++) { if (TryCollapseScalarBlockList(array, ref i, out string collapsedLine)) { stringBuilder.AppendLine(collapsedLine); } else { stringBuilder.AppendLine(array[i]); } } return stringBuilder.ToString().TrimEnd('\r', '\n'); } private static bool TryCollapseScalarBlockList(string[] lines, ref int index, out string collapsedLine) { collapsedLine = ""; string text = lines[index]; int num = text.IndexOf(':'); if (num < 0 || num != text.Length - 1) { return false; } int num2 = index + 1; if (num2 >= lines.Length) { return false; } int firstNonWhitespaceIndex = GetFirstNonWhitespaceIndex(text); int firstNonWhitespaceIndex2 = GetFirstNonWhitespaceIndex(lines[num2]); if (firstNonWhitespaceIndex < 0 || firstNonWhitespaceIndex2 <= firstNonWhitespaceIndex || !lines[num2].TrimStart(Array.Empty<char>()).StartsWith("- ", StringComparison.Ordinal)) { return false; } List<string> list = new List<string>(); int i; for (i = num2; i < lines.Length; i++) { string text2 = lines[i]; if (GetFirstNonWhitespaceIndex(text2) != firstNonWhitespaceIndex2 || !text2.TrimStart(Array.Empty<char>()).StartsWith("- ", StringComparison.Ordinal)) { break; } string text3 = text2.TrimStart(Array.Empty<char>()).Substring(2).Trim(); if (text3.Length == 0 || Enumerable.Contains(text3, ':') || Enumerable.Contains(text3, ',')) { return false; } list.Add(text3); } if (list.Count == 0) { return false; } collapsedLine = text + " [" + string.Join(", ", list) + "]"; index = i - 1; return true; } private static int GetFirstNonWhitespaceIndex(string line) { for (int i = 0; i < line.Length; i++) { if (!char.IsWhiteSpace(line[i])) { return i; } } return -1; } } internal static class DataForgeOwnerResolver { internal static string GetPrefabOwnerName(string? prefabName) { string text = NormalizeName(prefabName); if (text.Length == 0) { return "Unknown / Untracked"; } foreach (string item in EnumerateLookupCandidates(text)) { if (DataForgeVanillaAssetCatalog.IsVanillaPrefab(item)) { return "Valheim"; } } return DataForgeAssetOwnerCatalog.GetOwnerName(text); } internal static string GetAssetOwnerName(string? assetName) { string text = NormalizeName(assetName); if (text.Length == 0) { return "Unknown / Untracked"; } foreach (string item in EnumerateLookupCandidates(text)) { if (DataForgeVanillaAssetCatalog.IsVanillaAsset(item)) { return "Valheim"; } } return DataForgeAssetOwnerCatalog.GetOwnerName(text); } private static IEnumerable<string> EnumerateLookupCandidates(string normalizedName) { HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase); AddIfNew(normalizedName); AddIfNew(TrimCloneSuffix(normalizedName)); int num = normalizedName.IndexOf(':'); if (num > 0) { AddIfNew(normalizedName.Substring(0, num)); } foreach (string item in seen) { yield return item; } void AddIfNew(string candidate) { string text = NormalizeName(candidate); if (text.Length > 0) { seen.Add(text); } } } private static string NormalizeName(string? name) { return (name ?? "").Replace("(Clone)", "").Trim(); } private static string TrimCloneSuffix(string name) { if (!name.EndsWith("(Clone)", StringComparison.Ordinal)) { return name; } return name.Substring(0, name.Length - "(Clone)".Length).TrimEnd(Array.Empty<char>()); } } internal static class DataForgeVanillaAssetCatalog { private enum CatalogState { Uninitialized, Loaded, Unavailable } private static readonly object Sync = new object(); private static readonly HashSet<string> PrefabNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> AssetNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static CatalogState _state; internal static bool IsVanillaPrefab(string prefabName) { EnsureLoaded(); if (_state == CatalogState.Loaded && !string.IsNullOrWhiteSpace(prefabName)) { return PrefabNames.Contains(prefabName); } return false; } internal static bool IsVanillaAsset(string assetName) { EnsureLoaded(); if (_state == CatalogState.Loaded && !string.IsNullOrWhiteSpace(assetName)) { return AssetNames.Contains(assetName); } return false; } private static void EnsureLoaded() { if (_state != CatalogState.Uninitialized) { return; } lock (Sync) { if (_state != CatalogState.Uninitialized) { return; } string text = Path.Combine(Application.dataPath, "StreamingAssets", "SoftRef", "manifest_extended"); if (!File.Exists(text)) { _state = CatalogState.Unavailable; DataForgePlugin.Log.LogWarning((object)("Vanilla asset manifest was not found at '" + text + "'. Reference owner sections may place unmapped entries under 'Unknown / Untracked'.")); return; } foreach (string item in File.ReadLines(text)) { int num = item.IndexOf("path in bundle:", StringComparison.OrdinalIgnoreCase); if (num < 0) { continue; } string text2 = item.Substring(num + "path in bundle:".Length).Trim(); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text2); if (!string.IsNullOrWhiteSpace(fileNameWithoutExtension)) { if (text2.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) { PrefabNames.Add(fileNameWithoutExtension); } else if (text2.EndsWith(".asset", StringComparison.OrdinalIgnoreCase)) { AssetNames.Add(fileNameWithoutExtension); } } } _state = CatalogState.Loaded; DataForgePlugin.Log.LogDebug((object)$"Loaded {PrefabNames.Count} vanilla prefab names and {AssetNames.Count} vanilla asset names from '{text}'."); } } } internal static class DataForgeAssetOwnerCatalog { private sealed class PluginResourceSnapshot { public string OwnerName { get; set; } = ""; public string PluginName { get; set; } = ""; public string PluginGuid { get; set; } = ""; public string AssemblyName { get; set; } = ""; public string[] ResourceNames { get; set; } = Array.Empty<string>(); } private static readonly object Sync = new object(); private static readonly Dictionary<string, string> AssetOwners = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static string _loadedSignature = ""; internal static string GetOwnerName(string assetName) { EnsureMappingsLoaded(); foreach (string item in EnumerateLookupCandidates(assetName)) { if (AssetOwners.TryGetValue(item, out string value) && !string.IsNullOrWhiteSpace(value)) { return value; } } return "Unknown / Untracked"; } private static void EnsureMappingsLoaded() { string text = BuildSignature(); if (string.Equals(text, _loadedSignature, StringComparison.Ordinal)) { return; } lock (Sync) { if (string.Equals(text, _loadedSignature, StringComparison.Ordinal)) { return; } AssetOwners.Clear(); List<PluginResourceSnapshot> pluginResources = GetPluginResources(); foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles()) { string text2 = ((Object)allLoadedAssetBundle).name ?? ""; if (text2.Length == 0) { continue; } string value = ResolveOwnerName(text2, pluginResources); if (string.IsNullOrWhiteSpace(value)) { continue; } string[] allAssetNames = allLoadedAssetBundle.GetAllAssetNames(); foreach (string text3 in allAssetNames) { if (text3.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase) || text3.EndsWith(".asset", StringComparison.OrdinalIgnoreCase)) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text3); if (!string.IsNullOrWhiteSpace(fileNameWithoutExtension)) { AssetOwners[fileNameWithoutExtension] = value; } } } } _loadedSignature = text; DataForgePlugin.Log.LogDebug((object)$"Tracked {AssetOwners.Count} mod asset owner mapping(s) for reference sections."); } } private static IEnumerable<string> EnumerateLookupCandidates(string assetName) { string normalizedName = (assetName ?? "").Replace("(Clone)", "").Trim(); if (normalizedName.Length != 0) { yield return normalizedName; int num = normalizedName.IndexOf(':'); if (num > 0) { yield return normalizedName.Substring(0, num); } } } private static List<PluginResourceSnapshot> GetPluginResources() { return (from plugin in Chainloader.PluginInfos.Values.Select(delegate(PluginInfo pluginInfo) { string text = (pluginInfo.Metadata.Name ?? "").Trim(); string text2 = (pluginInfo.Metadata.GUID ?? "").Trim(); string assemblyName = ""; string[] resourceNames = Array.Empty<string>(); try { assemblyName = ((object)pluginInfo.Instance)?.GetType().Assembly.GetName().Name ?? ""; resourceNames = ((object)pluginInfo.Instance)?.GetType().Assembly.GetManifestResourceNames() ?? Array.Empty<string>(); } catch { } return new PluginResourceSnapshot { OwnerName = ((text.Length > 0) ? text : text2), PluginName = text, PluginGuid = text2, AssemblyName = assemblyName, ResourceNames = resourceNames }; }) where plugin.OwnerName.Length > 0 select plugin).ToList(); } private static string ResolveOwnerName(string bundleName, List<PluginResourceSnapshot> plugins) { PluginResourceSnapshot pluginResourceSnapshot = plugins.FirstOrDefault((PluginResourceSnapshot plugin) => plugin.ResourceNames.Any((string resourceName) => resourceName.EndsWith(bundleName, StringComparison.OrdinalIgnoreCase))); if (pluginResourceSnapshot != null) { return pluginResourceSnapshot.OwnerName; } string normalizedBundleName = NormalizeToken(Path.GetFileNameWithoutExtension(bundleName)); if (normalizedBundleName.Length == 0) { return ""; } return plugins.FirstOrDefault(delegate(PluginResourceSnapshot plugin) { string pluginToken = NormalizeToken(plugin.PluginName); string pluginToken2 = NormalizeToken(plugin.PluginGuid); string pluginToken3 = NormalizeToken(plugin.AssemblyName); return IsTokenMatch(normalizedBundleName, pluginToken) || IsTokenMatch(normalizedBundleName, pluginToken2) || IsTokenMatch(normalizedBundleName, pluginToken3); })?.OwnerName ?? ""; } private static bool IsTokenMatch(string bundleName, string pluginToken) { if (pluginToken.Length > 0) { if (bundleName.IndexOf(pluginToken, StringComparison.OrdinalIgnoreCase) < 0) { return pluginToken.IndexOf(bundleName, StringComparison.OrdinalIgnoreCase) >= 0; } return true; } return false; } private static string NormalizeToken(string value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } StringBuilder stringBuilder = new StringBuilder(); foreach (char c in value) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } private static string BuildSignature() { IEnumerable<string> values = (from bundle in AssetBundle.GetAllLoadedAssetBundles() select ((Object)bundle).name ?? "" into name where name.Length > 0 select name).OrderBy<string, string>((string name) => name, StringComparer.OrdinalIgnoreCase); IEnumerable<string> values2 = Chainloader.PluginInfos.Values.Select(delegate(PluginInfo pluginInfo) { string text = pluginInfo.Metadata.Name ?? ""; string text2 = pluginInfo.Metadata.GUID ?? ""; string text3 = ""; try { text3 = ((object)pluginInfo.Instance)?.GetType().Assembly.GetName().Name ?? ""; } catch { } return text2 + ":" + text + ":" + text3; }).OrderBy<string, string>((string token) => token, StringComparer.OrdinalIgnoreCase); return string.Join("|", values) + "||" + string.Join("|", values2); } } internal readonly struct DataForgeItemSortGroup { internal int BigGroupRank { get; } internal int SubGroupRank { get; } internal int DetailRank { get; } internal DataForgeItemSortGroup(int bigGroupRank, int subGroupRank, int detailRank) { BigGroupRank = bigGroupRank; SubGroupRank = subGroupRank; DetailRank = detailRank; } } internal static class DataForgeItemSortClassifier { private const int Melee = 0; private const int Ranged = 1; private const int Magic = 2; private const int Equipment = 3; private const int Food = 4; private const int Consumable = 5; private const int Meadbase = 6; private const int Misc = 7; private const int Unknown = 8; internal static DataForgeItemSortGroup Classify(string? itemName, SharedData? shared) { //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Invalid comparison between Unknown and I4 if (shared == null) { return ClassifyFallback(itemName); } if (MatchElementalMagic(shared)) { return Group(2, 0); } if (MatchBloodMagic(shared)) { return Group(2, 1); } if (MatchBow(itemName, shared)) { return Group(1, 0); } if (MatchArrow(itemName, shared)) { return Group(1, 1); } if (MatchCrossbow(itemName, shared)) { return Group(1, 2); } if (MatchBolt(itemName, shared)) { return Group(1, 3); } if (MatchAmmo(shared)) { return Group(1, 4); } if (MatchBomb(shared)) { return Group(1, 5); } if (MatchSword(itemName, shared)) { return Group(0, 0, SwordDetail(itemName, shared)); } if (MatchAxe(itemName, shared)) { return Group(0, 1, AxeDetail(itemName, shared)); } if (MatchClub(itemName, shared)) { return Group(0, 2, ClubDetail(itemName, shared)); } if (MatchKnife(itemName, shared)) { return Group(0, 3); } if (MatchSpear(itemName, shared)) { return Group(0, 4); } if (MatchPolearm(itemName, shared)) { return Group(0, 5); } if (MatchFists(itemName, shared)) { return Group(0, 6); } if (MatchShield(itemName, shared)) { return Group(0, 7, ShieldDetail(itemName)); } if (MatchPickaxe(itemName, shared)) { return Group(0, 8); } if (MatchTool(itemName, shared)) { return Group(0, 9); } if (MatchHelmet(shared)) { return Group(3, 0); } if (MatchChest(shared)) { return Group(3, 1); } if (MatchLegs(shared)) { return Group(3, 2); } if (MatchCape(shared)) { return Group(3, 3); } if (MatchUtility(shared)) { return Group(3, 4); } if (MatchTrinket(shared)) { return Group(3, 5); } if (MatchFeast(itemName)) { return Group(4, 3); } if (MatchNativeFood(shared, out var subGroupRank)) { return Group(4, subGroupRank); } if (MatchMeadbase(itemName, shared)) { return Group(6, 0); } if (MatchMead(itemName, shared)) { return Group(5, 0); } if (MatchPotion(itemName, shared)) { return Group(5, 1); } if ((int)shared.m_itemType == 13) { return Group(7, 0); } if (shared.m_value > 0) { return Group(7, 1); } return ClassifyFallback(itemName); } private static DataForgeItemSortGroup ClassifyFallback(string? itemName) { if (HasToken(itemName, "staff", "magic", "elemental")) { return Group(2, 0); } if (HasToken(itemName, "bloodmagic")) { return Group(2, 1); } if (HasToken(itemName, "bow") && !HasToken(itemName, "crossbow")) { return Group(1, 0); } if (HasToken(itemName, "arrow")) { return Group(1, 1); } if (HasToken(itemName, "crossbow", "arbalest")) { return Group(1, 2); } if (HasToken(itemName, "bolt")) { return Group(1, 3); } if (HasToken(itemName, "bomb")) { return Group(1, 5); } if (HasToken(itemName, "sword")) { return Group(0, 0, SwordDetail(itemName, null)); } if (HasToken(itemName, "axe", "battleaxe")) { return Group(0, 1, AxeDetail(itemName, null)); } if (HasToken(itemName, "club", "mace", "sledge")) { return Group(0, 2, ClubDetail(itemName, null)); } if (HasToken(itemName, "knife")) { return Group(0, 3); } if (HasToken(itemName, "spear")) { return Group(0, 4); } if (HasToken(itemName, "atgeir", "polearm")) { return Group(0, 5); } if (HasToken(itemName, "fist")) { return Group(0, 6); } if (HasToken(itemName, "shield", "buckler")) { return Group(0, 7, ShieldDetail(itemName)); } if (HasToken(itemName, "pickaxe")) { return Group(0, 8); } if (HasToken(itemName, "hammer", "hoe", "cultivator", "torch", "fishingrod", "tankard")) { return Group(0, 9); } if (HasToken(itemName, "helmet", "hood", "helm")) { return Group(3, 0); } if (HasToken(itemName, "chest", "cuirass", "tunic", "robe")) { return Group(3, 1); } if (HasToken(itemName, "legs", "leg", "pants", "greaves")) { return Group(3, 2); } if (HasToken(itemName, "cape", "shoulder")) { return Group(3, 3); } if (HasToken(itemName, "trinket")) { return Group(3, 5); } if (HasToken(itemName, "feast")) { return Group(4, 3); } if (HasToken(itemName, "meadbase")) { return Group(6, 0); } if (HasToken(itemName, "mead")) { return Group(5, 0); } if (HasToken(itemName, "potion")) { return Group(5, 1); } if (HasToken(itemName, "trophy")) { return Group(7, 0); } return Group(8, 0); } private static bool MatchSword(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)1)) { return HasToken(itemName, shared, "sword"); } return true; } private static bool MatchAxe(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)7)) { return HasToken(itemName, shared, "axe", "battleaxe"); } return true; } private static bool MatchClub(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)3)) { return HasToken(itemName, shared, "club", "mace", "sledge"); } return true; } private static bool MatchKnife(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)2)) { return HasToken(itemName, shared, "knife"); } return true; } private static bool MatchSpear(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)5)) { return HasToken(itemName, shared, "spear"); } return true; } private static bool MatchPolearm(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)4) && !IsItemTypeOrAttach(shared, (ItemType)20)) { return HasToken(itemName, shared, "atgeir", "polearm"); } return true; } private static bool MatchFists(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)11)) { return HasToken(itemName, shared, "fist"); } return true; } private static bool MatchShield(string? itemName, SharedData shared) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if (!IsItemTypeOrAttach(shared, (ItemType)5) && (int)shared.m_skillType != 6) { return HasToken(itemName, shared, "shield", "buckler"); } return true; } private static bool MatchPickaxe(string? itemName, SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 if ((int)shared.m_skillType != 12) { return HasToken(itemName, shared, "pickaxe"); } return true; } private static bool MatchTool(string? itemName, SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 if ((int)shared.m_itemType != 19 && !IsItemTypeOrAttach(shared, (ItemType)15) && (int)shared.m_skillType != 104 && (int)shared.m_skillType != 106) { return HasToken(itemName, shared, "hammer", "hoe", "cultivator", "torch", "fishingrod", "tankard"); } return true; } private static bool MatchBow(string? itemName, SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 if ((int)shared.m_skillType != 14 && (int)shared.m_skillType != 9 && (int)shared.m_skillType != 10) { if ((int)shared.m_itemType != 4 && !IsSkillAttack(shared, (SkillType)8)) { if (HasToken(itemName, shared, "bow")) { return !HasToken(itemName, shared, "crossbow"); } return false; } return true; } return false; } private static bool MatchCrossbow(string? itemName, SharedData shared) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 if (!MatchAmmo(shared) && ((int)shared.m_skillType == 14 || HasToken(itemName, shared, "crossbow", "arbalest"))) { if ((int)shared.m_itemType != 4 && !HasAttackAnimation(shared)) { return HasToken(itemName, shared, "crossbow", "arbalest"); } return true; } return false; } private static bool MatchArrow(string? itemName, SharedData shared) { if (!HasAttackAnimation(shared) && (string.Equals(shared.m_ammoType, "$ammo_arrows", StringComparison.Ordinal) || HasToken(itemName, shared, "arrow"))) { return GetTotalDamage(shared) > 0f; } return false; } private static bool MatchBolt(string? itemName, SharedData shared) { if (!HasAttackAnimation(shared) && (string.Equals(shared.m_ammoType, "$ammo_bolts", StringComparison.Ordinal) || HasToken(itemName, shared, "bolt"))) { return GetTotalDamage(shared) > 0f; } return false; } private static bool MatchAmmo(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 if ((int)shared.m_itemType != 9) { return (int)shared.m_itemType == 23; } return true; } private static bool MatchBomb(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 if ((int)shared.m_itemType == 3 && (int)shared.m_animationState == 0) { Attack attack = shared.m_attack; if (attack != null && (int)attack.m_attackType == 2) { return string.Equals(shared.m_attack?.m_attackAnimation, "throw_bomb", StringComparison.Ordinal); } } return false; } private static bool MatchElementalMagic(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 return (int)shared.m_skillType == 9; } private static bool MatchBloodMagic(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 return (int)shared.m_skillType == 10; } private static bool MatchHelmet(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)6); } private static bool MatchChest(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)7); } private static bool MatchLegs(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)11); } private static bool MatchCape(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)17); } private static bool MatchUtility(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)18); } private static bool MatchTrinket(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)24); } private static bool MatchNativeFood(SharedData shared, out int subGroupRank) { SharedData foodSharedData = GetFoodSharedData(shared); subGroupRank = 0; if (!HasFoodCarrier(shared) || (foodSharedData.m_food <= 0f && foodSharedData.m_foodStamina <= 0f && foodSharedData.m_foodEitr <= 0f)) { return false; } if (foodSharedData.m_food >= foodSharedData.m_foodStamina && foodSharedData.m_food >= foodSharedData.m_foodEitr) { subGroupRank = 0; return true; } if (foodSharedData.m_foodStamina >= foodSharedData.m_food && foodSharedData.m_foodStamina >= foodSharedData.m_foodEitr) { subGroupRank = 1; return true; } subGroupRank = 2; return true; } private static bool HasFoodCarrier(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if ((int)shared.m_itemType != 2) { if ((int)shared.m_itemType == 1) { return (Object)(object)shared.m_appendToolTip != (Object)null; } return false; } return true; } private static SharedData GetFoodSharedData(SharedData shared) { return shared.m_appendToolTip?.m_itemData?.m_shared ?? shared; } private static bool MatchFeast(string? itemName) { if (HasToken(itemName, "feast")) { return HasToken(itemName, "material"); } return false; } private static bool MatchMeadbase(string? itemName, SharedData shared) { return HasToken(itemName, shared, "meadbase"); } private static bool MatchMead(string? itemName, SharedData shared) { if (!MatchMeadbase(itemName, shared)) { return HasToken(itemName, shared, "mead"); } return false; } private static bool MatchPotion(string? itemName, SharedData shared) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Invalid comparison between Unknown and I4 if (!MatchMead(itemName, shared) && !MatchMeadbase(itemName, shared) && !MatchNativeFood(shared, out var _)) { if (!HasToken(itemName, shared, "potion")) { if ((int)shared.m_itemType == 1 || (int)shared.m_itemType == 2) { return (Object)(object)shared.m_consumeStatusEffect != (Object)null; } return false; } return true; } return false; } private static int SwordDetail(string? itemName, SharedData? shared) { if (IsTwoHanded(shared) || HasToken(itemName, "greatsword", "twohandedsword", "2hsword")) { return 1; } return 0; } private static int AxeDetail(string? itemName, SharedData? shared) { if (IsBattleaxe(itemName, shared)) { return 2; } if (!IsTwoHanded(shared) && !HasToken(itemName, "twohandedaxe", "2haxe", "dualaxe")) { return 0; } return 1; } private static bool IsBattleaxe(string? itemName, SharedData? shared) { if (HasToken(itemName, "dualaxe")) { return false; } if (HasToken(itemName, "battleaxe")) { return true; } string value = shared?.m_attack?.m_attackAnimation ?? ""; string text = shared?.m_secondaryAttack?.m_attackAnimation ?? ""; if (IsTwoHanded(shared) && string.Equals(text, "battleaxe_secondary", StringComparison.OrdinalIgnoreCase) && !ContainsIgnoreCase(value, "dualaxe")) { if (!ContainsIgnoreCase(value, "battleaxe")) { return ContainsIgnoreCase(text, "battleaxe"); } return true; } return false; } private static int ClubDetail(string? itemName, SharedData? shared) { if (!IsTwoHanded(shared) && !HasToken(itemName, "sledge", "twohandedclub", "2hclub")) { return 0; } return 1; } private static bool IsTwoHanded(SharedData? shared) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if (shared == null || (int)shared.m_itemType != 14) { if (shared == null) { return false; } return (int)shared.m_itemType == 22; } return true; } private static int ShieldDetail(string? itemName) { if (HasToken(itemName, "buckler")) { return 0; } if (!HasToken(itemName, "tower")) { return 1; } return 2; } private static bool IsSkillAttack(SharedData shared, SkillType skillType) { //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) if (shared.m_skillType == skillType && HasAttackAnimation(shared)) { return GetTotalDamage(shared) > 0f; } return false; } private static bool HasAttackAnimation(SharedData shared) { return !string.IsNullOrEmpty(shared.m_attack?.m_attackAnimation); } private static float GetTotalDamage(SharedData shared) { //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_0030: 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_003e: 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) DamageTypes damages = shared.m_damages; return damages.m_blunt + damages.m_slash + damages.m_pierce + damages.m_chop + damages.m_pickaxe + damages.m_fire + damages.m_frost + damages.m_lightning + damages.m_poison + damages.m_spirit; } private static bool IsItemTypeOrAttach(SharedData shared, ItemType itemType) { //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_000a: 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) if (shared.m_itemType != itemType) { return shared.m_attachOverride == itemType; } return true; } private static bool HasToken(string? itemName, SharedData? shared, params string[] tokens) { string[] array = new string[3] { itemName, shared?.m_name, StripLocalizationToken(shared?.m_name) }; for (int i = 0; i < array.Length; i++) { if (HasToken(array[i], tokens)) { return true; } } return false; } private static bool HasToken(string? value, params string[] tokens) { string text = DataForgeResourceMap.NormalizeResourceToken(value); if (text.Length == 0) { return false; } for (int i = 0; i < tokens.Length; i++) { string text2 = DataForgeResourceMap.NormalizeResourceToken(tokens[i]); if (text2.Length > 0 && text.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static bool ContainsIgnoreCase(string value, string token) { return value.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0; } private static string StripLocalizationToken(string? value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Trim(); if (!text.StartsWith("$item_", StringComparison.OrdinalIgnoreCase)) { return text.TrimStart(new char[1] { '$' }); } return text.Substring("$item_".Length); } private static DataForgeItemSortGroup Group(int bigGroupRank, int subGroupRank, int detailRank = 0) { return new DataForgeItemSortGroup(bigGroupRank, subGroupRank, detailRank); } } internal static class DataForgeResourceMap { private readonly struct RecipeLookupEntry { internal Recipe Recipe { get; } internal int? Tier { get; } internal RecipeLookupEntry(Recipe recipe, int? tier) { Recipe = recipe; Tier = tier; } } private const string ResourceMapFileName = "z_resourcemap.txt"; private const int UnknownTierSortValue = 999999; private static readonly object Sync = new object(); private static Dictionary<string, int> ResourceTierByToken = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, ItemDrop?> ItemDropCache = new Dictionary<string, ItemDrop>(StringComparer.OrdinalIgnoreCase); private static Dictionary<string, RecipeLookupEntry>? RecipeOutputLookup; private static readonly Dictionary<string, DataForgeItemSortGroup> ItemSortGroupCache = new Dictionary<string, DataForgeItemSortGroup>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, int> ItemTierSortValueCache = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); private static DateTime LoadedWriteTimeUtc = DateTime.MinValue; private static bool Loaded; private static int CachedObjectDbItemCount = -1; private static int CachedObjectDbRecipeCount = -1; private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); private static string ResourceMapPath => Path.Combine(ConfigDirectory, "z_resourcemap.txt"); internal static string BuildSortKey(int groupRank, int tierSortValue, string name) { return string.Join("|", Math.Max(0, groupRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, tierSortValue).ToString("D6", CultureInfo.InvariantCulture), name ?? ""); } internal static string BuildTierSortKey(int tierSortValue, string name) { return BuildSortKey(0, tierSortValue, name); } internal static string BuildItemSortKey(string? itemName, int tierSortValue, string name) { DataForgeItemSortGroup itemSortGroup = GetItemSortGroup(itemName); return string.Join("|", Math.Max(0, itemSortGroup.BigGroupRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, itemSortGroup.SubGroupRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, itemSortGroup.DetailRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, tierSortValue).ToString("D6", CultureInfo.InvariantCulture), name ?? ""); } private static DataForgeItemSortGroup GetItemSortGroup(string? itemName) { EnsureObjectDbCacheFresh(); string text = CleanPrefabName(itemName); if (text.Length > 0 && ItemSortGroupCache.TryGetValue(text, out var value)) { return value; } DataForgeItemSortGroup dataForgeItemSortGroup = DataForgeItemSortClassifier.Classify(itemName, ResolveItemDrop(itemName)?.m_itemData?.m_shared); if (text.Length > 0) { ItemSortGroupCache[text] = dataForgeItemSortGroup; } return dataForgeItemSortGroup; } internal static int GetItemTierSortValue(string? itemName) { EnsureObjectDbCacheFresh(); string text = CleanPrefabName(itemName); if (text.Length > 0 && ItemTierSortValueCache.TryGetValue(text, out var value)) { return value; } int? knownTierForItem = GetKnownTierForItem(itemName); int? knownTierForRecipe = GetKnownTierForRecipe(ResolveRecipeForItem(itemName)); int num = SortValue(MaxTier(knownTierForItem, knownTierForRecipe)); if (text.Length > 0) { ItemTierSortValueCache[text] = num; } return num; } internal static int GetResourceTierSortValue(IEnumerable<string?> resourceNames) { return SortValue(GetKnownTierForResourceNames(resourceNames)); } private static int SortValue(int? tier) { return tier ?? 999999; } private static int? GetKnownTierForRecipe(Recipe? recipe) { if (recipe?.m_resources == null) { return null; } return GetKnownTierForResourceNames(from requirement in recipe.m_resources where (Object)(object)requirement?.m_resItem != (Object)null && requirement.m_amount > 0 select ((Object)requirement.m_resItem).name); } private static int? GetKnownTierForItem(string? itemName) { ItemDrop val = ResolveItemDrop(itemName); List<string> list = new List<string> { itemName, ((Object)(object)val != (Object)null) ? ((Object)val).name : null, val?.m_itemData?.m_shared?.m_name, StripLocalizationToken(val?.m_itemData?.m_shared?.m_name) }; string text = val?.m_itemData?.m_shared?.m_name; if (text != null && text.Length > 0 && Localization.instance != null) { list.Add(Localization.instance.Localize(text)); } return GetKnownTierForResourceNames(list); } private static int? GetKnownTierForResourceNames(IEnumerable<string?> resourceNames) { EnsureLoaded(); int? num = null; foreach (string resourceName in resourceNames) { foreach (string resourceToken in GetResourceTokens(resourceName)) { if (ResourceTierByToken.TryGetValue(resourceToken, out var value)) { num = MaxTier(num, value); } } } return num; } private static int? MaxTier(int? left, int? right) { if (!left.HasValue) { return right; } if (!right.HasValue) { return left; } return Math.Max(left.Value, right.Value); } private static IEnumerable<string> GetResourceTokens(string? value) { string cleaned = CleanPrefabName(value); if (cleaned.Length != 0) { yield return NormalizeResourceToken(cleaned); string text = StripLocalizationToken(cleaned); if (text.Length > 0) { yield return NormalizeResourceToken(text); } } } private static void EnsureLoaded() { Directory.CreateDirectory(ConfigDirectory); EnsureDefaultResourceMapFile(); DateTime dateTime = (File.Exists(ResourceMapPath) ? File.GetLastWriteTimeUtc(ResourceMapPath) : DateTime.MinValue); lock (Sync) { if (!Loaded || !(dateTime == LoadedWriteTimeUtc)) { ResourceTierByToken = LoadResourceMap(ResourceMapPath); LoadedWriteTimeUtc = dateTime; Loaded = true; ClearSortCaches(); } } } private static void EnsureObjectDbCacheFresh() { int num = ObjectDB.instance?.m_items?.Count ?? (-1); int num2 = ObjectDB.instance?.m_recipes?.Count ?? (-1); if (num != CachedObjectDbItemCount || num2 != CachedObjectDbRecipeCount) { CachedObjectDbItemCount = num; CachedObjectDbRecipeCount = num2; ClearSortCaches(); } } private static void ClearSortCaches() { ItemDropCache.Clear(); RecipeOutputLookup = null; ItemSortGroupCache.Clear(); ItemTierSortValueCache.Clear(); } private static Dictionary<string, int> LoadResourceMap(string path) { Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); if (!File.Exists(path)) { return dictionary; } int num = -1; string[] array = File.ReadAllLines(path); for (int i = 0; i < array.Length; i++) { string text = StripComment(array[i]).Trim(); if (text.Length == 0) { continue; } if (text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) { num++; continue; } if (num < 0) { num = 0; } string text2 = NormalizeResourceToken(text); if (text2.Length > 0 && !dictionary.ContainsKey(text2)) { dictionary[text2] = num; } } return dictionary; } private static void EnsureDefaultResourceMapFile() { if (!File.Exists(ResourceMapPath)) { File.WriteAllText(ResourceMapPath, DefaultResourceMapText()); } } private static string StripComment(string line) { int num = line.IndexOf('#'); if (num < 0) { return line; } return line.Substring(0, num); } internal static string NormalizeResourceToken(string? token) { string text = CleanPrefabName(token); if (text.StartsWith("$item_", StringComparison.OrdinalIgnoreCase)) { text = text.Substring("$item_".Length); } else if (text.StartsWith("$", StringComparison.Ordinal)) { text = text.Substring(1); } return new string(text.Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray()); } private static string CleanPrefabName(string? name) { if (!string.IsNullOrWhiteSpace(name)) { return name.Replace("(Clone)", "").Trim(); } return ""; } private static string StripLocalizationToken(string? value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Trim(); if (!text.StartsWith("$item_", StringComparison.OrdinalIgnoreCase)) { return text.TrimStart(new char[1] { '$' }); } return text.Substring("$item_".Length); } private static ItemDrop? ResolveItemDrop(string? itemName) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrWhiteSpace(itemName)) { return null; } string normalized = CleanPrefabName(itemName); if (ItemDropCache.TryGetValue(normalized, out ItemDrop value)) { return value; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(normalized); ItemDrop val = default(ItemDrop); if ((Object)(object)itemPrefab != (Object)null && itemPrefab.TryGetComponent<ItemDrop>(ref val)) { ItemDropCache[normalized] = val; return val; } ItemDrop val2 = (from item in ObjectDB.instance.m_items where (Object)(object)item != (Object)null select item.GetComponent<ItemDrop>()).FirstOrDefault((Func<ItemDrop, bool>)((ItemDrop itemDrop) => (Object)(object)itemDrop != (Object)null && ItemNameMatches(itemDrop, normalized))); ItemDropCache[normalized] = val2; return val2; } private static Recipe? ResolveRecipeForItem(string? itemName) { if (ObjectDB.instance?.m_recipes == null || string.IsNullOrWhiteSpace(itemName)) { return null; } string itemName2 = CleanPrefabName(itemName); if (TryGetRecipeOutputLookupEntry(GetRecipeOutputLookup(), itemName2, out var entry)) { return entry.Recipe; } return null; } private static Dictionary<string, RecipeLookupEntry> GetRecipeOutputLookup() { if (RecipeOutputLookup != null) { return RecipeOutputLookup; } EnsureLoaded(); Dictionary<string, RecipeLookupEntry> dictionary = new Dictionary<string, RecipeLookupEntry>(StringComparer.OrdinalIgnoreCase); IEnumerable<Recipe> enumerable = ObjectDB.instance?.m_recipes; foreach (Recipe item in enumerable ?? Enumerable.Empty<Recipe>()) { if ((Object)(object)item?.m_item == (Object)null) { continue; } int? knownTierForRecipe = GetKnownTierForRecipe(item); foreach (string itemLookupKey in GetItemLookupKeys(item.m_item)) { AddRecipeOutputLookupEntry(dictionary, itemLookupKey, item, knownTierForRecipe); } } RecipeOutputLookup = dictionary; return dictionary; } private static bool TryGetRecipeOutputLookupEntry(Dictionary<string, RecipeLookupEntry> lookup, string itemName, out RecipeLookupEntry entry) { foreach (string lookupKey in GetLookupKeys(itemName)) { if (lookup.TryGetValue(lookupKey, out entry)) { return true; } } entry = default(RecipeLookupEntry); return false; } private static void AddRecipeOutputLookupEntry(Dictionary<string, RecipeLookupEntry> lookup, string key, Recipe recipe, int? tier) { if (key.Length != 0 && (!lookup.TryGetValue(key, out var value) || (tier ?? (-1)) > (value.Tier ?? (-1)))) { lookup[key] = new RecipeLookupEntry(recipe, tier); } } private static IEnumerable<string> GetItemLookupKeys(ItemDrop itemDrop) { if (itemDrop?.m_itemData?.m_shared == null) { yield break; } string[] array = new string[4] { ((Object)itemDrop).name, ((Object)((Component)itemDrop).gameObject).name, itemDrop.m_itemData.m_shared.m_name, StripLocalizationToken(itemDrop.m_itemData.m_shared.m_name) }; foreach (string value in array) { foreach (string lookupKey in GetLookupKeys(value)) { yield return lookupKey; } } } private static IEnumerable<string> GetLookupKeys(string? value) { string clean = CleanPrefabName(value); if (clean.Length > 0) { yield return clean; } string text = NormalizeResourceToken(clean); if (text.Length > 0 && !string.Equals(text, clean, StringComparison.OrdinalIgnoreCase)) { yield return text; } } private static bool ItemNameMatches(ItemDrop itemDrop, string token) { if (itemDrop?.m_itemData?.m_shared == null) { return false; } string value = NormalizeResourceToken(token); string[] array = new string[4] { ((Object)itemDrop).name, ((Object)((Component)itemDrop).gameObject).name, itemDrop.m_itemData.m_shared.m_name, StripLocalizationToken(itemDrop.m_itemData.m_shared.m_name) }; foreach (string text in array) { if (CleanPrefabName(text).Equals(token, StringComparison.OrdinalIgnoreCase) || NormalizeResourceToken(text).Equals(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static string DefaultResourceMapText() { return string.Join(Environment.NewLine, "# DataForge resource tier map.", "# User-editable sorting data for generated item, recipe, and piece reference/full scaffold files.", "# Sections near the top are lower tier; sections near the bottom are higher tier.", "# Generated files use the same direction: lower tiers first, higher tiers later.", "# Edit this file to change item/recipe/piece generated file sorting.", "", "[Meadows]", "Wood", "Stone", "Resin", "Dandelion", "Flint", "LeatherScraps", "BoneFragments", "Honey", "Raspberry", "Blueberries", "DeerHide", "DeerMeat", "Feathers", "GreydwarfEye", "RawMeat", "", "[BlackForest]", "HardAntler", "Bronze", "BronzeNails", "Copper", "Tin", "Ectoplasm", "SurtlingCore", "TrollHide", "BjornHide", "FineWood", "AncientSeed", "Carrot", "BjornMeat", "BjornPaw", "RoundLog", "Thistle", "", "[Swamp]", "Iron", "Ooze", "Entrails", "Guck", "Bloodbag", "Chain", "ElderBark", "IronNails", "Root", "Turnip", "WitheredBone", "CuredSquirrelHamstring", "", "[Ocean]", "Chitin", "SerpentScale", "SerpentMeat", "", "[Mountain]", "Silver", "Crystal", "DragonEgg", "JuteRed", "Obsidian", "WolfClaw", "WolfFang", "WolfHairBundle", "WolfMeat", "WolfPelt", "", "[Plains]", "UndeadBjornRibcage", "BlackMetal", "DragonTear", "Barley", "BarleyFlour", "ChickenEgg", "ChickenMeat", "Flax", "GoblinTotem", "LinenThread", "LoxMeat", "LoxPelt", "Needle", "Tar", "", "[Mistlands]", "Eitr", "Bilebag", "BlackCore", "BlackMarble", "BugMeat", "Carapace", "DvergrKeyFragment", "DvergrNeedle", "GiantBloodSack", "HareMeat", "JuteBlue", "Mandible", "Sap", "ScaleHide", "Softtissue", "Wisp", "YagluthDrop", "YggdrasilWood", "", "[Ashlands]", "FlametalNew", "AskBladder", "AskHide", "AsksvinEgg", "AsksvinMeat", "Blackwood", "BoneMawSerpentMeat", "BonemawSerpentTooth", "CelestialFeather", "CeramicPlate", "CharcoalResin", "CharredBone", "CharredCogwheel", "Charredskull", "Grausten", "MoltenCore", "MorgenHeart", "MorgenSinew", "ProustitePowder", "SulfurStone", "VoltureEgg", "VoltureMeat", ""); } } internal static class DataForgeValue { internal static void Copy(string? value, Action<string> assign) { if (value != null) { assign(value); } } internal static void Copy(bool? value, Action<bool> assign) { if (value.HasValue) { assign(value.Value); } } internal static void Copy(int? value, Action<int> assign) { if (value.HasValue) { assign(value.Value); } } internal static void Copy(float? value, Action<float> assign) { if (value.HasValue) { assign(value.Value); } } internal static string[] SplitTuple(string? value) { return (from part in value?.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray() ?? Array.Empty<string>(); } internal static bool IsNone(string? value) { string text = value?.Trim() ?? ""; if (text.Length != 0 && !text.Equals("none", StringComparison.OrdinalIgnoreCase)) { return text.Equals("null", StringComparison.OrdinalIgnoreCase); } return true; } } internal static class DataForgeSync { internal static bool PublishPayload(CustomSyncedValue<string>? syncedPayload, string domainName, string payload) { if (syncedPayload == null || string.Equals(syncedPayload.Value ?? "", payload, StringComparison.Ordinal)) { return false; } DataForgePlugin.Log.LogDebug((object)$"Publishing {domainName} payload ({Encoding.UTF8.GetByteCount(payload)} bytes)."); syncedPayload.AssignLocalValue(payload); return true; } } internal static class DataForgeReferenceState { private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); internal static bool ShouldSkip(string stateKey, string referencePath, string sourceSignature, string logicVersion) { if (!File.Exists(referencePath)) { return false; } string statePath = GetStatePath(stateKey); if (!File.Exists(statePath)) { return false; } try { string[] array = File.ReadAllLines(statePath); if (array.Length < 3) { return false; } return string.Equals(array[0].Trim(), sourceSignature, StringComparison.Ordinal) && string.Equals(array[1].Trim(), BuildFileStamp(referencePath), StringComparison.Ordinal) && string.Equals(array[2].Trim(), logicVersion, StringComparison.Ordinal); } catch { return false; } } internal static void Record(string stateKey, string referencePath, string sourceSignature, string logicVersion) { if (File.Exists(referencePath)) { string statePath = GetStatePath(stateKey); string directoryName = Path.GetDirectoryName(statePath); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } string content = sourceSignature.Trim() + Environment.NewLine + BuildFileStamp(referencePath) + Environment.NewLine + logicVersion + Environment.NewLine; GeneratedArtifactWriter.WriteTextIfChanged(statePath, content); } } internal static string BuildFileStamp(string path) { if (!File.Exists(path)) { return "missing"; } FileInfo fileInfo = new FileInfo(path); return fileInfo.Length.ToString(CultureInfo.InvariantCulture) + ":" + fileInfo.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture); } internal static string ComputeStableHash(string value) { using SHA256 sHA = SHA256.Create(); byte[] bytes = Encoding.UTF8.GetBytes(value ?? ""); byte[] array = sHA.ComputeHash(bytes); StringBuilder stringBuilder = new StringBuilder(array.Length * 2); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } private static string GetStatePath(string stateKey) { return Path.Combine(ConfigDirectory, "cache", ".reference-state." + stateKey + ".txt"); } } internal static class DataForgeProfiler { internal static void Profile(string label, Action action) { if (!DataForgePlugin.LogStartupTimings) { action(); return; } Stopwatch stopwatch = Stopwatch.StartNew(); try { action(); } finally { stopwatch.Stop(); DataForgeConnectionProfiler.MarkProfiledPhase(label, stopwatch.Elapsed.TotalMilliseconds); DataForgePlugin.Log.LogInfo((object)$"DataForge profile: {label} took {stopwatch.Elapsed.TotalMilliseconds:0.###} ms."); } } } internal static class DataForgeConnectionProfiler { private readonly struct Milestone { internal string Label { get; } internal double ElapsedMs { get; } internal Milestone(string label, double elapsedMs) { Label = label; ElapsedMs = elapsedMs; } } private static readonly object Lock = new object(); private static readonly Stopwatch Stopwatch = new Stopwatch(); private static readonly List<Milestone> Milestones = new List<Milestone>(); private static readonly HashSet<string> SeenOneShotMilestones = new HashSet<string>(StringComparer.Ordinal); private static bool Active; private static bool Completed; internal static void Begin(string label) { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { Active = true; Completed = false; Milestones.Clear(); SeenOneShotMilestones.Clear(); Stopwatch.Restart(); AddMilestone(label); } } internal static void Mark(string label) { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { if (Active && !Completed) { AddMilestone(label); } } } internal static void MarkOnce(string label) { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { if (Active && !Completed && SeenOneShotMilestones.Add(label)) { AddMilestone(label); } } } internal static void MarkProfiledPhase(string label, double elapsedMs) { if (DataForgePlugin.LogStartupTimings) { Mark($"DataForge {label} ({elapsedMs:0.###} ms)"); } } internal static void Complete(string label) { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { if (Active && !Completed) { AddMilestone(label); Completed = true; Stopwatch.Stop(); LogSummary("completed"); Active = false; } } } internal static void Abort(string label) { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { if (Active && !Completed) { AddMilestone(label); Completed = true; Stopwatch.Stop(); LogSummary("aborted"); Active = false; } } } private static void AddMilestone(string label) { Milestones.Add(new Milestone(label, Stopwatch.Elapsed.TotalMilliseconds)); } private static void LogSummary(string result) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"DataForge lobby-to-world profile {result}: total {Stopwatch.Elapsed.TotalMilliseconds:0.###} ms."); double num = 0.0; foreach (Milestone milestone in Milestones) { stringBuilder.Append(" +"); stringBuilder.Append(milestone.ElapsedMs.ToString("0.###")); stringBuilder.Append(" ms"); stringBuilder.Append(" (delta "); stringBuilder.Append((milestone.ElapsedMs - num).ToString("0.###")); stringBuilder.Append(" ms): "); stringBuilder.AppendLine(milestone.Label); num = milestone.ElapsedMs; } DataForgePlugin.Log.LogInfo((object)stringBuilder.ToString().TrimEnd(Array.Empty<char>())); } } [HarmonyPatch(typeof(FejdStartup), "JoinServer")] internal static class DataForgeFejdStartupJoinServerProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.Begin("FejdStartup.JoinServer"); } } [HarmonyPatch(typeof(FejdStartup), "OnStartGame")] internal static class DataForgeFejdStartupOnStartGameProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.Begin("FejdStartup.OnStartGame"); } } [HarmonyPatch(typeof(FejdStartup), "TransitionToMainScene")] internal static class DataForgeFejdStartupTransitionToMainSceneProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.Mark("FejdStartup.TransitionToMainScene"); } } [HarmonyPatch(typeof(FejdStartup), "LoadMainScene")] internal static class DataForgeFejdStartupLoadMainSceneProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.Mark("FejdStartup.LoadMainScene"); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] internal static class DataForgeFejdStartupShowConnectErrorProfilerPatch { private static void Postfix() { //IL_0