Skip to content

Instantly share code, notes, and snippets.

@JSandusky
Created August 27, 2026 19:40
Show Gist options
  • Select an option

  • Save JSandusky/0280b686dd8327a44b9f09401b6d4d1e to your computer and use it in GitHub Desktop.

Select an option

Save JSandusky/0280b686dd8327a44b9f09401b6d4d1e to your computer and use it in GitHub Desktop.
Skyrim scan
using Mutagen.Bethesda.Skyrim;
using System;
using System.Collections.Generic;
using System.Linq;
using Mutagen.Bethesda;
using Mutagen.Bethesda.Skyrim;
using Mutagen.Bethesda.Plugins.Cache.Internals.Implementations;
using BBox = System.Collections.Generic.KeyValuePair<Noggog.P3Float, Noggog.P3Float>;
namespace SkyrimData
{
public class SkyrimScan : SharedScan
{
void ReflectFormIDs()
{
ReflectFormIDs(typeof(Mutagen.Bethesda.FormKeys.SkyrimSE.Skyrim));
ReflectFormIDs(typeof(Mutagen.Bethesda.FormKeys.SkyrimSE.HearthFires));
ReflectFormIDs(typeof(Mutagen.Bethesda.FormKeys.SkyrimSE.Dawnguard));
ReflectFormIDs(typeof(Mutagen.Bethesda.FormKeys.SkyrimSE.Dragonborn));
}
BBox ExtractBounds(PlacedObject placed, string key, ImmutableModLinkCache<ISkyrimMod, ISkyrimModGetter> cache)
{
if (key == "Ingredient")
{
var found = placed.Base.Resolve<Ingredient>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "Static")
{
var found = placed.Base.Resolve<Static>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "Flora")
{
var found = placed.Base.Resolve<Flora>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "MiscItem")
{
var found = placed.Base.Resolve<MiscItem>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "Ingestible")
{
var found = placed.Base.Resolve<Ingestible>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "Furniture")
{
var found = placed.Base.Resolve<Furniture>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "Container")
{
var found = placed.Base.Resolve<Container>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "Book")
{
var found = placed.Base.Resolve<Book>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "Armor")
{
var found = placed.Base.Resolve<Armor>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "Weapon")
{
var found = placed.Base.Resolve<Weapon>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "Tree")
{
var found = placed.Base.Resolve<Tree>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "Light")
{
var found = placed.Base.Resolve<Light>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "MoveableStatic")
{
var found = placed.Base.Resolve<MoveableStatic>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
if (key == "SoundMarker")
{
var found = placed.Base.Resolve<SoundMarker>(cache);
return new BBox(ScaleBnds(found.ObjectBounds.First), ScaleBnds(found.ObjectBounds.Second));
}
return new BBox();
}
public void GenerateText(string modPath = "D:/Steam/steamapps/common/SkyrimVR/Data/Skyrim.esm", string outFile = "output.txt")
{
// we need to build this to get good lookups
System.Console.WriteLine("Building form key table... (approximately 10-20 seconds)");
ReflectFormIDs();
System.Console.WriteLine("Key table built");
var fs = System.IO.File.OpenWrite(outFile);
var sw = new System.IO.StreamWriter(fs);
System.Console.WriteLine($"----- LOADING ESM FILE -----");
SkyrimMod mod = SkyrimMod.CreateFromBinary(modPath, Mutagen.Bethesda.Skyrim.SkyrimRelease.SkyrimVR);
var cache = mod.ToImmutableLinkCache();
System.Console.WriteLine($"----- STARTING -----");
int unnamedCt = 0;
foreach (var cellBlock in mod.Cells.Records)
{
foreach (var cellSub in cellBlock.SubBlocks)
{
foreach (var cell in cellSub.Cells)
{
// scan all interiors
if (cell.Flags.HasFlag(Cell.Flag.IsInteriorCell))
{
string cellName = cell.Name;
if (String.IsNullOrEmpty(cellName))
cellName = "unnamed_" + (++unnamedCt).ToString();
cellName += " : " + cell.Temporary.Count;
System.Console.WriteLine(cellName);
sw.WriteLine(cellName);
foreach (var per in cell.Temporary)
{
sw.WriteLine($" {per.GetType().Name}");
PlacedObject placed = per as PlacedObject;
if (placed != null)
{
KeyValuePair<string, string> v;
if (lut.TryGetValue(placed.Base.FormKey, out v))
{
sw.WriteLine($" {v.Key} {v.Value}");
sw.WriteLine($" {placed.Placement.Position * ScalingFactor}");
sw.WriteLine($" {placed.Placement.Rotation}");
var bbox = ExtractBounds(placed, v.Key, cache);
sw.WriteLine($" {bbox.Key} : {bbox.Value}");
}
}
PlacedNpc placedNpc = per as PlacedNpc;
if (placedNpc != null)
{
var npc = placedNpc.Base.Resolve<Npc>(cache);
sw.WriteLine($" {npc.Name}");
sw.WriteLine($" {placedNpc.Placement.Position * ScalingFactor}");
sw.WriteLine($" {placedNpc.Placement.Rotation}");
}
}
}
}
}
}
System.Console.WriteLine($"----- FINISHED -----");
sw.Close();
fs.Close();
}
public void GenerateBinary(string modPath, string outFile)
{
System.Console.WriteLine("Building form key table... (approximately 10-20 seconds)");
ReflectFormIDs();
System.Console.WriteLine("Key table built");
var fs = System.IO.File.OpenWrite(outFile);
var sw = new System.IO.BinaryWriter(fs);
System.Console.WriteLine($"----- LOADING ESM FILE -----");
SkyrimMod mod = SkyrimMod.CreateFromBinary(modPath, Mutagen.Bethesda.Skyrim.SkyrimRelease.SkyrimVR);
var cache = mod.ToImmutableLinkCache();
System.Console.WriteLine($"----- STARTING -----");
int unnamedCt = 0;
foreach (var cellBlock in mod.Cells)
{
foreach (var cellSub in cellBlock.SubBlocks)
{
foreach (var cell in cellSub.Cells)
{
// scan all interiors
if (cell.Flags.HasFlag(Cell.Flag.IsInteriorCell))
{
sw.Write(ASCIICodes.FS);
string cellName = cell.Name;
if (String.IsNullOrEmpty(cellName))
cellName = "unnamed_" + (++unnamedCt).ToString();
cellName += " : " + cell.Temporary.Count;
System.Console.WriteLine(cellName);
sw.Write(cellName);
foreach (var per in cell.Temporary)
{
PlacedObject placed = per as PlacedObject;
if (placed != null)
{
KeyValuePair<string, string> v;
if (lut.TryGetValue(placed.Base.FormKey, out v))
{
sw.Write(ASCIICodes.RS);
sw.Write("${per.GetType().Name}");
sw.Write(placed.Placement.Position.X);
sw.Write(placed.Placement.Position.Y);
sw.Write(placed.Placement.Position.Z);
sw.Write(placed.Placement.Rotation.X);
sw.Write(placed.Placement.Rotation.Y);
sw.Write(placed.Placement.Rotation.Z);
var bbox = ExtractBounds(placed, v.Key, cache);
sw.Write(bbox.Key.X);
sw.Write(bbox.Key.Y);
sw.Write(bbox.Key.Z);
sw.Write(bbox.Value.X);
sw.Write(bbox.Value.Y);
sw.Write(bbox.Value.Z);
}
}
}
}
}
}
}
System.Console.WriteLine($"----- FINISHED -----");
sw.Close();
fs.Close();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment