use std::io::{BufRead, Write};
use std::path::{Path, PathBuf};
use anyhow::{Context, Result, bail};
use saturnus_core::Model;
use saturnus_drive::fetch::{self, Fetched, Wanted};
use saturnus_host::romid::{self, KnownRom};
pub use saturnus_drive::rom::load;
pub use saturnus_host::romid::revision;
pub fn fetch(model: Model, dir: &Path, yes: bool) -> Result<PathBuf> {
let Some(known) = romid::download(model) else {
bail!(
"there is no download for the {}: HP never published its ROM. Read the ROM \
out of your own calculator and pass the file with --rom",
model.name().to_uppercase()
);
};
let wanted = Wanted::from(known);
let target = dir.join(known.file);
let present =
saturnus_drive::files::read_capped(&target, saturnus_drive::files::max_rom_file())
.is_ok_and(|d| fetch::verify(&wanted, &d).is_ok());
if !present && target.exists() {
bail!(
"{} is not the expected ROM file (wrong size or SHA-256); delete it and \
run this again",
target.display()
);
}
if !present && !yes && !confirm(known, dir)? {
bail!("download cancelled");
}
let got = fetch::fetch(&wanted, dir, false)
.map_err(anyhow::Error::msg)
.with_context(|| {
format!(
"cannot download the {} ROM; download it yourself from {}",
model.name().to_uppercase(),
known.page
)
})?;
let what = match got {
Fetched::Present(_) => "is present and verified",
Fetched::Downloaded(_) => "downloaded and verified",
};
println!("{} {what} (SHA-256 {})", got.path().display(), known.sha256);
Ok(got.path().to_path_buf())
}
fn confirm(known: &KnownRom, dir: &Path) -> Result<bool> {
print!(
"Download {} into {}?\nThe ROM is HP's copyrighted software, hosted by hpcalc.org with \
HP's permission for use with emulators; it is not part of saturnus. [y/N] ",
known.url,
dir.display()
);
std::io::stdout().flush().context("cannot write prompt")?;
let mut answer = String::new();
std::io::stdin()
.lock()
.read_line(&mut answer)
.context("cannot read the answer")?;
Ok(matches!(answer.trim(), "y" | "Y" | "yes" | "YES" | "Yes"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_pioneers_have_no_download() {
for m in [Model::Hp27s, Model::Hp17bii, Model::Hp17b, Model::Hp28s] {
let e = fetch(m, Path::new("unused"), true).unwrap_err().to_string();
assert!(e.contains("your own calculator"), "{e}");
}
let e = fetch(Model::Hp42s, Path::new("unused"), true).unwrap_err();
assert!(e.to_string().contains("--rom"), "{e}");
}
#[test]
fn a_bad_existing_file_is_refused() {
let dir = std::env::temp_dir().join(format!("saturnus-cli-rom-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("sxrom-j"), [0u8; 10]).unwrap();
let e = fetch(Model::Hp48sx, &dir, false).unwrap_err();
assert!(e.to_string().contains("delete it"), "{e}");
std::fs::remove_dir_all(&dir).unwrap();
}
}