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 "); 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() which routes to the right database internally. var sections = new (string Section, (string Name, Func Count)[] Rows)[] { ("visual — geometry", new (string, Func)[] { ("GfxObj", () => dats.GetAllIdsOfType().Count()), ("GfxObjDegradeInfo", () => dats.GetAllIdsOfType().Count()), ("Setup", () => dats.GetAllIdsOfType().Count()), ("Scene", () => dats.GetAllIdsOfType().Count()), ("Environment", () => dats.GetAllIdsOfType().Count()), }), ("visual — texturing / materials", new (string, Func)[] { ("Surface", () => dats.GetAllIdsOfType().Count()), ("SurfaceTexture", () => dats.GetAllIdsOfType().Count()), ("RenderSurface", () => dats.GetAllIdsOfType().Count()), ("RenderTexture", () => dats.GetAllIdsOfType().Count()), ("RenderMaterial", () => dats.GetAllIdsOfType().Count()), ("MaterialInstance", () => dats.GetAllIdsOfType().Count()), ("MaterialModifier", () => dats.GetAllIdsOfType().Count()), ("Palette", () => dats.GetAllIdsOfType().Count()), ("PalSet", () => dats.GetAllIdsOfType().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() only // handles range-based types, so we walk the cell b-tree once manually. ("terrain / cells", CountCellByLow16(dats)), ("animation", new (string, Func)[] { ("Animation", () => dats.GetAllIdsOfType().Count()), ("MotionTable", () => dats.GetAllIdsOfType().Count()), }), ("particles & physics", new (string, Func)[] { ("ParticleEmitter", () => dats.GetAllIdsOfType().Count()), ("PhysicsScript", () => dats.GetAllIdsOfType().Count()), ("PhysicsScriptTable", () => dats.GetAllIdsOfType().Count()), }), ("audio", new (string, Func)[] { ("SoundTable", () => dats.GetAllIdsOfType().Count()), ("Wave", () => dats.GetAllIdsOfType().Count()), }), ("game data tables", new (string, Func)[] { ("SpellTable", () => dats.GetAllIdsOfType().Count()), ("SpellComponentTable", () => dats.GetAllIdsOfType().Count()), ("SkillTable", () => dats.GetAllIdsOfType().Count()), ("CombatTable", () => dats.GetAllIdsOfType().Count()), ("VitalTable", () => dats.GetAllIdsOfType().Count()), ("ExperienceTable", () => dats.GetAllIdsOfType().Count()), ("ClothingTable", () => dats.GetAllIdsOfType().Count()), ("CharGen", () => dats.GetAllIdsOfType().Count()), ("ChatPoseTable", () => dats.GetAllIdsOfType().Count()), ("ContractTable", () => dats.GetAllIdsOfType().Count()), }), ("ui / strings", new (string, Func)[] { ("Font", () => dats.GetAllIdsOfType().Count()), ("StringTable", () => dats.GetAllIdsOfType().Count()), ("LanguageString", () => dats.GetAllIdsOfType().Count()), ("LanguageInfo", () => dats.GetAllIdsOfType().Count()), ("LayoutDesc", () => dats.GetAllIdsOfType().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 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)[] { ("LandBlock", () => landBlocks), ("LandBlockInfo", () => landBlockInfos), ("EnvCell", () => envCells), ("Region", () => dats.GetAllIdsOfType().Count()), }; }