using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.Audio; using UnityEngine.EventSystems; using UnityEngine.UI; using WaterproofBrushLogic; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyFileVersion("1.0.12.0")] [assembly: AssemblyVersion("1.0.12.0")] [BepInPlugin("com.lesly.valheim.waterproofbrush", "Waterproofing Brush", "1.0.12")] public sealed class WaterproofBrushPlugin : BaseUnityPlugin { private sealed class GameInventory : IBrushInventory { private sealed class Snapshot { internal List Items; internal int[] Stacks; } private readonly Player _player; private readonly Inventory _inventory; internal bool LastCreatedCheated; public int FreeSlots => _inventory.GetEmptySlots(); internal GameInventory(Player player) { _player = player; _inventory = ((Humanoid)player).GetInventory(); } private static string ResourceName(string prefab) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefab); if (!Object.op_Implicit((Object)(object)itemPrefab)) { throw new InvalidOperationException("Missing resource prefab " + prefab); } return itemPrefab.GetComponent().m_itemData.m_shared.m_name; } public int Count(string resource) { return _inventory.CountItems(ResourceName(resource), -1, true); } public object Capture() { List list = new List(_inventory.GetAllItems()); return new Snapshot { Items = list, Stacks = list.Select((ItemData item) => item.m_stack).ToArray() }; } public void Restore(object state) { Snapshot snapshot = (Snapshot)state; List allItems = _inventory.GetAllItems(); allItems.Clear(); allItems.AddRange(snapshot.Items); for (int i = 0; i < allItems.Count; i++) { allItems[i].m_stack = snapshot.Stacks[i]; } try { NotifyInventory.Invoke(_inventory, new object[2] { false, false }); } catch (Exception ex) { Instance.ReportOnce("rollback.notify", ex); } } public bool AddBrush() { ItemData val = CreateBrushItem(); LastCreatedCheated |= Instance._craftCompatibility.IsItemCheated(val); if (_inventory.AddItem(val)) { return _inventory.ContainsItem(val); } return false; } internal ItemData CreateBrushItem() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) ItemData val = BrushPrefab.GetComponent().m_itemData.Clone(); val.m_dropPrefab = BrushPrefab; val.m_stack = 1; val.m_quality = 1; val.m_variant = 0; val.m_worldLevel = (byte)Game.m_worldLevel; val.m_crafterID = _player.GetPlayerID(); val.m_crafterName = _player.GetPlayerName(); val.m_gridPos = new Vector2i(-1, -1); val.m_equipped = false; val.m_durability = 100f; CraftCompatibility compatibility = Instance._craftCompatibility; if (compatibility.HasItemFlag) { string woodName = ResourceName("Wood"); bool resourceCheated = _inventory.GetAllItems().Any((ItemData resource) => resource.m_shared.m_name == woodName && resource.m_worldLevel >= Game.m_worldLevel && compatibility.IsItemCheated(resource)); int? stationCheatedKey = compatibility.StationCheatedKey; CraftingStation currentCraftingStation = _player.GetCurrentCraftingStation(); object obj; if (currentCraftingStation == null) { obj = null; } else { ZNetView component = ((Component)currentCraftingStation).GetComponent(); obj = ((component != null) ? component.GetZDO() : null); } ZDO val2 = (ZDO)obj; bool stationCheated = stationCheatedKey.HasValue && val2 != null && val2.GetBool(stationCheatedKey.Value, false); compatibility.SetItemMetadata(val, _player.NoCostCheat(), resourceCheated, stationCheated); } return val; } public void Spend(string resource, int amount) { _inventory.RemoveItem(ResourceName(resource), amount, -1, true); } } internal struct BrushTarget { internal Piece Piece; internal WearNTear Wear; internal Vector3 Point; } private sealed class GameCoating : ICoating { private readonly ZNetView _view; public bool CanWrite { get { if (Object.op_Implicit((Object)(object)_view) && _view.IsValid()) { return _view.IsOwner(); } return false; } } public bool Protected { get { if (Object.op_Implicit((Object)(object)_view) && _view.IsValid()) { return _view.GetZDO().GetBool(WaterproofHash, false); } return false; } set { if (!CanWrite) { throw new InvalidOperationException("Cannot save coating without piece ownership"); } _view.GetZDO().Set(WaterproofHash, value); } } internal GameCoating(ZNetView view) { _view = view; } } public const string PluginGuid = "com.lesly.valheim.waterproofbrush"; public const string PluginName = "Waterproofing Brush"; public const string PluginVersion = "1.0.12"; internal const string BrushPrefabName = "Lesly_WaterproofingBrush"; internal const string WaterproofKey = "Lesly_WaterproofBrush_Protected"; internal static readonly int WaterproofHash = StringExtensionMethods.GetStableHashCode("Lesly_WaterproofBrush_Protected"); internal static WaterproofBrushPlugin Instance; internal static GameObject BrushPrefab; internal static Recipe BrushRecipe; internal static bool Ready; private Harmony _harmony; private float _nextMaintenance; private float _nextUse; private int _lastCraftFrame = -1; private Recipe _lastSelected; private ObjectDB _selfTestDb; private bool _selfTestPassed; private ConfigEntry _diagnosticKey; private CraftCompatibility _craftCompatibility; private readonly HashSet _reportedErrors = new HashSet(); internal static FieldInfo SelectedRecipeField; internal static PropertyInfo SelectedRecipeProperty; internal static PropertyInfo SelectedItemProperty; internal static MethodInfo NativeCraftComplete; internal static MethodInfo RefreshPanel; internal static MethodInfo NotifyInventory; private static MethodInfo AddKnownRecipe; private static FieldInfo DbByHash; private static FieldInfo DbByData; private static FieldInfo SceneByHash; private static FieldInfo RemoveRayMask; private static FieldInfo RainTimer; private static FieldInfo RainWet; private static FieldInfo SnappingIcon; private GameObject _prefabRoot; private ObjectDB _registeredDb; private ZNetScene _registeredScene; private bool _registering; private Sprite _icon; private readonly List _materials = new List(); private readonly HashSet _tinted = new HashSet(); private readonly HashSet _highlighting = new HashSet(); private ConfigEntry _tintStrength; private ConfigEntry _soundVolume; private BrushTarget _hoverTarget; private Hud _brushHud; private Requirement _resinRequirement; private readonly AudioSource[] _brushSources = (AudioSource[])(object)new AudioSource[4]; private AudioClip _brushSound; private AudioMixerGroup _brushMixer; private int _nextBrushSource; private void Awake() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected O, but got Unknown Instance = this; _diagnosticKey = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "DumpStatus", new KeyboardShortcut((KeyCode)291, Array.Empty()), "Write brush registration, selected recipe, UI state and patch owners to BepInEx/LogOutput.log."); BindFeedbackConfig(); try { ResolveApis(); Assembly assembly = typeof(PlayerProfile).Assembly; _craftCompatibility = new CraftCompatibility(typeof(ItemData), typeof(PlayerProfile), assembly.GetType("PlayerStatType"), assembly.GetType("ZDOVars"), ReportOnce); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[compat.craft] " + _craftCompatibility.Description)); _harmony = new Harmony("com.lesly.valheim.waterproofbrush"); _harmony.PatchAll(typeof(WaterproofBrushPlugin).Assembly); VerifyPatches(); Ready = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[startup.ok] Waterproofing Brush 1.0.12; game=" + Application.version + "; unity=" + Application.unityVersion + "; plugin=" + ((BaseUnityPlugin)this).Info.Location)); } catch (Exception ex) { Ready = false; if (_harmony != null) { _harmony.UnpatchAll("com.lesly.valheim.waterproofbrush"); } ((BaseUnityPlugin)this).Logger.LogError((object)("[startup.failed] Brush disabled; critical API/patch missing. " + ex)); } } private static FieldInfo Field(Type type, string name) { return AccessTools.Field(type, name) ?? throw new MissingFieldException(type.FullName, name); } private static MethodInfo Method(Type type, string name, params Type[] arguments) { return AccessTools.Method(type, name, arguments, (Type[])null) ?? throw new MissingMethodException(type.FullName, name); } private static void ResolveApis() { SelectedRecipeField = Field(typeof(InventoryGui), "m_selectedRecipe"); Type fieldType = SelectedRecipeField.FieldType; SelectedRecipeProperty = fieldType.GetProperty("Recipe") ?? throw new MissingMemberException(fieldType.FullName, "Recipe"); SelectedItemProperty = fieldType.GetProperty("ItemData") ?? throw new MissingMemberException(fieldType.FullName, "ItemData"); NativeCraftComplete = Method(typeof(InventoryGui), "DoCrafting", typeof(Player)); RefreshPanel = Method(typeof(InventoryGui), "UpdateCraftingPanel", typeof(bool)); NotifyInventory = Method(typeof(Inventory), "Changed", typeof(bool), typeof(bool)); AddKnownRecipe = Method(typeof(Player), "AddKnownRecipe", typeof(Recipe)); DbByHash = Field(typeof(ObjectDB), "m_itemByHash"); DbByData = Field(typeof(ObjectDB), "m_itemByData"); SceneByHash = Field(typeof(ZNetScene), "m_namedPrefabs"); RemoveRayMask = Field(typeof(Player), "m_removeRayMask"); RainTimer = Field(typeof(WearNTear), "m_rainTimer"); RainWet = Field(typeof(WearNTear), "m_rainWet"); SnappingIcon = Field(typeof(Hud), "m_snappingIcon"); } private void VerifyPatches() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown int num = 0; Type[] types = typeof(WaterproofBrushPlugin).Assembly.GetTypes(); foreach (Type type in types) { object[] customAttributes = type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false); if (customAttributes.Length != 0) { HarmonyPatch val = (HarmonyPatch)customAttributes[0]; MethodInfo methodInfo = AccessTools.Method(((HarmonyAttribute)val).info.declaringType, ((HarmonyAttribute)val).info.methodName, (Type[])null, (Type[])null); Patches val2 = ((methodInfo == null) ? null : Harmony.GetPatchInfo((MethodBase)methodInfo)); if (val2 == null || !val2.Owners.Contains("com.lesly.valheim.waterproofbrush")) { throw new InvalidOperationException("Patch not installed: " + type.Name); } num++; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[patch.ok] " + methodInfo.DeclaringType.Name + "." + methodInfo.Name)); } } if (num != 13) { throw new InvalidOperationException("Incomplete patch set: " + num); } } private void Update() { //IL_000e: 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) if (!Ready) { return; } KeyboardShortcut value = _diagnosticKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { DumpStatus(); } if (Time.unscaledTime < _nextMaintenance) { return; } _nextMaintenance = Time.unscaledTime + 0.5f; SafeRegister(ObjectDB.instance); try { InventoryGui instance = InventoryGui.instance; Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && Object.op_Implicit((Object)(object)BrushRecipe) && (Object)(object)_selfTestDb != (Object)(object)ObjectDB.instance) { RunInventorySelfTest(); } if (Object.op_Implicit((Object)(object)localPlayer) && Object.op_Implicit((Object)(object)BrushRecipe) && AtWorkbench(localPlayer) && !localPlayer.IsRecipeKnown("Waterproof Brush") && localPlayer.HaveRequirements(BrushRecipe, true, 1, 1)) { AddKnownRecipe.Invoke(localPlayer, new object[1] { BrushRecipe }); } if (!Object.op_Implicit((Object)(object)instance) || !InventoryGui.IsVisible()) { return; } Recipe val = Selected(instance); if ((Object)(object)val != (Object)(object)_lastSelected) { _lastSelected = val; if (IsBrushRecipe(val)) { DumpStatus(); } } } catch (Exception ex) { ReportOnce("maintenance", ex); } } internal static bool IsBrush(ItemData item) { if (item != null && Object.op_Implicit((Object)(object)item.m_dropPrefab)) { return ((Object)item.m_dropPrefab).name == "Lesly_WaterproofingBrush"; } return false; } internal static bool IsBrushRecipe(Recipe recipe) { if (Object.op_Implicit((Object)(object)recipe) && Object.op_Implicit((Object)(object)recipe.m_item)) { return ((Object)((Component)recipe.m_item).gameObject).name == "Lesly_WaterproofingBrush"; } return false; } internal static Recipe Selected(InventoryGui gui) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown return (Recipe)SelectedRecipeProperty.GetValue(SelectedRecipeField.GetValue(gui), null); } internal static bool AtWorkbench(Player player) { CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if (Object.op_Implicit((Object)(object)currentCraftingStation) && currentCraftingStation.m_name == "$piece_workbench") { return !currentCraftingStation.m_upgrader; } return false; } internal void CompleteCraft(InventoryGui gui, Player player, Recipe recipe, ItemData upgrade, bool multi, int batchAmount) { //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) if (!Ready || _lastCraftFrame == Time.frameCount) { return; } _lastCraftFrame = Time.frameCount; try { if (!Object.op_Implicit((Object)(object)player) || !IsBrushRecipe(recipe)) { return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[craft.complete] recipe=" + ((Object)recipe).name)); if ((Object)(object)_selfTestDb != (Object)(object)ObjectDB.instance) { RunInventorySelfTest(); } if (!_selfTestPassed) { Tell(player, "Brush inventory self-test failed. Please send BepInEx/LogOutput.log."); return; } if (upgrade != null) { Tell(player, "The Waterproof Brush has no upgrades."); return; } if (!AtWorkbench(player)) { Tell(player, "Use a workbench to craft the Waterproof Brush."); return; } CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if (currentCraftingStation.GetLevel(true) < 1 || !currentCraftingStation.CheckUsable(player, true)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[craft.blocked] Workbench is not usable."); return; } int num = ((!multi) ? 1 : Math.Max(1, Math.Min(100, batchAmount))); bool free = player.NoCostCheat() || (Object.op_Implicit((Object)(object)ZoneSystem.instance) && ZoneSystem.instance.GetGlobalKey((GlobalKeys)25)); GameInventory gameInventory = new GameInventory(player); int num2 = gameInventory.Count("Wood"); Exception error; Outcome outcome = Transactions.Craft(gameInventory, num, free, out error); if (outcome == Outcome.Success) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[craft.ok] brushes=" + num + "; wood=" + num2 + "->" + gameInventory.Count("Wood"))); try { currentCraftingStation.m_craftItemDoneEffects.Create(((Component)player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); _craftCompatibility.RecordCraft(Game.instance.GetPlayerProfile(), recipe.m_item.m_itemData.m_shared.m_name, num, gameInventory.LastCreatedCheated); } catch (Exception ex) { ReportOnce("craft.feedback", ex); } } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[craft.blocked] result=" + outcome.ToString() + "; wood=" + num2 + "; slots=" + gameInventory.FreeSlots + "; amount=" + num)); if (error != null) { ((BaseUnityPlugin)this).Logger.LogError((object)error); } Tell(player, outcome switch { Outcome.FullInventory => "You need " + num + " empty inventory slot(s).", Outcome.MissingResource => "You need " + num * 2 + " Wood.", _ => "Craft failed; inventory restored. See BepInEx/LogOutput.log.", }); } try { RefreshPanel.Invoke(gui, new object[1] { false }); } catch (Exception ex2) { ReportOnce("ui.refresh", ex2); } } catch (Exception ex3) { ((BaseUnityPlugin)this).Logger.LogError((object)("[craft.error] " + ex3)); Tell(Player.m_localPlayer, "Brush crafting error. See BepInEx/LogOutput.log."); } } internal void ReportOnce(string context, Exception ex) { if (_reportedErrors.Add(context + ":" + ex.GetType().FullName + ":" + ex.Message)) { ((BaseUnityPlugin)this).Logger.LogError((object)("[" + context + ".error] " + ex)); } } internal static void Tell(Player player, string message) { if (Object.op_Implicit((Object)(object)player)) { ((Character)player).Message((MessageType)2, message, 0, (Sprite)null, false); } } private void RunInventorySelfTest() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown _selfTestDb = ObjectDB.instance; _selfTestPassed = false; bool forceDisableInit = ZNetView.m_forceDisableInit; try { Inventory val = new Inventory("WaterproofBrush self-test", (Sprite)null, 2, 1); ItemData val2 = new GameInventory(Player.m_localPlayer).CreateBrushItem(); if (!val.AddItem(val2) || !val.ContainsItem(val2)) { throw new InvalidOperationException("Native AddItem rejected the test brush"); } ZPackage val3 = new ZPackage(); val.Save(val3); Inventory val4 = new Inventory("WaterproofBrush restore self-test", (Sprite)null, 2, 1); val4.Load(new ZPackage(val3.GetArray())); List allItems = val4.GetAllItems(); if (allItems.Count != 1 || !IsBrush(allItems[0]) || allItems[0].m_stack != 1 || (Object)(object)allItems[0].m_shared.m_buildPieces != (Object)null || !Object.op_Implicit((Object)(object)allItems[0].GetIcon()) || allItems[0].HavePrimaryAttack() || allItems[0].HaveSecondaryAttack()) { throw new InvalidOperationException("Native save/load did not restore a valid brush tool"); } _selfTestPassed = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"[selftest.ok] Craft item factory + metadata + native Inventory.AddItem + Save + Load passed in throwaway inventories; player inventory untouched."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("[selftest.failed] " + ex)); } finally { ZNetView.m_forceDisableInit = forceDisableInit; } } private void DumpStatus() { //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Expected O, but got Unknown try { InventoryGui gui = InventoryGui.instance; Recipe val = (Object.op_Implicit((Object)(object)gui) ? Selected(gui) : null); Player localPlayer = Player.m_localPlayer; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[status] version=1.0.12; ready=" + Ready + "; prefab=" + ((Object)(object)BrushPrefab != (Object)null) + "; recipe=" + ((Object)(object)BrushRecipe != (Object)null) + "; selected=" + (Object.op_Implicit((Object)(object)val) ? ((Object)val).name : "none") + "; selectedIsBrush=" + IsBrushRecipe(val) + "; buttonActive=" + (Object.op_Implicit((Object)(object)gui) && Object.op_Implicit((Object)(object)gui.m_craftButton) && ((Component)gui.m_craftButton).gameObject.activeInHierarchy) + "; buttonInteractable=" + (Object.op_Implicit((Object)(object)gui) && Object.op_Implicit((Object)(object)gui.m_craftButton) && ((Selectable)gui.m_craftButton).interactable) + "; nativeCraftProgress=true; inventorySelfTest=" + _selfTestPassed + "; workbench=" + (Object.op_Implicit((Object)(object)localPlayer) && AtWorkbench(localPlayer)) + "; wood=" + (Object.op_Implicit((Object)(object)localPlayer) ? new GameInventory(localPlayer).Count("Wood") : (-1)))); Patches patchInfo = Harmony.GetPatchInfo((MethodBase)NativeCraftComplete); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[status.patches] DoCrafting owners=" + ((patchInfo == null) ? "NONE" : string.Join(",", patchInfo.Owners.ToArray())))); if (Object.op_Implicit((Object)(object)gui) && Object.op_Implicit((Object)(object)gui.m_craftButton) && InventoryGui.IsVisible() && Object.op_Implicit((Object)(object)EventSystem.current)) { Transform transform = ((Component)gui.m_craftButton).transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); Canvas componentInParent = ((Component)gui.m_craftButton).GetComponentInParent(); Camera val3 = ((Object.op_Implicit((Object)(object)componentInParent) && (int)componentInParent.renderMode != 0) ? componentInParent.worldCamera : null); PointerEventData val4 = new PointerEventData(EventSystem.current); Rect rect = val2.rect; val4.position = RectTransformUtility.WorldToScreenPoint(val3, ((Transform)val2).TransformPoint(Vector2.op_Implicit(((Rect)(ref rect)).center))); PointerEventData val5 = val4; List list = new List(); EventSystem.current.RaycastAll(val5, list); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[status.raycast] Craft button centre hits=" + string.Join(" | ", (from hit in list.Take(5) select ((Object)((RaycastResult)(ref hit)).gameObject).name + " (routesToCraft=" + ((Object)(object)((RaycastResult)(ref hit)).gameObject.GetComponentInParent