1use crate::rcvbp;
4use crate::util::{hex, hexdump, warn};
5use crate::{Loader, Progress};
6use anyhow::{Context, Result};
7use panelspec::PanelSpec;
8use receivers::CardModel;
9use std::fmt::Write as _;
10
11fn parse_types(s: &str) -> Result<Vec<u16>> {
13 s.split(',')
14 .map(str::trim)
15 .filter(|t| !t.is_empty())
16 .map(|t| {
17 u16::from_str_radix(t.trim_start_matches("0x"), 16)
18 .with_context(|| format!("bad record type {t:?}"))
19 })
20 .collect()
21}
22
23pub fn config_build(
24 base: &str,
25 copy_from: Option<&str>,
26 copy: &str,
27 remove: &str,
28 out: &str,
29 p: &mut dyn Progress,
30) -> Result<()> {
31 let mut cfg = rcvbp::Rcvbp::load(base)?;
32
33 let to_copy = parse_types(copy)?;
34 if !to_copy.is_empty() {
35 let src_path = copy_from.context("--copy needs --copy-from")?;
36 let src = rcvbp::Rcvbp::load(src_path)?;
37 for t in to_copy {
38 let rec = src
39 .find(t)
40 .with_context(|| format!("{src_path} has no record 0x{t:04x}"))?;
41 cfg.upsert(t, rec.payload.clone());
42 }
43 }
44
45 for t in parse_types(remove)? {
46 if !cfg.remove(t) {
47 warn(p, format!("{base} has no record 0x{t:04x} to remove"));
48 }
49 }
50
51 cfg.save(out)?;
52
53 let back = rcvbp::Rcvbp::load(out)?;
55 anyhow::ensure!(
56 back.records.len() == cfg.records.len(),
57 "{out}: wrote {} records but read back {}",
58 cfg.records.len(),
59 back.records.len()
60 );
61 p.out(out);
62 Ok(())
63}
64
65pub fn config_diff(a: &str, b: &str, p: &mut dyn Progress) -> Result<()> {
66 let fa = rcvbp::Rcvbp::load(a)?;
67 let fb = rcvbp::Rcvbp::load(b)?;
68 p.out(&format!("{a}: {} records", fa.records.len()));
69 p.out(&format!("{b}: {} records", fb.records.len()));
70
71 let types_a: Vec<u16> = fa.records.iter().map(rcvbp::Record::type_u16).collect();
72 let types_b: Vec<u16> = fb.records.iter().map(rcvbp::Record::type_u16).collect();
73 let only_a: Vec<String> = types_a
74 .iter()
75 .filter(|t| !types_b.contains(t))
76 .map(|t| format!("0x{t:04x}"))
77 .collect();
78 let only_b: Vec<String> = types_b
79 .iter()
80 .filter(|t| !types_a.contains(t))
81 .map(|t| format!("0x{t:04x}"))
82 .collect();
83 if !only_a.is_empty() {
84 p.out(&format!("only in {a}: {}", only_a.join(", ")));
85 }
86 if !only_b.is_empty() {
87 p.out(&format!("only in {b}: {}", only_b.join(", ")));
88 }
89
90 for t in &types_a {
91 let (Some(ra), Some(rb)) = (fa.find(*t), fb.find(*t)) else {
92 continue;
93 };
94 if ra.payload == rb.payload {
95 continue;
96 }
97 let diffs: Vec<usize> = ra
98 .payload
99 .iter()
100 .zip(&rb.payload)
101 .enumerate()
102 .filter(|(_, (x, y))| x != y)
103 .map(|(i, _)| i)
104 .collect();
105 p.out(&format!(
106 "record 0x{t:04x}: {} vs {} bytes, {} differ",
107 ra.payload.len(),
108 rb.payload.len(),
109 diffs.len()
110 ));
111 for i in diffs.iter().take(16) {
112 p.out(&format!(
113 " +0x{i:03x}: {:3} (0x{:02x}) vs {:3} (0x{:02x})",
114 ra.payload[*i], ra.payload[*i], rb.payload[*i], rb.payload[*i]
115 ));
116 }
117 if diffs.len() > 16 {
118 p.out(&format!(" ... and {} more", diffs.len() - 16));
119 }
120 }
121 Ok(())
122}
123
124pub fn rcvbp_info(path: &str, dump: bool, p: &mut dyn Progress) -> Result<()> {
125 let f = rcvbp::Rcvbp::load(path)?;
126 p.out(&format!(
127 "{path}\n version {}, {} bytes decompressed, {} records",
128 f.version,
129 f.to_blob()?.len(),
130 f.records.len()
131 ));
132 if let Some((w, _)) = f.geometry() {
133 p.out(&format!(" cabinet width: {w}"));
134 }
135 if let Some(scan) = f.scan() {
136 p.out(&format!(" scan: 1/{scan}"));
137 }
138 if let Some((w, scan)) = f.main_geometry() {
139 p.out(&format!(" main param block: width {w}, scan 1/{scan}"));
140 }
141 p.out(&format!(
142 "\n{:>8} {:>7} {:>7} {:>8} description",
143 "offset", "type", "bytes", "nonzero"
144 ));
145 for r in &f.records {
146 let nz = r.payload.iter().filter(|&&b| b != 0).count();
147 p.out(&format!(
148 "0x{:06x} 0x{:04x} {:7} {:8} {}",
149 r.offset,
150 r.type_u16(),
151 r.payload.len(),
152 nz,
153 r.describe()
154 ));
155 }
156 if dump {
157 for r in &f.records {
158 if r.is_empty_table() {
159 continue;
160 }
161 p.out(&format!(
162 "\n=== record 0x{:04x} ({} bytes)",
163 r.type_u16(),
164 r.payload.len()
165 ));
166 hexdump(p, &r.payload[..r.payload.len().min(512)]);
167 }
168 }
169 Ok(())
170}
171
172pub fn list_formats(p: &mut dyn Progress) {
174 p.out(&format!("{:<8} {:<12} {:<10} {:<9} import", "format", "vendor", "extension", "generate"));
175 let yes_no = |b: bool| if b { "yes" } else { "no" };
176 for f in rcvbp::formats() {
177 p.out(&format!(
178 "{:<8} {:<12} .{:<9} {:<9} {}",
179 f.name,
180 f.vendor,
181 f.extension,
182 yes_no(f.generate),
183 yes_no(f.import)
184 ));
185 }
186}
187
188pub fn import_config(path: &str, out: &str, format: Option<&str>, p: &mut dyn Progress) -> Result<()> {
195 let bytes = std::fs::read(path).with_context(|| format!("read {path}"))?;
196 let codec = match format {
197 Some(name) => rcvbp::codec(name)?,
198 None => rcvbp::detect(&bytes).with_context(|| path.to_owned())?,
199 };
200 let chips = |id: u16| panelspec::embedded::chip_by_family(id).map(|(p, t)| (p.to_owned(), t.to_owned()));
201 let (mut spec, unresolved) = codec.import(&bytes, &chips).with_context(|| path.to_owned())?;
202 spec.name = std::path::Path::new(path)
203 .file_stem()
204 .map_or_else(|| spec.name.clone(), |s| s.to_string_lossy().into_owned());
205 std::fs::write(out, spec.to_toml()?).with_context(|| format!("write {out}"))?;
206 for u in &unresolved {
207 warn(p, format!("{path}: not recovered: {u}"));
208 }
209 p.out(out);
210 Ok(())
211}
212
213pub struct GenOutputs {
215 pub name: String,
217 pub format: rcvbp::Format,
219 pub rcvbp: Vec<u8>,
221 pub basic_pack: Vec<u8>,
223 pub block7: Option<Vec<u8>>,
226 pub sources: Vec<String>,
228 pub notes: Vec<String>,
230 pub report: String,
232 pub paths: Vec<String>,
235}
236
237pub fn generate(
248 card: &CardModel,
249 spec: &PanelSpec,
250 label: &str,
251 format: &str,
252 load: Loader,
253) -> Result<GenOutputs> {
254 let format = rcvbp::codec(format)?.format();
257 let g = rcvbp::spec::generate(spec, &spec.chip_library(load)?)?;
258 let rcvbp = g.rcvbp.to_file_bytes()?;
259
260 let mut notes = Vec::new();
261 let block7 = match rcvbp::image::compile(&card.memory.boot_image, spec, &g) {
262 Ok(b) => {
263 notes.extend(b.notes);
264 notes.push(format!(
265 "pages written: {}: {}",
266 b.changed_pages.len(),
267 hex(&b.changed_pages, " ")
268 ));
269 Some(b.image)
270 }
271 Err(e) => {
272 notes.push(format!("{e:#}"));
273 None
274 }
275 };
276
277 let mut report = String::new();
278 let _ = writeln!(report, "spec: {label}\n\n# record and pack sources");
279 for line in &g.sources {
280 report.push_str(line);
281 report.push('\n');
282 }
283 report.push_str("\n# compiled image\n");
284 for n in ¬es {
285 report.push_str(n);
286 report.push('\n');
287 }
288
289 Ok(GenOutputs {
290 name: spec.name.clone(),
291 format,
292 rcvbp,
293 basic_pack: g.basic_pack.to_vec(),
294 block7,
295 sources: g.sources,
296 notes,
297 report,
298 paths: Vec::new(),
299 })
300}
301
302pub fn gen_config(
306 card: &CardModel,
307 spec_path: &str,
308 out_dir: &str,
309 format: &str,
310 load: Loader,
311 p: &mut dyn Progress,
312) -> Result<GenOutputs> {
313 let spec = PanelSpec::load(spec_path)?;
314 let mut g = generate(card, &spec, spec_path, format, load)?;
315
316 std::fs::create_dir_all(out_dir).with_context(|| format!("create {out_dir}"))?;
317 let stem = format!("{out_dir}/{}", g.name);
318 let rcvbp_path = format!("{stem}.{}", g.format.extension);
319 std::fs::write(&rcvbp_path, &g.rcvbp).with_context(|| format!("write {rcvbp_path}"))?;
320 let pack_path = format!("{stem}-basic-pack.bin");
321 std::fs::write(&pack_path, &g.basic_pack).with_context(|| format!("write {pack_path}"))?;
322
323 let Some(img) = &g.block7 else {
324 anyhow::bail!("{}", g.notes.last().map_or("", String::as_str));
325 };
326 let img_path = format!("{stem}-block7.bin");
327 std::fs::write(&img_path, img).with_context(|| format!("write {img_path}"))?;
328 let report_path = format!("{stem}-sources.txt");
329 std::fs::write(&report_path, &g.report).with_context(|| format!("write {report_path}"))?;
330
331 p.out(&format!(
332 "{rcvbp_path}\n{pack_path}\n{img_path}\n{report_path}"
333 ));
334 g.paths = vec![rcvbp_path, pack_path, img_path, report_path];
335 Ok(g)
336}