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 NarcLab Resources v0.1.1
BepInEx/plugins/NarcLabResources/NarcLabResources.dll
Decompiled 7 hours agousing System; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BepInEx; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyTitle("NarcLab Resources")] [assembly: AssemblyDescription("Private server-authoritative Wood and Stone commands for Valheim")] [assembly: AssemblyCompany("NarcLab")] [assembly: AssemblyProduct("NarcLab Resources")] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyVersion("0.1.1.0")] namespace NarcLab.Valheim.Resources; internal enum ResourceRequestError { None, Syntax, ResourceNotAllowed, AmountOutOfRange, ProtocolUnsupported } internal sealed class ResourceRequest { internal string Resource { get; private set; } internal int Amount { get; private set; } internal ResourceRequest(string resource, int amount) { Resource = resource; Amount = amount; } } internal sealed class ResourceRequestResult { internal bool IsValid => Error == ResourceRequestError.None; internal ResourceRequest Request { get; private set; } internal ResourceRequestError Error { get; private set; } private ResourceRequestResult(ResourceRequest request, ResourceRequestError error) { Request = request; Error = error; } internal static ResourceRequestResult Valid(string resource, int amount) { return new ResourceRequestResult(new ResourceRequest(resource, amount), ResourceRequestError.None); } internal static ResourceRequestResult Invalid(ResourceRequestError error) { return new ResourceRequestResult(null, error); } } internal static class ResourcePolicy { internal const int ProtocolVersion = 1; internal const int MinimumAmount = 1; internal const int MaximumAmount = 1000; private static readonly IDictionary<string, string> AllowedResources = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "wood", "Wood" }, { "stone", "Stone" } }; internal static int AllowedResourceCount => AllowedResources.Count; internal static ResourceRequestResult ParseCommand(string commandLine) { if (string.IsNullOrWhiteSpace(commandLine)) { return ResourceRequestResult.Invalid(ResourceRequestError.Syntax); } string[] array = commandLine.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 3 || !string.Equals(array[0], "spawn", StringComparison.OrdinalIgnoreCase)) { return ResourceRequestResult.Invalid(ResourceRequestError.Syntax); } if (!int.TryParse(array[2], NumberStyles.None, CultureInfo.InvariantCulture, out var result)) { return ResourceRequestResult.Invalid(ResourceRequestError.AmountOutOfRange); } return ValidateRpc(1, array[1], result); } internal static ResourceRequestResult ValidateRpc(int protocolVersion, string requestedResource, int amount) { if (protocolVersion != 1) { return ResourceRequestResult.Invalid(ResourceRequestError.ProtocolUnsupported); } if (string.IsNullOrWhiteSpace(requestedResource) || !AllowedResources.TryGetValue(requestedResource.Trim(), out var value)) { return ResourceRequestResult.Invalid(ResourceRequestError.ResourceNotAllowed); } if (amount < 1 || amount > 1000) { return ResourceRequestResult.Invalid(ResourceRequestError.AmountOutOfRange); } return ResourceRequestResult.Valid(value, amount); } } internal static class ResourceStackPlan { internal static bool TryCreate(int amount, int maximumStackSize, out List<int> stacks) { stacks = new List<int>(); if (amount < 1 || amount > 1000 || maximumStackSize < 1) { return false; } int num = amount; while (num > 0) { int num2 = Math.Min(num, maximumStackSize); stacks.Add(num2); num -= num2; } return true; } } internal static class ResourceDropTransaction { internal static bool TryCreate<T>(IList<int> stacks, Func<int, int, T> create, Action<T> cleanup, out List<T> created, out Exception failure) where T : class { created = new List<T>(); failure = null; if (stacks == null || stacks.Count == 0 || create == null || cleanup == null) { failure = new ArgumentException("invalid resource delivery transaction"); return false; } try { for (int i = 0; i < stacks.Count; i++) { T val = create(stacks[i], i); if (object.ReferenceEquals(val, null)) { throw new InvalidOperationException("resource delivery factory returned null"); } created.Add(val); } return true; } catch (Exception ex) { failure = ex; for (int num = created.Count - 1; num >= 0; num--) { try { cleanup(created[num]); } catch { } } return false; } } } [BepInPlugin("ca.narclab.valheim.resources", "NarcLab Resources", "0.1.1")] public sealed class NarcLabResourcesPlugin : BaseUnityPlugin { public const string PluginGuid = "ca.narclab.valheim.resources"; public const string PluginName = "NarcLab Resources"; public const string PluginVersion = "0.1.1"; private const string RequestRpc = "NarclabResources_Request_v1"; private const string ResponseRpc = "NarclabResources_Response_v1"; private ZRoutedRpc registeredRpc; private ZRoutedRpc failedRpc; private ConsoleCommand resourceCommand; private ConsoleCommand previousSpawnCommand; private bool commandRegistrationRefused; private void Awake() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"NarcLab Resources 0.1.1 initialized; server allowlist=Wood,Stone"); } private void Update() { EnsureRpcRegistration(); EnsureCommandRegistration(); } private void OnDestroy() { if (resourceCommand != null || commandRegistrationRefused) { NarclabTerminalRegistry.RestoreSpawn(resourceCommand, previousSpawnCommand); } } private void EnsureRpcRegistration() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null || object.ReferenceEquals(instance, registeredRpc) || object.ReferenceEquals(instance, failedRpc)) { return; } try { instance.Register<int, string, int>("NarclabResources_Request_v1", (Action<long, int, string, int>)HandleResourceRequest); instance.Register<int, string, int, string>("NarclabResources_Response_v1", (Method<int, string, int, string>)HandleResourceResponse); registeredRpc = instance; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Fixed NarcLab Resources RPC contract registered"); } catch (Exception ex) { failedRpc = instance; ((BaseUnityPlugin)this).Logger.LogError((object)("Fixed RPC registration failed: " + ex.GetType().Name)); } } private void EnsureCommandRegistration() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown if (resourceCommand != null) { return; } if (!NarclabTerminalRegistry.TryReplaceVanillaSpawn(new ConsoleEvent(HandleSpawnCommand), out var previous, out var replacement, out var failureReason)) { if (!string.IsNullOrEmpty(failureReason)) { ((BaseUnityPlugin)this).Logger.LogError((object)("Safe spawn command registration refused: " + failureReason)); commandRegistrationRefused = true; } } else { previousSpawnCommand = previous; resourceCommand = replacement; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Vanilla spawn command replaced with the restricted Wood/Stone command"); } } private void HandleSpawnCommand(ConsoleEventArgs args) { ResourceRequestResult resourceRequestResult = ResourcePolicy.ParseCommand(args?.FullLine); if (!resourceRequestResult.IsValid) { AddConsoleMessage(args, MessageForError(resourceRequestResult.Error)); return; } if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { AddConsoleMessage(args, "[NarcLab] Resource request failed."); return; } try { if (!TryGetServerPeerId(out var serverPeerId)) { AddConsoleMessage(args, "[NarcLab] Resource request failed."); return; } ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerId, "NarclabResources_Request_v1", new object[3] { 1, resourceRequestResult.Request.Resource, resourceRequestResult.Request.Amount }); AddConsoleMessage(args, "[NarcLab] Resource request sent."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Resource request RPC failed: " + ex.GetType().Name)); AddConsoleMessage(args, "[NarcLab] Resource request failed."); } } private void HandleResourceRequest(long sender, int protocolVersion, string resource, int amount) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("REJECT peer=" + sender + " reason=not_server")); return; } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer == null || !ZNet.instance.IsConnected(sender) || !peer.IsReady() || ((ZDOID)(ref peer.m_characterID)).IsNone()) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("REJECT peer=" + sender + " reason=peer_not_active")); return; } ResourceRequestResult resourceRequestResult = ResourcePolicy.ValidateRpc(protocolVersion, resource, amount); string error; Exception deliveryException; if (!resourceRequestResult.IsValid) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("REJECT player=" + SafeLogValue(peer.m_playerName) + " peer=" + peer.m_uid + " resource=" + SafeLogValue(resource) + " amount=" + amount + " reason=" + ErrorCode(resourceRequestResult.Error))); SendResponse(sender, resourceRequestResult.Error, string.Empty, 0); } else if (!TryDropResource(peer, resourceRequestResult.Request, out error, out deliveryException)) { ((BaseUnityPlugin)this).Logger.LogError((object)("REJECT player=" + SafeLogValue(peer.m_playerName) + " peer=" + peer.m_uid + " resource=" + resourceRequestResult.Request.Resource + " amount=" + resourceRequestResult.Request.Amount + " reason=" + error)); if (deliveryException != null) { ((BaseUnityPlugin)this).Logger.LogError((object)("DROP DIAGNOSTIC resource=" + resourceRequestResult.Request.Resource + " amount=" + resourceRequestResult.Request.Amount + " exception=" + SafeException(deliveryException))); } SendResponse(sender, ResourceRequestError.Syntax, string.Empty, 0, "delivery_failed"); } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)("ACCEPT player=" + SafeLogValue(peer.m_playerName) + " peer=" + peer.m_uid + " resource=" + resourceRequestResult.Request.Resource + " amount=" + resourceRequestResult.Request.Amount)); SendResponse(sender, ResourceRequestError.None, resourceRequestResult.Request.Resource, resourceRequestResult.Request.Amount); } } private bool TryDropResource(ZNetPeer peer, ResourceRequest request, out string error, out Exception deliveryException) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) error = null; deliveryException = null; if ((Object)(object)ZNetScene.instance == (Object)null) { error = "runtime_not_ready"; return false; } GameObject networkPrefab = ZNetScene.instance.GetPrefab(request.Resource); ItemDrop val = (((Object)(object)networkPrefab == (Object)null) ? null : networkPrefab.GetComponent<ItemDrop>()); ZNetView val2 = (((Object)(object)networkPrefab == (Object)null) ? null : networkPrefab.GetComponent<ZNetView>()); if ((Object)(object)val == (Object)null || val.m_itemData == null || val.m_itemData.m_shared == null || (Object)(object)val2 == (Object)null) { error = "qualified_network_prefab_missing"; return false; } int maxStackSize = val.m_itemData.m_shared.m_maxStackSize; if (maxStackSize < 1) { error = "invalid_stack_size"; return false; } if (!ResourceStackPlan.TryCreate(request.Amount, maxStackSize, out var stacks)) { error = "invalid_stack_plan"; return false; } Vector3 origin = peer.GetRefPos() + Vector3.up * 0.75f; if (!ResourceDropTransaction.TryCreate(stacks, delegate(int stack, int index) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_0041: Unknown result type (might be due to invalid IL or missing references) float num = (float)index * 0.7f; Vector3 val3 = new Vector3(Mathf.Cos(num), 0f, Mathf.Sin(num)) * (0.25f + 0.04f * (float)index); return CreateNetworkedDrop(networkPrefab, stack, origin + val3); }, DestroyTrackedDrop, out var _, out deliveryException)) { error = "drop_failed_" + ((deliveryException == null) ? "Unknown" : deliveryException.GetType().Name); return false; } return true; } private static ItemDrop CreateNetworkedDrop(GameObject networkPrefab, int stack, Vector3 position) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) GameObject val = null; try { val = Object.Instantiate<GameObject>(networkPrefab, position, Quaternion.identity); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("network prefab instantiation returned null"); } ItemDrop component = val.GetComponent<ItemDrop>(); ZNetView component2 = val.GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null || (Object)(object)component2 == (Object)null) { throw new InvalidOperationException("network prefab is missing required item components"); } if (!component2.IsValid() || !component2.IsOwner()) { throw new InvalidOperationException("created network item is not server-owned"); } component.m_itemData.m_stack = stack; ZDO zDO = component2.GetZDO(); if (zDO == null) { throw new InvalidOperationException("created network item has no ZDO"); } ItemDrop.SaveToZDO(component.m_itemData, zDO, -1); return component; } catch { DestroyTrackedObject(val); throw; } } private static void DestroyTrackedDrop(ItemDrop drop) { if ((Object)(object)drop != (Object)null) { DestroyTrackedObject(((Component)drop).gameObject); } } private static void DestroyTrackedObject(GameObject gameObject) { if (!((Object)(object)gameObject == (Object)null)) { if ((Object)(object)ZNetScene.instance != (Object)null) { ZNetScene.instance.Destroy(gameObject); } else { Object.Destroy((Object)(object)gameObject); } } } private void SendResponse(long peerId, ResourceRequestError error, string resource, int amount) { SendResponse(peerId, error, resource, amount, ErrorCode(error)); } private void SendResponse(long peerId, ResourceRequestError error, string resource, int amount, string code) { try { ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "NarclabResources_Response_v1", new object[4] { 1, resource ?? string.Empty, amount, code }); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Resource response RPC failed: " + ex.GetType().Name)); } } private void HandleResourceResponse(long sender, int protocolVersion, string resource, int amount, string code) { if (ZRoutedRpc.instance == null || !TryGetServerPeerId(out var serverPeerId) || sender != serverPeerId) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Ignored resource response from non-server peer=" + sender)); return; } if (protocolVersion != 1) { AddConsoleMessage(null, "[NarcLab] Resource request failed."); return; } ResourceRequestResult resourceRequestResult = ResourcePolicy.ValidateRpc(protocolVersion, resource, amount); if (string.Equals(code, "success", StringComparison.Ordinal) && resourceRequestResult.IsValid) { AddConsoleMessage(null, "[NarcLab] Spawned " + amount + " " + resourceRequestResult.Request.Resource + "."); } else { AddConsoleMessage(null, MessageForCode(code)); } } private static string MessageForError(ResourceRequestError error) { return error switch { ResourceRequestError.ResourceNotAllowed => "[NarcLab] Only Wood and Stone are allowed.", ResourceRequestError.AmountOutOfRange => "[NarcLab] Amount must be between 1 and 1000.", _ => "[NarcLab] Usage: spawn wood <1-1000> or spawn stone <1-1000>", }; } private static string MessageForCode(string code) { if (string.Equals(code, "resource_not_allowed", StringComparison.Ordinal)) { return "[NarcLab] Only Wood and Stone are allowed."; } if (string.Equals(code, "amount_out_of_range", StringComparison.Ordinal)) { return "[NarcLab] Amount must be between 1 and 1000."; } return "[NarcLab] Resource request failed."; } private static string ErrorCode(ResourceRequestError error) { return error switch { ResourceRequestError.None => "success", ResourceRequestError.ResourceNotAllowed => "resource_not_allowed", ResourceRequestError.AmountOutOfRange => "amount_out_of_range", ResourceRequestError.ProtocolUnsupported => "protocol_unsupported", _ => "invalid_request", }; } private static string SafeLogValue(string value) { if (string.IsNullOrEmpty(value)) { return "unknown"; } string text = value.Replace('\r', '_').Replace('\n', '_').Replace('\t', '_'); if (text.Length <= 64) { return text; } return text.Substring(0, 64); } private static string SafeException(Exception exception) { string text = exception.ToString().Replace('\r', '_').Replace('\n', ' ') .Replace('\t', ' '); if (text.Length <= 2048) { return text; } return text.Substring(0, 2048); } private static bool TryGetServerPeerId(out long serverPeerId) { serverPeerId = 0L; if ((Object)(object)ZNet.instance == (Object)null) { return false; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null || serverPeer.m_uid == 0) { return false; } serverPeerId = serverPeer.m_uid; return true; } private static void AddConsoleMessage(ConsoleEventArgs args, string message) { if (args != null && (Object)(object)args.Context != (Object)null) { args.Context.AddString(message); } else if ((Object)(object)Console.instance != (Object)null) { ((Terminal)Console.instance).AddString(message); } } } internal static class NarclabTerminalRegistry { private static readonly FieldInfo CommandsField = typeof(Terminal).GetField("commands", BindingFlags.Static | BindingFlags.NonPublic); private static Dictionary<string, ConsoleCommand> GetCommands() { if (!(CommandsField == null)) { return CommandsField.GetValue(null) as Dictionary<string, ConsoleCommand>; } return null; } internal static bool TryReplaceVanillaSpawn(ConsoleEvent action, out ConsoleCommand previous, out ConsoleCommand replacement, out string failureReason) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown previous = null; replacement = null; failureReason = null; Dictionary<string, ConsoleCommand> commands; try { commands = GetCommands(); } catch (Exception ex) { failureReason = "Valheim command registry read failed: " + ex.GetType().Name; return false; } if (commands == null) { failureReason = "Valheim 1.0.7 command registry is unavailable"; return false; } if (!commands.TryGetValue("spawn", out previous)) { return false; } if (!string.Equals(previous.Command, "spawn", StringComparison.OrdinalIgnoreCase) || !previous.IsCheat || !previous.OnlyAdmin) { failureReason = "existing spawn command is not the qualified vanilla command"; return false; } try { replacement = new ConsoleCommand("spawn", "wood|stone <1-1000> - request a server-authoritative NarcLab resource drop", action, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } catch (Exception ex2) { commands["spawn"] = previous; replacement = null; failureReason = "qualified spawn registration failed: " + ex2.GetType().Name; return false; } return object.ReferenceEquals(commands["spawn"], replacement); } internal static void RestoreSpawn(ConsoleCommand replacement, ConsoleCommand previous) { try { Dictionary<string, ConsoleCommand> commands = GetCommands(); if (commands != null && commands.TryGetValue("spawn", out var value) && object.ReferenceEquals(value, replacement) && previous != null) { commands["spawn"] = previous; } } catch { } } }