cli/formats: let the screen commands reach bundles with no .rat child
The developer-logo splash declares its sprites directly and has no .rat layout child, so is_build rejected it and no screen command could render it — despite it being one of only two screens whose paint order has been read off the running game, and the one where the layer key explains the whole permutation. Adds ui_layout::is_composable (a declaration table plus at least one element resolving to a T8aD the bundle carries) and an opt-in --all on screen list/info/render. Measured on the disc: 2859 RATC bundles, 965 pass is_build, 2751 pass is_composable, and 0 pass is_build without passing it — a strict superset. It is opt-in because the 1786 extra bundles are mostly two-element fragments (a button and its glow), and because widening the default would renumber --build for every pak, invalidating the build indices the corpus's notes cite by number. The splash now renders 6/7 elements, painting its glows first in the order measured off the game; a disc test pins that order.
This commit is contained in:
@@ -129,6 +129,14 @@ enum ScreenCommands {
|
||||
List {
|
||||
/// Path to a `GP_*.pak`
|
||||
pak: PathBuf,
|
||||
/// Widen the list from screen builds to **every composable bundle** —
|
||||
/// including the ones with no `.rat` layout child, such as the
|
||||
/// developer-logo splash. 2 859 RATC bundles on the disc compose; only
|
||||
/// 965 are screen builds, and the rest are mostly two-element fragments
|
||||
/// (a button and its glow), so this is off by default. **It renumbers
|
||||
/// `--build`**, which is why it is a flag and not the default.
|
||||
#[arg(long)]
|
||||
all: bool,
|
||||
},
|
||||
/// Print one build's element declaration table and resting placements
|
||||
Info {
|
||||
@@ -137,6 +145,15 @@ enum ScreenCommands {
|
||||
/// Which build (index into `screen list`); default = the largest
|
||||
#[arg(long)]
|
||||
build: Option<usize>,
|
||||
/// Widen the list from screen builds to **every composable bundle** —
|
||||
/// including the ones with no `.rat` layout child, such as the
|
||||
/// developer-logo splash. 2 859 RATC bundles on the disc compose; only
|
||||
/// 965 are screen builds, and the rest are mostly two-element fragments
|
||||
/// (a button and its glow), so this is off by default. **It renumbers
|
||||
/// `--build`**, which is why it is a flag and not the default.
|
||||
#[arg(long)]
|
||||
all: bool,
|
||||
|
||||
/// Also print the bundle's other orderings and, per element, the decoded
|
||||
/// sprite size beside the declared pivot and every keyframe's
|
||||
/// scale/position/time — what a placement or paint-order hypothesis has
|
||||
@@ -164,6 +181,14 @@ enum ScreenCommands {
|
||||
/// what a framebuffer capture must be compared against.
|
||||
#[arg(long)]
|
||||
black: bool,
|
||||
/// Widen the list from screen builds to **every composable bundle** —
|
||||
/// including the ones with no `.rat` layout child, such as the
|
||||
/// developer-logo splash. 2 859 RATC bundles on the disc compose; only
|
||||
/// 965 are screen builds, and the rest are mostly two-element fragments
|
||||
/// (a button and its glow), so this is off by default. **It renumbers
|
||||
/// `--build`**, which is why it is a flag and not the default.
|
||||
#[arg(long)]
|
||||
all: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -306,12 +331,13 @@ async fn main() -> Result<()> {
|
||||
AudioCommands::Info { file } => cmd_audio_info(&file),
|
||||
},
|
||||
Commands::Screen { cmd } => match cmd {
|
||||
ScreenCommands::List { pak } => cmd_screen_list(&pak),
|
||||
ScreenCommands::List { pak, all } => cmd_screen_list(&pak, all),
|
||||
ScreenCommands::Info {
|
||||
pak,
|
||||
build,
|
||||
geometry,
|
||||
} => cmd_screen_info(&pak, build, geometry),
|
||||
all,
|
||||
} => cmd_screen_info(&pak, build, geometry, all),
|
||||
ScreenCommands::Render {
|
||||
pak,
|
||||
output,
|
||||
@@ -319,8 +345,9 @@ async fn main() -> Result<()> {
|
||||
focus,
|
||||
animated,
|
||||
black,
|
||||
all,
|
||||
} => {
|
||||
cmd_screen_render(&pak, &output, build, focus, animated, black)
|
||||
cmd_screen_render(&pak, &output, build, focus, animated, black, all)
|
||||
}
|
||||
},
|
||||
Commands::Save { cmd } => match cmd {
|
||||
@@ -332,13 +359,24 @@ async fn main() -> Result<()> {
|
||||
// ── UI screens ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// Every RATC entry of a UI pak that parses as a screen build, with its bytes.
|
||||
fn screen_builds(pak: &Path) -> Result<Vec<(usize, Vec<u8>)>> {
|
||||
///
|
||||
/// `all` widens the filter from `is_build` — which requires a `.rat` layout
|
||||
/// child — to anything `compose` can draw. The developer-logo splash is the
|
||||
/// reason: it declares its sprites directly, has no `.rat` child at all, and so
|
||||
/// is invisible to every screen command without this. See
|
||||
/// `docs/re/structures/ui-composable-bundles.md`.
|
||||
fn screen_builds(pak: &Path, all: bool) -> Result<Vec<(usize, Vec<u8>)>> {
|
||||
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
||||
let ar = PakArchive::open(pak).context("open pak")?;
|
||||
let mut out = Vec::new();
|
||||
for (i, e) in ar.entries().iter().enumerate() {
|
||||
let Ok(bytes) = ar.read(e) else { continue };
|
||||
if ui_layout::is_build(&bytes) {
|
||||
let keep = if all {
|
||||
ui_layout::is_composable(&bytes)
|
||||
} else {
|
||||
ui_layout::is_build(&bytes)
|
||||
};
|
||||
if keep {
|
||||
out.push((i, bytes));
|
||||
}
|
||||
}
|
||||
@@ -364,9 +402,9 @@ fn pick_build(builds: &[(usize, Vec<u8>)], want: Option<usize>) -> Result<usize>
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_screen_list(pak: &Path) -> Result<()> {
|
||||
fn cmd_screen_list(pak: &Path, all: bool) -> Result<()> {
|
||||
use sylpheed_formats::ui_layout;
|
||||
let builds = screen_builds(pak)?;
|
||||
let builds = screen_builds(pak, all)?;
|
||||
println!("{} screen build(s) in {}", builds.len(), pak.display());
|
||||
for (i, (entry, bytes)) in builds.iter().enumerate() {
|
||||
match ui_layout::parse_build(bytes) {
|
||||
@@ -389,9 +427,9 @@ fn cmd_screen_list(pak: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd_screen_info(pak: &Path, want: Option<usize>, geometry: bool) -> Result<()> {
|
||||
fn cmd_screen_info(pak: &Path, want: Option<usize>, geometry: bool, all: bool) -> Result<()> {
|
||||
use sylpheed_formats::ui_layout;
|
||||
let builds = screen_builds(pak)?;
|
||||
let builds = screen_builds(pak, all)?;
|
||||
let idx = pick_build(&builds, want)?;
|
||||
let bytes = &builds[idx].1;
|
||||
let b = ui_layout::parse_build(bytes).context("build did not parse")?;
|
||||
@@ -529,9 +567,10 @@ fn cmd_screen_render(
|
||||
focus: bool,
|
||||
animated: bool,
|
||||
black: bool,
|
||||
all: bool,
|
||||
) -> Result<()> {
|
||||
use sylpheed_formats::ui_layout::{self, ComposeOptions};
|
||||
let builds = screen_builds(pak)?;
|
||||
let builds = screen_builds(pak, all)?;
|
||||
let idx = pick_build(&builds, want)?;
|
||||
let bytes = &builds[idx].1;
|
||||
let b = ui_layout::parse_build(bytes).context("build did not parse")?;
|
||||
|
||||
Reference in New Issue
Block a user