Compare commits
4 Commits
main
...
pi/clippy-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08416dbd0b | ||
|
|
bc79817488 | ||
|
|
d8dcc1fc29 | ||
|
|
4ac5c9f419 |
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@@ -42,7 +42,15 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
|
# `stable` installs a MINIMAL profile: rustc, cargo, rust-std and no
|
||||||
|
# more. Components have to be named. Without this line the Clippy step
|
||||||
|
# below dies on "'cargo-clippy' is not installed for the toolchain
|
||||||
|
# 'stable-aarch64-unknown-linux-gnu'" — which is not a lint result, it
|
||||||
|
# is the step never having run. The `fmt` job below always got this
|
||||||
|
# right; this one never did.
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: clippy
|
||||||
|
|
||||||
- name: Cache Cargo registry and build
|
- name: Cache Cargo registry and build
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
@@ -69,6 +77,20 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --workspace
|
run: cargo test --workspace
|
||||||
|
|
||||||
|
# This step has never once executed on this codebase: the toolchain above
|
||||||
|
# shipped without the component, so every run died on "not installed"
|
||||||
|
# before clippy saw a line of source. Its result was never pass or fail,
|
||||||
|
# only unmeasured. With the component installed it becomes a real check,
|
||||||
|
# and the first honest thing it will report is that the workspace is not
|
||||||
|
# clean — the build already emits ~13 plain rustc warnings (unused
|
||||||
|
# imports, unused variables, needless `mut`, dead fields) that
|
||||||
|
# `-D warnings` promotes to errors, before clippy's own lints are counted.
|
||||||
|
#
|
||||||
|
# Left gating on purpose. A red check that measures something is worth
|
||||||
|
# more than a green one that measures nothing, and the alternative —
|
||||||
|
# `continue-on-error`, or dropping `-D warnings` — cannot tell "debt not
|
||||||
|
# yet paid" from "debt paid", which is the shape PROTOCOL.md forbids.
|
||||||
|
# The debt is scoped in #13, as the rustfmt debt is in #12.
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --workspace -- -D warnings
|
run: cargo clippy --workspace -- -D warnings
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ use anyhow::{Context, Result};
|
|||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
use sylpheed_formats::vfs::{identify_format, GameAssets};
|
use sylpheed_formats::vfs::{identify_format, GameAssets};
|
||||||
use sylpheed_formats::{IdxdObject, PakArchive};
|
use sylpheed_formats::{IdxdObject, PakArchive};
|
||||||
@@ -572,6 +571,11 @@ fn print_geometry(b: &sylpheed_formats::ui_layout::UiBuild, bytes: &[u8]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 8 parameters against a threshold of 7 — a plain function, unlike the Bevy
|
||||||
|
// systems in the viewer, so this one is real if mild. Left as-is because the
|
||||||
|
// arguments are the CLI flags this subcommand takes; grouping them into a
|
||||||
|
// struct is a change to the command surface, not a lint fix.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn cmd_screen_render(
|
fn cmd_screen_render(
|
||||||
pak: &Path,
|
pak: &Path,
|
||||||
output: &Path,
|
output: &Path,
|
||||||
@@ -892,7 +896,7 @@ fn cmd_sniff(dir: &Path, unknown_only: bool) -> Result<()> {
|
|||||||
println!();
|
println!();
|
||||||
println!("{}", "Format Summary:".bold());
|
println!("{}", "Format Summary:".bold());
|
||||||
let mut summary: Vec<_> = counts.into_iter().collect();
|
let mut summary: Vec<_> = counts.into_iter().collect();
|
||||||
summary.sort_by(|a, b| b.1.cmp(&a.1));
|
summary.sort_by_key(|&(_, count)| std::cmp::Reverse(count));
|
||||||
for (fmt, count) in summary {
|
for (fmt, count) in summary {
|
||||||
println!(
|
println!(
|
||||||
" {:>6} .{}",
|
" {:>6} .{}",
|
||||||
@@ -1034,7 +1038,7 @@ fn cmd_mesh_info(file: &Path) -> Result<()> {
|
|||||||
let nv = sub.positions.len();
|
let nv = sub.positions.len();
|
||||||
let mut referenced = vec![false; nv];
|
let mut referenced = vec![false; nv];
|
||||||
let (mut degen, mut oob, mut imax) = (0usize, 0usize, 0u32);
|
let (mut degen, mut oob, mut imax) = (0usize, 0usize, 0u32);
|
||||||
for tri in sub.indices.chunks_exact(3) {
|
for tri in sub.indices.as_chunks::<3>().0 {
|
||||||
let (a, b, c) = (tri[0], tri[1], tri[2]);
|
let (a, b, c) = (tri[0], tri[1], tri[2]);
|
||||||
imax = imax.max(a).max(b).max(c);
|
imax = imax.max(a).max(b).max(c);
|
||||||
if a == b || b == c || a == c {
|
if a == b || b == c || a == c {
|
||||||
@@ -1056,7 +1060,7 @@ fn cmd_mesh_info(file: &Path) -> Result<()> {
|
|||||||
};
|
};
|
||||||
let mut maxedges: Vec<f32> = sub
|
let mut maxedges: Vec<f32> = sub
|
||||||
.indices
|
.indices
|
||||||
.chunks_exact(3)
|
.as_chunks::<3>().0.iter()
|
||||||
.map(|t| edge(t[0], t[1]).max(edge(t[1], t[2])).max(edge(t[0], t[2])))
|
.map(|t| edge(t[0], t[1]).max(edge(t[1], t[2])).max(edge(t[0], t[2])))
|
||||||
.collect();
|
.collect();
|
||||||
maxedges.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
maxedges.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||||
@@ -1220,7 +1224,7 @@ fn cmd_mesh_render(
|
|||||||
let med = {
|
let med = {
|
||||||
let mut e: Vec<f32> = sub
|
let mut e: Vec<f32> = sub
|
||||||
.indices
|
.indices
|
||||||
.chunks_exact(3)
|
.as_chunks::<3>().0.iter()
|
||||||
.filter(|t| (t[0] as usize) < n && (t[1] as usize) < n && (t[2] as usize) < n)
|
.filter(|t| (t[0] as usize) < n && (t[1] as usize) < n && (t[2] as usize) < n)
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
let d = |a: u32, b: u32| {
|
let d = |a: u32, b: u32| {
|
||||||
@@ -1254,7 +1258,7 @@ fn cmd_mesh_render(
|
|||||||
(p[2] - center[2]) * scale * mirror[2] + cell[2],
|
(p[2] - center[2]) * scale * mirror[2] + cell[2],
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
for tri in sub.indices.chunks_exact(3) {
|
for tri in sub.indices.as_chunks::<3>().0 {
|
||||||
let (a, b, c) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
|
let (a, b, c) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
|
||||||
if a < n && b < n && c < n {
|
if a < n && b < n && c < n {
|
||||||
if span_only || span_hide {
|
if span_only || span_hide {
|
||||||
@@ -1440,7 +1444,7 @@ fn decode_to_rgba8(tex: &sylpheed_formats::texture::X360Texture) -> Result<Vec<u
|
|||||||
// [A,R,G,B] byte order (verified against the retail Acheron backdrop).
|
// [A,R,G,B] byte order (verified against the retail Acheron backdrop).
|
||||||
// Emit RGBA. X8 has no meaningful alpha.
|
// Emit RGBA. X8 has no meaningful alpha.
|
||||||
let opaque = matches!(tex.format, F::X8R8G8B8);
|
let opaque = matches!(tex.format, F::X8R8G8B8);
|
||||||
for (px, out) in tex.data.chunks_exact(4).zip(rgba.chunks_exact_mut(4)) {
|
for (px, out) in tex.data.as_chunks::<4>().0.iter().zip(rgba.as_chunks_mut::<4>().0) {
|
||||||
out[0] = px[1]; // R
|
out[0] = px[1]; // R
|
||||||
out[1] = px[2]; // G
|
out[1] = px[2]; // G
|
||||||
out[2] = px[3]; // B
|
out[2] = px[3]; // B
|
||||||
@@ -1690,12 +1694,12 @@ fn cmd_pak_textures(pak: &Path, output: &Path, verbose: bool) -> Result<()> {
|
|||||||
let mut idx = 0usize;
|
let mut idx = 0usize;
|
||||||
while let Some(pos) = payload[off..]
|
while let Some(pos) = payload[off..]
|
||||||
.windows(4)
|
.windows(4)
|
||||||
.position(|w| w == &t8ad::T8AD_MAGIC)
|
.position(|w| w == t8ad::T8AD_MAGIC)
|
||||||
{
|
{
|
||||||
let start = off + pos;
|
let start = off + pos;
|
||||||
let next = payload[start + 4..]
|
let next = payload[start + 4..]
|
||||||
.windows(4)
|
.windows(4)
|
||||||
.position(|w| w == &t8ad::T8AD_MAGIC)
|
.position(|w| w == t8ad::T8AD_MAGIC)
|
||||||
.map(|p| start + 4 + p)
|
.map(|p| start + 4 + p)
|
||||||
.unwrap_or(payload.len());
|
.unwrap_or(payload.len());
|
||||||
emit_t8ad(
|
emit_t8ad(
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ pub struct BgmSpec {
|
|||||||
pub loop_start_s: Option<f64>,
|
pub loop_start_s: Option<f64>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub loop_end_s: Option<f64>,
|
pub loop_end_s: Option<f64>,
|
||||||
|
// Deserialised to model the sidecar schema, not read in Rust.
|
||||||
|
#[allow(dead_code)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub loop_end_why: Option<serde_json::Value>,
|
pub loop_end_why: Option<serde_json::Value>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|||||||
@@ -158,6 +158,9 @@ fn load_names(authored: &Path) -> Result<NameMap> {
|
|||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct File {
|
struct File {
|
||||||
archives: NameMap,
|
archives: NameMap,
|
||||||
|
// Deserialised to model the on-disc schema, not read in Rust.
|
||||||
|
// Removing it would silently change what this struct accepts.
|
||||||
|
#[allow(dead_code)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
also_export: AlsoExport,
|
also_export: AlsoExport,
|
||||||
}
|
}
|
||||||
@@ -275,7 +278,7 @@ fn run_export(disc: &Path, out: &Path, authored_dir: &Path) -> Result<()> {
|
|||||||
// `video/` that this run did not claim, so a movie that stops being exported
|
// `video/` that this run did not claim, so a movie that stops being exported
|
||||||
// still stops existing.
|
// still stops existing.
|
||||||
if out.exists() {
|
if out.exists() {
|
||||||
for entry in std::fs::read_dir(&out).context("clear the output tree")? {
|
for entry in std::fs::read_dir(out).context("clear the output tree")? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
if entry.file_name() == "video" {
|
if entry.file_name() == "video" {
|
||||||
continue;
|
continue;
|
||||||
@@ -288,7 +291,7 @@ fn run_export(disc: &Path, out: &Path, authored_dir: &Path) -> Result<()> {
|
|||||||
.with_context(|| format!("clear {}", entry.path().display()))?;
|
.with_context(|| format!("clear {}", entry.path().display()))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::fs::create_dir_all(&out)?;
|
std::fs::create_dir_all(out)?;
|
||||||
|
|
||||||
let archive = "dat/GP_TITLE.pak";
|
let archive = "dat/GP_TITLE.pak";
|
||||||
let pak = disc.join(archive);
|
let pak = disc.join(archive);
|
||||||
@@ -315,7 +318,7 @@ fn run_export(disc: &Path, out: &Path, authored_dir: &Path) -> Result<()> {
|
|||||||
None => (format!("build_{entry:02}"), "index", None),
|
None => (format!("build_{entry:02}"), "index", None),
|
||||||
};
|
};
|
||||||
let ex = screen::export_build(
|
let ex = screen::export_build(
|
||||||
&out,
|
out,
|
||||||
archive,
|
archive,
|
||||||
*entry,
|
*entry,
|
||||||
build_idx,
|
build_idx,
|
||||||
|
|||||||
@@ -226,9 +226,10 @@ fn parse_riff_wave(bytes: &[u8]) -> Option<AudioInfo> {
|
|||||||
match codec {
|
match codec {
|
||||||
AudioCodec::Pcm | AudioCodec::PcmFloat => {
|
AudioCodec::Pcm | AudioCodec::PcmFloat => {
|
||||||
if let (Some(d), true) = (data_bytes, channels > 0 && bits > 0) {
|
if let (Some(d), true) = (data_bytes, channels > 0 && bits > 0) {
|
||||||
|
// `bits` under 8 makes the frame size zero, so this stays
|
||||||
|
// fallible: `checked_div` states that once, where it happens.
|
||||||
let frame = channels as u64 * (bits as u64 / 8);
|
let frame = channels as u64 * (bits as u64 / 8);
|
||||||
if frame > 0 {
|
if let Some(spc) = d.checked_div(frame) {
|
||||||
let spc = d / frame;
|
|
||||||
info.samples_per_channel = Some(spc);
|
info.samples_per_channel = Some(spc);
|
||||||
if rate > 0 {
|
if rate > 0 {
|
||||||
info.duration_secs = Some(spc as f32 / rate as f32);
|
info.duration_secs = Some(spc as f32 / rate as f32);
|
||||||
@@ -275,11 +276,11 @@ impl GameAudio {
|
|||||||
let samples: Vec<f32> = match (info.codec, bits) {
|
let samples: Vec<f32> = match (info.codec, bits) {
|
||||||
(AudioCodec::Pcm, 8) => data.iter().map(|&b| (b as f32 - 128.0) / 128.0).collect(),
|
(AudioCodec::Pcm, 8) => data.iter().map(|&b| (b as f32 - 128.0) / 128.0).collect(),
|
||||||
(AudioCodec::Pcm, 16) => data
|
(AudioCodec::Pcm, 16) => data
|
||||||
.chunks_exact(2)
|
.as_chunks::<2>().0.iter()
|
||||||
.map(|c| i16::from_le_bytes([c[0], c[1]]) as f32 / 32768.0)
|
.map(|c| i16::from_le_bytes([c[0], c[1]]) as f32 / 32768.0)
|
||||||
.collect(),
|
.collect(),
|
||||||
(AudioCodec::Pcm, 24) => data
|
(AudioCodec::Pcm, 24) => data
|
||||||
.chunks_exact(3)
|
.as_chunks::<3>().0.iter()
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
let v = ((c[2] as i32) << 16) | ((c[1] as i32) << 8) | c[0] as i32;
|
let v = ((c[2] as i32) << 16) | ((c[1] as i32) << 8) | c[0] as i32;
|
||||||
let v = (v << 8) >> 8; // sign-extend 24→32
|
let v = (v << 8) >> 8; // sign-extend 24→32
|
||||||
@@ -287,7 +288,7 @@ impl GameAudio {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
(AudioCodec::PcmFloat, 32) => data
|
(AudioCodec::PcmFloat, 32) => data
|
||||||
.chunks_exact(4)
|
.as_chunks::<4>().0.iter()
|
||||||
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
|
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
|
||||||
.collect(),
|
.collect(),
|
||||||
_ => return Err(AudioError::UnsupportedPcm { tag: 0, bits }),
|
_ => return Err(AudioError::UnsupportedPcm { tag: 0, bits }),
|
||||||
|
|||||||
@@ -1131,7 +1131,7 @@ pub fn load_squadrons(pak: &PakArchive) -> Vec<Squadron> {
|
|||||||
let Some(f) = records.record(id) else { continue };
|
let Some(f) = records.record(id) else { continue };
|
||||||
let slots: Vec<&str> = f.positional.iter().map(|(_, v)| v.as_str()).collect();
|
let slots: Vec<&str> = f.positional.iter().map(|(_, v)| v.as_str()).collect();
|
||||||
let members = slots
|
let members = slots
|
||||||
.chunks_exact(4)
|
.as_chunks::<4>().0.iter()
|
||||||
.map(|m| SquadronMember {
|
.map(|m| SquadronMember {
|
||||||
unit: m[0].to_string(),
|
unit: m[0].to_string(),
|
||||||
message_set: text(Some(m[1])),
|
message_set: text(Some(m[1])),
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ pub fn name_hash(name: &str) -> u32 {
|
|||||||
// through untouched, including any high-bit bytes, which are then sign-extended).
|
// through untouched, including any high-bit bytes, which are then sign-extended).
|
||||||
let mut bytes = name.as_bytes().to_vec();
|
let mut bytes = name.as_bytes().to_vec();
|
||||||
for byte in &mut bytes {
|
for byte in &mut bytes {
|
||||||
if (b'A'..=b'Z').contains(byte) {
|
if byte.is_ascii_uppercase() {
|
||||||
*byte += 0x20;
|
*byte += 0x20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ pub fn parse(bytes: &[u8]) -> Option<Subtitle> {
|
|||||||
fn utf16be_tokens(bytes: &[u8]) -> Vec<String> {
|
fn utf16be_tokens(bytes: &[u8]) -> Vec<String> {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
let mut cur = String::new();
|
let mut cur = String::new();
|
||||||
for pair in bytes.chunks_exact(2) {
|
for pair in bytes.as_chunks::<2>().0 {
|
||||||
let u = u16::from_be_bytes([pair[0], pair[1]]);
|
let u = u16::from_be_bytes([pair[0], pair[1]]);
|
||||||
match char::from_u32(u as u32) {
|
match char::from_u32(u as u32) {
|
||||||
Some(c) if !c.is_control() => cur.push(c),
|
Some(c) if !c.is_control() => cur.push(c),
|
||||||
|
|||||||
@@ -700,7 +700,7 @@ impl Xbg7Model {
|
|||||||
let (vc, ic) = r.markers[0];
|
let (vc, ic) = r.markers[0];
|
||||||
let sig = (r.decl.stride, vc, ic);
|
let sig = (r.decl.stride, vc, ic);
|
||||||
let floor = last_by_sig.get(&sig).map_or(0, |o| o + 1);
|
let floor = last_by_sig.get(&sig).map_or(0, |o| o + 1);
|
||||||
if m.meshes[0].vbuf_offset.map_or(false, |o| o < floor) {
|
if m.meshes[0].vbuf_offset.is_some_and(|o| o < floor) {
|
||||||
if let Some(alt) = anchor_pool_mesh(
|
if let Some(alt) = anchor_pool_mesh(
|
||||||
bytes,
|
bytes,
|
||||||
starts,
|
starts,
|
||||||
@@ -1339,6 +1339,13 @@ fn vertex_run_starts(bytes: &[u8], data_base: usize, stride: usize) -> Vec<usize
|
|||||||
/// the `index_count` indices ending just before it are all `< vtx_count`,
|
/// the `index_count` indices ending just before it are all `< vtx_count`,
|
||||||
/// reference (nearly) all vertices, and produce non-degenerate triangles with a
|
/// reference (nearly) all vertices, and produce non-degenerate triangles with a
|
||||||
/// real spatial extent — a signature strong enough to pin the block.
|
/// real spatial extent — a signature strong enough to pin the block.
|
||||||
|
// 8 parameters against clippy's threshold of 7. Not refactored here on
|
||||||
|
// purpose: the fix is a shared params struct across this and the two
|
||||||
|
// `validate_block*` functions below, which is a change to the decoder's
|
||||||
|
// mesh-anchoring signatures and belongs to whoever owns that path — not to
|
||||||
|
// a CI-lint pass. Recorded rather than silenced: this allow covers one lint
|
||||||
|
// at one site, and any new violation elsewhere still fails the build.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn anchor_pool_mesh(
|
fn anchor_pool_mesh(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
starts: &[usize],
|
starts: &[usize],
|
||||||
@@ -1406,7 +1413,7 @@ fn anchor_pool_mesh(
|
|||||||
}
|
}
|
||||||
let (degen, wind) = index_run_quality(bytes, ib, vb, index_count, decl);
|
let (degen, wind) = index_run_quality(bytes, ib, vb, index_count, decl);
|
||||||
let cand = (degen, -wind, pad);
|
let cand = (degen, -wind, pad);
|
||||||
if best.map_or(true, |b| cand < b) {
|
if best.is_none_or(|b| cand < b) {
|
||||||
best = Some(cand);
|
best = Some(cand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1452,6 +1459,11 @@ fn anchor_pool_mesh(
|
|||||||
/// for both the adjacency anchor ([`anchor_pool_mesh`]) and the grouped-pool
|
/// for both the adjacency anchor ([`anchor_pool_mesh`]) and the grouped-pool
|
||||||
/// anchor ([`anchor_grouped_meshes`]); the two differ only in how they *place*
|
/// anchor ([`anchor_grouped_meshes`]); the two differ only in how they *place*
|
||||||
/// `ib`/`vb`, not in how they validate a placement.
|
/// `ib`/`vb`, not in how they validate a placement.
|
||||||
|
// 8/7, as above. This and `validate_block_report` take the *same* eight
|
||||||
|
// parameters — one delegates straight to the other — which is exactly why a
|
||||||
|
// params struct is the right fix and why it should be done once, deliberately,
|
||||||
|
// rather than piecemeal here.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn validate_block(
|
fn validate_block(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
ib: usize,
|
ib: usize,
|
||||||
@@ -1478,6 +1490,8 @@ fn validate_block(
|
|||||||
/// [`validate_block`], but naming the gate that rejected a block. A runtime
|
/// [`validate_block`], but naming the gate that rejected a block. A runtime
|
||||||
/// capture can prove a block is real; when the decoder still refuses it, this
|
/// capture can prove a block is real; when the decoder still refuses it, this
|
||||||
/// says which test is wrong rather than leaving a threshold to be guessed at.
|
/// says which test is wrong rather than leaving a threshold to be guessed at.
|
||||||
|
// 8/7 — the signature `validate_block` delegates into. See above.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn validate_block_report(
|
fn validate_block_report(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
ib: usize,
|
ib: usize,
|
||||||
@@ -1679,7 +1693,7 @@ fn anchor_grouped_meshes(
|
|||||||
if n == 0 || decls.len() < n {
|
if n == 0 || decls.len() < n {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
let decl = &decls[0];
|
let _decl = &decls[0];
|
||||||
|
|
||||||
// Relative index-buffer offsets (ib0 = 0), 4-byte aligned between buffers,
|
// Relative index-buffer offsets (ib0 = 0), 4-byte aligned between buffers,
|
||||||
// and cumulative vertex offsets (vb0 = 0) — both derived from the marker list.
|
// and cumulative vertex offsets (vb0 = 0) — both derived from the marker list.
|
||||||
@@ -1723,7 +1737,7 @@ fn anchor_grouped_meshes(
|
|||||||
let (vc, ic) = markers[i];
|
let (vc, ic) = markers[i];
|
||||||
let ib = ib0 + rel_ib[i];
|
let ib = ib0 + rel_ib[i];
|
||||||
if ib + ic * 2 > bytes.len()
|
if ib + ic * 2 > bytes.len()
|
||||||
|| vc.checked_mul(decls[i].stride).map_or(true, |b| vb + b > bytes.len())
|
|| vc.checked_mul(decls[i].stride).is_none_or(|b| vb + b > bytes.len())
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1784,7 +1798,7 @@ fn anchor_grouped_meshes(
|
|||||||
}
|
}
|
||||||
let (degen, wind) = index_run_quality(bytes, ib_k, vb_k, ick, &decls[kmax]);
|
let (degen, wind) = index_run_quality(bytes, ib_k, vb_k, ick, &decls[kmax]);
|
||||||
let cand = (degen, -wind, pad);
|
let cand = (degen, -wind, pad);
|
||||||
if best.map_or(true, |b| cand < b) {
|
if best.is_none_or(|b| cand < b) {
|
||||||
best = Some(cand);
|
best = Some(cand);
|
||||||
}
|
}
|
||||||
if pad_first_match() {
|
if pad_first_match() {
|
||||||
@@ -1797,7 +1811,7 @@ fn anchor_grouped_meshes(
|
|||||||
if meshes.len() == n {
|
if meshes.len() == n {
|
||||||
return meshes;
|
return meshes;
|
||||||
}
|
}
|
||||||
if partial.as_ref().map_or(true, |p| meshes.len() > p.len()) {
|
if partial.as_ref().is_none_or(|p| meshes.len() > p.len()) {
|
||||||
partial = Some(meshes);
|
partial = Some(meshes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1929,7 +1943,7 @@ fn find_index_marker(desc: &[u8]) -> Option<(usize, usize)> {
|
|||||||
while rel + 40 <= desc.len() {
|
while rel + 40 <= desc.len() {
|
||||||
let a = be32(desc, rel);
|
let a = be32(desc, rel);
|
||||||
let c = be32(desc, rel + 4);
|
let c = be32(desc, rel + 4);
|
||||||
if c >= 3 && c % 3 == 0 && c < 400_000 && a == c * 2 {
|
if c >= 3 && c.is_multiple_of(3) && c < 400_000 && a == c * 2 {
|
||||||
return Some((rel, c as usize));
|
return Some((rel, c as usize));
|
||||||
}
|
}
|
||||||
rel += 4;
|
rel += 4;
|
||||||
@@ -1951,7 +1965,7 @@ fn all_index_markers(desc: &[u8]) -> Vec<(usize, usize)> {
|
|||||||
while rel + 8 <= desc.len() {
|
while rel + 8 <= desc.len() {
|
||||||
let a = be32(desc, rel);
|
let a = be32(desc, rel);
|
||||||
let c = be32(desc, rel + 4);
|
let c = be32(desc, rel + 4);
|
||||||
if c >= 3 && c % 3 == 0 && c < 400_000 && a == c * 2 && rel >= 32 {
|
if c >= 3 && c.is_multiple_of(3) && c < 400_000 && a == c * 2 && rel >= 32 {
|
||||||
let vc = be32(desc, rel - 32) as usize;
|
let vc = be32(desc, rel - 32) as usize;
|
||||||
if (3..=200_000).contains(&vc) {
|
if (3..=200_000).contains(&vc) {
|
||||||
out.push((vc, c as usize));
|
out.push((vc, c as usize));
|
||||||
@@ -1979,7 +1993,7 @@ fn all_vertex_decls(desc: &[u8]) -> Vec<VertexDecl> {
|
|||||||
while rel + 8 <= desc.len() {
|
while rel + 8 <= desc.len() {
|
||||||
let a = be32(desc, rel);
|
let a = be32(desc, rel);
|
||||||
let c = be32(desc, rel + 4);
|
let c = be32(desc, rel + 4);
|
||||||
if c >= 3 && c % 3 == 0 && c < 400_000 && a == c * 2 && rel >= 32 {
|
if c >= 3 && c.is_multiple_of(3) && c < 400_000 && a == c * 2 && rel >= 32 {
|
||||||
let vc = be32(desc, rel - 32) as usize;
|
let vc = be32(desc, rel - 32) as usize;
|
||||||
if (3..=200_000).contains(&vc) {
|
if (3..=200_000).contains(&vc) {
|
||||||
match parse_decl_at(desc, rel + 8) {
|
match parse_decl_at(desc, rel + 8) {
|
||||||
@@ -2086,9 +2100,8 @@ fn submesh_records(desc: &[u8]) -> Vec<(usize, usize)> {
|
|||||||
let t = be32(desc, rel + 12);
|
let t = be32(desc, rel + 12);
|
||||||
if (3..=65535).contains(&a)
|
if (3..=65535).contains(&a)
|
||||||
&& z == 0
|
&& z == 0
|
||||||
&& c >= 3
|
&& (3..=200_000).contains(&c)
|
||||||
&& c <= 200_000
|
&& c.is_multiple_of(3)
|
||||||
&& c % 3 == 0
|
|
||||||
&& (1..=64).contains(&t)
|
&& (1..=64).contains(&t)
|
||||||
{
|
{
|
||||||
out.push((a as usize, c as usize));
|
out.push((a as usize, c as usize));
|
||||||
@@ -2151,7 +2164,7 @@ fn topology_report(
|
|||||||
let mut agree = 0usize;
|
let mut agree = 0usize;
|
||||||
let mut counted = 0usize;
|
let mut counted = 0usize;
|
||||||
let mut edges: HashMap<(u32, u32), u32> = HashMap::new();
|
let mut edges: HashMap<(u32, u32), u32> = HashMap::new();
|
||||||
for t in indices.chunks_exact(3) {
|
for t in indices.as_chunks::<3>().0 {
|
||||||
let (a, b, c) = (t[0] as usize, t[1] as usize, t[2] as usize);
|
let (a, b, c) = (t[0] as usize, t[1] as usize, t[2] as usize);
|
||||||
if a == b || b == c || a == c {
|
if a == b || b == c || a == c {
|
||||||
degen += 1;
|
degen += 1;
|
||||||
@@ -2330,7 +2343,7 @@ pub fn submesh_albedos(bytes: &[u8], resource_name: &str) -> Vec<(usize, usize,
|
|||||||
if (3..=65535).contains(&vtx)
|
if (3..=65535).contains(&vtx)
|
||||||
&& z == 0
|
&& z == 0
|
||||||
&& idx >= 3
|
&& idx >= 3
|
||||||
&& idx % 3 == 0
|
&& idx.is_multiple_of(3)
|
||||||
&& idx < 400_000
|
&& idx < 400_000
|
||||||
&& tail > 0
|
&& tail > 0
|
||||||
&& tail < 0x10_0000
|
&& tail < 0x10_0000
|
||||||
@@ -2423,7 +2436,7 @@ pub fn material_groups(bytes: &[u8], resource_name: &str, target: usize) -> Vec<
|
|||||||
if (1..=70000).contains(&vtx)
|
if (1..=70000).contains(&vtx)
|
||||||
&& tail == 4
|
&& tail == 4
|
||||||
&& cnt >= 3
|
&& cnt >= 3
|
||||||
&& cnt % 3 == 0
|
&& cnt.is_multiple_of(3)
|
||||||
&& cnt < 400_000
|
&& cnt < 400_000
|
||||||
&& (off as usize) < 4_000_000
|
&& (off as usize) < 4_000_000
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
//! table (statically reversed). Beyond the movie→voice binding, it defines the
|
//! table (statically reversed). Beyond the movie→voice binding, it defines the
|
||||||
//! whole cutscene *structure*: which video (+ subtitle, + on-screen text overlay,
|
//! whole cutscene *structure*: which video (+ subtitle, + on-screen text overlay,
|
||||||
//! + voice) plays at each mission phase, and of what kind (story intro, in-mission
|
//! + voice) plays at each mission phase, and of what kind (story intro, in-mission
|
||||||
//! phase cutscene, resupply, …).
|
//! phase cutscene, resupply, …).
|
||||||
//!
|
//!
|
||||||
//! ## On-disc structure (`dat/tables.pak`, entry hash `0x5b983a08`)
|
//! ## On-disc structure (`dat/tables.pak`, entry hash `0x5b983a08`)
|
||||||
//!
|
//!
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ pub fn find_descriptor_before(buf: &[u8], before: usize) -> Option<usize> {
|
|||||||
let mut o = (before - 1).min(cap) & !3;
|
let mut o = (before - 1).min(cap) & !3;
|
||||||
loop {
|
loop {
|
||||||
let id = be(o);
|
let id = be(o);
|
||||||
if id >= 1 && id < ID_MAX && be(o + 4) == DESC_MARK && be(o + DESC_REPEAT) == id {
|
if (1..ID_MAX).contains(&id) && be(o + 4) == DESC_MARK && be(o + DESC_REPEAT) == id {
|
||||||
return Some(o);
|
return Some(o);
|
||||||
}
|
}
|
||||||
if o < 4 {
|
if o < 4 {
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ pub fn parse_drawlog(text: &str) -> Vec<CapturedDraw> {
|
|||||||
let mut pos: Vec<[f32; 3]> = Vec::new();
|
let mut pos: Vec<[f32; 3]> = Vec::new();
|
||||||
let mut consts: Vec<(usize, [f64; 4])> = Vec::new();
|
let mut consts: Vec<(usize, [f64; 4])> = Vec::new();
|
||||||
|
|
||||||
let mut flush = |base: u32,
|
let flush = |base: u32,
|
||||||
size: u32,
|
size: u32,
|
||||||
stride: u32,
|
stride: u32,
|
||||||
pos: &mut Vec<[f32; 3]>,
|
pos: &mut Vec<[f32; 3]>,
|
||||||
@@ -426,7 +426,7 @@ pub fn correlate(
|
|||||||
let Some((score, mirrored)) = pos_validate(d, &key.ref_pos) else {
|
let Some((score, mirrored)) = pos_validate(d, &key.ref_pos) else {
|
||||||
continue; // positions disagree — not this part
|
continue; // positions disagree — not this part
|
||||||
};
|
};
|
||||||
if best.map_or(true, |(_, s, _)| score > s) {
|
if best.is_none_or(|(_, s, _)| score > s) {
|
||||||
best = Some((d, score, mirrored));
|
best = Some((d, score, mirrored));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ pub fn to_xma_riff_best(slb: &[u8]) -> Option<Vec<u8>> {
|
|||||||
while let Some(di) = find(slb, b"data", i) {
|
while let Some(di) = find(slb, b"data", i) {
|
||||||
let declared = le32(slb, di + 4).unwrap_or(0) as usize;
|
let declared = le32(slb, di + 4).unwrap_or(0) as usize;
|
||||||
let usable = declared.min(slb.len().saturating_sub(di + 8));
|
let usable = declared.min(slb.len().saturating_sub(di + 8));
|
||||||
if best.map_or(true, |(_, b)| usable > b) {
|
if best.is_none_or(|(_, b)| usable > b) {
|
||||||
best = Some((di, usable));
|
best = Some((di, usable));
|
||||||
}
|
}
|
||||||
i = di + 4;
|
i = di + 4;
|
||||||
|
|||||||
@@ -587,19 +587,19 @@ pub fn apply_endian_swap(data: &mut [u8], endianness: u8) {
|
|||||||
match endianness {
|
match endianness {
|
||||||
1 => {
|
1 => {
|
||||||
// k8in16 — swap the two bytes of each 16-bit half.
|
// k8in16 — swap the two bytes of each 16-bit half.
|
||||||
for c in data.chunks_exact_mut(2) {
|
for c in data.as_chunks_mut::<2>().0 {
|
||||||
c.swap(0, 1);
|
c.swap(0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
// k8in32 — reverse each 32-bit word.
|
// k8in32 — reverse each 32-bit word.
|
||||||
for c in data.chunks_exact_mut(4) {
|
for c in data.as_chunks_mut::<4>().0 {
|
||||||
c.reverse();
|
c.reverse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
3 => {
|
3 => {
|
||||||
// k16in32 — swap the two 16-bit halves of each 32-bit word.
|
// k16in32 — swap the two 16-bit halves of each 32-bit word.
|
||||||
for c in data.chunks_exact_mut(4) {
|
for c in data.as_chunks_mut::<4>().0 {
|
||||||
c.swap(0, 2);
|
c.swap(0, 2);
|
||||||
c.swap(1, 3);
|
c.swap(1, 3);
|
||||||
}
|
}
|
||||||
@@ -626,8 +626,8 @@ fn decode_surface(
|
|||||||
} else {
|
} else {
|
||||||
// Linear layout — copy only the mip-0 slice.
|
// Linear layout — copy only the mip-0 slice.
|
||||||
let block_size = format.block_size() as u32;
|
let block_size = format.block_size() as u32;
|
||||||
let bw = ((width + block_size - 1) / block_size).max(1);
|
let bw = width.div_ceil(block_size).max(1);
|
||||||
let bh = ((height + block_size - 1) / block_size).max(1);
|
let bh = height.div_ceil(block_size).max(1);
|
||||||
let needed = bw as usize * bh as usize * format.bytes_per_block();
|
let needed = bw as usize * bh as usize * format.bytes_per_block();
|
||||||
if raw_data.len() < needed {
|
if raw_data.len() < needed {
|
||||||
return Err(TextureError::BufferTooSmall { needed, have: raw_data.len() });
|
return Err(TextureError::BufferTooSmall { needed, have: raw_data.len() });
|
||||||
@@ -651,7 +651,7 @@ fn decode_surface(
|
|||||||
match format {
|
match format {
|
||||||
X360TextureFormat::Dxt1 => swap_bc_block_dwords(&mut linear_data),
|
X360TextureFormat::Dxt1 => swap_bc_block_dwords(&mut linear_data),
|
||||||
X360TextureFormat::Dxt3 | X360TextureFormat::Dxt5 => {
|
X360TextureFormat::Dxt3 | X360TextureFormat::Dxt5 => {
|
||||||
for block in linear_data.chunks_exact_mut(16) {
|
for block in linear_data.as_chunks_mut::<16>().0 {
|
||||||
swap_bc_block_dwords(&mut block[8..16]);
|
swap_bc_block_dwords(&mut block[8..16]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -666,8 +666,8 @@ fn decode_surface(
|
|||||||
/// rounded up to the 4 KiB subresource alignment (`kTextureSubresourceAlignmentBytes`).
|
/// rounded up to the 4 KiB subresource alignment (`kTextureSubresourceAlignmentBytes`).
|
||||||
fn tiled_face_stride(width: u32, height: u32, format: X360TextureFormat) -> usize {
|
fn tiled_face_stride(width: u32, height: u32, format: X360TextureFormat) -> usize {
|
||||||
let bs = format.block_size() as u32;
|
let bs = format.block_size() as u32;
|
||||||
let bw = ((width + bs - 1) / bs).max(1);
|
let bw = width.div_ceil(bs).max(1);
|
||||||
let bh = ((height + bs - 1) / bs).max(1);
|
let bh = height.div_ceil(bs).max(1);
|
||||||
let pitch_aligned = align_up(bw, STORAGE_ALIGN_BLOCKS).max(MACRO_TILE_BLOCKS);
|
let pitch_aligned = align_up(bw, STORAGE_ALIGN_BLOCKS).max(MACRO_TILE_BLOCKS);
|
||||||
let height_aligned = align_up(bh, STORAGE_ALIGN_BLOCKS).max(MACRO_TILE_BLOCKS);
|
let height_aligned = align_up(bh, STORAGE_ALIGN_BLOCKS).max(MACRO_TILE_BLOCKS);
|
||||||
let surface = pitch_aligned as usize * height_aligned as usize * format.bytes_per_block();
|
let surface = pitch_aligned as usize * height_aligned as usize * format.bytes_per_block();
|
||||||
@@ -682,7 +682,7 @@ fn tiled_face_stride(width: u32, height: u32, format: X360TextureFormat) -> usiz
|
|||||||
/// after the byte-level endian swap. Any trailing bytes that don't fill a full
|
/// after the byte-level endian swap. Any trailing bytes that don't fill a full
|
||||||
/// 8-byte group are left untouched.
|
/// 8-byte group are left untouched.
|
||||||
pub fn swap_bc_block_dwords(data: &mut [u8]) {
|
pub fn swap_bc_block_dwords(data: &mut [u8]) {
|
||||||
for unit in data.chunks_exact_mut(8) {
|
for unit in data.as_chunks_mut::<8>().0 {
|
||||||
// [d0 d1 d2 d3 | d4 d5 d6 d7] → [d4 d5 d6 d7 | d0 d1 d2 d3]
|
// [d0 d1 d2 d3 | d4 d5 d6 d7] → [d4 d5 d6 d7 | d0 d1 d2 d3]
|
||||||
let (lo, hi) = unit.split_at_mut(4);
|
let (lo, hi) = unit.split_at_mut(4);
|
||||||
lo.swap_with_slice(hi);
|
lo.swap_with_slice(hi);
|
||||||
@@ -742,8 +742,8 @@ pub fn detile(
|
|||||||
let bpb_log2 = (bpb as u32).trailing_zeros();
|
let bpb_log2 = (bpb as u32).trailing_zeros();
|
||||||
|
|
||||||
// Visible dimensions in blocks, and the padded storage pitch/height.
|
// Visible dimensions in blocks, and the padded storage pitch/height.
|
||||||
let blocks_wide = ((width + block_size - 1) / block_size).max(1);
|
let blocks_wide = width.div_ceil(block_size).max(1);
|
||||||
let blocks_tall = ((height + block_size - 1) / block_size).max(1);
|
let blocks_tall = height.div_ceil(block_size).max(1);
|
||||||
let pitch_aligned = align_up(blocks_wide, STORAGE_ALIGN_BLOCKS).max(MACRO_TILE_BLOCKS);
|
let pitch_aligned = align_up(blocks_wide, STORAGE_ALIGN_BLOCKS).max(MACRO_TILE_BLOCKS);
|
||||||
let height_aligned = align_up(blocks_tall, STORAGE_ALIGN_BLOCKS).max(MACRO_TILE_BLOCKS);
|
let height_aligned = align_up(blocks_tall, STORAGE_ALIGN_BLOCKS).max(MACRO_TILE_BLOCKS);
|
||||||
|
|
||||||
|
|||||||
@@ -910,7 +910,7 @@ pub fn compose(
|
|||||||
// A dim backdrop stands in for the PRMD dim-quad + the live 3D scene behind
|
// A dim backdrop stands in for the PRMD dim-quad + the live 3D scene behind
|
||||||
// an in-mission screen.
|
// an in-mission screen.
|
||||||
let mut canvas = vec![0u8; (w as usize) * (h as usize) * 4];
|
let mut canvas = vec![0u8; (w as usize) * (h as usize) * 4];
|
||||||
for px in canvas.chunks_exact_mut(4) {
|
for px in canvas.as_chunks_mut::<4>().0 {
|
||||||
px.copy_from_slice(&opts.backdrop);
|
px.copy_from_slice(&opts.backdrop);
|
||||||
}
|
}
|
||||||
let mut drawn = Vec::new();
|
let mut drawn = Vec::new();
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ pub fn decode_text(bytes: &[u8]) -> (String, &'static str) {
|
|||||||
|
|
||||||
fn decode_utf16(bytes: &[u8], big_endian: bool) -> String {
|
fn decode_utf16(bytes: &[u8], big_endian: bool) -> String {
|
||||||
let units: Vec<u16> = bytes
|
let units: Vec<u16> = bytes
|
||||||
.chunks_exact(2)
|
.as_chunks::<2>().0.iter()
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
if big_endian {
|
if big_endian {
|
||||||
u16::from_be_bytes([c[0], c[1]])
|
u16::from_be_bytes([c[0], c[1]])
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ impl<F: Read + Seek + Send + Sync + 'static> XisoReader<F> {
|
|||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Opened XISO: root directory table at sector {}",
|
"Opened XISO: root directory table at sector {}",
|
||||||
{ let s = volume.root_table.region.sector; s }
|
{ volume.root_table.region.sector }
|
||||||
);
|
);
|
||||||
Ok(Self { volume, file: wrapper })
|
Ok(Self { volume, file: wrapper })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ pub fn x360_texture_to_bevy_image(tex: X360Texture) -> Result<Image, Xpr2LoadErr
|
|||||||
X360TextureFormat::A8R8G8B8 | X360TextureFormat::X8R8G8B8 => {
|
X360TextureFormat::A8R8G8B8 | X360TextureFormat::X8R8G8B8 => {
|
||||||
let opaque = matches!(tex.format, X360TextureFormat::X8R8G8B8);
|
let opaque = matches!(tex.format, X360TextureFormat::X8R8G8B8);
|
||||||
let mut out = tex.data;
|
let mut out = tex.data;
|
||||||
for px in out.chunks_exact_mut(4) {
|
for px in out.as_chunks_mut::<4>().0 {
|
||||||
let (a, r, g, b) = (px[0], px[1], px[2], px[3]);
|
let (a, r, g, b) = (px[0], px[1], px[2], px[3]);
|
||||||
px[0] = r;
|
px[0] = r;
|
||||||
px[1] = g;
|
px[1] = g;
|
||||||
|
|||||||
@@ -2035,6 +2035,10 @@ fn decode_audio_wav(video: &Path, wav: &Path) -> Result<(), String> {
|
|||||||
|
|
||||||
// ── Video decode + audio (main thread) ────────────────────────────────────────
|
// ── Video decode + audio (main thread) ────────────────────────────────────────
|
||||||
|
|
||||||
|
/// `(pts, rgba_bytes)` as the reader thread forwards them from `ffmpeg`.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
type FrameRx = mpsc::Receiver<(f32, Vec<u8>)>;
|
||||||
|
|
||||||
/// Spawn an `ffmpeg` process decoding to raw RGBA on stdout, plus a reader
|
/// Spawn an `ffmpeg` process decoding to raw RGBA on stdout, plus a reader
|
||||||
/// thread that chunks it into frames and forwards `(pts, bytes)` over a bounded
|
/// thread that chunks it into frames and forwards `(pts, bytes)` over a bounded
|
||||||
/// channel. Optional `-ss start` seeks the input; output pts re-base to 0, so we
|
/// channel. Optional `-ss start` seeks the input; output pts re-base to 0, so we
|
||||||
@@ -2046,7 +2050,7 @@ fn spawn_video_decoder(
|
|||||||
h: u32,
|
h: u32,
|
||||||
fps: f32,
|
fps: f32,
|
||||||
start: f32,
|
start: f32,
|
||||||
) -> Result<(Child, mpsc::Receiver<(f32, Vec<u8>)>), String> {
|
) -> Result<(Child, FrameRx), String> {
|
||||||
let mut cmd = Command::new("ffmpeg");
|
let mut cmd = Command::new("ffmpeg");
|
||||||
cmd.arg("-v").arg("error");
|
cmd.arg("-v").arg("error");
|
||||||
if start > 0.0 {
|
if start > 0.0 {
|
||||||
@@ -2177,6 +2181,12 @@ fn scrub_worker(
|
|||||||
|
|
||||||
/// Polls the mpsc channel, updating `IsoState`, `FileBrowserState`, and
|
/// Polls the mpsc channel, updating `IsoState`, `FileBrowserState`, and
|
||||||
/// `PendingFileBytes` as messages arrive.
|
/// `PendingFileBytes` as messages arrive.
|
||||||
|
// Bevy system: every parameter is a `Res`/`ResMut`/`EventWriter` the
|
||||||
|
// scheduler injects. The count is the framework's dependency list, not a
|
||||||
|
// signature anyone calls by hand, and it cannot be reduced without
|
||||||
|
// bundling into a `SystemParam` struct. clippy's general heuristic does
|
||||||
|
// not know about the idiom.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn poll_loader_channel(
|
fn poll_loader_channel(
|
||||||
channels: Res<IsoChannels>,
|
channels: Res<IsoChannels>,
|
||||||
@@ -2557,7 +2567,7 @@ fn pick_albedo_index(model_name: &str, tex_names: &[String]) -> Option<usize> {
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn compute_smooth_normals(positions: &[[f32; 3]], indices: &[u32]) -> Vec<[f32; 3]> {
|
fn compute_smooth_normals(positions: &[[f32; 3]], indices: &[u32]) -> Vec<[f32; 3]> {
|
||||||
let mut acc = vec![Vec3::ZERO; positions.len()];
|
let mut acc = vec![Vec3::ZERO; positions.len()];
|
||||||
for tri in indices.chunks_exact(3) {
|
for tri in indices.as_chunks::<3>().0 {
|
||||||
let (a, b, c) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
|
let (a, b, c) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
|
||||||
if a >= positions.len() || b >= positions.len() || c >= positions.len() {
|
if a >= positions.len() || b >= positions.len() || c >= positions.len() {
|
||||||
continue;
|
continue;
|
||||||
@@ -3106,7 +3116,7 @@ fn prepare_models_impl(
|
|||||||
// Append the slice's triangles, re-emitting vertices per index
|
// Append the slice's triangles, re-emitting vertices per index
|
||||||
// (no dedup) so each material buffer stays self-contained. A
|
// (no dedup) so each material buffer stays self-contained. A
|
||||||
// reflected instance swaps two corners to keep front faces out.
|
// reflected instance swaps two corners to keep front faces out.
|
||||||
for tri in sub.indices[(*off).min(end)..end].chunks_exact(3) {
|
for tri in sub.indices[(*off).min(end)..end].as_chunks::<3>().0 {
|
||||||
let corners = if reflect {
|
let corners = if reflect {
|
||||||
[tri[0], tri[2], tri[1]]
|
[tri[0], tri[2], tri[1]]
|
||||||
} else {
|
} else {
|
||||||
@@ -3405,6 +3415,12 @@ fn apply_prepared_xpr(
|
|||||||
|
|
||||||
/// Consumes a staged pack, freeing the previous texture/text previews and
|
/// Consumes a staged pack, freeing the previous texture/text previews and
|
||||||
/// populating `PakView` for the master-detail browser.
|
/// populating `PakView` for the master-detail browser.
|
||||||
|
// Bevy system: every parameter is a `Res`/`ResMut`/`EventWriter` the
|
||||||
|
// scheduler injects. The count is the framework's dependency list, not a
|
||||||
|
// signature anyone calls by hand, and it cannot be reduced without
|
||||||
|
// bundling into a `SystemParam` struct. clippy's general heuristic does
|
||||||
|
// not know about the idiom.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn apply_pak(
|
fn apply_pak(
|
||||||
mut pending: ResMut<PendingPak>,
|
mut pending: ResMut<PendingPak>,
|
||||||
@@ -4076,7 +4092,7 @@ fn build_game_snapshot(source: &SourceKind) -> Option<GameSnapshot> {
|
|||||||
Some(CharRow { name, faction: c.faction.unwrap_or_default(), faces: c.faces.len() })
|
Some(CharRow { name, faction: c.faction.unwrap_or_default(), faces: c.faces.len() })
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
characters.sort_by(|a, b| (a.faction.clone(), a.name.clone()).cmp(&(b.faction.clone(), b.name.clone())));
|
characters.sort_by_key(|a| (a.faction.clone(), a.name.clone()));
|
||||||
|
|
||||||
// Combat rosters, keyed by stage where the table self-identifies.
|
// Combat rosters, keyed by stage where the table self-identifies.
|
||||||
let rosters = gd::load_unit_rosters(&main);
|
let rosters = gd::load_unit_rosters(&main);
|
||||||
@@ -4742,7 +4758,7 @@ fn build_ship_model(
|
|||||||
*nrm = rot(&p.m, nrm);
|
*nrm = rot(&p.m, nrm);
|
||||||
}
|
}
|
||||||
if det < 0.0 {
|
if det < 0.0 {
|
||||||
for tri in sub.indices.chunks_exact_mut(3) {
|
for tri in sub.indices.as_chunks_mut::<3>().0 {
|
||||||
tri.swap(1, 2);
|
tri.swap(1, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4911,7 +4927,7 @@ fn handle_cutscene_cues_request(
|
|||||||
// The naming convention is the obvious route and it is wrong often
|
// The naming convention is the obvious route and it is wrong often
|
||||||
// enough to matter -- resolving the region is the only reading that
|
// enough to matter -- resolving the region is the only reading that
|
||||||
// yields the right audio.
|
// yields the right audio.
|
||||||
let voice = (|| {
|
let voice = {
|
||||||
use sylpheed_formats::{hash::name_hash, media, PakArchive};
|
use sylpheed_formats::{hash::name_hash, media, PakArchive};
|
||||||
let vlang = match lang.pak_code() {
|
let vlang = match lang.pak_code() {
|
||||||
"jpn" => sylpheed_formats::slb::VoiceLang::Japanese,
|
"jpn" => sylpheed_formats::slb::VoiceLang::Japanese,
|
||||||
@@ -4934,7 +4950,7 @@ fn handle_cutscene_cues_request(
|
|||||||
named_range,
|
named_range,
|
||||||
region,
|
region,
|
||||||
})
|
})
|
||||||
})();
|
};
|
||||||
|
|
||||||
let _ = sender.send(IsoLoaderMsg::CutsceneCuesLoaded {
|
let _ = sender.send(IsoLoaderMsg::CutsceneCuesLoaded {
|
||||||
generation,
|
generation,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ pub fn run() {
|
|||||||
DefaultPlugins.set(WindowPlugin {
|
DefaultPlugins.set(WindowPlugin {
|
||||||
primary_window: Some(Window {
|
primary_window: Some(Window {
|
||||||
title: "Project Sylpheed: Arc of Deception — Asset Viewer".into(),
|
title: "Project Sylpheed: Arc of Deception — Asset Viewer".into(),
|
||||||
resolution: (1280.0, 720.0).into(),
|
resolution: (1280.0_f32, 720.0_f32).into(),
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
..default()
|
..default()
|
||||||
|
|||||||
@@ -157,6 +157,12 @@ struct UiEvents<'w> {
|
|||||||
cutscenes: EventWriter<'w, RequestCutscenes>,
|
cutscenes: EventWriter<'w, RequestCutscenes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bevy system: every parameter is a `Res`/`ResMut`/`EventWriter` the
|
||||||
|
// scheduler injects. The count is the framework's dependency list, not a
|
||||||
|
// signature anyone calls by hand, and it cannot be reduced without
|
||||||
|
// bundling into a `SystemParam` struct. clippy's general heuristic does
|
||||||
|
// not know about the idiom.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn draw_viewer_ui(
|
fn draw_viewer_ui(
|
||||||
mut contexts: EguiContexts,
|
mut contexts: EguiContexts,
|
||||||
mut viewer: ResMut<ViewerState>,
|
mut viewer: ResMut<ViewerState>,
|
||||||
@@ -656,7 +662,7 @@ fn draw_viewer_ui(
|
|||||||
ui.strong("Notes");
|
ui.strong("Notes");
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
|
|
||||||
let mut row = |ui: &mut egui::Ui, fmt: &str, c, status: &str, notes: &str| {
|
let row = |ui: &mut egui::Ui, fmt: &str, c, status: &str, notes: &str| {
|
||||||
ui.label(fmt);
|
ui.label(fmt);
|
||||||
ui.colored_label(c, status);
|
ui.colored_label(c, status);
|
||||||
ui.label(notes);
|
ui.label(notes);
|
||||||
@@ -1236,7 +1242,7 @@ fn draw_video_player(
|
|||||||
// Subtitle + voice controls (right-aligned): language, CC, and Voice.
|
// Subtitle + voice controls (right-aligned): language, CC, and Voice.
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
let before = subs.lang;
|
let before = subs.lang;
|
||||||
egui::ComboBox::from_id_source("subtitle_lang")
|
egui::ComboBox::from_id_salt("subtitle_lang")
|
||||||
.selected_text(subs.lang.label())
|
.selected_text(subs.lang.label())
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
for lang in SubLang::ALL {
|
for lang in SubLang::ALL {
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ neither its own branch nor `main`.
|
|||||||
|
|
||||||
## Checks that were kind once
|
## Checks that were kind once
|
||||||
|
|
||||||
Two rules that look unrelated and are the same failure.
|
Three instances now, and they are the same failure.
|
||||||
|
|
||||||
**A check may only soften against a condition it can test.**
|
**A check may only soften against a condition it can test.**
|
||||||
|
|
||||||
@@ -189,6 +189,45 @@ a collaborator exists, so the "yet" was never needed.
|
|||||||
stays. That is why they survive review, and why the smell is worth naming:
|
stays. That is why they survive review, and why the smell is worth naming:
|
||||||
*leniency with an expiry date nobody set.*
|
*leniency with an expiry date nobody set.*
|
||||||
|
|
||||||
|
### The third instance was authored dirty, not decayed into
|
||||||
|
|
||||||
|
The two above were **correct when written**. The third was not, and it is worth
|
||||||
|
separating because it arrives by a different route and is caught at a different
|
||||||
|
moment.
|
||||||
|
|
||||||
|
CI's `Clippy` step turned out never to have run — the toolchain shipped without
|
||||||
|
the component, so `cargo clippy -- -D warnings` died on *"not installed"* on
|
||||||
|
every commit in the repo's history. Fixing that is two lines. But the tree is
|
||||||
|
not clippy-clean: the build already emits ~13 rustc warnings that `-D warnings`
|
||||||
|
promotes to errors. So the fix and the first red result arrive together, and the
|
||||||
|
first draft paired the two-line fix with `continue-on-error: true` and a comment
|
||||||
|
saying *delete this line once the debt is paid* — which is precisely an expiry
|
||||||
|
date nobody set. It was reverted within the hour, on reading #12's own closing
|
||||||
|
line ruling the same shape out for rustfmt.
|
||||||
|
|
||||||
|
The difference that matters:
|
||||||
|
|
||||||
|
| | first two | third |
|
||||||
|
|---|---|---|
|
||||||
|
| when it was wrong | became wrong later | wrong on the first commit |
|
||||||
|
| what caused it | the world moved | the tree was already dirty |
|
||||||
|
| what catches it | auditing old allowances | noticing the impulse at the keyboard |
|
||||||
|
|
||||||
|
**This is the default way a check gets written when the tree is not clean yet.**
|
||||||
|
Not a rare slip — the ordinary shape of the first draft. Whenever a real check
|
||||||
|
goes in against a tree that does not yet pass it, the softening is *right there*,
|
||||||
|
it looks like pragmatism, and it comes with a sincere comment promising removal.
|
||||||
|
The mechanical test still catches it after the fact. The earlier tell is this:
|
||||||
|
|
||||||
|
> **If you are writing the softening in the same commit as the check, the thing
|
||||||
|
> you want is an issue, not a flag.**
|
||||||
|
|
||||||
|
A red check that measures something is worth more than a green one that measures
|
||||||
|
nothing, and it is worth strictly more than a green one that *used to* measure
|
||||||
|
something. Land the check gating, let it be red, and scope the debt where it can
|
||||||
|
be read, argued with and closed — #12 for rustfmt, #13 for clippy. An issue has
|
||||||
|
the expiry date the flag never gets.
|
||||||
|
|
||||||
`share put <file> --note "…" --for port` records the sender, the time, **the
|
`share put <file> --note "…" --for port` records the sender, the time, **the
|
||||||
commit they were on**, and whether their tree was dirty. A capture with no
|
commit they were on**, and whether their tree was dirty. A capture with no
|
||||||
provenance is not evidence, it is a picture.
|
provenance is not evidence, it is a picture.
|
||||||
|
|||||||
Reference in New Issue
Block a user