Wire asset resolution into a live Godot render. VM executes set-texture -> ResourceMap resolves (scene,resId) -> files[section_base+resId] across all archives -> pre-converted BMP -> composite behind the dialogue. The full-screen event-CG layer (EV052*) renders end-to-end from the executed bytecode. - Age.Engine/Sys4/ResourceMap.cs: Resolve + BMP TexturePath; Paths: asset JSONs - GodotAdvHost: create/set/draw-texture -> TextureRect in a _stage layer - IHost.DrawTexture + VM dispatch extended with dst x/y (draw-texture args 7/8) - project.godot 800x600; convert_agf.py all-archive + --scene batch - engine 8/8, C# --selftest still byte-matches vm0 trace (VM behaviour unchanged) Known limitations (next chunk = graphics geometry/blend subsystem): - sprites + BG* via the CG-load subroutine get garbage dst/size — native ops stubbed (0x208 get-texture-size + sprite position/anim chain) - AE* fades draw opaque/instant (no alpha); no chromakey - slot model approximates the game's immediate-mode blit-onto-slot-0 canvas - AGF pre-converted to BMP offline (runtime decoder deferred) See docs/phase-a-slice-plan.md (A2b section). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
72 lines
2.3 KiB
Python
72 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Convert AGF images to BMP via AGF2BMP2AGF.exe, into build/textures/.
|
|
|
|
py -3.11 -X utf8 tools/convert_agf.py EV052CA.AGF BG030A.AGF ... # named assets
|
|
py -3.11 -X utf8 tools/convert_agf.py --scene SC0000 # all .AGF in the scene's
|
|
# SYS4INI section manifest
|
|
|
|
Searches every extracted/DATA* dir (DATA1 holds BG/CS/CB/CA/AE/EM sprites, DATA2 the EV CGs).
|
|
"""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import paths
|
|
|
|
EXE = paths.EXTRACTED / "DATA1" / "AGF2BMP2AGF.exe" # tool lives in extracted/DATA1
|
|
SRC_DIRS = [paths.EXTRACTED / d for d in ("DATA1", "DATA2", "DATA3", "DATA4", "DATA5")]
|
|
OUT = paths.BUILD / "textures"
|
|
|
|
|
|
def find(name):
|
|
for d in SRC_DIRS:
|
|
p = d / name
|
|
if p.exists():
|
|
return p
|
|
return None
|
|
|
|
|
|
def scene_agfs(scene):
|
|
"""The .AGF asset names in a scene's SYS4INI section manifest."""
|
|
secs = json.loads((paths.BUILD / "asset-sections.json").read_text(encoding="utf-8"))
|
|
files = json.loads((paths.BUILD / "asset-index.json").read_text(encoding="utf-8"))["files"]
|
|
base = secs["scene_base"][scene.upper().removesuffix(".BIN")]
|
|
end = next(s["end"] for s in secs["sections"] if s["start"] == base)
|
|
return [files[p]["name"] for p in range(base, end + 1)
|
|
if files[p]["name"].upper().endswith(".AGF")]
|
|
|
|
|
|
def convert(name):
|
|
src = find(name)
|
|
if not src:
|
|
print(f"NOT FOUND: {name}"); return False
|
|
out_bmp = OUT / (os.path.splitext(name)[0] + ".BMP")
|
|
if out_bmp.exists():
|
|
return True
|
|
tmp = OUT / name
|
|
shutil.copy(src, tmp)
|
|
r = subprocess.run([str(EXE), name], cwd=str(OUT), capture_output=True, text=True)
|
|
tmp.unlink(missing_ok=True)
|
|
if not out_bmp.exists():
|
|
print(f"FAILED: {name} ({r.stdout.strip()[:60]})"); return False
|
|
return True
|
|
|
|
|
|
def main(argv):
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
if argv and argv[0] == "--scene":
|
|
names = scene_agfs(argv[1])
|
|
print(f"{argv[1]}: {len(names)} .AGF assets in manifest")
|
|
else:
|
|
names = argv
|
|
ok = sum(convert(n) for n in names)
|
|
print(f"converted {ok}/{len(names)} -> {OUT.relative_to(paths.REPO)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1:]))
|