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);
}
}