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 VikingCodex v1.0.8
plugins/VikingCodex/VikingCodex.dll
Decompiled 18 seconds 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.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; using VikingCodex.Codex; using VikingCodex.Codex.UI; using VikingCodex.Compatibility; using VikingCodex.Configuration; using VikingCodex.Core; using VikingCodex.Sharing; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("VikingCodex")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.8.0")] [assembly: AssemblyInformationalVersion("1.0.8")] [assembly: AssemblyProduct("VikingCodex")] [assembly: AssemblyTitle("VikingCodex")] [assembly: AssemblyMetadata("AI_Assisted_Creation", "Code and documentation generated with assistance from OpenAI Codex.")] [assembly: AssemblyMetadata("AI_Model_Vendor", "OpenAI")] [assembly: AssemblyMetadata("AI_Agent", "OpenAI Codex")] [assembly: AssemblyVersion("1.0.8.0")] [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 VikingCodex { [BepInPlugin("com.vikingcodex", "Viking Codex", "1.0.8")] [BepInDependency("com.jotunn.jotunn", "2.30.2")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "com.vikingcodex"; public const string Version = "1.0.8"; internal static Plugin Instance; internal static ManualLogSource Log; internal ModSettings Settings; internal readonly CodexRegistry Registry = new CodexRegistry(); internal readonly DiscoveryTracker Tracker = new DiscoveryTracker(); internal DiscoverySync Sync; private CodexPanel panel; private MainMenu mainMenu; private Harmony harmony; private ObjectDB database; private ZNetScene scene; private float retryAt; private bool registryModded; private static readonly Dictionary<string, float> warningTimes = new Dictionary<string, float>(StringComparer.Ordinal); private void Awake() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; warningTimes.Clear(); Settings = new ModSettings(((BaseUnityPlugin)this).Config); Sync = new DiscoverySync(this); harmony = new Harmony("com.vikingcodex"); harmony.PatchAll(typeof(Plugin).Assembly); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new DiagnosticsCommand()); if (!GUIManager.IsHeadless()) { panel = new CodexPanel(this); mainMenu = new MainMenu(); } ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("Viking Codex {0}, protocol {1}. Player runtime acceptance remains pending.", "1.0.8", 2)); } internal void ReloadRegistry() { if (!Object.op_Implicit((Object)(object)ObjectDB.instance) || !Object.op_Implicit((Object)(object)ZNetScene.instance)) { throw new InvalidOperationException("A loaded world is required"); } Registry.Build(Settings.Modded.Value, Path.Combine(Paths.ConfigPath, "VikingCodex", "metadata")); database = ObjectDB.instance; scene = ZNetScene.instance; registryModded = Settings.Modded.Value; Sync.RegistryChanged(); Tracker.Changed(); } private void Update() { if (Object.op_Implicit((Object)(object)ObjectDB.instance) && Object.op_Implicit((Object)(object)ZNetScene.instance) && ((Object)(object)database != (Object)(object)ObjectDB.instance || (Object)(object)scene != (Object)(object)ZNetScene.instance || registryModded != Settings.Modded.Value) && Time.unscaledTime >= retryAt) { retryAt = Time.unscaledTime + 5f; try { ReloadRegistry(); } catch (Exception e) { Warn("Registry rebuild failed; retaining previous snapshot", e); } } try { Sync.Tick(); } catch (Exception e2) { Warn("Discovery synchronization", e2); } try { mainMenu?.Tick(); } catch (Exception e3) { mainMenu?.Dispose(); Warn("Codex main menu", e3); } try { panel?.Tick(); } catch (Exception e4) { panel?.RecoverFromError(); Warn("Codex interface", e4); } } private void OnDestroy() { try { mainMenu?.Dispose(); panel?.Dispose(); } finally { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } Instance = null; } } internal static void Warn(string operation, Exception e) { float unscaledTime = Time.unscaledTime; if (!warningTimes.TryGetValue(operation, out var value) || !(unscaledTime - value < 5f)) { if (warningTimes.Count > 256) { warningTimes.Clear(); } warningTimes[operation] = unscaledTime; ManualLogSource log = Log; if (log != null) { log.LogWarning((object)(operation + ": " + e.GetType().Name + ": " + e.Message)); } } } } } namespace VikingCodex.Sharing { internal sealed class DiscoverySync { private readonly Plugin plugin; private readonly CustomRPC rpc; private WorldKnowledgeStore store; private DiscoveryAuthority authority; private DiscoveryClient client; private ZNet session; private Player character; private long characterId; private long sessionWorld; private int hostCursor; private bool applying; private bool failed; private bool hostStarted; private readonly HashSet<string> encountered = new HashSet<string>(StringComparer.Ordinal); private int sentPackets; private int receivedPackets; private int rejectedPackets; private int appliedItems; private float nextPoll; private float nextPeerPrune; private float nextApplyAttempt; internal string Diagnostics => $"Sent {sentPackets}; received {receivedPackets}; malformed {rejectedPackets}; applied {appliedItems}; encountered {encountered.Count}; incoming {client?.HasHistory}; pending {client?.PendingCount}"; internal string Status { get; private set; } = "Waiting for character"; private bool Enabled { get { if (plugin.Settings.Sharing.Value && plugin.Settings.Materials.Value && store != null) { return !failed; } return false; } } internal DiscoverySync(Plugin owner) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0050: Expected O, but got Unknown plugin = owner; rpc = NetworkManager.Instance.AddRPC("Discovery.v2", new CoroutineHandler(ServerReceive), new CoroutineHandler(ClientReceive)); } internal void Tick() { ZNet instance = ZNet.instance; Player localPlayer = Player.m_localPlayer; long num = (Object.op_Implicit((Object)(object)localPlayer) ? localPlayer.GetPlayerID() : 0); long num2 = ((Object.op_Implicit((Object)(object)instance) && instance.IsServer()) ? instance.GetWorldUID() : 0); if ((Object)(object)session != (Object)(object)instance || sessionWorld != num2) { session = instance; sessionWorld = num2; store = null; authority = null; failed = false; ResetCharacter(localPlayer, num); } else if ((Object)(object)character != (Object)(object)localPlayer || characterId != num) { ResetCharacter(localPlayer, num); } if (!Object.op_Implicit((Object)(object)instance) || !plugin.Registry.Ready || (instance.IsServer() && (instance.GetWorld() == null || instance.GetWorld().m_menu))) { Status = "Waiting for world"; return; } if (instance.IsServer() && store == null && !failed && num2 != 0L) { try { store = new WorldKnowledgeStore(Path.Combine(Paths.ConfigPath, "VikingCodex", "worlds"), num2); authority = new DiscoveryAuthority(store, (string value) => plugin.Registry.Materials.ContainsKey(value)); if (store.Recovered) { Plugin.Log.LogWarning((object)"Shared history recovered from backup. Review the latest discoveries."); } } catch (Exception e) { Fail("History unavailable", e); } } if (instance.IsServer() && Time.unscaledTime >= nextPeerPrune) { authority?.RemoveDisconnected(from p in instance.GetPeers() where p.IsReady() select p.m_uid); nextPeerPrune = Time.unscaledTime + 2f; } if (instance.IsServer()) { Status = (failed ? "Sharing unavailable: history error" : (Enabled ? $"Sharing enabled; history {store.Count}" : "Sharing disabled")); } if (!Object.op_Implicit((Object)(object)localPlayer) || num == 0L) { return; } if (instance.IsServer()) { HostTick(); } else { if (client == null) { return; } if (!plugin.Settings.Sharing.Value || !plugin.Settings.Materials.Value) { client.Pause(); } if (Time.unscaledTime >= nextApplyAttempt && client.HasHistory) { try { if (client.Drain(Apply, 4) == 0 && client.HasHistory) { nextApplyAttempt = Time.unscaledTime + 2f; } } catch (Exception e2) { nextApplyAttempt = Time.unscaledTime + 2f; Plugin.Warn("Native discovery application will retry", e2); } } Status = ((!plugin.Settings.Sharing.Value) ? "Sharing disabled" : ((client.World == 0L) ? "Connecting discovery history" : (client.Enabled ? $"Shared history cursor {client.Cursor}; pending {client.PendingCount}" : "Server sharing disabled"))); if (!(Time.unscaledTime < nextPoll) && instance.GetServerPeer() != null && instance.GetServerPeer().IsReady()) { nextPoll = Time.unscaledTime + 2f; long uid = instance.GetServerPeer().m_uid; if (!client.HasHistory) { Send(uid, client.Hello()); } if (client.Enabled && plugin.Settings.Sharing.Value && plugin.Settings.Materials.Value && client.PendingCount > 0) { Send(uid, client.Discover()); } } } } private void ResetCharacter(Player player, long id) { character = player; characterId = id; client = ((id == 0L) ? null : new DiscoveryClient(id)); encountered.Clear(); hostStarted = false; hostCursor = 0; nextPoll = 0f; nextApplyAttempt = 0f; plugin.Tracker.Reset(player); } internal void RegistryChanged() { client?.RebuildHistory(); hostStarted = false; nextPoll = 0f; } private void HostTick() { Status = (failed ? "Sharing unavailable: history error" : (Enabled ? $"Sharing enabled; history {store.Count}" : "Sharing disabled")); if (!Enabled) { return; } if (!hostStarted) { hostCursor = ((!plugin.Settings.CatchUp.Value) ? store.Count : 0); hostStarted = true; } if (!(Time.unscaledTime >= nextApplyAttempt)) { return; } try { foreach (string item in store.Since(hostCursor).Take(4)) { if (!Apply(item)) { nextApplyAttempt = Time.unscaledTime + 2f; break; } hostCursor++; } } catch (Exception e) { nextApplyAttempt = Time.unscaledTime + 2f; Plugin.Warn("Host catch-up will retry", e); } } internal void Observe(ItemData item) { if (applying || !Object.op_Implicit((Object)(object)session) || !Object.op_Implicit((Object)(object)character) || !plugin.Registry.Ready || !plugin.Settings.Materials.Value || !plugin.Settings.Sharing.Value || item?.m_shared == null) { return; } string preferredPrefab = (Object.op_Implicit((Object)(object)item.m_dropPrefab) ? Utils.GetPrefabName(item.m_dropPrefab) : null); string text = plugin.Registry.MaterialIdentity.Resolve(item.m_shared.m_name, preferredPrefab); if (text == null || encountered.Contains(text)) { return; } if (session.IsServer()) { if (Enabled) { CommitHost(new string[1] { text }); if (!failed) { encountered.Add(text); } } } else if (client != null && client.Queue(text)) { encountered.Add(text); } } private void CommitHost(string[] ids) { if (!Enabled) { return; } try { store.CommitBatch(ids); } catch (Exception e) { Fail("Discovery commit failed", e); } } private void Fail(string operation, Exception e) { failed = true; Status = "Sharing unavailable: history error"; Plugin.Warn(operation + "; sharing disabled", e); } private void Send(long target, Message message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(Protocol.Encode(message)); rpc.SendPackage(target, val); sentPackets++; } private static Message Read(ZPackage package) { int num = package.Size() - package.GetPos(); if (num < 4 || num > 16388) { throw new InvalidDataException("Oversized or truncated transport frame"); } int num2 = package.ReadInt(); Protocol.ValidateTransportFrame(num, num2); return Protocol.Decode(package.ReadByteArray(num2)); } private IEnumerator ServerReceive(long sender, ZPackage package) { try { if (!Object.op_Implicit((Object)(object)session) || !session.IsServer() || authority == null || failed) { yield break; } ZNetPeer peer = session.GetPeer(sender); if (peer == null || !peer.IsReady() || peer.m_server) { yield break; } Message request = Read(package); receivedPackets++; ServerResult serverResult; try { serverResult = authority.Handle(sender, connectionReady: true, request, Time.unscaledTimeAsDouble, Enabled, plugin.Settings.CatchUp.Value); } catch (Exception e) { Fail("Shared history transaction failed", e); yield break; } if (serverResult.Committed.Length != 0) { Plugin.Log.LogInfo((object)$"Shared history committed {serverResult.Committed.Length} discoveries; total {store.Count}."); } foreach (Message reply in serverResult.Replies) { Send(sender, reply); } } catch (Exception e2) { rejectedPackets++; Plugin.Warn("Rejected discovery packet", e2); } } private IEnumerator ClientReceive(long sender, ZPackage package) { try { ZNet instance = ZNet.instance; if (Object.op_Implicit((Object)(object)instance) && !instance.IsServer() && client != null && instance.GetServerPeer() != null && sender == instance.GetServerPeer().m_uid) { Message message = Read(package); receivedPackets++; if (client.Receive(message, delegate(string id) { Plugin.Log.LogWarning((object)("Server does not support discovery " + id)); }) && message.Kind == MessageKind.History && message.Ids.Length != 0) { nextPoll = Time.unscaledTime + 0.25f; } } } catch (Exception e) { rejectedPackets++; Plugin.Warn("Rejected history packet", e); } yield break; } private bool Apply(string id) { Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { return false; } if (!plugin.Registry.Materials.TryGetValue(id, out var value) || !Object.op_Implicit((Object)(object)value.Item)) { return true; } if (localPlayer.IsMaterialKnown(value.Item.m_itemData.m_shared.m_name)) { return true; } applying = true; try { localPlayer.AddKnownItem(value.Item.m_itemData); appliedItems++; plugin.Tracker.Changed(); return localPlayer.IsMaterialKnown(value.Item.m_itemData.m_shared.m_name); } finally { applying = false; } } } } namespace VikingCodex.Core { [Serializable] public sealed class CatalogDocument { public int schema; public CatalogMetadata[] entries = Array.Empty<CatalogMetadata>(); } [Serializable] public sealed class CatalogMetadata { public string id = ""; public string biome = "Unclassified"; public string category = ""; public string wiki = ""; public string displayName = ""; public string description = ""; public string hint = ""; public string iconPrefab = ""; public string discoveryBehavior = ""; public string sharing = ""; public string source = ""; public string visibility = ""; public int order = 100000; public bool shareMaterial; } public static class CatalogValidation { public static Dictionary<string, CatalogMetadata> Validate(CatalogDocument document) { if (document == null || document.schema != 1 || document.entries == null || document.entries.Length > 20000) { throw new InvalidDataException("Unsupported metadata schema or size"); } Dictionary<string, CatalogMetadata> dictionary = new Dictionary<string, CatalogMetadata>(StringComparer.Ordinal); CatalogMetadata[] entries = document.entries; foreach (CatalogMetadata catalogMetadata in entries) { if (catalogMetadata == null) { throw new InvalidDataException("Null metadata entry"); } catalogMetadata.category = catalogMetadata.category ?? ""; catalogMetadata.wiki = catalogMetadata.wiki ?? ""; catalogMetadata.displayName = catalogMetadata.displayName ?? ""; catalogMetadata.description = catalogMetadata.description ?? ""; catalogMetadata.hint = catalogMetadata.hint ?? ""; catalogMetadata.iconPrefab = catalogMetadata.iconPrefab ?? ""; catalogMetadata.discoveryBehavior = catalogMetadata.discoveryBehavior ?? ""; catalogMetadata.sharing = catalogMetadata.sharing ?? ""; catalogMetadata.source = catalogMetadata.source ?? ""; catalogMetadata.visibility = catalogMetadata.visibility ?? ""; if (catalogMetadata == null || !ContentId(catalogMetadata.id) || !Text(catalogMetadata.biome, 80) || string.IsNullOrWhiteSpace(catalogMetadata.biome) || !Text(catalogMetadata.category, 80) || catalogMetadata.order < 0 || !Text(catalogMetadata.wiki, 150) || !Text(catalogMetadata.displayName, 160) || !Text(catalogMetadata.description, 4000, multiline: true) || !Text(catalogMetadata.source, 200)) { throw new InvalidDataException("Invalid metadata entry"); } if (catalogMetadata.iconPrefab != "" && !ContentId(catalogMetadata.iconPrefab)) { throw new InvalidDataException("Invalid icon source"); } if (!Text(catalogMetadata.hint, 180)) { throw new InvalidDataException("Invalid discovery hint"); } if (catalogMetadata.wiki.IndexOfAny(new char[4] { ':', '\\', '?', '#' }) >= 0) { throw new InvalidDataException("Wiki reference must be a page slug"); } if (catalogMetadata.visibility != "" && catalogMetadata.visibility != "exclude") { throw new InvalidDataException("Unsupported visibility policy"); } if (catalogMetadata.sharing != "" && catalogMetadata.sharing != "allow" && catalogMetadata.sharing != "deny") { throw new InvalidDataException("Unknown sharing policy"); } if (catalogMetadata.discoveryBehavior != "" && catalogMetadata.discoveryBehavior != "native-material" && catalogMetadata.discoveryBehavior != "native-recipe" && catalogMetadata.discoveryBehavior != "encounter") { throw new InvalidDataException("Unsupported discovery behavior"); } if (dictionary.ContainsKey(catalogMetadata.id)) { throw new InvalidDataException("Duplicate metadata identifier: " + catalogMetadata.id); } dictionary.Add(catalogMetadata.id, catalogMetadata); } return dictionary; } public static bool ContentId(string id) { if (string.IsNullOrWhiteSpace(id) || id.Length > 180 || id != id.Trim()) { return false; } foreach (char c in id) { if (char.IsControl(c) || c == '/' || c == '\\' || c == '|') { return false; } } return true; } private static bool Text(string value, int maximum, bool multiline = false) { if (value == null || value.Length > maximum) { return false; } foreach (char c in value) { if (char.IsControl(c) && (!multiline || (c != '\n' && c != '\r' && c != '\t'))) { return false; } } return true; } public static bool Shareable(bool knownVanilla, bool nativeMaterial, CatalogMetadata metadata) { if (!nativeMaterial || metadata?.sharing == "deny") { return false; } if (metadata != null && metadata.discoveryBehavior != "" && metadata.discoveryBehavior != "native-material") { return false; } if (!knownVanilla && !(metadata?.sharing == "allow")) { return metadata?.shareMaterial ?? false; } return true; } } public enum DiscoveryFilter { All, Discovered, Undiscovered } public sealed class EntryView { public string Id = ""; public string Name = ""; public string Hint = ""; public string Biome = "Unclassified"; public string Category = "Miscellaneous"; public int Order = 100000; public bool Discovered; public bool RecipeKnown; public bool Craftable; public string VisibleName(bool hide) { if (!hide || Discovered) { return Name; } return "???"; } public string RowLabel(bool hide, string kind, bool isNew) { if (!hide || Discovered) { return Name + " · " + kind + (isNew ? " NEW" : ""); } return "Undiscovered · " + kind + "\n" + ((Hint.Length > 86) ? (Hint.Substring(0, 83) + "…") : Hint); } } public static class CodexQuery { public static IEnumerable<EntryView> Filter(IEnumerable<EntryView> entries, string biome, string category, DiscoveryFilter state, string search, bool hide, bool alphabetical) { IEnumerable<EntryView> source = from e in entries where (biome == "All" || e.Biome == biome) && (category == "All" || e.Category == category) where state == DiscoveryFilter.All || e.Discovered == (state == DiscoveryFilter.Discovered) where string.IsNullOrWhiteSpace(search) || ((hide && !e.Discovered) ? e.Hint : e.Name).IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0 select e; if (!alphabetical) { return source.OrderBy((EntryView e) => BiomeOrder(e.Biome)).ThenBy<EntryView, string>((EntryView e) => e.Biome, StringComparer.Ordinal).ThenBy((EntryView e) => e.Order) .ThenBy<EntryView, string>((EntryView e) => e.VisibleName(hide), StringComparer.OrdinalIgnoreCase) .ThenBy<EntryView, string>((EntryView e) => e.Id, StringComparer.Ordinal); } return source.OrderBy<EntryView, string>((EntryView e) => e.VisibleName(hide), StringComparer.OrdinalIgnoreCase).ThenBy<EntryView, string>((EntryView e) => e.Id, StringComparer.Ordinal); } public static int BiomeOrder(string biome) { int num = Array.IndexOf(new string[12] { "Meadows", "Black Forest", "Swamp", "Mountains", "Plains", "Mistlands", "Ashlands", "Deep North", "Ocean", "Cross-biome", "Special", "Unclassified" }, biome); if (num >= 0) { return num; } return 12; } } public sealed class ContentIdentity { private readonly Dictionary<string, string> names; private readonly Dictionary<string, string?> unique; public ContentIdentity(IEnumerable<KeyValuePair<string, string>> idsAndKnowledgeKeys) { names = idsAndKnowledgeKeys.Where<KeyValuePair<string, string>>((KeyValuePair<string, string> p) => Protocol.ValidId(p.Key) && !string.IsNullOrEmpty(p.Value)).ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> p) => p.Key, (KeyValuePair<string, string> p) => p.Value, StringComparer.Ordinal); unique = names.GroupBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> p) => p.Value, StringComparer.Ordinal).ToDictionary<IGrouping<string, KeyValuePair<string, string>>, string, string>((IGrouping<string, KeyValuePair<string, string>> g) => g.Key, (IGrouping<string, KeyValuePair<string, string>> g) => (g.Count() != 1) ? null : g.First().Key, StringComparer.Ordinal); } public string? Resolve(string knowledgeKey, string? preferredPrefab) { if (string.IsNullOrEmpty(knowledgeKey)) { return null; } if (preferredPrefab != null && names.TryGetValue(preferredPrefab, out string value) && value == knowledgeKey) { return preferredPrefab; } if (!unique.TryGetValue(knowledgeKey, out string value2)) { return null; } return value2; } } public sealed class ServerResult { public readonly List<Message> Replies = new List<Message>(); public string[] Committed = Array.Empty<string>(); } public sealed class DiscoveryAuthority { private sealed class Peer { internal readonly RateLimit Limit = new RateLimit(); internal long Character; internal int Floor; internal bool Handshake; } private readonly WorldKnowledgeStore store; private readonly Func<string, bool> eligible; private readonly Dictionary<long, Peer> peers = new Dictionary<long, Peer>(); public int PeerCount => peers.Count; public DiscoveryAuthority(WorldKnowledgeStore store, Func<string, bool> eligible) { this.store = store; this.eligible = eligible; } public void RemoveDisconnected(IEnumerable<long> connected) { HashSet<long> active = new HashSet<long>(connected); long[] array = peers.Keys.Where((long id) => !active.Contains(id)).ToArray(); foreach (long key in array) { peers.Remove(key); } } public ServerResult Handle(long sender, bool connectionReady, Message request, double now, bool enabled, bool catchUp) { ServerResult serverResult = new ServerResult(); if (!connectionReady || sender == 0L || request.Character == 0L || request.Ids == null || request.Ids.Length > 64) { return serverResult; } if (!peers.TryGetValue(sender, out Peer value)) { value = (peers[sender] = new Peer()); } int cost = ((request.Kind != MessageKind.Discover) ? 1 : Math.Max(1, (request.Ids.Length + 7) / 8)); if (!value.Limit.Accept(now, cost)) { return serverResult; } if (request.Kind == MessageKind.Hello) { if (request.Ids.Length != 0 || request.Cursor < 0) { return serverResult; } if (!value.Handshake || value.Character != request.Character) { value.Character = request.Character; value.Floor = ((!catchUp) ? store.Count : 0); value.Handshake = true; } int val = ((request.World == store.World && request.Epoch == store.Epoch && request.Cursor <= store.Count) ? request.Cursor : 0); val = Math.Max(val, value.Floor); serverResult.Replies.Add(Envelope(MessageKind.History, request.Character, enabled, enabled ? store.Since(val) : Array.Empty<string>(), val)); return serverResult; } if (request.Kind != MessageKind.Discover || !value.Handshake || value.Character != request.Character || request.World != store.World || request.Epoch != store.Epoch || request.Ids.Length == 0) { return serverResult; } if (!enabled) { serverResult.Replies.Add(Envelope(MessageKind.History, request.Character, enabled: false, Array.Empty<string>(), 0)); return serverResult; } string[] array = request.Ids.Where((string id) => Protocol.ValidId(id) && eligible(id)).Distinct<string>(StringComparer.Ordinal).ToArray(); string[] array2 = request.Ids.Except<string>(array, StringComparer.Ordinal).ToArray(); serverResult.Committed = store.CommitBatch(array); if (array.Length != 0) { serverResult.Replies.Add(Envelope(MessageKind.Acknowledge, request.Character, enabled: true, array, 0)); } if (array2.Length != 0) { serverResult.Replies.Add(Envelope(MessageKind.Rejected, request.Character, enabled: true, array2, 0)); } return serverResult; } private Message Envelope(MessageKind kind, long character, bool enabled, string[] ids, int cursor) { return new Message { Kind = kind, Character = character, World = store.World, Epoch = store.Epoch, Enabled = enabled, Ids = ids, Cursor = cursor }; } } public sealed class DiscoveryClient { private Message? pendingHistory; private int pendingOffset; private readonly LinkedList<string> pending = new LinkedList<string>(); private readonly Dictionary<string, LinkedListNode<string>> queued = new Dictionary<string, LinkedListNode<string>>(StringComparer.Ordinal); public long Character { get; } public long World { get; private set; } public string Epoch { get; private set; } = ""; public int Cursor { get; private set; } public bool Enabled { get; private set; } public bool HasHistory => pendingHistory != null; public int PendingCount => queued.Count; public DiscoveryClient(long character) { if (character == 0L) { throw new ArgumentException("Character ID must be initialized"); } Character = character; } public void Pause() { Enabled = false; pendingHistory = null; pendingOffset = 0; } public void RebuildHistory() { World = 0L; Epoch = ""; Cursor = 0; Pause(); } public Message Hello() { return Envelope(MessageKind.Hello, Array.Empty<string>()); } public Message Discover() { return Envelope(MessageKind.Discover, pending.Take(64).ToArray()); } private Message Envelope(MessageKind kind, string[] ids) { return new Message { Kind = kind, Character = Character, World = World, Epoch = Epoch, Cursor = Cursor, Ids = ids }; } public bool Queue(string id) { if (!Protocol.ValidId(id) || queued.Count >= 100000 || queued.ContainsKey(id)) { return false; } queued.Add(id, pending.AddLast(id)); return true; } private void Remove(string id) { if (queued.TryGetValue(id, out LinkedListNode<string> value)) { pending.Remove(value); queued.Remove(id); } } public bool Receive(Message message, Action<string> rejected) { if (message.Character != Character || message.World == 0L || !Guid.TryParseExact(message.Epoch, "N", out var _) || message.Cursor < 0 || message.Ids == null || message.Ids.Length > 64) { return false; } if (message.Kind != MessageKind.History && message.Kind != MessageKind.Acknowledge && message.Kind != MessageKind.Rejected) { return false; } if (World != 0L && (message.World != World || message.Epoch != Epoch)) { return false; } if (World == 0L) { if (message.Kind != MessageKind.History) { return false; } World = message.World; Epoch = message.Epoch; Cursor = message.Cursor; } if (message.Kind == MessageKind.Acknowledge || message.Kind == MessageKind.Rejected) { string[] ids = message.Ids; foreach (string text in ids) { Remove(text); if (message.Kind == MessageKind.Rejected) { rejected(text); } } return true; } Enabled = message.Enabled; if (!Enabled) { pendingHistory = null; pendingOffset = 0; return true; } if (message.Cursor != Cursor || HasHistory) { return false; } if (message.Ids.Length != 0) { pendingHistory = message; pendingOffset = 0; } return true; } public int Drain(Func<string, bool> apply, int maximum) { if (maximum < 1) { throw new ArgumentOutOfRangeException("maximum"); } int num = 0; while (pendingHistory != null && num < maximum) { string text = pendingHistory.Ids[pendingOffset]; if (!apply(text)) { break; } Remove(text); Cursor++; pendingOffset++; num++; if (pendingOffset == pendingHistory.Ids.Length) { pendingHistory = null; pendingOffset = 0; } } return num; } } public static class ProgressionHints { public static bool IsBaseGame(string id, string prefab, ISet<string> vanilla) { if (!vanilla.Contains(id)) { return vanilla.Contains(prefab); } return true; } public static string Resolve(string authored, string biome, string category, string form) { if (!string.IsNullOrWhiteSpace(authored)) { return authored; } object obj; switch (biome) { default: obj = "the " + biome; break; case "Unclassified": case "Cross-biome": case "Special": obj = "the world"; break; } string text = (string)obj; if (!string.IsNullOrWhiteSpace(form)) { return form; } return category switch { "Bosses" => "Follow the summoning clues in " + text + ".", "Creatures" => "Encounter a living inhabitant of " + text + ".", "Raw materials" => "Gather a natural resource from " + text + ".", "Processed materials" => "Process resources from " + text + ".", "Food" => "Prepare a meal with resources from " + text + ".", "Consumables" => "Prepare a useful consumable for your travels.", "Crafting stations" => "Learn a workshop that opens more crafting options.", "Building pieces" => "Learn a construction using this biome's resources.", "Armor" => "Learn protective clothing for this stage of your journey.", "Weapons" => "Learn a weapon suited to this stage of your journey.", "Tools" => "Learn a tool that helps you gather or build.", "Equipment" => "Learn equipment that helps you survive and explore.", _ => "Collect a new object while exploring " + text + ".", }; } public static string Checklist(IEnumerable<EntryView> entries, string biome) { EntryView[] array = entries.Where((EntryView e) => biome == "All" || e.Biome == biome).ToArray(); int num = array.Length; int num2 = array.Count((EntryView e) => e.Discovered); string text = ((num == 0) ? "—" : (((int)((long)num2 * 100L / num)).ToString(CultureInfo.InvariantCulture) + "%")); string text2 = ((biome == "All") ? "Base-game checklist" : (biome + " checklist")); return text2 + ": " + num2 + "/" + num + " (" + text + ") · " + (num - num2) + " left"; } } public enum MessageKind : byte { Hello = 1, Discover, History, Acknowledge, Rejected } public sealed class Message { public MessageKind Kind; public long World; public long Character; public string Epoch = ""; public int Cursor; public bool Enabled; public string[] Ids = Array.Empty<string>(); } public static class Protocol { public const int Version = 2; public const int MaxBytes = 16384; public const int MaxBatch = 64; public const int MaxIdLength = 128; public static bool ValidId(string id) { if (string.IsNullOrEmpty(id) || id.Length > 128) { return false; } foreach (char c in id) { if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_' && c != '-' && c != '.') { return false; } } return true; } public static void ValidateTransportFrame(int availableBytes, int declaredBytes) { if (availableBytes < 4 || availableBytes > 16388 || declaredBytes < 0 || declaredBytes > 16384 || declaredBytes != availableBytes - 4) { throw new InvalidDataException("Invalid transport frame length"); } } public static byte[] Encode(Message m) { if (m == null || m.Ids == null || m.Ids.Length > 64 || m.Cursor < 0 || !Enum.IsDefined(typeof(MessageKind), m.Kind)) { throw new InvalidDataException("Batch too large"); } using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write(2); binaryWriter.Write((byte)m.Kind); binaryWriter.Write(m.World); binaryWriter.Write(m.Character); WriteString(binaryWriter, m.Epoch, 64); binaryWriter.Write(m.Cursor); binaryWriter.Write(m.Enabled); binaryWriter.Write(m.Ids.Length); string[] ids = m.Ids; foreach (string text in ids) { if (!ValidId(text)) { throw new InvalidDataException("Invalid identifier"); } WriteString(binaryWriter, text, 128); } binaryWriter.Flush(); if (memoryStream.Length > 16384) { throw new InvalidDataException("Message too large"); } return memoryStream.ToArray(); } public static Message Decode(byte[] bytes) { if (bytes == null || bytes.Length > 16384) { throw new InvalidDataException("Message too large"); } using MemoryStream memoryStream = new MemoryStream(bytes, writable: false); using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true); if (binaryReader.ReadInt32() != 2) { throw new InvalidDataException("Unsupported protocol"); } Message message = new Message { Kind = (MessageKind)binaryReader.ReadByte(), World = binaryReader.ReadInt64(), Character = binaryReader.ReadInt64(), Epoch = ReadString(binaryReader, 64), Cursor = binaryReader.ReadInt32(), Enabled = binaryReader.ReadBoolean() }; if (!Enum.IsDefined(typeof(MessageKind), message.Kind) || message.Cursor < 0) { throw new InvalidDataException("Invalid envelope"); } int num = binaryReader.ReadInt32(); if (num < 0 || num > 64) { throw new InvalidDataException("Invalid count"); } message.Ids = new string[num]; for (int i = 0; i < num; i++) { message.Ids[i] = ReadString(binaryReader, 128); if (!ValidId(message.Ids[i])) { throw new InvalidDataException("Invalid identifier"); } } if (memoryStream.Position != memoryStream.Length) { throw new InvalidDataException("Trailing data"); } return message; } internal static void WriteString(BinaryWriter w, string value, int limit) { byte[] bytes = Encoding.UTF8.GetBytes(value ?? ""); if (bytes.Length > limit) { throw new InvalidDataException("String too long"); } w.Write((ushort)bytes.Length); w.Write(bytes); } internal static string ReadString(BinaryReader r, int limit) { int num = r.ReadUInt16(); if (num > limit) { throw new InvalidDataException("String too long"); } byte[] array = r.ReadBytes(num); if (array.Length != num) { throw new EndOfStreamException(); } return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(array); } } public sealed class RateLimit { private double tokens = 24.0; private double last; public bool Accept(double now, int cost = 1) { if (double.IsNaN(now) || double.IsInfinity(now)) { return false; } tokens = Math.Min(24.0, tokens + Math.Max(0.0, now - last) * 4.0); last = Math.Max(last, now); if (cost < 1 || cost > 24 || tokens < (double)cost) { return false; } tokens -= cost; return true; } } public sealed class UnsupportedSchemaException : Exception { public UnsupportedSchemaException() : base("History schema is newer than this plugin; refusing downgrade") { } } public sealed class WorldKnowledgeStore { public const int Schema = 1; public const int MaxEntries = 100000; private readonly string path; private readonly List<string> entries = new List<string>(); private readonly HashSet<string> membership = new HashSet<string>(StringComparer.Ordinal); public long World { get; } public string Epoch { get; private set; } = Guid.NewGuid().ToString("N"); public int Count => entries.Count; public IReadOnlyList<string> Entries => entries; public bool Recovered { get; private set; } public WorldKnowledgeStore(string directory, long world) { World = world; Directory.CreateDirectory(directory); path = Path.Combine(directory, world.ToString(CultureInfo.InvariantCulture) + ".vck"); if (!File.Exists(path)) { if (File.Exists(path + ".bak")) { Load(path + ".bak"); Recovered = true; } return; } try { Load(path); } catch (Exception ex) when (ex is IOException || ex is InvalidDataException || ex is ArgumentException) { if (!File.Exists(path + ".bak")) { throw new InvalidDataException("History damaged; sharing disabled until owner recovery", ex); } Load(path + ".bak"); Recovered = true; } } public bool Contains(string id) { return membership.Contains(id); } public string[] Since(int cursor) { return entries.Skip(Math.Max(0, Math.Min(cursor, Count))).Take(64).ToArray(); } public bool Commit(string id) { return CommitBatch(new string[1] { id }).Length != 0; } public string[] CommitBatch(IEnumerable<string> identifiers) { string[] array = identifiers.ToArray(); if (array.Length > 64 || array.Any((string id) => !Protocol.ValidId(id))) { throw new InvalidDataException("Invalid discovery batch"); } string[] array2 = (from id in array.Distinct<string>(StringComparer.Ordinal) where !membership.Contains(id) select id).ToArray(); if (array2.Length == 0) { return Array.Empty<string>(); } if (Count + array2.Length > 100000) { throw new InvalidDataException("History capacity exceeded"); } entries.AddRange(array2); membership.UnionWith(array2); try { Save(); return array2; } catch { entries.RemoveRange(entries.Count - array2.Length, array2.Length); membership.ExceptWith(array2); throw; } } private void Load(string source) { FileInfo fileInfo = new FileInfo(source); if (fileInfo.Length < 32 || fileInfo.Length > 16000000) { throw new InvalidDataException("Invalid history size"); } byte[] array = File.ReadAllBytes(source); using (SHA256 sHA = SHA256.Create()) { if (!sHA.ComputeHash(array, 32, array.Length - 32).SequenceEqual(array.Take(32))) { throw new InvalidDataException("History checksum mismatch"); } } List<string> list = new List<string>(); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); using (MemoryStream memoryStream = new MemoryStream(array, 32, array.Length - 32, writable: false)) { using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8); if (binaryReader.ReadInt32() != 1447250737) { throw new InvalidDataException("Invalid history header"); } if (binaryReader.ReadInt32() != 1) { throw new UnsupportedSchemaException(); } if (binaryReader.ReadInt64() != World) { throw new InvalidDataException("Wrong world"); } string text = Protocol.ReadString(binaryReader, 64); if (!Guid.TryParseExact(text, "N", out var _)) { throw new InvalidDataException("Invalid epoch"); } int num = binaryReader.ReadInt32(); if (num < 0 || num > 100000) { throw new InvalidDataException("Invalid history count"); } for (int i = 0; i < num; i++) { string text2 = Protocol.ReadString(binaryReader, 128); if (!Protocol.ValidId(text2) || !hashSet.Add(text2)) { throw new InvalidDataException("Invalid history entry"); } list.Add(text2); } if (memoryStream.Position != memoryStream.Length) { throw new InvalidDataException("Trailing history data"); } Epoch = text; } entries.Clear(); entries.AddRange(list); membership.Clear(); membership.UnionWith(hashSet); } private void Save() { byte[] array; using (MemoryStream memoryStream = new MemoryStream()) { using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write(1447250737); binaryWriter.Write(1); binaryWriter.Write(World); Protocol.WriteString(binaryWriter, Epoch, 64); binaryWriter.Write(Count); foreach (string entry in entries) { Protocol.WriteString(binaryWriter, entry, 128); } binaryWriter.Flush(); array = memoryStream.ToArray(); } string sourceFileName = path + ".tmp"; using (FileStream fileStream = new FileStream(sourceFileName, FileMode.Create, FileAccess.Write, FileShare.None)) { using SHA256 sHA = SHA256.Create(); byte[] array2 = sHA.ComputeHash(array); fileStream.Write(array2, 0, array2.Length); fileStream.Write(array, 0, array.Length); fileStream.Flush(flushToDisk: true); } if (File.Exists(path)) { if (Recovered) { File.Copy(path, path + ".corrupt-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"), overwrite: false); File.Delete(path); File.Move(sourceFileName, path); Recovered = false; } else { File.Replace(sourceFileName, path, path + ".bak"); } } else { File.Move(sourceFileName, path); } } } } namespace VikingCodex.Configuration { internal sealed class ModSettings { internal readonly ConfigEntry<bool> Codex; internal readonly ConfigEntry<bool> Sharing; internal readonly ConfigEntry<bool> CatchUp; internal readonly ConfigEntry<bool> Import; internal readonly ConfigEntry<bool> Materials; internal readonly ConfigEntry<bool> HideNames; internal readonly ConfigEntry<bool> Placeholders; internal readonly ConfigEntry<bool> Modded; internal readonly ConfigEntry<bool> Wiki; internal readonly ConfigEntry<bool> Debug; internal readonly ConfigEntry<bool> InventoryButton; internal readonly ConfigEntry<Vector2> InventoryButtonOffset; internal readonly ConfigEntry<KeyboardShortcut> Shortcut; internal ModSettings(ConfigFile c) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) InventoryButton = c.Bind<bool>("Client", "ShowInventoryButton", true, "Show the Codex button when inventory is visible. Disable if another UI mod conflicts."); InventoryButtonOffset = c.Bind<Vector2>("Client", "InventoryButtonOffset", new Vector2(-100f, 35f), "Button offset from the bottom-right corner; adjust for UI mods."); Codex = c.Bind<bool>("Client", "EnableCodex", true, "Enable the Codex interface independently of sharing."); Sharing = Server("EnableSharedDiscovery", value: true, "Share new eligible discoveries with this server. Requires compatible clients."); CatchUp = Server("OfflineCatchUp", value: true, "Send previously recorded history to reconnecting characters."); Import = Server("ImportExistingDiscoveries", value: false, "Deprecated: ignored. Incoming character knowledge is never imported; only session item encounters contribute."); Materials = Server("ShareEligibleMaterials", value: true, "Share Material-type items registered in the supported registry."); Modded = Server("EnableModdedContentDetection", value: false, "Legacy setting retained for config compatibility. The Codex and material registry now include base-game identifiers only, regardless of this value."); HideNames = c.Bind<bool>("Client", "HideUndiscoveredNames", true, "Keep unknown identities and recipe details hidden; show discovery clues and icon silhouettes."); Placeholders = c.Bind<bool>("Client", "ShowUnknownPlaceholders", true, "Show unknown slots in the Codex."); Wiki = c.Bind<bool>("Client", "EnableWikiLinks", true, "Allow an intentional click to open known entries on the Wiki."); Debug = c.Bind<bool>("Diagnostics", "EnableDebugLogging", false, "Enable additional diagnostic logs."); Shortcut = c.Bind<KeyboardShortcut>("Client", "CodexShortcut", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Open Codex. Set MainKey to None to disable."); ConfigEntry<bool> Server(string key, bool value, string text) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown return c.Bind<bool>("Server", key, value, new ConfigDescription(text, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); } } } } namespace VikingCodex.Compatibility { internal sealed class DiagnosticsCommand : ConsoleCommand { public override string Name => "vikingcodex"; public override string Help => "status | reload | export confirm (export contains catalog spoilers)"; public override void Run(string[] args) { Execute(args, null); } public override void Run(string[] args, Terminal terminal) { Execute(args, terminal); } private static void Execute(string[] args, Terminal terminal) { Plugin instance = Plugin.Instance; if (!Object.op_Implicit((Object)(object)instance)) { Print("Viking Codex is not initialized."); return; } try { switch ((args.Length == 0) ? "status" : args[0].ToLowerInvariant()) { case "status": Print(string.Format("Viking Codex {0}; protocol {1}; {2}", "1.0.8", 2, instance.Sync.Status)); Print(instance.Sync.Diagnostics); Print($"Registry: {instance.Registry.Entries.Count} entries, {instance.Registry.Materials.Count} eligible materials, {instance.Registry.UnclassifiedCount} unclassified; last build {instance.Registry.BuildMilliseconds:F1} ms."); break; case "reload": instance.ReloadRegistry(); Print("Local catalog metadata reloaded. Existing character knowledge was not imported or revoked."); break; case "export": { if (args.Length != 2 || args[1] != "confirm") { Print("Export includes undiscovered identities. Use: vikingcodex export confirm"); break; } if (!instance.Registry.Ready) { Print("Enter a world before exporting its loaded registry."); break; } string text = Path.Combine(Paths.ConfigPath, "VikingCodex", "diagnostics"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, "catalog-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmssfff") + ".tsv"); using (StreamWriter streamWriter = new StreamWriter(text2, append: false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))) { streamWriter.WriteLine("id\tlocalized_name\tbiome\tcategory\torder\tshareable\thas_native_icon"); foreach (CodexEntry item in instance.Registry.Entries.Values.OrderBy<CodexEntry, string>((CodexEntry e) => e.Id, StringComparer.Ordinal)) { streamWriter.WriteLine(string.Join("\t", Cell(item.Id), Cell(Localization.instance.Localize(item.Name)), Cell(item.Biome), Cell(item.Category), item.Order.ToString(CultureInfo.InvariantCulture), item.Shareable.ToString(), ((Object)(object)item.Icon != (Object)null).ToString())); } } Print("Full loaded-content catalog exported to " + text2); break; } default: Print("Usage: vikingcodex status | reload | export confirm"); break; } } catch (Exception ex) { Print("Viking Codex diagnostic failed: " + ex.Message); } static string Cell(string text3) { return (text3 ?? "").Replace("\t", " ").Replace("\r", " ").Replace("\n", " "); } void Print(string value) { if ((Object)(object)terminal != (Object)null) { terminal.AddString(value); } Plugin.Log.LogInfo((object)value); } } } } namespace VikingCodex.Codex { internal sealed class CodexEntry { internal string Id; internal string Prefab; internal string Name; internal string Description; internal string Hint; internal string Biome; internal string Category; internal string Wiki; internal string DiscoveryBehavior; internal int Order; internal bool Shareable; internal ItemDrop Item; internal Recipe Recipe; internal readonly List<Recipe> Recipes = new List<Recipe>(); internal Piece Piece; internal Character Creature; internal Sprite Icon; } internal sealed class CodexRegistry { private Dictionary<string, List<string>> related = new Dictionary<string, List<string>>(StringComparer.Ordinal); internal Dictionary<string, CodexEntry> Entries { get; private set; } = new Dictionary<string, CodexEntry>(StringComparer.Ordinal); internal Dictionary<string, CodexEntry> Materials { get; private set; } = new Dictionary<string, CodexEntry>(StringComparer.Ordinal); internal ContentIdentity MaterialIdentity { get; private set; } = new ContentIdentity(Enumerable.Empty<KeyValuePair<string, string>>()); internal bool Ready { get; private set; } internal int Revision { get; private set; } internal double BuildMilliseconds { get; private set; } internal int UnclassifiedCount { get; private set; } private static CatalogDocument Read(string json) { return JsonConvert.DeserializeObject<CatalogDocument>(json); } private static CatalogDocument Embedded(string name) { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("VikingCodex." + name); if (stream == null) { throw new InvalidDataException("Missing embedded catalog " + name); } using StreamReader streamReader = new StreamReader(stream); CatalogDocument catalogDocument = Read(streamReader.ReadToEnd()); if (catalogDocument?.entries == null || catalogDocument.entries.Length == 0) { throw new InvalidDataException("Embedded catalog is empty: " + name); } return catalogDocument; } internal void Build(bool includeModded, string directory) { //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Invalid comparison between Unknown and I4 //IL_0404: Unknown result type (might be due to invalid IL or missing references) //IL_040a: Invalid comparison between Unknown and I4 //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_0421: Invalid comparison between Unknown and I4 //IL_0431: Unknown result type (might be due to invalid IL or missing references) //IL_0438: Invalid comparison between Unknown and I4 //IL_0448: Unknown result type (might be due to invalid IL or missing references) //IL_044e: Invalid comparison between Unknown and I4 //IL_045e: Unknown result type (might be due to invalid IL or missing references) //IL_0465: Invalid comparison between Unknown and I4 //IL_0469: Unknown result type (might be due to invalid IL or missing references) //IL_0470: Invalid comparison between Unknown and I4 Stopwatch stopwatch = Stopwatch.StartNew(); Dictionary<string, CatalogMetadata> vanilla = CatalogValidation.Validate(Embedded("vanilla-content.json")); Dictionary<string, CatalogMetadata> metadata = new Dictionary<string, CatalogMetadata>(vanilla, StringComparer.Ordinal); foreach (KeyValuePair<string, CatalogMetadata> item in CatalogValidation.Validate(Embedded("progression.json"))) { metadata[item.Key] = item.Value; } if (Directory.Exists(directory)) { foreach (string item2 in Directory.GetFiles(directory, "*.json").OrderBy<string, string>((string p) => p, StringComparer.Ordinal)) { try { if (new FileInfo(item2).Length > 4000000) { throw new InvalidDataException("Metadata exceeds 4MB"); } foreach (KeyValuePair<string, CatalogMetadata> item3 in CatalogValidation.Validate(Read(File.ReadAllText(item2)))) { metadata[item3.Key] = item3.Value; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Metadata ignored: " + Path.GetFileName(item2) + ": " + ex.Message)); } } } Dictionary<string, CodexEntry> entries = new Dictionary<string, CodexEntry>(StringComparer.Ordinal); Dictionary<string, CodexEntry> materials = new Dictionary<string, CodexEntry>(StringComparer.Ordinal); HashSet<string> vanillaIds = new HashSet<string>(vanilla.Keys, StringComparer.Ordinal); GameObject[] array = ObjectDB.instance.m_items.Where((GameObject p) => Object.op_Implicit((Object)(object)p)).ToArray(); foreach (GameObject prefab in array) { Isolate(((Object)prefab).name, delegate { ItemDrop component = prefab.GetComponent<ItemDrop>(); if (Object.op_Implicit((Object)(object)component) && component.m_itemData?.m_shared != null && Include(((Object)prefab).name, ((Object)prefab).name)) { CodexEntry codexEntry = Make(((Object)prefab).name, ((Object)prefab).name, component.m_itemData.m_shared.m_name, component.m_itemData.m_shared.m_description, Classify(component)); codexEntry.Item = component; codexEntry.Icon = Icon(component); codexEntry.Shareable = !string.IsNullOrWhiteSpace(component.m_itemData.m_shared.m_name) && Protocol.ValidId(codexEntry.Prefab) && CatalogValidation.Shareable(vanilla.ContainsKey(((Object)prefab).name), (Object)(object)codexEntry.Icon != (Object)null, Metadata(codexEntry.Id, codexEntry.Prefab)); if (Metadata(codexEntry.Id, codexEntry.Prefab)?.visibility != "exclude") { entries[codexEntry.Id] = codexEntry; } if (codexEntry.Shareable) { materials[codexEntry.Prefab] = codexEntry; } } }); } Recipe[] array2 = ObjectDB.instance.m_recipes.Where((Recipe r) => Object.op_Implicit((Object)(object)r) && Object.op_Implicit((Object)(object)r.m_item) && r.m_enabled).ToArray(); foreach (Recipe recipe in array2) { Isolate(((Object)recipe).name, delegate { string name2 = ((Object)((Component)recipe.m_item).gameObject).name; if (entries.TryGetValue(name2, out var value2)) { string text = "recipe." + ((Object)recipe).name; if (!(Metadata(text, name2)?.visibility == "exclude")) { if (entries.ContainsKey(text)) { Plugin.Log.LogWarning((object)("Duplicate recipe identifier skipped: " + text)); } else { CodexEntry codexEntry = Make(text, name2, value2.Name, value2.Description, "Crafting recipes"); codexEntry.Category = "Crafting recipes"; codexEntry.Recipe = recipe; codexEntry.Recipes.Add(recipe); codexEntry.Item = recipe.m_item; codexEntry.Icon = value2.Icon; entries.Add(text, codexEntry); value2.Recipes.Add(recipe); if (!Object.op_Implicit((Object)(object)value2.Recipe)) { value2.Recipe = recipe; } } } } }); } array = (from p in ZNetScene.instance.m_prefabs.Concat(ZNetScene.instance.m_nonNetViewPrefabs) where Object.op_Implicit((Object)(object)p) select p).Distinct().ToArray(); foreach (GameObject prefab2 in array) { Isolate(((Object)prefab2).name, delegate { Character component = prefab2.GetComponent<Character>(); if (Object.op_Implicit((Object)(object)component) && !(component is Player) && Include("creature." + ((Object)prefab2).name, ((Object)prefab2).name) && Metadata("creature." + ((Object)prefab2).name, ((Object)prefab2).name)?.visibility != "exclude") { CodexEntry codexEntry = Make("creature." + ((Object)prefab2).name, ((Object)prefab2).name, component.m_name, "", component.m_boss ? "Bosses" : "Creatures"); codexEntry.Creature = component; entries[codexEntry.Id] = codexEntry; } Piece component2 = prefab2.GetComponent<Piece>(); if (Object.op_Implicit((Object)(object)component2) && component2.m_enabled && component2.m_resources != null && component2.m_resources.Length != 0 && Include("piece." + ((Object)prefab2).name, ((Object)prefab2).name) && Metadata("piece." + ((Object)prefab2).name, ((Object)prefab2).name)?.visibility != "exclude") { CodexEntry codexEntry2 = Make("piece." + ((Object)prefab2).name, ((Object)prefab2).name, component2.m_name, component2.m_description, Object.op_Implicit((Object)(object)prefab2.GetComponent<CraftingStation>()) ? "Crafting stations" : "Building pieces"); codexEntry2.Piece = component2; codexEntry2.Icon = component2.m_icon; entries[codexEntry2.Id] = codexEntry2; } }); } foreach (CodexEntry value4 in entries.Values) { CatalogMetadata catalogMetadata = Metadata(value4.Id, value4.Prefab); string form = ""; if (Object.op_Implicit((Object)(object)value4.Item)) { SharedData shared = value4.Item.m_itemData.m_shared; if (value4.Prefab.StartsWith("Trophy", StringComparison.Ordinal)) { form = "A keepsake earned by defeating a creature."; } else if ((int)shared.m_itemType == 6) { form = "Learn protection worn on your head."; } else if ((int)shared.m_itemType == 7) { form = "Learn protection worn on your torso."; } else if ((int)shared.m_itemType == 11) { form = "Learn protection worn on your legs."; } else if ((int)shared.m_itemType == 17) { form = "Learn a cloak for your shoulders."; } else if ((int)shared.m_itemType == 5) { form = "Learn a shield to block incoming attacks."; } else if ((int)shared.m_itemType == 9 || (int)shared.m_itemType == 23) { form = "Learn ammunition for a ranged weapon."; } else if (value4.Category == "Weapons" || value4.Category == "Tools") { switch (((object)Unsafe.As<SkillType, SkillType>(ref shared.m_skillType)/*cast due to .constrained prefix*/).ToString()) { case "Axes": form = "Learn a chopping blade for this stage of your journey."; break; case "Pickaxes": form = "Learn a stronger tool for breaking mineral deposits."; break; case "Swords": form = "Learn a long blade for close combat."; break; case "Knives": form = "Learn a short blade for quick attacks."; break; case "Spears": form = "Learn a pointed weapon for thrusting and throwing."; break; case "Polearms": form = "Learn a long weapon for keeping enemies at a distance."; break; case "Clubs": form = "Learn a weapon that crushes rather than cuts."; break; case "Bows": form = "Learn a hunting weapon that sends arrows from afar."; break; case "Crossbows": form = "Learn a heavy ranged weapon that fires bolts."; break; case "ElementalMagic": form = "Learn a weapon that channels elemental power."; break; case "BloodMagic": form = "Learn a weapon that channels life into magic."; break; } } } value4.Hint = ProgressionHints.Resolve(value4.Hint, value4.Biome, value4.Category, form); if (!string.IsNullOrEmpty(catalogMetadata?.iconPrefab)) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(catalogMetadata.iconPrefab); ItemDrop val = (Object.op_Implicit((Object)(object)itemPrefab) ? itemPrefab.GetComponent<ItemDrop>() : null); if (Object.op_Implicit((Object)(object)val)) { value4.Icon = Icon(val); } } } Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.Ordinal); foreach (CodexEntry item4 in entries.Values.Where((CodexEntry e) => e.Id.StartsWith("recipe.", StringComparison.Ordinal) || Object.op_Implicit((Object)(object)e.Piece))) { foreach (Requirement item5 in ((Object.op_Implicit((Object)(object)item4.Recipe) ? item4.Recipe.m_resources : item4.Piece.m_resources) ?? Array.Empty<Requirement>()).Where((Requirement r) => Object.op_Implicit((Object)(object)r?.m_resItem))) { string name = ((Object)((Component)item5.m_resItem).gameObject).name; if (!dictionary.TryGetValue(name, out var value)) { value = (dictionary[name] = new List<string>()); } if (!value.Contains(item4.Id)) { value.Add(item4.Id); } } } ContentIdentity materialIdentity = new ContentIdentity(materials.Select((KeyValuePair<string, CodexEntry> p) => new KeyValuePair<string, string>(p.Key, p.Value.Item.m_itemData.m_shared.m_name))); Entries = entries; Materials = materials; MaterialIdentity = materialIdentity; related = dictionary; UnclassifiedCount = entries.Values.Count((CodexEntry e) => e.Biome == "Unclassified"); Ready = true; Revision++; stopwatch.Stop(); BuildMilliseconds = stopwatch.Elapsed.TotalMilliseconds; Plugin.Log.LogInfo((object)$"Registry: {entries.Count} entries, {materials.Count} materials, {UnclassifiedCount} unclassified, {BuildMilliseconds:F1} ms on this machine."); static Sprite Icon(ItemDrop item) { try { return item.m_itemData.GetIcon(); } catch (Exception) { return null; } } bool Include(string id, string prefab3) { return ProgressionHints.IsBaseGame(id, prefab3, vanillaIds); } static void Isolate(string id, Action action) { try { action(); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Content skipped: " + id + ": " + ex2.Message)); } } CodexEntry Make(string id, string text, string text2, string description, string category) { CatalogMetadata catalogMetadata2 = Metadata(id, text); return new CodexEntry { Id = id, Prefab = text, Name = ((!string.IsNullOrEmpty(catalogMetadata2?.displayName)) ? catalogMetadata2.displayName : (string.IsNullOrEmpty(text2) ? text : text2)), Description = (string.IsNullOrEmpty(catalogMetadata2?.description) ? (description ?? "") : catalogMetadata2.description), DiscoveryBehavior = ((!string.IsNullOrEmpty(catalogMetadata2?.discoveryBehavior)) ? catalogMetadata2.discoveryBehavior : (id.StartsWith("creature.", StringComparison.Ordinal) ? "encounter" : ((id.StartsWith("piece.", StringComparison.Ordinal) || id.StartsWith("recipe.", StringComparison.Ordinal)) ? "native-recipe" : "native-material"))), Hint = (catalogMetadata2?.hint ?? ""), Biome = (catalogMetadata2?.biome ?? "Unclassified"), Category = (string.IsNullOrEmpty(catalogMetadata2?.category) ? category : catalogMetadata2.category), Order = (catalogMetadata2?.order ?? 100000), Wiki = (catalogMetadata2?.wiki ?? "") }; } CatalogMetadata Metadata(string id, string key) { if (metadata.TryGetValue(id, out var value2)) { return value2; } metadata.TryGetValue(key, out var value3); return value3; } } internal IEnumerable<CodexEntry> RelatedTo(CodexEntry entry) { if (!related.TryGetValue(entry.Prefab, out var value)) { return Enumerable.Empty<CodexEntry>(); } return from id in value.Where(Entries.ContainsKey) select Entries[id]; } private static string Classify(ItemDrop item) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected I4, but got Unknown SharedData shared = item.m_itemData.m_shared; ItemType itemType = shared.m_itemType; switch (itemType - 1) { case 0: return "Raw materials"; case 1: if (!(shared.m_food > 0f) && !(shared.m_foodStamina > 0f) && !(shared.m_foodEitr > 0f)) { return "Consumables"; } return "Food"; case 18: return "Tools"; case 5: case 6: case 10: case 11: case 16: return "Armor"; case 2: case 3: case 8: case 13: case 19: case 21: case 22: return "Weapons"; case 4: case 17: case 23: return "Equipment"; default: return "Miscellaneous items"; } } } internal sealed class DiscoveryTracker { private const string ObservationKey = "com.vikingcodex.observations.v1"; private Player character; private readonly HashSet<string> observed = new HashSet<string>(StringComparer.Ordinal); private const string ViewedKey = "com.vikingcodex.viewed.v1"; private readonly HashSet<string> viewed = new HashSet<string>(StringComparer.Ordinal); private readonly HashSet<string> baseline = new HashSet<string>(StringComparer.Ordinal); private bool baselineReady; internal int Revision { get; private set; } internal void Reset(Player player) { character = player; observed.Clear(); viewed.Clear(); baseline.Clear(); baselineReady = false; if (Object.op_Implicit((Object)(object)player) && player.m_customData.TryGetValue("com.vikingcodex.viewed.v1", out var value) && value != null && value.Length <= 4000000) { string[] array = value.Split(new char[1] { '|' }, 20001); foreach (string text in array) { if (viewed.Count < 20000 && CatalogValidation.ContentId(text)) { viewed.Add(text); } } } if (Object.op_Implicit((Object)(object)player) && player.m_customData.TryGetValue("com.vikingcodex.observations.v1", out var value2) && value2 != null && value2.Length <= 4000000) { string[] array = value2.Split(new char[1] { '|' }, 20001); foreach (string text2 in array) { if (observed.Count < 20000 && CatalogValidation.ContentId(text2)) { observed.Add(text2); } } } Revision++; } internal void Changed() { Revision++; } internal void SeedHighlights(IEnumerable<CodexEntry> entries, Player player) { if (baselineReady || !Object.op_Implicit((Object)(object)player)) { return; } foreach (CodexEntry entry in entries) { if (View(entry, player).Discovered) { baseline.Add(entry.Id); } } baselineReady = true; } internal bool IsNew(CodexEntry entry, Player player) { if (baselineReady && !baseline.Contains(entry.Id) && !viewed.Contains(entry.Id)) { return View(entry, player).Discovered; } return false; } internal void MarkViewed(string id) { if (Object.op_Implicit((Object)(object)character) && viewed.Count < 20000 && viewed.Add(id)) { character.m_customData["com.vikingcodex.viewed.v1"] = string.Join("|", viewed.OrderBy<string, string>((string x) => x, StringComparer.Ordinal)); } } internal void Observe(string id) { if (Object.op_Implicit((Object)(object)character) && CatalogValidation.ContentId(id) && observed.Count < 20000 && observed.Add(id)) { character.m_customData["com.vikingcodex.observations.v1"] = string.Join("|", observed.OrderBy<string, string>((string x) => x, StringComparer.Ordinal)); Revision++; } } internal bool Known(CodexEntry e, Player p) { if (!Object.op_Implicit((Object)(object)p)) { return false; } if (e.DiscoveryBehavior == "encounter") { return observed.Contains(e.Id); } if (Object.op_Implicit((Object)(object)e.Piece)) { if (!observed.Contains(e.Id)) { return p.IsRecipeKnown(e.Piece.m_name); } return true; } if (e.DiscoveryBehavior == "native-recipe") { if (Object.op_Implicit((Object)(object)e.Item)) { return p.IsRecipeKnown(e.Item.m_itemData.m_shared.m_name); } return false; } if (Object.op_Implicit((Object)(object)e.Item)) { return p.IsMaterialKnown(e.Item.m_itemData.m_shared.m_name); } return false; } internal EntryView View(CodexEntry e, Player p, bool evaluateCraftable = false) { bool flag = Known(e, p); Recipe[] array = e.Recipes.Where((Recipe r) => Object.op_Implicit((Object)(object)r) && Object.op_Implicit((Object)(object)r.m_item) && Object.op_Implicit((Object)(object)p) && p.IsRecipeKnown(r.m_item.m_itemData.m_shared.m_name)).ToArray(); bool flag2 = array.Length != 0 || (Object.op_Implicit((Object)(object)p) && Object.op_Implicit((Object)(object)e.Piece) && p.IsRecipeKnown(e.Piece.m_name)); return new EntryView { Id = e.Id, Name = Localization.instance.Localize(e.Name), Hint = e.Hint, Biome = e.Biome, Category = e.Category, Order = e.Order, Discovered = (flag || flag2), RecipeKnown = flag2, Craftable = (evaluateCraftable && flag2 && (array.Any((Recipe r) => r.m_enabled && !r.m_noCraftOnlyUpgrade && p.RequiredCraftingStation(r, 1, false) && p.HaveRequirements(r, false, 1, 1)) || (Object.op_Implicit((Object)(object)e.Piece) && p.HaveRequirements(e.Piece, (RequirementMode)0)))) }; } } [HarmonyPatch(typeof(Player), "AddKnownItem")] internal static class MaterialObservation { private static void Prefix(Player __instance, ItemData item, out bool __state) { __state = false; try { __state = (Object)(object)__instance == (Object)(object)Player.m_localPlayer && item?.m_shared != null; } catch (Exception e) { Plugin.Warn("Discovery observation", e); } } private static void Postfix(Player __instance, ItemData item, bool __state) { try { if (__state && Object.op_Implicit((Object)(object)Plugin.Instance) && __instance.IsMaterialKnown(item.m_shared.m_name)) { Plugin.Instance.Tracker.Changed(); Plugin.Instance.Sync.Observe(item); } } catch (Exception e) { Plugin.Warn("Discovery propagation", e); } } } [HarmonyPatch(typeof(Player), "UpdateHover")] internal static class EncounterObservation { private static void Postfix(Player __instance) { try { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || !Object.op_Implicit((Object)(object)Plugin.Instance) || !Plugin.Instance.Settings.Codex.Value) { return; } Character hoverCreature = __instance.GetHoverCreature(); if (Object.op_Implicit((Object)(object)hoverCreature) && !(hoverCreature is Player)) { Plugin.Instance.Tracker.Observe("creature." + Utils.GetPrefabName(((Component)hoverCreature).gameObject)); } Piece hoveringPiece = __instance.GetHoveringPiece(); if (Object.op_Implicit((Object)(object)hoveringPiece)) { Plugin.Instance.Tracker.Observe("piece." + Utils.GetPrefabName(((Component)hoveringPiece).gameObject)); } GameObject hoverObject = ((Humanoid)__instance).GetHoverObject(); ItemDrop val = (Object.op_Implicit((Object)(object)hoverObject) ? hoverObject.GetComponentInParent<ItemDrop>() : null); if (Object.op_Implicit((Object)(object)val)) { string prefabName = Utils.GetPrefabName(((Component)val).gameObject); if (Plugin.Instance.Registry.Entries.TryGetValue(prefabName, out var value) && value.DiscoveryBehavior == "encounter") { Plugin.Instance.Tracker.Observe(prefabName); } } } catch (Exception e) { Plugin.Warn("Encounter observation", e); } } } [HarmonyPatch(typeof(Player), "AddKnownStation")] internal static class StationObservation { private static void Postfix(Player __instance) { try { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && Object.op_Implicit((Object)(object)Plugin.Instance)) { Plugin.Instance.Tracker.Changed(); } } catch (Exception e) { Plugin.Warn("Station knowledge observation", e); } } } } namespace VikingCodex.Codex.UI { internal sealed class CodexPanel : IDisposable { private readonly Plugin plugin; private const int RowHeight = 64; private GameObject root; private GameObject menuButton; private InventoryGui inventory; private Text progress; private Text details; private Text title; private Image detailIcon; private Texture2D fallbackTexture; private Sprite fallbackIcon; private Button wiki; private Dropdown biome; private Dropdown category; private Dropdown state; private Dropdown sort; private InputField search; private ScrollRect scroll; private ScrollRect detailScrollRect; private RectTransform content; private readonly List<Button> rows = new List<Button>(); private readonly List<Image> icons = new List<Image>(); private List<EntryView> views = new List<EntryView>(); private string selected; private int revision = -1; private int registryRevision = -1; private readonly int[] rowIndices = Enumerable.Repeat(-1, 14).ToArray(); private string renderedDetail = ""; private bool filtering; private bool opened; private float detailAt; private float nextRefresh; private bool lastHide = true; private bool lastPlaceholders = true; private readonly Vector2 topLeft = new Vector2(0f, 1f); private int First => Math.Max(0, Math.Min(Math.Max(0, views.Count - 1), (int)(content.anchoredPosition.y / 64f))); internal CodexPanel(Plugin p) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) plugin = p; } internal void Tick() { //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) if (opened && !Object.op_Implicit((Object)(object)root)) { Close(); } if (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || !plugin.Settings.Codex.Value) { if (opened) { Close(); } if (Object.op_Implicit((Object)(object)menuButton)) { menuButton.SetActive(false); } return; } if (Object.op_Implicit((Object)(object)InventoryGui.instance) && (Object)(object)inventory != (Object)(object)InventoryGui.instance) { inventory = InventoryGui.instance; if (Object.op_Implicit((Object)(object)menuButton)) { Object.Destroy((Object)(object)menuButton); } menuButton = GUIManager.Instance.CreateButton("Codex", ((Component)inventory).transform, new Vector2(1f, 0f), new Vector2(1f, 0f), plugin.Settings.InventoryButtonOffset.Value, 160f, 38f); ((Object)menuButton).name = "VikingCodex.Open"; ((UnityEvent)menuButton.GetComponent<Button>().onClick).AddListener(new UnityAction(Toggle)); } if (Object.op_Implicit((Object)(object)menuButton)) { menuButton.SetActive(plugin.Settings.InventoryButton.Value && InventoryGui.IsVisible()); menuButton.GetComponent<RectTransform>().anchoredPosition = plugin.Settings.InventoryButtonOffset.Value; } KeyboardShortcut value = plugin.Settings.Shortcut.Value; if (((KeyboardShortcut)(ref value)).IsDown() && (!Object.op_Implicit((Object)(object)Chat.instance) || !Chat.instance.HasFocus())) { Toggle(); } if (plugin.Registry.Ready) { plugin.Tracker.SeedHighlights(plugin.Registry.Entries.Values, Player.m_localPlayer); } if (!Object.op_Implicit((Object)(object)root) || !root.activeSelf) { return; } if (Input.GetKeyDown((KeyCode)27)) { Close(); return; } if (!search.isFocused) { GameObject val = (Object.op_Implicit((Object)(object)EventSystem.current) ? EventSystem.current.currentSelectedGameObject : null); bool flag = Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.GetComponentInParent<Dropdown>()); if (!flag && Input.GetKeyDown((KeyCode)274)) { MoveSelection(1); } else if (!flag && Input.GetKeyDown((KeyCode)273)) { MoveSelection(-1); } } Transform parent = root.transform.parent; RectTransform val2 = (RectTransform)(object)((parent is RectTransform) ? parent : null); if (Object.op_Implicit((Object)(object)val2)) { Transform transform = root.transform; Vector3 one = Vector3.one; Rect rect = val2.rect; float num = ((Rect)(ref rect)).width / 1140f; rect = val2.rect; transform.localScale = one * Mathf.Min(1f, Mathf.Min(num, ((Rect)(ref rect)).height / 750f)); } if (Time.unscaledTime >= nextRefresh && (registryRevision != plugin.Registry.Revision || revision != plugin.Tracker.Revision || lastHide != plugin.Settings.HideNames.Value || lastPlaceholders != plugin.Settings.Placeholders.Value)) { nextRefresh = Time.unscaledTime + 0.15f; Refresh(resetScroll: false); } if (Time.unscaledTime >= detailAt) { detailAt = Time.unscaledTime + 1f; Detail(); } } private void Toggle() { if (!plugin.Settings.Codex.Value || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)GUIManager.CustomGUIFront)) { return; } if (!Object.op_Implicit((Object)(object)root)) { try { rows.Clear(); icons.Clear(); Create(); } catch (Exception e) { Close(); if (Object.op_Implicit((Object)(object)root)) { Object.Destroy((Object)(object)root); } root = null; Plugin.Warn("Codex interface creation failed", e); return; } } if (root.activeSelf) { Close(); return; } root.SetActive(true); opened = true; GUIManager.BlockInput(true); Refresh(); ((Selectable)search).Select(); } private void Close() { if (Object.op_Implicit((Object)(object)root)) { root.SetActive(false); } if (opened) { GUIManager.BlockInput(false); } opened = false; } private Text Text(Transform parent, string value, float x, float y, float w, float h, int size = 18) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) GameObject obj = GUIManager.Instance.CreateText(value, parent, topLeft, topLeft, new Vector2(x, y), GUIManager.Instance.AveriaSerif, size, new Color(0.94f, 0.88f, 0.73f), false, Color.black, w, h, false); obj.GetComponent<RectTransform>().pivot = topLeft; Text component = obj.GetComponent<Text>(); component.alignment = (TextAnchor)0; component.supportRichText = false; return component; } private Button Button(Transform parent, string label, float x, float y, float w, float h, Action action) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown GameObject obj = GUIManager.Instance.CreateButton(label, parent, topLeft, topLeft, new Vector2(x, y), w, h); obj.GetComponent<RectTransform>().pivot = topLeft; Button component = obj.GetComponent<Button>(); ((UnityEvent)component.onClick).AddListener((UnityAction)delegate { action(); }); return component; } private Dropdown Drop(Transform parent, float x, float y, float w, IEnumerable<string> options) { //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_002e: Unknown result type (might be due to invalid IL or missing references) GameObject obj = GUIManager.Instance.CreateDropDown(parent, topLeft, topLeft, new Vector2(x, y), 16, w, 36f); obj.GetComponent<RectTransform>().pivot = topLeft; Dropdown component = obj.GetComponent<Dropdown>(); component.ClearOptions(); component.AddOptions(options.ToList()); ((UnityEvent<int>)(object)component.onValueChanged).AddListener((UnityAction<int>)delegate { if (!filtering) { Refresh(); } }); return component; } private void Create() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_03f0: Unknown result type (might be due to invalid IL or missing references) //IL_03f6: Expected O, but got Unknown //IL_0418: Unknown result type (might be due to invalid IL or missing references) //IL_041d: Unknown result type (might be due to invalid IL or missing references) //IL_041e: Unknown result type (might be due to invalid IL or missing references) //IL_0425: Unknown result type (might be due to invalid IL or missing references) //IL_0427: Unknown result type (might be due to invalid IL or missing references) //IL_0428: Unknown result type (might be due to invalid IL or missing references) //IL_042f: Unknown result type (might be due to invalid IL or missing references) //IL_0441: Unknown result type (might be due to invalid IL or missing references) //IL_0456: Unknown result type (might be due to invalid IL or missing references) //IL_047a: Unknown result type (might be due to invalid IL or missing references) //IL_04a9: Unknown result type (might be due to invalid IL or missing references) //IL_04d8: Unknown result type (might be due to invalid IL or missing references) //IL_04dd: Unknown result type (might be due to invalid IL or missing references) //IL_04de: Unknown result type (might be due to invalid IL or missing references) //IL_04e5: Unknown result type (might be due to invalid IL or missing references) //IL_04e7: Unknown result type (might be due to invalid IL or missing references) //IL_04e8: Unknown result type (might be due to invalid IL or missing references) //IL_04ef: Unknown result type (might be due to invalid IL or missing references) //IL_0506: Unknown result type (might be due to invalid IL or missing references) //IL_05ab: Unknown result type (might be due to invalid IL or missing references) //IL_05b1: Expected O, but got Unknown //IL_05d7: Unknown result type (might be due to invalid IL or missing references) //IL_05dc: Unknown result type (might be due to invalid IL or missing references) //IL_05dd: Unknown result type (might be due to invalid IL or missing references) //IL_05e4: Unknown result type (might be due to invalid IL or missing references) //IL_05e6: Unknown result type (might be due to invalid IL or missing references) //IL_05e7: Unknown result type (might be due to invalid IL or missing references) //IL_05ee: Unknown result type (might be due to invalid IL or missing references) //IL_0601: Unknown result type (might be due to invalid IL or missing references) //IL_0617: Unknown result type (might be due to invalid IL or missing references) //IL_063b: Unknown result type (might be due to invalid IL or missing references) //IL_066a: Unknown result type (might be due to invalid IL or missing references) //IL_0696: Unknown result type (might be due to invalid IL or missing references) //IL_06a7: Unknown result type (might be due to invalid IL or missing references) //IL_06b8: Unknown result type (might be due to invalid IL or missing references) //IL_06d3: Unknown result type (might be due to invalid IL or missing references) //IL_06e4: Unknown result type (might be due to invalid IL or missing references) //IL_06f5: Unknown result type (might be due to invalid IL or missing references) //IL_07b3: Unknown result type (might be due to invalid IL or missing references) //IL_07b8: Unknown result type (might be due to invalid IL or missing references) //IL_07c4: Unknown result type (might be due to invalid IL or missing references) //IL_0817: Unknown result type (might be due to invalid IL or missing references) //IL_082b: Unknown result type (might be due to invalid IL or missing references) //IL_085a: Unknown result type (might be due to invalid IL or missing references) //IL_0894: Unknown result type (might be due to invalid IL or missing references) //IL_089b: Unknown result type (might be due to invalid IL or missing references) //IL_08ad: Unknown result type (might be due to invalid IL or missing references) //IL_08c2: Unknown result type (might be due to invalid IL or missing references) //IL_08d6: Unknown result type (might be due to invalid IL or missing references) //IL_093f: Unknown result type (might be due to invalid IL or missing references) //IL_0979: Unknown result type (might be due to invalid IL or missing references) //IL_097e: Unknown result type (might be due to invalid IL or missing references) //IL_097f: Unknown result type (might be due to invalid IL or missing references) //IL_0986: Unknown result type (might be due to invalid IL or missing references) //IL_0988: Unknown result type (might be due to invalid IL or missing references) //IL_0989: Unknown result type (might be due to invalid IL or missing references) //IL_0990: Unknown result type (might be due to invalid IL or missing references) //IL_09a2: Unknown result type (might be due to invalid IL or missing references) //IL_09b6: Unknown result type (might be due to invalid IL or missing references) //IL_09ed: Unknown result type (might be due to invalid IL or missing references) //IL_0a06: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0a8d: Unknown result type (might be due to invalid IL or missing references) //IL_0a92: Unknown result type (might be due to invalid IL or missing references) //IL_0a93: Unknown result type (might be due to invalid IL or missing references) //IL_0a9a: Unknown result type (might be due to invalid IL or missing references) //IL_0a9c: Unknown result type (might be due to invalid IL or missing references) //IL_0a9d: Unknown result type (might be due to invalid IL or missing references) //IL_0aa4: Unknown result type (might be due to invalid IL or missing references) //IL_0ab5: Unknown result type (might be due to invalid IL or missing references) //IL_0afe: Unknown result type (might be due to invalid IL or missing references) fallbackTexture = new Texture2D(32, 32, (TextureFormat)4, false); Color[] array = (Color[])(object)new Color[1024]; for (int i = 4; i < 28; i++) { for (int j = 7; j < 25; j++) { if (j < 21 || i < 24) { array[i * 32 + j] = Color.white; } } } fallbackTexture.SetPixels(array); fallbackTexture.Apply(); fallbackIcon = Sprite.Create(fallbackTexture, new Rect(0f, 0f, 32f, 32f), new Vector2(0.5f, 0.5f)); root = GUIManager.Instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 1120f, 720f, false); ((Object)root).name = "VikingCodex.Panel"; root.SetActive(false); title = Text(root.transform, "Viking Codex", 32f, -24f, 600f, 44f, 32); Button(root.transform, "Close", 980f, -24f, 104f, 38f, Close); search = GUIManager.Instance.CreateInputField(root.transform, topLeft, topLeft, new Vector2(32f, -84f), (ContentType)0, "Search names or discovery clues…", 18, 410f, 38f).GetComponent<InputField>(); ((Component)search).GetComponent<RectTransform>().pivot = topLeft; search.characterLimit = 100; ((UnityEvent<string>)(object)search.onValueChanged).AddListener((UnityAction<string>)delegate { if (!filtering) { Refresh(); } }); Text(root.transform, "Biome", 460f, -62f, 190f, 22f, 14); Text(root.transform, "Category", 668f, -62f, 220f, 22f, 14); biome = Drop(root.transform, 460f, -84f, 190f, new string[1] { "All" }); category = Drop(root.transform, 668f, -84f, 220f, new string[1] { "All" }); Button(root.transform, "Clear filters", 908f, -84f, 176f, 38f, Clear); state = Drop(root.transform, 32f, -138f, 210f, new string[3] { "All", "Discovered", "Undiscovered" }); sort = Drop(root.transform, 260f, -138f, 220f, new string[2] { "Progression order", "Alphabetical" }); progress = Text(root.transform, "", 500f, -140f, 584f, 44f, 17); GameObject val = new GameObject("VikingCodex.List", new Type[4] { typeof(RectTransform), typeof(Image), typeof(Mask), typeof(ScrollRect) }); val.transform.SetParent(root.transform, false); RectTransform component = val.GetComponent<RectTransform>(); Vector2 val2 = (component.pivot = topLeft); Vector2 val4 = (component.anchorMin = (component.anchorMax = val2)); component.anchoredPosition = new Vector2(32f, -194f); component.sizeDelta = new Vector2(490f, 480f); ((Graphic)val.GetComponent<Image>()).color = new Color(0.08f, 0.065f, 0.045f, 0.8f); val.GetComponent<Mask>().showMaskGraphic = true; content = new GameObject("Content", new Type[1] { typeof(RectTransform) }).GetComponent<RectTransform>(); ((Transform)content).SetParent((Transform)(object)component, false); RectTransform obj = content; RectTransform obj2 = content; val2 = (content.pivot = topLeft); val4 = (obj.anchorMin = (obj2.anchorMax = val2)); content.sizeDelta = new Vector2(486f, 480f); scroll = val.GetComponent<ScrollRect>(); scroll.viewport = component; scroll.content = content; scroll.horizontal = false; scroll.vertical = true; scroll.movementType = (MovementType)2; scroll.scrollSensitivity = 132f; scroll.inertia = false; GameObject val10 = new GameObject("VikingCodex.ListScrollbar", new Type[3] { typeof(RectTransform), typeof(Image), typeof(Scrollbar) }); val10.transform.SetParent(root.transform, false); RectTransform component2 = val10.GetComponent<RectTransform>(); val2 = (component2.pivot = topLeft); val4 = (component2.anchorMin = (component2.anchorMax = val2)); component2.anchoredPosition = new Vector2(528f, -194f); component2.sizeDelta = new Vector2(20f, 480f); ((Graphic)val10.GetComponent<Image>()).color = new Color(0.12f, 0.09f, 0.05f, 0.9f); Image component3 = new GameObject("Handle", new Type[2] { typeof(RectTransform), typeof(Image) }).GetComponent<Image>(); ((Component)component3).transform.SetParent((Transform)(object)component2, false); ((Graphic)component3).color = new Color(0.72f, 0.51f, 0.23f); ((Graphic)component3).rectTransform.anchorMin = Vector2.zero; ((Graphic)component3).rectTransform.anchorMax = Vector2.one; ((Graphic)component3).rectTransform.pivot = new Vector2(0.5f, 0.5f); ((Graphic)component3).rectTransform.sizeDelta = Vector2.zero; ((Graphic)component3).rectTransform.anchoredPosition = Vector2.zero; Scrollbar component4 = val10.GetComponent<Scrollbar>(); component4.direction = (Direction)2; component4.handleRect = ((Graphic)component3).rectTransform; ((Selectable)component4).targetGraphic = (Graphic)(object)component3; scroll.verticalScrollbar = component4; scroll.verticalScrollbarVisibility = (ScrollbarVisibility)0; ((UnityEvent<Vector2>)(object)scroll.onValueChanged).AddListener((UnityAction<Vector2>)delegate { RenderRows(); }); for (int num = 0; num < 14; num++) { int slot = num; Button val14 = Button((Transform)(object)content, "", 0f, -num * 64, 480f, 62f, delegate { SelectRow(slot); }); Navigation navigation = ((Selectable)val14).navigation; ((Navigation)(ref navigation)).mode = (Mode)0; ((Selectable)val14).navigation = navigation; ((Component)val14).GetComponentInChildren<Text>().supportRichText = false; ((Component)val14).GetComponentInChildren<Text>().alignment = (TextAnchor)3; ((Component)val14).GetComponentInChildren<Text>().fontSize = 16; ((Component)val14).GetComponentInChildren<Text>().horizontalOverflow = (HorizontalWrapMode)0; RectTransform rectTransform = ((Graphic)((Component)val14).GetComponentInChildren<Text>()).rectTransform; rectTransform.offsetMin = new Vector2(46f, 0f); rectTransform.offsetMax = new Vector2(-12f, 0f); Image component5 = new GameObject("Icon", new Type[2] { typeof(RectTransform), typeof(Image) }).GetComponent<Image>(); ((Component)component5).transform.SetParent(((Component)val14).transform, false); RectTransform rectTransform2 = ((Graphic)component5).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform2.anchorMax = val4; rectTransform2.anchorMin = val4; rectTransform2.pivot = new Vector2(0f, 0.5f); rectTransform2.anchoredPosition = new Vector2(8f, 0f); rectTransform2.sizeDelta = new Vector2(30f, 30f); component5.preserveAspect = true; ((Graphic)component5).raycastTarget = false; rows.Add(val14); icons.Add(component5); } detailIcon = new GameObject("EntryIcon", new Type[2] { typeof(RectTransform), typeof(Image) }).GetComponent<Image>(); ((Component)detailIcon).transform.SetParent(root.transform, false); RectTransform rectTransform3 = ((Graphic)detailIcon).rectTransform; val2 = (rectTransform3.pivot = topLeft); val4 = (rectTransform3.anchorMin = (rectTransform3.anchorMax = val2)); rectTransform3.anchoredPosition = new Vector2(580f, -198f); rectTransform3.sizeDelta = new Vector2(64f, 64f); detailIcon.preserveAspect = true; GameObject obj3 = GUIManager.Instance.CreateScrollView(root.transform, false, true, 0f, 10f, GUIManager.Instance.ValheimScrollbarHandleColorBlock, new Color(0.08f, 0.065f, 0.045f, 0.7f), 510f, 350f); ScrollRect val18 = (detailScrollRect = obj3.GetComponentInChildren<ScrollRect>()); val18.scrollSensitivity = 132f; val18.inertia = false; val18.movementType = (MovementType)2; VerticalLayoutGroup component6 = ((Component)val18.content).GetComponent<VerticalLayoutGroup>(); if (Object.op_Implicit((Object)(object)component6)) { ((Behaviour)component6).enabled = false; } ContentSizeFitter component7 = ((Component)val18.content).GetComponent<ContentSizeFitter>(); if (Object.op_Implicit((Object)(object)component7)) { ((Behaviour)component7).enabled = false; } RectTransform component8 = obj3.GetComponent<RectTransform>(); val2 = (component8.pivot = topLeft); val4 = (component8.anchorMin = (component8.anchorMax = val2)); component8.anchoredPosition = new Vector2(574f, -280f); details = Text((Transform)(object)val18.content, "Select an entry.", 10f, -10f, 460f, 800f); val18.content.sizeDelta = new Vector2(480f, 800f); wiki = Button(root.transform, "Open Wiki", 574f, -642f, 180f, 38f, OpenWiki); Text(root.transform, "Base-game checklist. Unclassified entries are listed separately; biome coverage is still under review.", 32f, -686f, 1050f, 24f, 15); } private static string Option(Dropdown d) { if (d.options.Count != 0) { return d.options[d.value].text.Split(new string[1] { " | " }, StringSplitOptions.None)[0]; } return "All"; } private void UpdateOptions(Dropdown d, IEnumerable<string>