chore: phase 0 — skeleton + dat asset inventory
Brand-new solution targeting .NET 10, using Chorizite.DatReaderWriter 2.1.4 to walk a retail AC dat directory and print how many of each asset type live in client_portal / client_cell_1 / client_highres / client_local_English. Opens the four dats in ~16 ms and counts 887,381 indexed assets across 40+ tracked DBObj types. Cell-database terrain (LandBlock, LandBlockInfo, EnvCell) uses mask-based IDs that DatReaderWriter 2.1.4's GetAllIdsOfType<T> does not support; worked around with a manual b-tree walk in CountCellByLow16. Sanity check: LandBlock count is 65,025 = 255 x 255, exactly the AC world grid. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
020ec2a35d
5 changed files with 227 additions and 0 deletions
14
src/AcDream.Cli/AcDream.Cli.csproj
Normal file
14
src/AcDream.Cli/AcDream.Cli.csproj
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Chorizite.DatReaderWriter" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
162
src/AcDream.Cli/Program.cs
Normal file
162
src/AcDream.Cli/Program.cs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
using System.Diagnostics;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Options;
|
||||
using Env = System.Environment;
|
||||
|
||||
// Phase 0: open the four AC dat files and print how many of each asset type live in them.
|
||||
// This proves DatReaderWriter works on our retail dats and gives us a baseline inventory
|
||||
// to compare against what a future renderer needs.
|
||||
|
||||
string? datDir = args.FirstOrDefault() ?? Env.GetEnvironmentVariable("ACDREAM_DAT_DIR");
|
||||
if (string.IsNullOrWhiteSpace(datDir))
|
||||
{
|
||||
Console.Error.WriteLine("usage: AcDream.Cli <dat-directory>");
|
||||
Console.Error.WriteLine(" or: set ACDREAM_DAT_DIR and run with no args");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(datDir))
|
||||
{
|
||||
Console.Error.WriteLine($"error: directory not found: {datDir}");
|
||||
return 2;
|
||||
}
|
||||
|
||||
string[] required = ["client_portal.dat", "client_cell_1.dat", "client_highres.dat", "client_local_English.dat"];
|
||||
var missing = required.Where(f => !File.Exists(Path.Combine(datDir, f))).ToArray();
|
||||
if (missing.Length > 0)
|
||||
{
|
||||
Console.Error.WriteLine($"error: missing dat files in {datDir}:");
|
||||
foreach (var f in missing) Console.Error.WriteLine($" - {f}");
|
||||
return 2;
|
||||
}
|
||||
|
||||
Console.WriteLine($"acdream asset dump");
|
||||
Console.WriteLine($"dat dir: {datDir}");
|
||||
Console.WriteLine();
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
sw.Stop();
|
||||
Console.WriteLine($"opened 4 dats in {sw.ElapsedMilliseconds} ms");
|
||||
Console.WriteLine();
|
||||
|
||||
// File sizes, just so we can see they're real
|
||||
foreach (var f in required)
|
||||
{
|
||||
var path = Path.Combine(datDir, f);
|
||||
var mb = new FileInfo(path).Length / 1024.0 / 1024.0;
|
||||
Console.WriteLine($" {f,-30} {mb,8:F1} MB");
|
||||
}
|
||||
Console.WriteLine();
|
||||
|
||||
// Count assets by type. Grouped by what matters for a renderer / client.
|
||||
// Uses DatCollection.GetAllIdsOfType<T>() which routes to the right database internally.
|
||||
var sections = new (string Section, (string Name, Func<int> Count)[] Rows)[]
|
||||
{
|
||||
("visual — geometry", new (string, Func<int>)[]
|
||||
{
|
||||
("GfxObj", () => dats.GetAllIdsOfType<GfxObj>().Count()),
|
||||
("GfxObjDegradeInfo", () => dats.GetAllIdsOfType<GfxObjDegradeInfo>().Count()),
|
||||
("Setup", () => dats.GetAllIdsOfType<Setup>().Count()),
|
||||
("Scene", () => dats.GetAllIdsOfType<Scene>().Count()),
|
||||
("Environment", () => dats.GetAllIdsOfType<DatReaderWriter.DBObjs.Environment>().Count()),
|
||||
}),
|
||||
("visual — texturing / materials", new (string, Func<int>)[]
|
||||
{
|
||||
("Surface", () => dats.GetAllIdsOfType<Surface>().Count()),
|
||||
("SurfaceTexture", () => dats.GetAllIdsOfType<SurfaceTexture>().Count()),
|
||||
("RenderSurface", () => dats.GetAllIdsOfType<RenderSurface>().Count()),
|
||||
("RenderTexture", () => dats.GetAllIdsOfType<RenderTexture>().Count()),
|
||||
("RenderMaterial", () => dats.GetAllIdsOfType<RenderMaterial>().Count()),
|
||||
("MaterialInstance", () => dats.GetAllIdsOfType<MaterialInstance>().Count()),
|
||||
("MaterialModifier", () => dats.GetAllIdsOfType<MaterialModifier>().Count()),
|
||||
("Palette", () => dats.GetAllIdsOfType<Palette>().Count()),
|
||||
("PalSet", () => dats.GetAllIdsOfType<PalSet>().Count()),
|
||||
}),
|
||||
// Note: Cell dat uses mask-based IDs for LandBlock/LandBlockInfo/EnvCell — the low
|
||||
// 16 bits distinguish the type. DatReaderWriter 2.1.4's GetAllIdsOfType<T>() only
|
||||
// handles range-based types, so we walk the cell b-tree once manually.
|
||||
("terrain / cells", CountCellByLow16(dats)),
|
||||
("animation", new (string, Func<int>)[]
|
||||
{
|
||||
("Animation", () => dats.GetAllIdsOfType<Animation>().Count()),
|
||||
("MotionTable", () => dats.GetAllIdsOfType<MotionTable>().Count()),
|
||||
}),
|
||||
("particles & physics", new (string, Func<int>)[]
|
||||
{
|
||||
("ParticleEmitter", () => dats.GetAllIdsOfType<ParticleEmitter>().Count()),
|
||||
("PhysicsScript", () => dats.GetAllIdsOfType<PhysicsScript>().Count()),
|
||||
("PhysicsScriptTable", () => dats.GetAllIdsOfType<PhysicsScriptTable>().Count()),
|
||||
}),
|
||||
("audio", new (string, Func<int>)[]
|
||||
{
|
||||
("SoundTable", () => dats.GetAllIdsOfType<SoundTable>().Count()),
|
||||
("Wave", () => dats.GetAllIdsOfType<Wave>().Count()),
|
||||
}),
|
||||
("game data tables", new (string, Func<int>)[]
|
||||
{
|
||||
("SpellTable", () => dats.GetAllIdsOfType<SpellTable>().Count()),
|
||||
("SpellComponentTable", () => dats.GetAllIdsOfType<SpellComponentTable>().Count()),
|
||||
("SkillTable", () => dats.GetAllIdsOfType<SkillTable>().Count()),
|
||||
("CombatTable", () => dats.GetAllIdsOfType<CombatTable>().Count()),
|
||||
("VitalTable", () => dats.GetAllIdsOfType<VitalTable>().Count()),
|
||||
("ExperienceTable", () => dats.GetAllIdsOfType<ExperienceTable>().Count()),
|
||||
("ClothingTable", () => dats.GetAllIdsOfType<ClothingTable>().Count()),
|
||||
("CharGen", () => dats.GetAllIdsOfType<CharGen>().Count()),
|
||||
("ChatPoseTable", () => dats.GetAllIdsOfType<ChatPoseTable>().Count()),
|
||||
("ContractTable", () => dats.GetAllIdsOfType<ContractTable>().Count()),
|
||||
}),
|
||||
("ui / strings", new (string, Func<int>)[]
|
||||
{
|
||||
("Font", () => dats.GetAllIdsOfType<Font>().Count()),
|
||||
("StringTable", () => dats.GetAllIdsOfType<StringTable>().Count()),
|
||||
("LanguageString", () => dats.GetAllIdsOfType<LanguageString>().Count()),
|
||||
("LanguageInfo", () => dats.GetAllIdsOfType<LanguageInfo>().Count()),
|
||||
("LayoutDesc", () => dats.GetAllIdsOfType<LayoutDesc>().Count()),
|
||||
}),
|
||||
};
|
||||
|
||||
long grandTotal = 0;
|
||||
foreach (var (section, rows) in sections)
|
||||
{
|
||||
Console.WriteLine($"[{section}]");
|
||||
long sectionTotal = 0;
|
||||
foreach (var (name, count) in rows)
|
||||
{
|
||||
var n = count();
|
||||
sectionTotal += n;
|
||||
Console.WriteLine($" {name,-22} {n,10:N0}");
|
||||
}
|
||||
Console.WriteLine($" {"subtotal",-22} {sectionTotal,10:N0}");
|
||||
Console.WriteLine();
|
||||
grandTotal += sectionTotal;
|
||||
}
|
||||
|
||||
Console.WriteLine($"grand total: {grandTotal:N0} indexed assets across tracked types");
|
||||
return 0;
|
||||
|
||||
static (string Name, Func<int> Count)[] CountCellByLow16(DatCollection dats)
|
||||
{
|
||||
// Walk the cell b-tree once and bucket by low 16 bits:
|
||||
// 0xFFFF → LandBlock (terrain heightmap)
|
||||
// 0xFFFE → LandBlockInfo (static objects on the landblock)
|
||||
// other → EnvCell (indoor dungeon cell)
|
||||
int landBlocks = 0, landBlockInfos = 0, envCells = 0, other = 0;
|
||||
foreach (var file in dats.Cell.Tree)
|
||||
{
|
||||
var low = file.Id & 0xFFFFu;
|
||||
if (low == 0xFFFFu) landBlocks++;
|
||||
else if (low == 0xFFFEu) landBlockInfos++;
|
||||
else if (file.Id != 0) envCells++;
|
||||
else other++;
|
||||
}
|
||||
return new (string, Func<int>)[]
|
||||
{
|
||||
("LandBlock", () => landBlocks),
|
||||
("LandBlockInfo", () => landBlockInfos),
|
||||
("EnvCell", () => envCells),
|
||||
("Region", () => dats.GetAllIdsOfType<Region>().Count()),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue