Decompiled source of ValheimPrefabParser v2.0.0

BepInEx/plugins/Valheimprefabparser.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Valheimprefabparser")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Valheimprefabparser")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("3b0a1528-adb0-4755-9b2f-2d2b5d70ab58")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ValheimPrefabParser;

[BepInPlugin("com.yourname.valheimprefabparser", "Valheim Prefab Parser", "2.0.0")]
public class PrefabParserPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	public static class ZNetScene_Awake_Patch
	{
		private static void Postfix()
		{
			Log.LogInfo((object)"ZNetScene.Awake finished — waiting for ObjectDB...");
			if (!_parsingStarted)
			{
				((MonoBehaviour)_instance).StartCoroutine(_instance.WaitForObjectDBAndParse());
			}
		}
	}

	[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
	public static class ObjectDB_CopyOtherDB_Patch
	{
		private static void Postfix()
		{
			Log.LogInfo((object)"ObjectDB.CopyOtherDB finished.");
			if (!_parsingStarted && (Object)(object)ZNetScene.instance != (Object)null)
			{
				Log.LogInfo((object)"ZNetScene is ready — starting parse from CopyOtherDB.");
				_parsingStarted = true;
				TriggerParsing();
			}
		}
	}

	[CompilerGenerated]
	private sealed class <ParseCoroutine>d__22 : IEnumerator<object>, IDisposable, IEnumerator
	{
		private int <>1__state;

		private object <>2__current;

		private List<GameObject> <prefabs>5__2;

		private Dictionary<string, List<GameObject>> <categories>5__3;

		private int <processed>5__4;

		private List<GameObject>.Enumerator <>7__wrap4;

		object IEnumerator<object>.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		object IEnumerator.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		[DebuggerHidden]
		public <ParseCoroutine>d__22(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			int num = <>1__state;
			if (num == -3 || num == 2)
			{
				try
				{
				}
				finally
				{
					<>m__Finally1();
				}
			}
			<prefabs>5__2 = null;
			<categories>5__3 = null;
			<>7__wrap4 = default(List<GameObject>.Enumerator);
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			try
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					Log.LogInfo((object)"Starting parse (coroutine mode)...");
					<prefabs>5__2 = GetAllPrefabs();
					<>2__current = null;
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					<categories>5__3 = new Dictionary<string, List<GameObject>>();
					<processed>5__4 = 0;
					<>7__wrap4 = <prefabs>5__2.GetEnumerator();
					<>1__state = -3;
					break;
				case 2:
					<>1__state = -3;
					break;
				}
				while (<>7__wrap4.MoveNext())
				{
					GameObject current = <>7__wrap4.Current;
					if (!((Object)(object)current == (Object)null))
					{
						string key = DeterminePrefabCategory(current);
						if (!<categories>5__3.TryGetValue(key, out var value))
						{
							value = new List<GameObject>();
							<categories>5__3[key] = value;
						}
						value.Add(current);
						if (++<processed>5__4 % 50 == 0)
						{
							Log.LogInfo((object)$"Categorized: {<processed>5__4}/{<prefabs>5__2.Count}");
							<>2__current = null;
							<>1__state = 2;
							return true;
						}
					}
				}
				<>m__Finally1();
				<>7__wrap4 = default(List<GameObject>.Enumerator);
				WriteFile(BuildContent(<prefabs>5__2.Count, <categories>5__3));
				Log.LogInfo((object)$"Done! Total prefabs found: {<prefabs>5__2.Count}");
				return false;
			}
			catch
			{
				//try-fault
				((IDisposable)this).Dispose();
				throw;
			}
		}

		bool IEnumerator.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			return this.MoveNext();
		}

		private void <>m__Finally1()
		{
			<>1__state = -1;
			((IDisposable)<>7__wrap4).Dispose();
		}

		[DebuggerHidden]
		void IEnumerator.Reset()
		{
			throw new NotSupportedException();
		}
	}

	[CompilerGenerated]
	private sealed class <WaitForObjectDBAndParse>d__15 : IEnumerator<object>, IDisposable, IEnumerator
	{
		private int <>1__state;

		private object <>2__current;

		private float <elapsed>5__2;

		object IEnumerator<object>.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		object IEnumerator.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		[DebuggerHidden]
		public <WaitForObjectDBAndParse>d__15(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<elapsed>5__2 = 0f;
				break;
			case 1:
				<>1__state = -1;
				break;
			}
			if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_items.Count == 0)
			{
				<elapsed>5__2 += Time.deltaTime;
				if (!(<elapsed>5__2 > 30f))
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				Log.LogWarning((object)"Timeout: ObjectDB was not ready within 30 seconds. Starting parse without it.");
			}
			if (_parsingStarted)
			{
				return false;
			}
			_parsingStarted = true;
			Log.LogInfo((object)$"ObjectDB ready ({ObjectDB.instance?.m_items.Count ?? 0} items) — starting parse.");
			TriggerParsing();
			return false;
		}

		bool IEnumerator.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			return this.MoveNext();
		}

		[DebuggerHidden]
		void IEnumerator.Reset()
		{
			throw new NotSupportedException();
		}
	}

	public const string PluginGUID = "com.yourname.valheimprefabparser";

	public const string PluginName = "Valheim Prefab Parser";

	public const string PluginVersion = "2.0.0";

	internal static ManualLogSource Log;

	private static PrefabParserPlugin _instance;

	private Harmony _harmony;

	internal static ConfigEntry<bool> UseCoroutine;

	internal static ConfigEntry<string> OutputFileName;

	internal static ConfigEntry<bool> IncludeComponentList;

	private static bool _parsingStarted;

	private void Awake()
	{
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Expected O, but got Unknown
		_instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		UseCoroutine = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UseCoroutine", true, "Spread parsing across frames to avoid a freeze on load.");
		OutputFileName = ((BaseUnityPlugin)this).Config.Bind<string>("General", "OutputFileName", "valheim_prefabs.txt", "Output file name. Created next to the plugin .dll.");
		IncludeComponentList = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "IncludeComponentList", false, "Print every Unity component on each prefab. Makes the file large.");
		_harmony = new Harmony("com.yourname.valheimprefabparser");
		_harmony.PatchAll();
		Log.LogInfo((object)"Valheim Prefab Parser v2.0.0 loaded.");
	}

	private void OnDestroy()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
	}

	internal static void TriggerParsing()
	{
		if (!((Object)(object)_instance == (Object)null))
		{
			if (UseCoroutine.Value)
			{
				((MonoBehaviour)_instance).StartCoroutine(_instance.ParseCoroutine());
			}
			else
			{
				ParseSync();
			}
		}
	}

	[IteratorStateMachine(typeof(<WaitForObjectDBAndParse>d__15))]
	private IEnumerator WaitForObjectDBAndParse()
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <WaitForObjectDBAndParse>d__15(0);
	}

	private static List<GameObject> GetAllPrefabs()
	{
		//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
		HashSet<int> seen = new HashSet<int>();
		List<GameObject> prefabs = new List<GameObject>();
		if ((Object)(object)ObjectDB.instance != (Object)null)
		{
			Log.LogInfo((object)"  Reading ObjectDB.m_items...");
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				if ((Object)(object)item != (Object)null)
				{
					TryAdd(item.gameObject);
				}
			}
			Log.LogInfo((object)"  Reading ObjectDB.m_recipes...");
			foreach (Recipe recipe in ObjectDB.instance.m_recipes)
			{
				if ((Object)(object)recipe?.m_item != (Object)null)
				{
					TryAdd(((Component)recipe.m_item).gameObject);
				}
			}
			Log.LogInfo((object)$"  Items: {ObjectDB.instance.m_items.Count}, Recipes: {ObjectDB.instance.m_recipes.Count}");
		}
		else
		{
			Log.LogWarning((object)"ObjectDB.instance is null — item data will be incomplete.");
		}
		if ((Object)(object)ZNetScene.instance != (Object)null)
		{
			Log.LogInfo((object)"  Reading ZNetScene.m_prefabs...");
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				TryAdd(prefab);
			}
			Log.LogInfo((object)$"  ZNetScene prefabs: {ZNetScene.instance.m_prefabs.Count}");
		}
		else
		{
			Log.LogWarning((object)"ZNetScene.instance is null — scene data will be incomplete.");
		}
		Log.LogInfo((object)"  Scanning Resources.FindObjectsOfTypeAll...");
		GameObject[] array = Resources.FindObjectsOfTypeAll<GameObject>();
		foreach (GameObject val in array)
		{
			Scene scene = val.scene;
			if (string.IsNullOrEmpty(((Scene)(ref scene)).name))
			{
				TryAdd(val);
			}
		}
		Log.LogInfo((object)$"  Total unique prefabs collected: {prefabs.Count}");
		return prefabs;
		void TryAdd(GameObject go)
		{
			if (!((Object)(object)go == (Object)null) && seen.Add(((Object)go).GetInstanceID()))
			{
				prefabs.Add(go);
			}
		}
	}

	private static string DeterminePrefabCategory(GameObject go)
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: 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_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Expected I4, but got Unknown
		ItemDrop component = go.GetComponent<ItemDrop>();
		if ((Object)(object)component != (Object)null)
		{
			ItemType itemType = component.m_itemData.m_shared.m_itemType;
			switch (itemType - 1)
			{
			case 2:
			case 3:
			case 13:
			case 21:
				return "01_Weapons";
			case 4:
				return "02_Shields";
			case 5:
			case 6:
			case 10:
			case 11:
			case 16:
				return "03_Armor";
			case 8:
			case 22:
				return "04_Ammunition";
			case 1:
				if (!(component.m_itemData.m_shared.m_food > 0f))
				{
					return "06_Consumables";
				}
				return "05_Food";
			case 0:
				return "07_Materials";
			case 12:
				return "08_Trophies";
			case 18:
				return "09_Tools";
			case 14:
				return "10_Torches";
			case 17:
				return "11_Utility";
			default:
				return "12_Other_Items";
			}
		}
		if ((Object)(object)go.GetComponent<Piece>() != (Object)null)
		{
			return "13_Buildings";
		}
		Character component2 = go.GetComponent<Character>();
		if ((Object)(object)component2 != (Object)null)
		{
			if (component2.m_boss)
			{
				return "14_Bosses";
			}
			if (((Object)go).name.Contains("Player"))
			{
				return "15_Player";
			}
			if ((Object)(object)go.GetComponent<Humanoid>() != (Object)null)
			{
				return "16_Humanoids";
			}
			if ((Object)(object)go.GetComponent<MonsterAI>() != (Object)null)
			{
				return "17_Monsters";
			}
			if ((Object)(object)go.GetComponent<Tameable>() != (Object)null)
			{
				return "18_Tameable";
			}
			return "19_Creatures";
		}
		if ((Object)(object)go.GetComponent<Plant>() != (Object)null)
		{
			return "20_Plants";
		}
		if ((Object)(object)go.GetComponent<TreeBase>() != (Object)null)
		{
			return "21_Trees";
		}
		if ((Object)(object)go.GetComponent<MineRock>() != (Object)null)
		{
			return "22_Minerals";
		}
		if ((Object)(object)go.GetComponent<MineRock5>() != (Object)null)
		{
			return "22_Minerals";
		}
		if ((Object)(object)go.GetComponent<Container>() != (Object)null)
		{
			return "23_Containers";
		}
		if ((Object)(object)go.GetComponent<CraftingStation>() != (Object)null)
		{
			return "24_Crafting_Stations";
		}
		if ((Object)(object)go.GetComponent<Fireplace>() != (Object)null)
		{
			return "25_Fireplaces";
		}
		if ((Object)(object)go.GetComponent<TeleportWorld>() != (Object)null)
		{
			return "26_Portals";
		}
		if ((Object)(object)go.GetComponent<Pickable>() != (Object)null)
		{
			return "27_Pickables";
		}
		if ((Object)(object)go.GetComponent<SpawnArea>() != (Object)null)
		{
			return "28_Spawners";
		}
		if ((Object)(object)go.GetComponent<Ship>() != (Object)null)
		{
			return "29_Ships";
		}
		if ((Object)(object)go.GetComponent<Projectile>() != (Object)null)
		{
			return "30_Projectiles";
		}
		if ((Object)(object)go.GetComponent<ParticleSystem>() != (Object)null)
		{
			return "31_VFX_Effects";
		}
		if ((Object)(object)go.GetComponent<AudioSource>() != (Object)null && (Object)(object)go.GetComponent<ZNetView>() == (Object)null)
		{
			return "32_SFX";
		}
		string name = ((Object)go).name;
		if (name.StartsWith("vfx_") || name.StartsWith("sfx_") || name.StartsWith("fx_"))
		{
			return "31_VFX_Effects";
		}
		return "99_Other";
	}

	private static Dictionary<string, List<GameObject>> CategorizePrefabs(List<GameObject> prefabs)
	{
		Dictionary<string, List<GameObject>> dictionary = new Dictionary<string, List<GameObject>>();
		foreach (GameObject prefab in prefabs)
		{
			if (!((Object)(object)prefab == (Object)null))
			{
				string key = DeterminePrefabCategory(prefab);
				if (!dictionary.TryGetValue(key, out var value))
				{
					value = (dictionary[key] = new List<GameObject>());
				}
				value.Add(prefab);
			}
		}
		return dictionary;
	}

	private static string BuildContent(int totalCount, Dictionary<string, List<GameObject>> categories)
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.AppendLine("╔══════════════════════════════════════════════════════════╗");
		stringBuilder.AppendLine("║           VALHEIM PREFAB PARSER — FULL LIST              ║");
		stringBuilder.AppendLine("╚══════════════════════════════════════════════════════════╝");
		stringBuilder.AppendLine($"  Date:             {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
		stringBuilder.AppendLine($"  Total prefabs:    {totalCount}");
		stringBuilder.AppendLine($"  Categories:       {categories.Count}");
		stringBuilder.AppendLine();
		stringBuilder.AppendLine("── SUMMARY ─────────────────────────────────────────────────");
		foreach (KeyValuePair<string, List<GameObject>> item in categories.OrderBy((KeyValuePair<string, List<GameObject>> c) => c.Key))
		{
			stringBuilder.AppendLine($"  {item.Key,-35} {item.Value.Count,5} pcs.");
		}
		stringBuilder.AppendLine();
		stringBuilder.AppendLine("── DETAILED LIST ───────────────────────────────────────────");
		foreach (KeyValuePair<string, List<GameObject>> item2 in categories.OrderBy((KeyValuePair<string, List<GameObject>> c) => c.Key))
		{
			stringBuilder.AppendLine();
			stringBuilder.AppendLine($"┌─ {item2.Key} ({item2.Value.Count})");
			foreach (GameObject item3 in item2.Value.OrderBy((GameObject g) => ((Object)g).name))
			{
				stringBuilder.AppendLine("│  " + ((Object)item3).name);
				if (IncludeComponentList.Value)
				{
					IOrderedEnumerable<string> values = from s in (from c in item3.GetComponents<Component>()
							where (Object)(object)c != (Object)null
							select ((object)c).GetType().Name).Distinct()
						orderby s
						select s;
					stringBuilder.AppendLine("│    [" + string.Join(", ", values) + "]");
				}
			}
			stringBuilder.AppendLine("└───────────────────────────────────────────────────────────");
		}
		return stringBuilder.ToString();
	}

	private static void WriteFile(string content)
	{
		try
		{
			string text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), OutputFileName.Value);
			File.WriteAllText(text, content, Encoding.UTF8);
			Log.LogInfo((object)("File written: " + text));
		}
		catch (Exception ex)
		{
			Log.LogError((object)("Failed to write file: " + ex.Message));
		}
	}

	private static void ParseSync()
	{
		try
		{
			Log.LogInfo((object)"Starting parse (synchronous mode)...");
			List<GameObject> allPrefabs = GetAllPrefabs();
			Dictionary<string, List<GameObject>> categories = CategorizePrefabs(allPrefabs);
			WriteFile(BuildContent(allPrefabs.Count, categories));
			Log.LogInfo((object)$"Done! Total prefabs found: {allPrefabs.Count}");
		}
		catch (Exception arg)
		{
			Log.LogError((object)$"Parse failed: {arg}");
		}
	}

	[IteratorStateMachine(typeof(<ParseCoroutine>d__22))]
	private IEnumerator ParseCoroutine()
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <ParseCoroutine>d__22(0);
	}
}