using System.Text.Json;
namespace Age.Engine.Sys4;
/// One SYS4INI asset entry.
public sealed record AssetEntry(string Name, string Archive, long Offset, long Size);
///
/// Static asset resolver. SYS4INI's file list is sectioned (one per scene: SCxxxx.BIN + its
/// cross-archive asset manifest); file_number is the index within a section. So a bytecode
/// resId resolves as files[section_base(scene) + resId] -- unified for graphics and audio.
/// See docs/asset-resolution-re.md. Built from build/asset-index.json + build/asset-sections.json.
///
public sealed class ResourceMap
{
private readonly IReadOnlyList _files;
private readonly IReadOnlyDictionary _sceneBase; // "SC0000" -> section base index
public ResourceMap(IReadOnlyList files, IReadOnlyDictionary sceneBase)
{
_files = files;
_sceneBase = sceneBase;
}
public static ResourceMap Load(string indexPath, string sectionsPath)
{
var files = new List();
using (var idx = JsonDocument.Parse(File.ReadAllText(indexPath)))
foreach (var f in idx.RootElement.GetProperty("files").EnumerateArray())
files.Add(new AssetEntry(
f.GetProperty("name").GetString()!,
f.GetProperty("archive").GetString()!,
f.GetProperty("offset").GetInt64(),
f.GetProperty("size").GetInt64()));
var sceneBase = new Dictionary(StringComparer.OrdinalIgnoreCase);
using (var sec = JsonDocument.Parse(File.ReadAllText(sectionsPath)))
foreach (var p in sec.RootElement.GetProperty("scene_base").EnumerateObject())
sceneBase[p.Name] = p.Value.GetInt32();
return new ResourceMap(files, sceneBase);
}
public static ResourceMap Load() => Load(Paths.AssetIndexJson, Paths.AssetSectionsJson);
/// Resolve a scene-local resId to its asset, or null if out of range / unknown scene.
public AssetEntry? Resolve(string scene, long resId)
{
var key = scene.EndsWith(".BIN", StringComparison.OrdinalIgnoreCase)
? scene[..^4] : scene;
if (!_sceneBase.TryGetValue(key, out var b)) return null;
long p = b + resId;
return p >= 0 && p < _files.Count ? _files[(int)p] : null;
}
/// Pre-converted BMP path for an AGF asset (see tools/convert_agf.py).
public static string? TexturePath(AssetEntry a)
{
if (!a.Name.EndsWith(".AGF", StringComparison.OrdinalIgnoreCase)) return null;
var bmp = Path.Combine(Paths.Textures, Path.GetFileNameWithoutExtension(a.Name) + ".BMP");
return File.Exists(bmp) ? bmp : null;
}
}