Use universal packed resource resolution
This commit is contained in:
@@ -43,7 +43,7 @@ if (args[0] == "audio")
|
||||
var sceneName = args[1];
|
||||
var sceneKey = Path.GetFileNameWithoutExtension(sceneName).ToUpperInvariant();
|
||||
var res = ResourceMap.Load();
|
||||
var host = new AudioTraceHost(res, sceneKey);
|
||||
var host = new AudioTraceHost(res);
|
||||
var vm = new VirtualMachine(ScriptByName(sceneName), table, host);
|
||||
// optional: seed globals, e.g. `audio SC0000.BIN 0xa57=1` to set Lily's form-A flag
|
||||
foreach (var s in args.Skip(2))
|
||||
@@ -70,7 +70,7 @@ if (args[0] == "gfx")
|
||||
var sceneName = args.First(a => a.EndsWith(".BIN", StringComparison.OrdinalIgnoreCase));
|
||||
var sceneKey = Path.GetFileNameWithoutExtension(sceneName).ToUpperInvariant();
|
||||
var res = ResourceMap.Load();
|
||||
var host = new GfxTraceHost(res, sceneKey);
|
||||
var host = new GfxTraceHost(res);
|
||||
var session = new GameSession();
|
||||
foreach (var s in args.Where(a => a.Contains('=')))
|
||||
{
|
||||
@@ -100,7 +100,7 @@ if (args[0] == "gfx")
|
||||
var vis = vm.Gfx.SnapshotVisibleObjects();
|
||||
Console.WriteLine($" visible objects ({vis.Count}, ascending-handle = z-order):");
|
||||
foreach (var v in vis)
|
||||
Console.WriteLine($" h=0x{v.Handle:x} surf=0x{v.SurfaceResId:x} ({res.Resolve(sceneKey, v.SurfaceResId)?.Name ?? "?"}) src=({v.SrcX},{v.SrcY} {v.W}x{v.H}) dst=({v.DstX},{v.DstY})");
|
||||
Console.WriteLine($" h=0x{v.Handle:x} surf=0x{v.SurfaceResId:x} ({res.ResolveTexture(v.SurfaceResId)?.Name ?? "?"}) src=({v.SrcX},{v.SrcY} {v.W}x{v.H}) dst=({v.DstX},{v.DstY})");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -345,17 +345,16 @@ sealed class TraceSetup : IDisposable
|
||||
sealed class AudioTraceHost : IHost
|
||||
{
|
||||
private readonly ResourceMap _res;
|
||||
private readonly string _scene;
|
||||
public List<(string Kind, long Id, string Resolved)> Events { get; } = new();
|
||||
public AudioTraceHost(ResourceMap res, string scene) { _res = res; _scene = scene; }
|
||||
public AudioTraceHost(ResourceMap res) { _res = res; }
|
||||
public void PlayBgm(long id) // BGM: direct name, not the manifest
|
||||
{
|
||||
var entry = _res.ResolveBgm(id);
|
||||
Events.Add(("play-bgm", id, entry != null ? $"{entry.Archive} {entry.Name}" : $"BGM{id:D3}.OGG <missing>"));
|
||||
}
|
||||
public void PlayVoice(long id) // voice: SC section, then frontend raw id
|
||||
public void PlayVoice(long id) // voice: universal packed catalog id
|
||||
{
|
||||
var e = _res.ResolveVoice(_scene, id);
|
||||
var e = _res.ResolveVoice(id);
|
||||
Events.Add(("play-voice", id, e == null ? "<unresolved>" : $"{e.Archive} {e.Name}"));
|
||||
}
|
||||
public void ShowText(int offset, string text) { }
|
||||
@@ -371,12 +370,11 @@ sealed class AudioTraceHost : IHost
|
||||
sealed class GfxTraceHost : IHost
|
||||
{
|
||||
private readonly ResourceMap _res;
|
||||
private readonly string _scene;
|
||||
private readonly Dictionary<int, string?> _slotAsset = new(); // slot -> resolved AGF name (or null)
|
||||
// slot -> dimensions of the currently allocated surface. Slot 0 starts as the engine's primary surface.
|
||||
private readonly Dictionary<int, (int W, int H)> _slotDims = new() { { 0, (800, 600) } };
|
||||
public List<string> Events { get; } = new();
|
||||
public GfxTraceHost(ResourceMap res, string scene) { _res = res; _scene = scene; }
|
||||
public GfxTraceHost(ResourceMap res) { _res = res; }
|
||||
|
||||
public (int Width, int Height) GetTextureSize(int slot)
|
||||
{
|
||||
@@ -387,7 +385,7 @@ sealed class GfxTraceHost : IHost
|
||||
|
||||
public void SetTexture(long resId, int slot)
|
||||
{
|
||||
var e = _res.ResolveTexture(_scene, resId);
|
||||
var e = _res.ResolveTexture(resId);
|
||||
RgbaImage? image = e != null ? _res.DecodeTexture(e) : null;
|
||||
_slotAsset[slot] = e?.Name;
|
||||
_slotDims[slot] = image != null ? (image.Width, image.Height) : (0, 0);
|
||||
|
||||
@@ -75,7 +75,7 @@ public class AgfDecoderTests
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
var store = new Sys4AssetStore(catalog, Paths.GameDir, Paths.GameDir);
|
||||
var resources = new ResourceMap(catalog, store);
|
||||
Assert.Equal("SO001.AGF", resources.ResolveTexture("SC0000", 0x337e)?.Name);
|
||||
Assert.Equal("SO001.AGF", resources.ResolveTexture(0x337e)?.Name);
|
||||
var image = AgfDecoder.Decode(store, catalog.ResolveRaw(0x337e)!);
|
||||
Assert.Equal((800, 300), (image.Width, image.Height));
|
||||
Assert.Contains(image.Pixels.Where((_, i) => (i & 3) == 3), a => a is > 0 and < 255);
|
||||
@@ -89,7 +89,7 @@ public class AgfDecoderTests
|
||||
public void InstalledFieldMapSheetsResolveAndDecodeByRawCatalogIndex(int rawId, string name)
|
||||
{
|
||||
var resources = ResourceMap.Load();
|
||||
var asset = resources.ResolveRawTexture(rawId);
|
||||
var asset = resources.ResolveTexture(rawId);
|
||||
Assert.NotNull(asset);
|
||||
Assert.Equal(name, asset.Name);
|
||||
var image = resources.DecodeTexture(asset);
|
||||
|
||||
@@ -15,7 +15,6 @@ public class CallScriptTests
|
||||
{
|
||||
public virtual void EnterScriptContext(string scriptName) { }
|
||||
public virtual void ExitScriptContext() { }
|
||||
public virtual long ResolveTextureResourceId(long resourceId) => resourceId;
|
||||
public void ShowText(int o, string t) { }
|
||||
public void WaitForInput() { }
|
||||
public void Sleep(long duration) { }
|
||||
@@ -52,9 +51,6 @@ public class CallScriptTests
|
||||
Events.Add($"exit:{_contexts.Pop()}");
|
||||
}
|
||||
|
||||
public override long ResolveTextureResourceId(long resourceId)
|
||||
=> resourceId + (_contexts.Peek() == "CALLEE" ? 700 : 70);
|
||||
|
||||
public override void SetTexture(long resourceId, int slot) => Textures.Add((resourceId, slot));
|
||||
}
|
||||
|
||||
@@ -133,7 +129,7 @@ public class CallScriptTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptLocalTextureIdsFollowTheActiveNestedFrame()
|
||||
public void PackedTextureIdsRemainUnchangedAcrossNestedFrames()
|
||||
{
|
||||
var t = Table();
|
||||
var callee = Asm(t, "CALLEE",
|
||||
@@ -149,7 +145,7 @@ public class CallScriptTests
|
||||
|
||||
vm.Run();
|
||||
|
||||
Assert.Equal(new[] { (77L, 1), (707L, 2), (77L, 3) }, host.Textures);
|
||||
Assert.Equal(new[] { (7L, 1), (7L, 2), (7L, 3) }, host.Textures);
|
||||
Assert.Equal(new[] { "enter:CALLER", "enter:CALLEE", "exit:CALLEE", "exit:CALLER" }, host.Events);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ public class GfxCommandBufferTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RawTextureLoadBypassesSceneResourceNormalizationAndFeedsRetainedDraws()
|
||||
public void ModeOneTextureLoadPreservesPackedIdAndFeedsRetainedDraws()
|
||||
{
|
||||
var t = T();
|
||||
var scene = ScriptAssembler.Assemble(t, "RAW-GFX", new List<(int, Operand[])>
|
||||
@@ -135,7 +135,7 @@ public class GfxCommandBufferTests
|
||||
(0x1fb, new[] { I(0x100), I(0x3e), I(0), I(0), I(100), I(100), I(20), I(30) }),
|
||||
Exit(),
|
||||
}, System.Array.Empty<string>());
|
||||
var host = new RecordingHost { TextureResourceIdOffset = 0x1000 };
|
||||
var host = new RecordingHost();
|
||||
var vm = new VirtualMachine(scene, t, host);
|
||||
|
||||
vm.Run();
|
||||
|
||||
@@ -342,7 +342,7 @@ public class HistoryInteractionOpsTests
|
||||
Assert.Equal(new[] { (expectedVoice, checked((int)expectedVariant)) }, host.VoiceRequests);
|
||||
|
||||
var resources = ResourceMap.Load();
|
||||
var voice = Assert.IsType<AssetEntry>(resources.Resolve("SC0000", expectedVoice));
|
||||
var voice = Assert.IsType<AssetEntry>(resources.ResolveVoice(expectedVoice));
|
||||
Assert.Equal("MAN999.OGG", voice.Name);
|
||||
Assert.NotEmpty(resources.ReadAudio(voice).Bytes);
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ public class MovieOpcodeTests
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
var resources = new ResourceMap(catalog, new Sys4AssetStore(catalog, Paths.GameDir));
|
||||
var entry = resources.Resolve("SC0000", 0x33);
|
||||
var entry = resources.ResolveMovie(0x33);
|
||||
|
||||
Assert.Equal("CHAPTER.AGF", entry?.Name);
|
||||
var movie = resources.ReadMovie(entry!);
|
||||
@@ -207,11 +207,11 @@ public class MovieOpcodeTests
|
||||
[Theory]
|
||||
[InlineData(0x335f, "LOGO.AGF")]
|
||||
[InlineData(0x3364, "OP.AGF")]
|
||||
public void ModalMoviePayloadResolvesFromUniversalRawCatalog(int rawId, string expectedName)
|
||||
public void ModalMoviePayloadResolvesFromUniversalPackedCatalog(int resourceId, string expectedName)
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
var resources = new ResourceMap(catalog, new Sys4AssetStore(catalog, Paths.GameDir));
|
||||
var entry = resources.ResolveRawMovie(rawId);
|
||||
var entry = resources.ResolveMovie(resourceId);
|
||||
|
||||
Assert.Equal(expectedName, entry?.Name);
|
||||
var movie = resources.ReadMovie(entry!);
|
||||
@@ -224,7 +224,7 @@ public class MovieOpcodeTests
|
||||
if (!OperatingSystem.IsWindows()) return;
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
var resources = new ResourceMap(catalog, new Sys4AssetStore(catalog, Paths.GameDir));
|
||||
var entry = resources.Resolve("SC0000", 0x33)!;
|
||||
var entry = resources.ResolveMovie(0x33)!;
|
||||
using var decoder = new DirectShowMovieDecoder(resources.ReadMovie(entry));
|
||||
|
||||
Assert.True(decoder.StopTimeMs > 0, "DirectShow should expose a positive IMediaPosition stop time");
|
||||
|
||||
@@ -22,6 +22,7 @@ public class Sys4AssetStoreTests
|
||||
Assert.All(append.Files, entry =>
|
||||
{
|
||||
Assert.Equal(1, entry.PackId);
|
||||
Assert.Equal(0x01000000 | entry.RawIndex, entry.PackedId);
|
||||
Assert.StartsWith("$1$", entry.Name);
|
||||
Assert.Equal("APPEND01.ALF", entry.Archive);
|
||||
});
|
||||
@@ -178,14 +179,14 @@ public class Sys4AssetStoreTests
|
||||
Assert.Equal("BGM005.OGG", bgm?.Name);
|
||||
AssertOgg(resources.ReadAudio(bgm!));
|
||||
|
||||
var voice = resources.ResolveVoice("SC0000", 0x24);
|
||||
var voice = resources.ResolveVoice(0x24);
|
||||
Assert.Equal("MAN999.OGG", voice?.Name);
|
||||
AssertOgg(resources.ReadAudio(voice!));
|
||||
|
||||
var roomVoice = resources.ResolveVoice("ROOM", 0x3365);
|
||||
var roomVoice = resources.ResolveVoice(0x3365);
|
||||
Assert.Equal("EUA0016.OGG", roomVoice?.Name);
|
||||
AssertOgg(resources.ReadAudio(roomVoice!));
|
||||
Assert.Null(resources.ResolveVoice("ROOM", 0x337e)); // SO001.AGF is not voice audio.
|
||||
Assert.Null(resources.ResolveVoice(0x337e)); // SO001.AGF is not voice audio.
|
||||
|
||||
var sfx = resources.ResolveSoundEffect(0x28);
|
||||
Assert.Equal("E0808.WAV", sfx?.Name);
|
||||
@@ -194,7 +195,6 @@ public class Sys4AssetStoreTests
|
||||
Assert.Equal("RIFF", Encoding.ASCII.GetString(wav.Bytes, 0, 4));
|
||||
Assert.Equal("WAVE", Encoding.ASCII.GetString(wav.Bytes, 8, 4));
|
||||
|
||||
Assert.Null(resources.Resolve("TITLE", 0x2aea));
|
||||
Assert.Equal("SE020.WAV", resources.ResolveSoundEffect(0x2aea)?.Name);
|
||||
Assert.Equal("SE013.WAV", resources.ResolveSoundEffect(0x2aeb)?.Name);
|
||||
Assert.Equal("SE015.WAV", resources.ResolveSoundEffect(0x3321)?.Name);
|
||||
@@ -204,6 +204,33 @@ public class Sys4AssetStoreTests
|
||||
Assert.Throws<InvalidDataException>(() => resources.ReadAudio(catalog.ResolveName("SO001.AGF")!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sc0010LowOperandsAreAlreadyUniversalPackedIds()
|
||||
{
|
||||
var resources = ResourceMap.Load();
|
||||
|
||||
Assert.Equal("SO013A.AGF", resources.ResolveTexture(0x21)?.Name);
|
||||
Assert.Equal("LILA1414.OGG", resources.ResolveVoice(0x120)?.Name);
|
||||
Assert.Equal("LILB0053.OGG", resources.ResolveVoice(0x121)?.Name);
|
||||
Assert.Equal("LILC0054.OGG", resources.ResolveVoice(0x122)?.Name);
|
||||
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
Assert.Equal("COL0023.OGG", catalog.ResolveRaw(0x11e + 0x21)?.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TypedResourceResolutionPreservesAppendPackSelector()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
var resources = new ResourceMap(catalog);
|
||||
var append = Assert.Single(catalog.AppendPacks).Value;
|
||||
var texture = Assert.Single(append.Files.Where(entry =>
|
||||
entry.Name.EndsWith(".AGF", StringComparison.OrdinalIgnoreCase)).Take(1));
|
||||
|
||||
Assert.Same(texture, resources.ResolveTexture(texture.PackedId));
|
||||
Assert.NotEqual(texture, resources.ResolveTexture(texture.RawIndex));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllInstalledLooseScriptOverridesShadowArchiveCopies()
|
||||
{
|
||||
|
||||
@@ -55,9 +55,7 @@ internal class RecordingHost : IHost
|
||||
public readonly List<bool> AdvPagePresentationSuspended = new();
|
||||
public int CursorClearCount;
|
||||
public int SceneContextResets;
|
||||
public long TextureResourceIdOffset;
|
||||
public void ReportWarning(string message) => Warnings.Add(message);
|
||||
public long ResolveTextureResourceId(long resourceId) => resourceId + TextureResourceIdOffset;
|
||||
public void ShowText(int offset, string text) => Lines.Add((offset, text));
|
||||
public void SetAdvTextCursor(int layoutSlot, int x, int y) => TextCursors.Add((layoutSlot, x, y));
|
||||
public void DrawStringToSurface(int surfaceSlot, int x, int y, string text)
|
||||
@@ -162,8 +160,8 @@ internal class RecordingHost : IHost
|
||||
return MovieStopTimeMs;
|
||||
}
|
||||
public bool IsMovieSurfaceActive(int surfaceSlot) => ActiveMovieSurfaces.Contains(surfaceSlot);
|
||||
public void PlayModalMovieToSurface(long rawResourceId, int surfaceSlot, long movieFlags)
|
||||
=> ModalMovies.Add((rawResourceId, surfaceSlot, movieFlags));
|
||||
public void PlayModalMovieToSurface(long resourceId, int surfaceSlot, long movieFlags)
|
||||
=> ModalMovies.Add((resourceId, surfaceSlot, movieFlags));
|
||||
}
|
||||
|
||||
internal sealed class MapProvider : IScriptProvider
|
||||
|
||||
@@ -21,11 +21,9 @@ public interface IHost
|
||||
{
|
||||
/// <summary>Report a recoverable runtime discrepancy while allowing script execution to continue.</summary>
|
||||
void ReportWarning(string message) => System.Console.Error.WriteLine(message);
|
||||
// Script-local resource ids resolve against the currently executing frame's SYS4INI section.
|
||||
// Interactive hosts track this stack; headless hosts may keep the no-op/default identity behavior.
|
||||
// Script context is retained for diagnostics/page location; resource operands are universal packed ids.
|
||||
void EnterScriptContext(string scriptName) { }
|
||||
void ExitScriptContext() { }
|
||||
long ResolveTextureResourceId(long resourceId) => resourceId;
|
||||
void ShowText(int offset, string text);
|
||||
// Native ADV text subsystem: op 0x7a updates the selected layout's last 20-byte cursor record;
|
||||
// op 0x204 rasterizes a string into a numbered surface before 0x1fb binds that surface.
|
||||
@@ -125,8 +123,8 @@ public interface IHost
|
||||
/// immediately after 0x236 returns.</returns>
|
||||
long? PlayMovieToSurface(long resourceId, int surfaceSlot, long movieFlags, long syncMask) => null;
|
||||
bool IsMovieSurfaceActive(int surfaceSlot) => false;
|
||||
// Native op 0x20f uses a universal raw-catalog id and parks script execution until the movie
|
||||
// Native op 0x20f uses a universal packed id and parks script execution until the movie
|
||||
// reaches EOF or the player cancels it. The decoder remains asynchronous; the interactive host
|
||||
// owns the modal wait so its render loop can continue publishing frames.
|
||||
void PlayModalMovieToSurface(long rawResourceId, int surfaceSlot, long movieFlags) { }
|
||||
void PlayModalMovieToSurface(long resourceId, int surfaceSlot, long movieFlags) { }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
namespace Age.Engine.Sys4;
|
||||
|
||||
/// <summary>
|
||||
/// Compatibility facade over the runtime SYS4 catalog. Scene-local graphics/voice/movie ids resolve
|
||||
/// through the executing script's manifest; BGM uses direct names; SFX/cursors use universal packed ids.
|
||||
/// Typed facade over the runtime SYS4 catalog. Graphics, voice, movie, SFX, and cursor operands use
|
||||
/// universal packed ids; BGM uses direct names.
|
||||
/// See docs/asset-resolution-re.md.
|
||||
/// </summary>
|
||||
public sealed class ResourceMap
|
||||
@@ -18,43 +18,26 @@ public sealed class ResourceMap
|
||||
|
||||
public static ResourceMap Load() => new(Sys4AssetCatalog.Load(Paths.Sys4Ini));
|
||||
|
||||
/// <summary>Resolve a scene-local resId to its asset, or null if out of range / unknown scene.</summary>
|
||||
public AssetEntry? Resolve(string scene, long resId)
|
||||
/// <summary>Resolve a universal packed SYS4INI/AAI id to an AGF texture record.</summary>
|
||||
public AssetEntry? ResolveTexture(long resourceId)
|
||||
{
|
||||
return _catalog.ResolveScene(scene, resId);
|
||||
}
|
||||
|
||||
/// <summary>Resolve graphics normally through the scene manifest, with the universal raw-id
|
||||
/// fallback used by SYSTEM4-owned assets such as SO001.</summary>
|
||||
public AssetEntry? ResolveTexture(string scene, long resId)
|
||||
{
|
||||
var entry = _catalog.ResolveScene(scene, resId) ?? _catalog.ResolveRaw(resId);
|
||||
var entry = _catalog.ResolvePacked(resourceId);
|
||||
return entry is { IsPlaceholder: false } &&
|
||||
entry.Name.EndsWith(".AGF", StringComparison.OrdinalIgnoreCase) ? entry : null;
|
||||
}
|
||||
|
||||
/// <summary>Resolve voice audio through the active SC section when one exists, then through the
|
||||
/// universal raw catalog used by non-SC frontend scripts such as ROOM.</summary>
|
||||
public AssetEntry? ResolveVoice(string scene, long resId)
|
||||
/// <summary>Resolve a universal packed SYS4INI/AAI id to a voice audio record.</summary>
|
||||
public AssetEntry? ResolveVoice(long resourceId)
|
||||
{
|
||||
var entry = _catalog.ResolveScene(scene, resId) ?? _catalog.ResolveRaw(resId);
|
||||
var entry = _catalog.ResolvePacked(resourceId);
|
||||
return entry is { IsPlaceholder: false } && IsAudio(entry) ? entry : null;
|
||||
}
|
||||
|
||||
/// <summary>Resolve an already-normalized packed catalog id without applying a scene section base.</summary>
|
||||
public AssetEntry? ResolveRawTexture(long rawId)
|
||||
/// <summary>Resolve a universal packed SYS4INI/AAI id to an AGF-named movie record. ReadMovie
|
||||
/// validates the MPEG signature because still images use the same extension.</summary>
|
||||
public AssetEntry? ResolveMovie(long resourceId)
|
||||
{
|
||||
var entry = _catalog.ResolvePacked(rawId);
|
||||
return entry is { IsPlaceholder: false } &&
|
||||
entry.Name.EndsWith(".AGF", StringComparison.OrdinalIgnoreCase) ? entry : null;
|
||||
}
|
||||
|
||||
/// <summary>Resolve op 0x20f's universal raw-catalog movie id without applying the executing
|
||||
/// script's manifest base. AGE stores these MPEG program streams under .AGF names; ReadMovie
|
||||
/// validates the payload signature before playback.</summary>
|
||||
public AssetEntry? ResolveRawMovie(long rawId)
|
||||
{
|
||||
var entry = _catalog.ResolveRaw(rawId);
|
||||
var entry = _catalog.ResolvePacked(resourceId);
|
||||
return entry is { IsPlaceholder: false } &&
|
||||
entry.Name.EndsWith(".AGF", StringComparison.OrdinalIgnoreCase) ? entry : null;
|
||||
}
|
||||
@@ -81,7 +64,7 @@ public sealed class ResourceMap
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a BGM id to its catalog entry. BGM is addressed by DIRECT LITERAL NAME (BGM{id:D3}.OGG), NOT the
|
||||
/// per-scene section manifest that voices/textures use. Confirmed by ear (play-bgm 5->BGM005, 8->BGM008)
|
||||
/// universal packed resource table used by voices/textures. Confirmed by ear (play-bgm 5->BGM005, 8->BGM008)
|
||||
/// and by the play-bgm 0x23->BGM035 case: BGM035 is a real standalone track (the BGM set skips 030-034),
|
||||
/// which the manifest mis-resolved to a graphics entry. See docs/asset-resolution-re.md.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,7 +14,11 @@ public sealed record AssetEntry(
|
||||
int ArchiveId = -1,
|
||||
int FileNumber = -1,
|
||||
bool IsPlaceholder = false,
|
||||
int PackId = 0);
|
||||
int PackId = 0)
|
||||
{
|
||||
/// <summary>The exact packed SYS4INI/AAI id AGE uses to address this record.</summary>
|
||||
public int PackedId => checked((PackId << 24) | RawIndex);
|
||||
}
|
||||
|
||||
/// <summary>A real catalog entry paired with the packed resource id AGE uses at runtime.</summary>
|
||||
public sealed record PackedAssetEntry(long PackedId, AssetEntry Asset);
|
||||
|
||||
@@ -1539,29 +1539,27 @@ public sealed class VirtualMachine
|
||||
_host.CreateTexture((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); return pc + 1;
|
||||
case "set-texture": // 0x1f9 (resId)(slot)(colorkey) — load a file into the slot's surface
|
||||
{
|
||||
long requestedResourceId = Read(a[0]);
|
||||
long resolvedResourceId = _host.ResolveTextureResourceId(requestedResourceId);
|
||||
long resourceId = Read(a[0]);
|
||||
if (_diagSetTexture) // AGE_DIAG_SETTEX: log the SLOT operand source (literal vs which global) — grey-BG slot dig
|
||||
System.Console.Error.WriteLine($"[settex] resId=0x{requestedResourceId:x}->0x{resolvedResourceId:x} slot={(int)Read(a[1])} " +
|
||||
System.Console.Error.WriteLine($"[settex] resId=0x{resourceId:x} slot={(int)Read(a[1])} " +
|
||||
$"slotOp=(type={a[1].Type} val=0x{a[1].Value:x}){(a[1].Type == 3 ? $" G[0x{a[1].Value:x}]" : "")}");
|
||||
_host.ReleaseSurface((int)Read(a[1]));
|
||||
long colorKey = a.Count > 2 ? Read(a[2]) : -1;
|
||||
Gfx.SetSurface((int)Read(a[1]), resolvedResourceId, colorKey);
|
||||
_host.SetTexture(resolvedResourceId, (int)Read(a[1]), colorKey);
|
||||
Gfx.SetSurface((int)Read(a[1]), resourceId, colorKey);
|
||||
_host.SetTexture(resourceId, (int)Read(a[1]), colorKey);
|
||||
return pc + 1; // host still tracks dims for get-texture-size
|
||||
}
|
||||
case "u00422EB0": // pre-reference compatibility
|
||||
case "load-raw-texture-surface": // 0x249 (raw catalog id)(slot)(colorkey)
|
||||
case "load-raw-texture-surface": // 0x249 (packed resource id)(slot)(colorkey)
|
||||
{
|
||||
// Native shares 0x1f9's release/load/colorkey path, but constructs its mode-1
|
||||
// surface subclass and receives an already-global SYS4INI catalog index. The
|
||||
// CPU compositor does not need the D3D subclass distinction; it does need the
|
||||
// resource id to bypass the executing script's scene-section normalization.
|
||||
long rawResourceId = Read(a[0]);
|
||||
// surface subclass. Both texture opcodes receive the same universal packed id;
|
||||
// the CPU compositor does not need the D3D subclass distinction.
|
||||
long resourceId = Read(a[0]);
|
||||
int surfaceSlot = (int)Read(a[1]);
|
||||
_host.ReleaseSurface(surfaceSlot);
|
||||
Gfx.SetSurface(surfaceSlot, rawResourceId, Read(a[2]));
|
||||
_host.SetTexture(rawResourceId, surfaceSlot, Read(a[2]));
|
||||
Gfx.SetSurface(surfaceSlot, resourceId, Read(a[2]));
|
||||
_host.SetTexture(resourceId, surfaceSlot, Read(a[2]));
|
||||
return pc + 1;
|
||||
}
|
||||
case "draw-texture": // 0x1fb (handle)(slot)(srcX)(srcY)(w)(h)(dstX)(dstY) — bind object -> surface + rect + pos
|
||||
@@ -1652,14 +1650,14 @@ public sealed class VirtualMachine
|
||||
case "get-initial-root-run": // 0x130 (out)
|
||||
Write(a[0], _initialRootRun ? 1 : 0);
|
||||
return pc + 1;
|
||||
case "play-modal-movie-to-surface": // 0x20f (raw resource)(surface)(movie flags)
|
||||
case "play-modal-movie-to-surface": // 0x20f (packed resource)(surface)(movie flags)
|
||||
{
|
||||
long rawResourceId = Read(a[0]);
|
||||
long resourceId = Read(a[0]);
|
||||
int surfaceSlot = (int)Read(a[1]);
|
||||
// The modal and scene-local paths share retained-surface composition. The host's
|
||||
// distinct entry point preserves 0x20f's raw-id resolver and blocking lifecycle.
|
||||
Gfx.SetSurface(surfaceSlot, rawResourceId, 0);
|
||||
_host.PlayModalMovieToSurface(rawResourceId, surfaceSlot, Read(a[2]));
|
||||
// Modal and non-modal paths share packed resolution and retained-surface composition.
|
||||
// The distinct host entry point owns only 0x20f's blocking lifecycle.
|
||||
Gfx.SetSurface(surfaceSlot, resourceId, 0);
|
||||
_host.PlayModalMovieToSurface(resourceId, surfaceSlot, Read(a[2]));
|
||||
return pc + 1;
|
||||
}
|
||||
case "u004221A0": // pre-reference compatibility
|
||||
|
||||
Reference in New Issue
Block a user