Compare commits
4 Commits
fix/corpus
...
pi/reauth3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
addbafdf7b | ||
|
|
06a90add7b | ||
|
|
b5705043bb | ||
|
|
4ac5c9f419 |
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@@ -42,7 +42,15 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Cache Cargo registry and build
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -69,6 +77,20 @@ jobs:
|
||||
- name: Run tests
|
||||
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
|
||||
run: cargo clippy --workspace -- -D warnings
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ use anyhow::{Context, Result};
|
||||
use clap::{Parser, Subcommand};
|
||||
use colored::*;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use tracing::info;
|
||||
|
||||
use sylpheed_formats::vfs::{identify_format, GameAssets};
|
||||
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(
|
||||
pak: &Path,
|
||||
output: &Path,
|
||||
@@ -892,7 +896,7 @@ fn cmd_sniff(dir: &Path, unknown_only: bool) -> Result<()> {
|
||||
println!();
|
||||
println!("{}", "Format Summary:".bold());
|
||||
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 {
|
||||
println!(
|
||||
" {:>6} .{}",
|
||||
@@ -1034,7 +1038,7 @@ fn cmd_mesh_info(file: &Path) -> Result<()> {
|
||||
let nv = sub.positions.len();
|
||||
let mut referenced = vec![false; nv];
|
||||
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]);
|
||||
imax = imax.max(a).max(b).max(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
|
||||
.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])))
|
||||
.collect();
|
||||
maxedges.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
@@ -1220,7 +1224,7 @@ fn cmd_mesh_render(
|
||||
let med = {
|
||||
let mut e: Vec<f32> = sub
|
||||
.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)
|
||||
.map(|t| {
|
||||
let d = |a: u32, b: u32| {
|
||||
@@ -1254,7 +1258,7 @@ fn cmd_mesh_render(
|
||||
(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);
|
||||
if a < n && b < n && c < n {
|
||||
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).
|
||||
// Emit RGBA. X8 has no meaningful alpha.
|
||||
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[1] = px[2]; // G
|
||||
out[2] = px[3]; // B
|
||||
@@ -1690,12 +1694,12 @@ fn cmd_pak_textures(pak: &Path, output: &Path, verbose: bool) -> Result<()> {
|
||||
let mut idx = 0usize;
|
||||
while let Some(pos) = payload[off..]
|
||||
.windows(4)
|
||||
.position(|w| w == &t8ad::T8AD_MAGIC)
|
||||
.position(|w| w == t8ad::T8AD_MAGIC)
|
||||
{
|
||||
let start = off + pos;
|
||||
let next = payload[start + 4..]
|
||||
.windows(4)
|
||||
.position(|w| w == &t8ad::T8AD_MAGIC)
|
||||
.position(|w| w == t8ad::T8AD_MAGIC)
|
||||
.map(|p| start + 4 + p)
|
||||
.unwrap_or(payload.len());
|
||||
emit_t8ad(
|
||||
|
||||
@@ -109,6 +109,8 @@ pub struct BgmSpec {
|
||||
pub loop_start_s: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub loop_end_s: Option<f64>,
|
||||
// Deserialised to model the sidecar schema, not read in Rust.
|
||||
#[allow(dead_code)]
|
||||
#[serde(default)]
|
||||
pub loop_end_why: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
|
||||
@@ -158,6 +158,9 @@ fn load_names(authored: &Path) -> Result<NameMap> {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct File {
|
||||
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)]
|
||||
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
|
||||
// still stops existing.
|
||||
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?;
|
||||
if entry.file_name() == "video" {
|
||||
continue;
|
||||
@@ -288,7 +291,7 @@ fn run_export(disc: &Path, out: &Path, authored_dir: &Path) -> Result<()> {
|
||||
.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 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),
|
||||
};
|
||||
let ex = screen::export_build(
|
||||
&out,
|
||||
out,
|
||||
archive,
|
||||
*entry,
|
||||
build_idx,
|
||||
|
||||
@@ -226,9 +226,10 @@ fn parse_riff_wave(bytes: &[u8]) -> Option<AudioInfo> {
|
||||
match codec {
|
||||
AudioCodec::Pcm | AudioCodec::PcmFloat => {
|
||||
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);
|
||||
if frame > 0 {
|
||||
let spc = d / frame;
|
||||
if let Some(spc) = d.checked_div(frame) {
|
||||
info.samples_per_channel = Some(spc);
|
||||
if rate > 0 {
|
||||
info.duration_secs = Some(spc as f32 / rate as f32);
|
||||
@@ -275,11 +276,11 @@ impl GameAudio {
|
||||
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, 16) => data
|
||||
.chunks_exact(2)
|
||||
.as_chunks::<2>().0.iter()
|
||||
.map(|c| i16::from_le_bytes([c[0], c[1]]) as f32 / 32768.0)
|
||||
.collect(),
|
||||
(AudioCodec::Pcm, 24) => data
|
||||
.chunks_exact(3)
|
||||
.as_chunks::<3>().0.iter()
|
||||
.map(|c| {
|
||||
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
|
||||
@@ -287,7 +288,7 @@ impl GameAudio {
|
||||
})
|
||||
.collect(),
|
||||
(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]]))
|
||||
.collect(),
|
||||
_ => 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 slots: Vec<&str> = f.positional.iter().map(|(_, v)| v.as_str()).collect();
|
||||
let members = slots
|
||||
.chunks_exact(4)
|
||||
.as_chunks::<4>().0.iter()
|
||||
.map(|m| SquadronMember {
|
||||
unit: m[0].to_string(),
|
||||
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).
|
||||
let mut bytes = name.as_bytes().to_vec();
|
||||
for byte in &mut bytes {
|
||||
if (b'A'..=b'Z').contains(byte) {
|
||||
if byte.is_ascii_uppercase() {
|
||||
*byte += 0x20;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ pub fn parse(bytes: &[u8]) -> Option<Subtitle> {
|
||||
fn utf16be_tokens(bytes: &[u8]) -> Vec<String> {
|
||||
let mut tokens = Vec::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]]);
|
||||
match char::from_u32(u as u32) {
|
||||
Some(c) if !c.is_control() => cur.push(c),
|
||||
|
||||
@@ -700,7 +700,7 @@ impl Xbg7Model {
|
||||
let (vc, ic) = r.markers[0];
|
||||
let sig = (r.decl.stride, vc, ic);
|
||||
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(
|
||||
bytes,
|
||||
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`,
|
||||
/// reference (nearly) all vertices, and produce non-degenerate triangles with a
|
||||
/// 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(
|
||||
bytes: &[u8],
|
||||
starts: &[usize],
|
||||
@@ -1406,7 +1413,7 @@ fn anchor_pool_mesh(
|
||||
}
|
||||
let (degen, wind) = index_run_quality(bytes, ib, vb, index_count, decl);
|
||||
let cand = (degen, -wind, pad);
|
||||
if best.map_or(true, |b| cand < b) {
|
||||
if best.is_none_or(|b| cand < b) {
|
||||
best = Some(cand);
|
||||
}
|
||||
}
|
||||
@@ -1452,6 +1459,11 @@ fn anchor_pool_mesh(
|
||||
/// for both the adjacency anchor ([`anchor_pool_mesh`]) and the grouped-pool
|
||||
/// anchor ([`anchor_grouped_meshes`]); the two differ only in how they *place*
|
||||
/// `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(
|
||||
bytes: &[u8],
|
||||
ib: usize,
|
||||
@@ -1478,6 +1490,8 @@ fn validate_block(
|
||||
/// [`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
|
||||
/// 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(
|
||||
bytes: &[u8],
|
||||
ib: usize,
|
||||
@@ -1679,7 +1693,7 @@ fn anchor_grouped_meshes(
|
||||
if n == 0 || decls.len() < n {
|
||||
return Vec::new();
|
||||
}
|
||||
let decl = &decls[0];
|
||||
let _decl = &decls[0];
|
||||
|
||||
// Relative index-buffer offsets (ib0 = 0), 4-byte aligned between buffers,
|
||||
// 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 ib = ib0 + rel_ib[i];
|
||||
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;
|
||||
}
|
||||
@@ -1784,7 +1798,7 @@ fn anchor_grouped_meshes(
|
||||
}
|
||||
let (degen, wind) = index_run_quality(bytes, ib_k, vb_k, ick, &decls[kmax]);
|
||||
let cand = (degen, -wind, pad);
|
||||
if best.map_or(true, |b| cand < b) {
|
||||
if best.is_none_or(|b| cand < b) {
|
||||
best = Some(cand);
|
||||
}
|
||||
if pad_first_match() {
|
||||
@@ -1797,7 +1811,7 @@ fn anchor_grouped_meshes(
|
||||
if meshes.len() == n {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1929,7 +1943,7 @@ fn find_index_marker(desc: &[u8]) -> Option<(usize, usize)> {
|
||||
while rel + 40 <= desc.len() {
|
||||
let a = be32(desc, rel);
|
||||
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));
|
||||
}
|
||||
rel += 4;
|
||||
@@ -1951,7 +1965,7 @@ fn all_index_markers(desc: &[u8]) -> Vec<(usize, usize)> {
|
||||
while rel + 8 <= desc.len() {
|
||||
let a = be32(desc, rel);
|
||||
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;
|
||||
if (3..=200_000).contains(&vc) {
|
||||
out.push((vc, c as usize));
|
||||
@@ -1979,7 +1993,7 @@ fn all_vertex_decls(desc: &[u8]) -> Vec<VertexDecl> {
|
||||
while rel + 8 <= desc.len() {
|
||||
let a = be32(desc, rel);
|
||||
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;
|
||||
if (3..=200_000).contains(&vc) {
|
||||
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);
|
||||
if (3..=65535).contains(&a)
|
||||
&& z == 0
|
||||
&& c >= 3
|
||||
&& c <= 200_000
|
||||
&& c % 3 == 0
|
||||
&& (3..=200_000).contains(&c)
|
||||
&& c.is_multiple_of(3)
|
||||
&& (1..=64).contains(&t)
|
||||
{
|
||||
out.push((a as usize, c as usize));
|
||||
@@ -2151,7 +2164,7 @@ fn topology_report(
|
||||
let mut agree = 0usize;
|
||||
let mut counted = 0usize;
|
||||
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);
|
||||
if a == b || b == c || a == c {
|
||||
degen += 1;
|
||||
@@ -2330,7 +2343,7 @@ pub fn submesh_albedos(bytes: &[u8], resource_name: &str) -> Vec<(usize, usize,
|
||||
if (3..=65535).contains(&vtx)
|
||||
&& z == 0
|
||||
&& idx >= 3
|
||||
&& idx % 3 == 0
|
||||
&& idx.is_multiple_of(3)
|
||||
&& idx < 400_000
|
||||
&& tail > 0
|
||||
&& tail < 0x10_0000
|
||||
@@ -2423,7 +2436,7 @@ pub fn material_groups(bytes: &[u8], resource_name: &str, target: usize) -> Vec<
|
||||
if (1..=70000).contains(&vtx)
|
||||
&& tail == 4
|
||||
&& cnt >= 3
|
||||
&& cnt % 3 == 0
|
||||
&& cnt.is_multiple_of(3)
|
||||
&& cnt < 400_000
|
||||
&& (off as usize) < 4_000_000
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! table (statically reversed). Beyond the movie→voice binding, it defines the
|
||||
//! whole cutscene *structure*: which video (+ subtitle, + on-screen text overlay,
|
||||
//! + 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`)
|
||||
//!
|
||||
|
||||
@@ -96,7 +96,7 @@ pub fn find_descriptor_before(buf: &[u8], before: usize) -> Option<usize> {
|
||||
let mut o = (before - 1).min(cap) & !3;
|
||||
loop {
|
||||
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);
|
||||
}
|
||||
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 consts: Vec<(usize, [f64; 4])> = Vec::new();
|
||||
|
||||
let mut flush = |base: u32,
|
||||
let flush = |base: u32,
|
||||
size: u32,
|
||||
stride: u32,
|
||||
pos: &mut Vec<[f32; 3]>,
|
||||
@@ -426,7 +426,7 @@ pub fn correlate(
|
||||
let Some((score, mirrored)) = pos_validate(d, &key.ref_pos) else {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,7 +517,7 @@ pub fn to_xma_riff_best(slb: &[u8]) -> Option<Vec<u8>> {
|
||||
while let Some(di) = find(slb, b"data", i) {
|
||||
let declared = le32(slb, di + 4).unwrap_or(0) as usize;
|
||||
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));
|
||||
}
|
||||
i = di + 4;
|
||||
|
||||
@@ -587,19 +587,19 @@ pub fn apply_endian_swap(data: &mut [u8], endianness: u8) {
|
||||
match endianness {
|
||||
1 => {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
// 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(1, 3);
|
||||
}
|
||||
@@ -626,8 +626,8 @@ fn decode_surface(
|
||||
} else {
|
||||
// Linear layout — copy only the mip-0 slice.
|
||||
let block_size = format.block_size() as u32;
|
||||
let bw = ((width + block_size - 1) / block_size).max(1);
|
||||
let bh = ((height + block_size - 1) / block_size).max(1);
|
||||
let bw = width.div_ceil(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();
|
||||
if raw_data.len() < needed {
|
||||
return Err(TextureError::BufferTooSmall { needed, have: raw_data.len() });
|
||||
@@ -651,7 +651,7 @@ fn decode_surface(
|
||||
match format {
|
||||
X360TextureFormat::Dxt1 => swap_bc_block_dwords(&mut linear_data),
|
||||
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]);
|
||||
}
|
||||
}
|
||||
@@ -666,8 +666,8 @@ fn decode_surface(
|
||||
/// rounded up to the 4 KiB subresource alignment (`kTextureSubresourceAlignmentBytes`).
|
||||
fn tiled_face_stride(width: u32, height: u32, format: X360TextureFormat) -> usize {
|
||||
let bs = format.block_size() as u32;
|
||||
let bw = ((width + bs - 1) / bs).max(1);
|
||||
let bh = ((height + bs - 1) / bs).max(1);
|
||||
let bw = width.div_ceil(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 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();
|
||||
@@ -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
|
||||
/// 8-byte group are left untouched.
|
||||
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]
|
||||
let (lo, hi) = unit.split_at_mut(4);
|
||||
lo.swap_with_slice(hi);
|
||||
@@ -742,8 +742,8 @@ pub fn detile(
|
||||
let bpb_log2 = (bpb as u32).trailing_zeros();
|
||||
|
||||
// Visible dimensions in blocks, and the padded storage pitch/height.
|
||||
let blocks_wide = ((width + block_size - 1) / block_size).max(1);
|
||||
let blocks_tall = ((height + block_size - 1) / block_size).max(1);
|
||||
let blocks_wide = width.div_ceil(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 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
|
||||
// an in-mission screen.
|
||||
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);
|
||||
}
|
||||
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 {
|
||||
let units: Vec<u16> = bytes
|
||||
.chunks_exact(2)
|
||||
.as_chunks::<2>().0.iter()
|
||||
.map(|c| {
|
||||
if big_endian {
|
||||
u16::from_be_bytes([c[0], c[1]])
|
||||
|
||||
@@ -53,7 +53,7 @@ impl<F: Read + Seek + Send + Sync + 'static> XisoReader<F> {
|
||||
|
||||
info!(
|
||||
"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 })
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ pub fn x360_texture_to_bevy_image(tex: X360Texture) -> Result<Image, Xpr2LoadErr
|
||||
X360TextureFormat::A8R8G8B8 | X360TextureFormat::X8R8G8B8 => {
|
||||
let opaque = matches!(tex.format, X360TextureFormat::X8R8G8B8);
|
||||
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]);
|
||||
px[0] = r;
|
||||
px[1] = g;
|
||||
|
||||
@@ -2035,6 +2035,10 @@ fn decode_audio_wav(video: &Path, wav: &Path) -> Result<(), String> {
|
||||
|
||||
// ── 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
|
||||
/// 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
|
||||
@@ -2046,7 +2050,7 @@ fn spawn_video_decoder(
|
||||
h: u32,
|
||||
fps: f32,
|
||||
start: f32,
|
||||
) -> Result<(Child, mpsc::Receiver<(f32, Vec<u8>)>), String> {
|
||||
) -> Result<(Child, FrameRx), String> {
|
||||
let mut cmd = Command::new("ffmpeg");
|
||||
cmd.arg("-v").arg("error");
|
||||
if start > 0.0 {
|
||||
@@ -2177,6 +2181,12 @@ fn scrub_worker(
|
||||
|
||||
/// Polls the mpsc channel, updating `IsoState`, `FileBrowserState`, and
|
||||
/// `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"))]
|
||||
fn poll_loader_channel(
|
||||
channels: Res<IsoChannels>,
|
||||
@@ -2557,7 +2567,7 @@ fn pick_albedo_index(model_name: &str, tex_names: &[String]) -> Option<usize> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn compute_smooth_normals(positions: &[[f32; 3]], indices: &[u32]) -> Vec<[f32; 3]> {
|
||||
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);
|
||||
if a >= positions.len() || b >= positions.len() || c >= positions.len() {
|
||||
continue;
|
||||
@@ -3106,7 +3116,7 @@ fn prepare_models_impl(
|
||||
// Append the slice's triangles, re-emitting vertices per index
|
||||
// (no dedup) so each material buffer stays self-contained. A
|
||||
// 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 {
|
||||
[tri[0], tri[2], tri[1]]
|
||||
} else {
|
||||
@@ -3405,6 +3415,12 @@ fn apply_prepared_xpr(
|
||||
|
||||
/// Consumes a staged pack, freeing the previous texture/text previews and
|
||||
/// 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"))]
|
||||
fn apply_pak(
|
||||
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() })
|
||||
})
|
||||
.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.
|
||||
let rosters = gd::load_unit_rosters(&main);
|
||||
@@ -4742,7 +4758,7 @@ fn build_ship_model(
|
||||
*nrm = rot(&p.m, nrm);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -4911,7 +4927,7 @@ fn handle_cutscene_cues_request(
|
||||
// The naming convention is the obvious route and it is wrong often
|
||||
// enough to matter -- resolving the region is the only reading that
|
||||
// yields the right audio.
|
||||
let voice = (|| {
|
||||
let voice = {
|
||||
use sylpheed_formats::{hash::name_hash, media, PakArchive};
|
||||
let vlang = match lang.pak_code() {
|
||||
"jpn" => sylpheed_formats::slb::VoiceLang::Japanese,
|
||||
@@ -4934,7 +4950,7 @@ fn handle_cutscene_cues_request(
|
||||
named_range,
|
||||
region,
|
||||
})
|
||||
})();
|
||||
};
|
||||
|
||||
let _ = sender.send(IsoLoaderMsg::CutsceneCuesLoaded {
|
||||
generation,
|
||||
|
||||
@@ -50,7 +50,7 @@ pub fn run() {
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
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()
|
||||
|
||||
@@ -157,6 +157,12 @@ struct UiEvents<'w> {
|
||||
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(
|
||||
mut contexts: EguiContexts,
|
||||
mut viewer: ResMut<ViewerState>,
|
||||
@@ -656,7 +662,7 @@ fn draw_viewer_ui(
|
||||
ui.strong("Notes");
|
||||
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.colored_label(c, status);
|
||||
ui.label(notes);
|
||||
@@ -1236,7 +1242,7 @@ fn draw_video_player(
|
||||
// Subtitle + voice controls (right-aligned): language, CC, and Voice.
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let before = subs.lang;
|
||||
egui::ComboBox::from_id_source("subtitle_lang")
|
||||
egui::ComboBox::from_id_salt("subtitle_lang")
|
||||
.selected_text(subs.lang.label())
|
||||
.show_ui(ui, |ui| {
|
||||
for lang in SubLang::ALL {
|
||||
|
||||
@@ -167,7 +167,7 @@ neither its own branch nor `main`.
|
||||
|
||||
## 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.**
|
||||
|
||||
@@ -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:
|
||||
*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
|
||||
commit they were on**, and whether their tree was dirty. A capture with no
|
||||
provenance is not evidence, it is a picture.
|
||||
|
||||
Reference in New Issue
Block a user