use opticaldiscs::browse::open_disc_filesystem;
use opticaldiscs::detect::DiscImageInfo;
use opticaldiscs::formats::FilesystemType;
use opticaldiscs::gameid::Console;
const BLOCK: usize = 2048;
fn be32(v: u32) -> [u8; 4] {
v.to_be_bytes()
}
fn entry(type_tag: &[u8; 4], name: &str, block: u32, byte_count: u32, last: bool) -> Vec<u8> {
let mut e = vec![0u8; 68 + 4];
let flags: u32 = if last { 0x8000_0000 } else { 0 } | 0x02;
e[0..4].copy_from_slice(&be32(flags));
e[8..12].copy_from_slice(type_tag);
e[12..16].copy_from_slice(&be32(BLOCK as u32)); e[16..20].copy_from_slice(&be32(byte_count));
e[20..24].copy_from_slice(&be32(byte_count.div_ceil(BLOCK as u32).max(1)));
let nb = name.as_bytes();
e[32..32 + nb.len()].copy_from_slice(nb);
e[64..68].copy_from_slice(&be32(0)); e[68..72].copy_from_slice(&be32(block)); e
}
fn dir_block(entries: &[Vec<u8>]) -> Vec<u8> {
let mut blk = vec![0u8; BLOCK];
blk[0..4].copy_from_slice(&be32(u32::MAX)); blk[4..8].copy_from_slice(&be32(u32::MAX)); let first_entry = 20u32;
let mut pos = first_entry as usize;
for e in entries {
blk[pos..pos + e.len()].copy_from_slice(e);
pos += e.len();
}
blk[12..16].copy_from_slice(&be32(pos as u32)); blk[16..20].copy_from_slice(&be32(first_entry));
blk
}
fn build_opera_image() -> Vec<u8> {
let mut img = vec![0u8; 24 * BLOCK];
let vh = &mut img[0..BLOCK];
vh[0] = 0x01;
vh[1..6].copy_from_slice(&[0x5A; 5]);
vh[6] = 0x01; vh[40..49].copy_from_slice(b"SYNTH 3DO"); vh[0x4C..0x50].copy_from_slice(&be32(BLOCK as u32)); vh[0x58..0x5C].copy_from_slice(&be32(1)); vh[0x64..0x68].copy_from_slice(&be32(20));
let root = dir_block(&[
entry(b" ", "HELLO", 21, 11, false),
entry(b"*dir", "SUB", 22, BLOCK as u32, true),
]);
img[20 * BLOCK..21 * BLOCK].copy_from_slice(&root);
img[21 * BLOCK..21 * BLOCK + 11].copy_from_slice(b"hello 3do!!");
let sub = dir_block(&[entry(b" ", "INNER", 23, 5, true)]);
img[22 * BLOCK..23 * BLOCK].copy_from_slice(&sub);
img[23 * BLOCK..23 * BLOCK + 5].copy_from_slice(b"inner");
img
}
#[test]
fn opera_detect_and_browse() {
let img = build_opera_image();
use std::io::Write;
let mut f = tempfile::Builder::new().suffix(".iso").tempfile().unwrap();
f.write_all(&img).unwrap();
f.flush().unwrap();
let info = DiscImageInfo::open(f.path()).expect("open");
assert_eq!(info.filesystem, FilesystemType::Opera);
assert_eq!(info.volume_label.as_deref(), Some("SYNTH 3DO"));
assert_eq!(info.game.as_ref().unwrap().console, Console::ThreeDo);
let mut fs = open_disc_filesystem(&info).expect("open fs");
assert_eq!(fs.volume_name(), Some("SYNTH 3DO"));
let root = fs.root().unwrap();
let mut top = fs.list_directory(&root).unwrap();
top.sort_by(|a, b| a.name.cmp(&b.name));
let names: Vec<&str> = top.iter().map(|e| e.name.as_str()).collect();
assert_eq!(names, vec!["HELLO", "SUB"]);
let hello = top.iter().find(|e| e.name == "HELLO").unwrap();
assert!(!hello.is_directory());
assert_eq!(fs.read_file(hello).unwrap(), b"hello 3do!!");
let sub = top.iter().find(|e| e.name == "SUB").unwrap();
assert!(sub.is_directory());
let inner = fs.list_directory(sub).unwrap();
assert_eq!(inner.len(), 1);
assert_eq!(inner[0].name, "INNER");
assert_eq!(fs.read_file(&inner[0]).unwrap(), b"inner");
}