From c17769db76a6effa4c439af78955002f089a73df Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Sun, 9 Jan 2022 11:16:46 +0100 Subject: [PATCH 1/1] LucasArts SMUSH formats support --- nihav-game/Cargo.toml | 6 +- nihav-game/src/codecs/mod.rs | 10 + nihav-game/src/codecs/smush/iact.rs | 211 ++++ nihav-game/src/codecs/smush/mod.rs | 120 +++ nihav-game/src/codecs/smush/v1.rs | 1417 +++++++++++++++++++++++++++ nihav-game/src/codecs/smush/v2.rs | 459 +++++++++ nihav-game/src/codecs/smush/vima.rs | 172 ++++ nihav-game/src/demuxers/mod.rs | 4 + nihav-game/src/demuxers/smush.rs | 564 +++++++++++ nihav-registry/src/detect.rs | 12 + nihav-registry/src/register.rs | 4 + 11 files changed, 2977 insertions(+), 2 deletions(-) create mode 100644 nihav-game/src/codecs/smush/iact.rs create mode 100644 nihav-game/src/codecs/smush/mod.rs create mode 100644 nihav-game/src/codecs/smush/v1.rs create mode 100644 nihav-game/src/codecs/smush/v2.rs create mode 100644 nihav-game/src/codecs/smush/vima.rs create mode 100644 nihav-game/src/demuxers/smush.rs diff --git a/nihav-game/Cargo.toml b/nihav-game/Cargo.toml index 8079a21..24a846e 100644 --- a/nihav-game/Cargo.toml +++ b/nihav-game/Cargo.toml @@ -18,7 +18,7 @@ nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, feature [features] default = ["all_decoders", "all_demuxers"] demuxers = [] -all_demuxers = ["demuxer_bmv", "demuxer_bmv3", "demuxer_fcmp", "demuxer_fst", "demuxer_gdv", "demuxer_imax", "demuxer_q", "demuxer_vmd", "demuxer_vx"] +all_demuxers = ["demuxer_bmv", "demuxer_bmv3", "demuxer_fcmp", "demuxer_fst", "demuxer_gdv", "demuxer_imax", "demuxer_q", "demuxer_smush", "demuxer_vmd", "demuxer_vx"] demuxer_bmv = ["demuxers"] demuxer_bmv3 = ["demuxers"] demuxer_fcmp = ["demuxers"] @@ -26,13 +26,14 @@ demuxer_fst = ["demuxers"] demuxer_gdv = ["demuxers"] demuxer_imax = ["demuxers"] demuxer_q = ["demuxers"] +demuxer_smush = ["demuxers"] demuxer_vmd = ["demuxers"] demuxer_vx = ["demuxers"] all_decoders = ["all_video_decoders", "all_audio_decoders"] decoders = [] -all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_imax", "decoder_ipma", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_vmd", "decoder_vx"] +all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_imax", "decoder_ipma", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_smush", "decoder_vmd", "decoder_vx"] decoder_bmv = ["decoders"] decoder_bmv3 = ["decoders"] decoder_fstvid = ["decoders"] @@ -42,6 +43,7 @@ decoder_ipma = ["decoders"] decoder_midivid = ["decoders"] decoder_midivid3 = ["decoders"] decoder_q = ["decoders"] +decoder_smush = ["decoders"] decoder_vmd = ["decoders"] decoder_vx = ["decoders"] diff --git a/nihav-game/src/codecs/mod.rs b/nihav-game/src/codecs/mod.rs index f5e9532..b65bcc8 100644 --- a/nihav-game/src/codecs/mod.rs +++ b/nihav-game/src/codecs/mod.rs @@ -25,6 +25,8 @@ pub mod midivid; pub mod midivid3; #[cfg(feature="decoder_q")] pub mod q; +#[cfg(feature="decoder_smush")] +pub mod smush; #[cfg(feature="decoder_vmd")] pub mod vmd; #[cfg(feature="decoder_vx")] @@ -57,6 +59,14 @@ const GAME_CODECS: &[DecoderInfo] = &[ DecoderInfo { name: "ipma2", get_decoder: ipma::get_decoder_v2 }, #[cfg(feature="decoder_q")] DecoderInfo { name: "legend-q-video", get_decoder: q::get_decoder }, +#[cfg(feature="decoder_smush")] + DecoderInfo { name: "smushv1", get_decoder: smush::get_decoder_video_v1 }, +#[cfg(feature="decoder_smush")] + DecoderInfo { name: "smushv2", get_decoder: smush::get_decoder_video_v2 }, +#[cfg(feature="decoder_smush")] + DecoderInfo { name: "smush-iact", get_decoder: smush::get_decoder_iact }, +#[cfg(feature="decoder_smush")] + DecoderInfo { name: "smush-vima", get_decoder: smush::get_decoder_vima }, #[cfg(feature="decoder_vmd")] DecoderInfo { name: "vmd-audio", get_decoder: vmd::get_decoder_audio }, #[cfg(feature="decoder_vmd")] diff --git a/nihav-game/src/codecs/smush/iact.rs b/nihav-game/src/codecs/smush/iact.rs new file mode 100644 index 0000000..7dccff4 --- /dev/null +++ b/nihav-game/src/codecs/smush/iact.rs @@ -0,0 +1,211 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; +use std::str::FromStr; + +struct IACTDecoder { + ainfo: NAAudioInfo, + chmap: NAChannelMap, + bits: u8, + tot_size: u32, + old: bool, + queued: Vec, +} + +impl IACTDecoder { + fn new() -> Self { + Self { + ainfo: NAAudioInfo::new(0, 1, SND_S16_FORMAT, 0), + chmap: NAChannelMap::new(), + bits: 0, + tot_size: 0, + old: false, + queued: Vec::new(), + } + } +} + +impl NADecoder for IACTDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() { + self.ainfo = NAAudioInfo::new(ainfo.get_sample_rate(), ainfo.get_channels(), SND_S16_FORMAT, 1); + self.chmap = NAChannelMap::from_str(if ainfo.get_channels() == 1 { "C" } else { "L,R" }).unwrap(); + self.bits = ainfo.get_format().bits; + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let info = pkt.get_stream().get_info(); + if let NACodecTypeInfo::Audio(_) = info.get_properties() { + let src = pkt.get_buffer(); + validate!(src.len() > 18); + + let code = read_u16le(&src[0..])?; + let flags = read_u16le(&src[2..])?; + if code != 8 || flags != 0x2E { + let mut frm = NAFrame::new_from_pkt(pkt, info, NABufferType::None); + frm.set_duration(Some(0)); + frm.set_keyframe(true); + return Ok(frm.into_ref()); + } + let _left = read_u32le(&src[14..])?; + let offset = if self.tot_size == 0 { + let mut mr = MemoryReader::new_read(&src[18..]); + let mut br = ByteReader::new(&mut mr); + let tag = br.read_tag()?; + if &tag == b"iMUS" { + self.tot_size = br.read_u32be()?; + validate!(self.tot_size != 0); + loop { + let tag = br.read_tag()?; + let size = br.read_u32be()?; + match &tag { + b"DATA" => { + break; + }, + _ => br.read_skip(size as usize)?, + }; + } + br.tell() as usize + } else { + self.tot_size = 1; + self.old = true; + self.bits = 8; + 0 + } + } else { 0 }; + + let data = &src[offset + 18..]; + if self.old { + self.queued.extend_from_slice(data); + } + let nsamples = (match self.bits { + _ if self.old => { + let mut mr = MemoryReader::new_read(&self.queued); + let mut br = ByteReader::new(&mut mr); + let mut nblocks = 0; + while br.left() > 0 { + let len = br.read_u16be()? as usize; + if br.left() < (len as i64) { + break; + } + nblocks += 1; + br.read_skip(len)?; + } + nblocks * 1024 * self.chmap.num_channels() + }, + 8 => data.len(), + 12 => data.len() * 2 / 3, + 16 => data.len() / 2, + _ => unimplemented!(), + }) / self.chmap.num_channels(); + + let abuf = alloc_audio_buffer(self.ainfo, nsamples, self.chmap.clone())?; + let mut adata = abuf.get_abuf_i16().unwrap(); + let adata = adata.get_data_mut().unwrap(); + match self.bits { + _ if self.old => { + let mut mr = MemoryReader::new_read(&self.queued); + let mut br = ByteReader::new(&mut mr); + for dst in adata.chunks_exact_mut(1024 * 2) { + let len = br.read_u16be()? as usize; + let end = br.tell() + (len as u64); + let b = br.read_byte()?; + let scale1 = b >> 4; + let scale2 = b & 0xF; + for pair in dst.chunks_exact_mut(2) { + if br.left() < 2 { + break; + } + let b = br.read_byte()? as i8; + if b != -0x80 { + pair[0] = i16::from(b) << scale1; + } else { + pair[0] = br.read_u16be()? as i16; + } + let b = br.read_byte()? as i8; + if b != -0x80 { + pair[1] = i16::from(b) << scale2; + } else { + pair[1] = br.read_u16be()? as i16; + } + } + validate!(br.tell() <= end); + br.seek(SeekFrom::Start(end))?; + } + let consumed = br.tell() as usize; + self.queued.drain(..consumed); + }, + 8 => { + for (dst, &src) in adata.iter_mut().zip(data.iter()) { + *dst = (u16::from(src) << 8) as i16; + } + }, + 12 => { + for (dst, src) in adata.chunks_exact_mut(2).zip(data.chunks_exact(3)) { + dst[0] = (((u16::from(src[1] << 4) << 8) | (u16::from(src[0]) << 4)) ^ 0x8000) as i16; + dst[1] = (((u16::from(src[1] & 0xF0) << 8) | (u16::from(src[2]) << 4)) ^ 0x8000) as i16; + } + }, + 16 => { + for (dst, src) in adata.iter_mut().zip(data.chunks_exact(2)) { + *dst = read_u16le(src)? as i16; + } + }, + _ => unreachable!(), + } + + let mut frm = NAFrame::new_from_pkt(pkt, info, abuf); + frm.set_duration(Some(nsamples as u64)); + frm.set_keyframe(true); + Ok(frm.into_ref()) + } else { + Err(DecoderError::InvalidData) + } + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for IACTDecoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + +pub fn get_decoder_iact() -> Box { + Box::new(IACTDecoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::game_register_all_decoders; + use crate::game_register_all_demuxers; + #[test] + fn test_smush_iact_imuse() { + let mut dmx_reg = RegisteredDemuxers::new(); + game_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + // sample from The Dig + test_decoding("smush", "smush-iact", "assets/Game/smush/PIGOUT.SAN", None, &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x7d731a75, 0x9869cb8f, 0xded5b893, 0xb507b17a])); + } + #[test] + fn test_smush_iact_raw() { + let mut dmx_reg = RegisteredDemuxers::new(); + game_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + // sample from Curse of Monkey Island + test_decoding("smush", "smush-iact", "assets/Game/smush/ZAP010.SAN", None, &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0xecf88bc6, 0x5c89ce94, 0x8e40a22a, 0xfc4ba86c])); + } +} + diff --git a/nihav-game/src/codecs/smush/mod.rs b/nihav-game/src/codecs/smush/mod.rs new file mode 100644 index 0000000..9274cba --- /dev/null +++ b/nihav-game/src/codecs/smush/mod.rs @@ -0,0 +1,120 @@ +enum GlyphEdge { + Left, + Top, + Right, + Bottom, + None +} + +impl GlyphEdge { + fn get(x: usize, y: usize, size: usize) -> Self { + if y == 0 { + GlyphEdge::Bottom + } else if y == size - 1 { + GlyphEdge::Top + } else if x == 0 { + GlyphEdge::Left + } else if x == size - 1 { + GlyphEdge::Right + } else { + GlyphEdge::None + } + } +} + +enum GlyphDir { + Left, + Up, + Right, + Down, + None +} + +impl GlyphDir { + fn get(edge0: GlyphEdge, edge1: GlyphEdge) -> Self { + match (edge0, edge1) { + (GlyphEdge::Left, GlyphEdge::Right) | + (GlyphEdge::Right, GlyphEdge::Left) => GlyphDir::Up, + (GlyphEdge::Top, GlyphEdge::Bottom) | + (GlyphEdge::Bottom, GlyphEdge::Top) => GlyphDir::Right, + (GlyphEdge::Bottom, _) | + (_, GlyphEdge::Bottom) => GlyphDir::Up, + (GlyphEdge::Top, _) | + (_, GlyphEdge::Top) => GlyphDir::Down, + (GlyphEdge::Left, _) | + (_, GlyphEdge::Left) => GlyphDir::Left, + (GlyphEdge::Right, _) | + (_, GlyphEdge::Right) => GlyphDir::Right, + _ => GlyphDir::None, + } + } +} + +const XVEC4: [usize; 16] = [0, 1, 2, 3, 3, 3, 3, 2, 1, 0, 0, 0, 1, 2, 2, 1]; +const YVEC4: [usize; 16] = [0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 2, 1, 1, 1, 2, 2]; +const XVEC8: [usize; 16] = [0, 2, 5, 7, 7, 7, 7, 7, 7, 5, 2, 0, 0, 0, 0, 0]; +const YVEC8: [usize; 16] = [0, 0, 0, 0, 1, 3, 4, 6, 7, 7, 7, 7, 6, 4, 3, 1]; + +fn make_glyphs_47(glyphs4: &mut [[u8; 16]; 256], glyphs8: &mut [[u8; 64]; 256]) { + for (n, glyph) in glyphs4.iter_mut().enumerate() { + let i = n >> 4; + let j = n & 0xF; + make_glyph_47(glyph, XVEC4[i], YVEC4[i], XVEC4[j], YVEC4[j], 4); + } + for (n, glyph) in glyphs8.iter_mut().enumerate() { + let i = n >> 4; + let j = n & 0xF; + make_glyph_47(glyph, XVEC8[i], YVEC8[i], XVEC8[j], YVEC8[j], 8); + } +} +fn make_glyph_47(dst: &mut [u8], xi: usize, yi: usize, xj: usize, yj: usize, size: usize) { + let edge0 = GlyphEdge::get(xi, yi, size); + let edge1 = GlyphEdge::get(xj, yj, size); + let dir = GlyphDir::get(edge0, edge1); + let npoints = if xi > xj { xi - xj } else { xj - xi }.max(if yi > yj { yi - yj } else { yj - yi }); + for ipoint in 0..=npoints { + let (p0, p1) = if npoints > 0 { + (interpolate(xi, xj, ipoint, npoints), + interpolate(yi, yj, ipoint, npoints)) + } else { + (xi, yi) + }; + let off = p0 + p1 * size; + match dir { + GlyphDir::Up => { + for i in 0..=p1 { + dst[off - i * size] = 1; + } + }, + GlyphDir::Down => { + for i in 0..size-p1 { + dst[off + i * size] = 1; + } + }, + GlyphDir::Left => { + for i in 0..=p0 { + dst[off - i] = 1; + } + }, + GlyphDir::Right => { + for i in 0..size-p0 { + dst[off + i] = 1; + } + }, + _ => {}, + }; + } +} +fn interpolate(a: usize, b: usize, pos1: usize, range: usize) -> usize { + (a * pos1 + b * (range - pos1) + range / 2) / range +} + +mod v1; +pub use v1::get_decoder_video_v1; +mod v2; +pub use v2::get_decoder_video_v2; + +mod iact; +pub use iact::get_decoder_iact; +mod vima; +pub use vima::get_decoder_vima; diff --git a/nihav-game/src/codecs/smush/v1.rs b/nihav-game/src/codecs/smush/v1.rs new file mode 100644 index 0000000..6731a29 --- /dev/null +++ b/nihav-game/src/codecs/smush/v1.rs @@ -0,0 +1,1417 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; + +struct Glyphs { + data: [[[u8; 16]; 256]; 2], + glyph8: [[u8; 64]; 256], + glyph8_init: bool, +} + +impl Glyphs { + fn new() -> Self { + Self { + data: [[[0; 16]; 256]; 2], + glyph8: [[0; 64]; 256], + glyph8_init: false, + } + } + fn make_glyphs_4(&mut self, mode: u8) { + for i in (1..16).step_by(2) { + let cy = (i as u8) + mode; + for j in 0..16 { + let dst = &mut self.data[0][(i / 2) * 16 + j]; + + let cx = (j as u8) + mode; + let avg = mode + (((i + j) >> 1) as u8); + if avg == cx || avg == cy { + dst[ 0] = cx; dst[ 1] = cy; dst[ 2] = cx; dst[ 3] = cy; + dst[ 4] = cy; dst[ 5] = cx; dst[ 6] = cy; dst[ 7] = cy; + dst[ 8] = cx; dst[ 9] = cy; dst[10] = cx; dst[11] = cy; + dst[12] = cx; dst[13] = cx; dst[14] = cy; dst[15] = cx; + } else { + let c0 = avg; + let c1 = ((u16::from(avg) + u16::from(cy)) >> 1) as u8; + let c2 = ((u16::from(avg) + u16::from(cx)) >> 1) as u8; + dst[ 0] = c0; dst[ 1] = c0; dst[ 2] = c1; dst[ 3] = cy; + dst[ 4] = c0; dst[ 5] = c0; dst[ 6] = c1; dst[ 7] = cy; + dst[ 8] = c2; dst[ 9] = c2; dst[10] = c0; dst[11] = c1; + dst[12] = cx; dst[13] = cx; dst[14] = c2; dst[15] = c0; + } + } + } + for i in (0..16).step_by(2) { + let cy = (i as u8) + mode; + for j in 0..16 { + let dst = &mut self.data[0][128 + (i / 2) * 16 + j]; + + let cx = (j as u8) + mode; + let avg = mode + (((i + j) >> 1) as u8); + if avg == cx || avg == cy { + dst[ 0] = cy; dst[ 1] = cy; dst[ 2] = cx; dst[ 3] = cy; + dst[ 4] = cy; dst[ 5] = cy; dst[ 6] = cy; dst[ 7] = cx; + dst[ 8] = cx; dst[ 9] = cy; dst[10] = cx; dst[11] = cx; + dst[12] = cy; dst[13] = cx; dst[14] = cy; dst[15] = cx; + } else { + let c1 = ((u16::from(avg) + u16::from(cy)) >> 1) as u8; + let c2 = ((u16::from(avg) + u16::from(cx)) >> 1) as u8; + dst[ 0] = cy; dst[ 1] = cy; dst[ 2] = c1; dst[ 3] = cx; + dst[ 4] = cy; dst[ 5] = cy; dst[ 6] = c1; dst[ 7] = cx; + dst[ 8] = c1; dst[ 9] = c1; dst[10] = cx; dst[11] = c2; + dst[12] = cx; dst[13] = cx; dst[14] = c2; dst[15] = cx; + } + } + } + } + fn make_glyphs_5(&mut self, mode: u8) { + for i in 0..8 { + let cy = (i as u8) + mode; + for j in 0..8 { + let dst = &mut self.data[0][i * 8 + j]; + + let cx = (j as u8) + mode; + let avg = mode + (((i + j) >> 1) as u8); + let c0 = avg; + let c1 = ((u16::from(avg) + u16::from(cy)) >> 1) as u8; + let c2 = ((u16::from(avg) + u16::from(cx)) >> 1) as u8; + + dst[ 0] = c0; dst[ 1] = c0; dst[ 2] = c1; dst[ 3] = cy; + dst[ 4] = c0; dst[ 5] = c0; dst[ 6] = c1; dst[ 7] = cy; + dst[ 8] = c2; dst[ 9] = c2; dst[10] = c0; dst[11] = c1; + dst[12] = cx; dst[13] = cx; dst[14] = c2; dst[15] = c0; + } + } + for i in 0..8 { + let cy = (i as u8) + mode; + for j in 0..8 { + let dst = &mut self.data[0][i * 8 + j + 64]; + + let cx = (j as u8) + mode; + let avg = mode + (((i + j) >> 1) as u8); + let c0 = avg; + let c2 = ((u16::from(avg) + u16::from(cx)) >> 1) as u8; + + dst[ 0] = cy; dst[ 1] = cy; dst[ 2] = cy; dst[ 3] = cy; + dst[ 4] = c0; dst[ 5] = c0; dst[ 6] = c0; dst[ 7] = c0; + dst[ 8] = c2; dst[ 9] = c2; dst[10] = c2; dst[11] = c2; + dst[12] = cx; dst[13] = cx; dst[14] = cx; dst[15] = cx; + } + } + for i in 0..8 { + let cy = (i as u8) + mode; + for j in 0..8 { + let dst = &mut self.data[0][i * 8 + j + 128]; + + let cx = (j as u8) + mode; + let avg = mode + (((i + j) >> 1) as u8); + let c0 = avg; + let c1 = ((u16::from(avg) + u16::from(cy)) >> 1) as u8; + let c2 = ((u16::from(avg) + u16::from(cx)) >> 1) as u8; + + dst[ 0] = cy; dst[ 1] = cy; dst[ 2] = c1; dst[ 3] = c0; + dst[ 4] = cy; dst[ 5] = cy; dst[ 6] = c1; dst[ 7] = c0; + dst[ 8] = c1; dst[ 9] = c1; dst[10] = c0; dst[11] = c2; + dst[12] = c0; dst[13] = c0; dst[14] = c2; dst[15] = cx; + } + } + for i in 0..8 { + let cy = (i as u8) + mode; + for j in 0..8 { + let dst = &mut self.data[0][i * 8 + j + 192]; + + let cx = (j as u8) + mode; + let avg = mode + (((i + j) >> 1) as u8); + let c0 = avg; + let c2 = ((u16::from(avg) + u16::from(cx)) >> 1) as u8; + + dst[ 0] = cy; dst[ 1] = c0; dst[ 2] = c2; dst[ 3] = cx; + dst[ 4] = cy; dst[ 5] = c0; dst[ 6] = c2; dst[ 7] = cx; + dst[ 8] = cy; dst[ 9] = c0; dst[10] = c2; dst[11] = cx; + dst[12] = cy; dst[13] = c0; dst[14] = c2; dst[15] = cx; + } + } + } + fn read_additional(&mut self, br: &mut ByteReader, add: u16) -> DecoderResult<()> { + if add > 0 { + validate!(add <= 256); + let mut gbuf = [0; 8]; + for glyph in self.data[1].iter_mut().take(add as usize) { + br.read_buf(&mut gbuf)?; + for (pair, &b) in glyph.chunks_mut(2).zip(gbuf.iter()) { + pair[0] = b >> 4; + pair[1] = b & 0xF; + } + } + } + Ok(()) + } + fn make_glyphs_47(&mut self) { + super::make_glyphs_47(&mut self.data[0], &mut self.glyph8); + self.glyph8_init = true; + } +} + +struct FrameData { + info: NACodecInfoRef, + pal: [u8; 768], + fpal: [u16; 768], + pdelta: [u16; 768], + width: usize, + height: usize, + frm0: Vec, + frm1: Vec, + frm2: Vec, +} + +impl FrameData { + fn new() -> Self { + Self { + info: NACodecInfoRef::default(), + pal: [0; 768], + fpal: [0; 768], + pdelta: [0; 768], + width: 0, + height: 0, + frm0: Vec::new(), + frm1: Vec::new(), + frm2: Vec::new(), + } + } + fn init(&mut self, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + self.width = vinfo.get_width(); + self.height = vinfo.get_height(); +self.width = (self.width + 7) & !7; +self.height = (self.height + 7) & !7; + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, PAL8_FORMAT)); + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); + if let Some(edata) = info.get_extradata() { + validate!(edata.len() > 768); + self.pal.copy_from_slice(&edata[1..][..768]); + } + + self.frm0.resize(self.width * self.height, 0); + self.frm1.resize(self.width * self.height, 0); + self.frm2.resize(self.width * self.height, 0); + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + #[inline] + fn set_pixel(&mut self, xoff: i16, yoff: i16, x: usize, y: usize, pix: u8) { + let xpos = (xoff as isize) + (x as isize); + if xpos < 0 { return; } + let xpos = xpos as usize; + let ypos = (yoff as isize) + (y as isize); + if ypos < 0 { return; } + let ypos = ypos as usize; + if xpos < self.width && ypos < self.height { + self.frm0[xpos + ypos * self.width] = pix; + } + } + fn get_pixel(&mut self, xoff: i16, yoff: i16, x: usize, y: usize) -> u8 { + let xpos = (xoff as isize) + (x as isize); + if xpos < 0 { return 0; } + let xpos = xpos as usize; + let ypos = (yoff as isize) + (y as isize); + if ypos < 0 { return 0; } + let ypos = ypos as usize; + if xpos < self.width && ypos < self.height { + self.frm0[xpos + ypos * self.width] + } else { + 0 + } + } + fn loop_filter(&mut self, _xoff: i16, _yoff: i16, _x: usize, _y: usize) { +/* let xpos = (xoff as isize) + (x as isize); + if xpos < 0 { return; } + let xpos = xpos as usize; + let ypos = (yoff as isize) + (y as isize); + if ypos < 0 { return; } + let ypos = ypos as usize; + if xpos < self.width && ypos < self.height { + let start = xpos + ypos * self.width; + if xpos > 0 { + for row in self.frm0[start - 1..].chunks_mut(self.width).take(4) { + let x0 = row[0]; + let x1 = row[1]; + row[1] = 0x80 | (x0.wrapping_add(x1) >> 1); + } + } + if ypos > 0 { + for i in 0..4 { + let y0 = self.frm0[start + i]; + let y1 = &mut self.frm0[start + i + self.width]; + *y1 = 0x80 | (y1.wrapping_add(y0) >> 1); + } + } + }*/ + } + fn get_frame(&mut self, pkt: &NAPacket) -> DecoderResult { + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?; + if let Some(ref mut vbuf) = bufinfo.get_vbuf() { + let stride = vbuf.get_stride(0); + let paloff = vbuf.get_offset(1); + let data = vbuf.get_data_mut().unwrap(); + for (dst, src) in data.chunks_mut(stride).zip(self.frm0.chunks(self.width).take(self.height)) { + dst[..self.width].copy_from_slice(src); + } + data[paloff..][..768].copy_from_slice(&self.pal); + } else { + return Err(DecoderError::Bug); + } + + let is_intra = pkt.keyframe; + let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo); + frm.set_keyframe(is_intra); + frm.set_frame_type(if is_intra { FrameType::I } else { FrameType::P }); + Ok(frm.into_ref()) + } +} + +fn do_mc(dst: &mut [u8], src: &[u8], stride: usize, xoff: isize, yoff: isize, w: usize, h: usize) { + let mut pos = xoff + yoff * (stride as isize); + for row in dst.chunks_mut(stride).take(4) { + for i in 0..4 { + row[i] = if pos >= 0 && (pos as usize) < w + (h - 1) * stride { + src[pos as usize] + } else { 0 }; + pos += 1; + } + pos -= 4; + pos += stride as isize; + } +} + +#[allow(clippy::too_many_arguments)] +fn do_block47(br: &mut ByteReader, dst: &mut [u8], frm1: &[u8], frm2: &[u8], x: usize, y: usize, stride: usize, bsize: usize, clr: &[u8; 6], glyphs: &Glyphs) -> DecoderResult<()> { + let op = br.read_byte()?; + match op { + 0xFF if bsize > 2 => { + let hsize = bsize / 2; + do_block47(br, dst, frm1, frm2, x, y, stride, hsize, clr, glyphs)?; + do_block47(br, &mut dst[hsize..], frm1, frm2, x + hsize, y, stride, bsize / 2, clr, glyphs)?; + do_block47(br, &mut dst[hsize * stride..], frm1, frm2, x, y + hsize, stride, hsize, clr, glyphs)?; + do_block47(br, &mut dst[hsize * (stride + 1)..], frm1, frm2, x + hsize, y + hsize, stride, bsize / 2, clr, glyphs)?; + }, + 0xFF => { + br.read_buf(&mut dst[..2])?; + br.read_buf(&mut dst[stride..][..2])?; + }, + 0xFE => { + let pix = br.read_byte()?; + for dst in dst.chunks_mut(stride).take(bsize) { + for el in dst[..bsize].iter_mut() { + *el = pix; + } + } + }, + 0xFD => { + let idx = br.read_byte()? as usize; + let mut clr = [0; 2]; + clr[1] = br.read_byte()?; + clr[0] = br.read_byte()?; + let mut glyph = if bsize == 8 { glyphs.glyph8[idx].iter() } else { glyphs.data[0][idx].iter() }; + + for dst in dst.chunks_mut(stride).take(bsize) { + for el in dst[..bsize].iter_mut() { + *el = clr[*glyph.next().unwrap_or(&0) as usize]; + } + } + }, + 0xFC => { + let off = x + y * stride; + let src = &frm1[off..]; + for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { + dst[..bsize].copy_from_slice(&src[..bsize]); + } + }, + 0xF8..=0xFB => { + let pix = clr[(op & 7) as usize]; + for dst in dst.chunks_mut(stride).take(bsize) { + for el in dst[..bsize].iter_mut() { + *el = pix; + } + } + }, + _ => { + let mx = C47_MV[0][op as usize][0] as isize; + let my = C47_MV[0][op as usize][1] as isize; + let off = (x as isize) + mx + ((y as isize) + my) * (stride as isize); + validate!(off >= 0); + let src = &frm2[off as usize..]; + for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { + dst[..bsize].copy_from_slice(&src[..bsize]); + } + }, + }; + Ok(()) +} + +macro_rules! c48_mv { + (index; $dst: expr, $src: expr, $br: expr, $x: expr, $y: expr, $stride: expr, $size: expr, $mvsel: expr) => ({ + for yy in (0..8).step_by($size) { + for xx in (0..8).step_by($size) { + let idx = $br.read_byte()? as usize; + validate!(idx < 255); + let mx = C47_MV[$mvsel][idx][0] as isize; + let my = C47_MV[$mvsel][idx][1] as isize; + c48_mv!(common; &mut $dst[xx + yy * $stride..], $src, $x + xx, $y + yy, mx, my, $stride, $size) + } + } + + }); + (offset; $dst: expr, $src: expr, $br: expr, $x: expr, $y: expr, $w: expr, $stride: expr, $size: expr) => ({ + for yy in (0..8).step_by($size) { + for xx in (0..8).step_by($size) { + let offset = $br.read_u16le()? as i16 as isize; + let mx = offset % ($w as isize); + let my = offset / ($w as isize); + c48_mv!(common; &mut $dst[xx + yy * $stride..], $src, $x + xx, $y + yy, mx, my, $stride, $size) + } + } + }); + (common; $dst: expr, $src: expr, $x: expr, $y: expr, $mx: expr, $my: expr, $stride: expr, $size: expr) => {{ + let srcpos = ($x as isize) + $mx + (($y as isize) + $my) * ($stride as isize); + validate!(srcpos >= 0); + for (dst, src) in $dst.chunks_mut($stride).zip($src[srcpos as usize..].chunks($stride)).take($size) { + let size = dst.len().min(src.len()).min($size); + dst[..size].copy_from_slice(&src[..size]); + } + }} +} + +fn scale2x(block: &[u8; 16], dst: &mut [u8], stride: usize) { + for (drow, src) in dst.chunks_mut(stride * 2).zip(block.chunks_exact(4)) { + for row in drow.chunks_mut(stride) { + for (dpair, &el) in row.chunks_exact_mut(2).zip(src.iter()) { + dpair[0] = el; + dpair[1] = el; + } + } + } +} + +struct Smush1Decoder { + glyphs: Glyphs, + pic: FrameData, + version: u8, + prev_seq: u16, + reorder: u8, + filter: [[u8; 256]; 256], +} + +impl Smush1Decoder { + fn new() -> Self { + Self { + glyphs: Glyphs::new(), + pic: FrameData::new(), + version: 0, + prev_seq: 0, + reorder: 0, + filter: [[0; 256]; 256], + } + } + + fn decode_rle(br: &mut ByteReader, dst: &mut [u8], w: usize, h: usize, stride: usize) -> DecoderResult<()> { + let mut x = 0; + let mut y = 0; + let mut len = 0; + let mut clr = 0; + let mut run = false; + while (x != 0) || (y != h) { + if len == 0 { + let op = br.read_byte()?; + run = (op & 1) != 0; + if run { + clr = br.read_byte()?; + } + len = ((op >> 1) + 1) as usize; + } + if run { + dst[x + y * stride] = clr; + } else { + dst[x + y * stride] = br.read_byte()?; + } + len -= 1; + x += 1; + if x == w { + x = 0; + y += 1; + } + } + validate!(len == 0); + + Ok(()) + } + + fn decode_1(&mut self, br: &mut ByteReader, x: i16, y: i16, w: usize, h: usize, transparent: bool) -> DecoderResult<()> { + for yy in 0..h { + let len = u64::from(br.read_u16le()?); + let end = br.tell() + len; + let mut xx = 0; + while (br.tell() < end) && (xx < w) { + let op = br.read_byte()?; + let len = ((op >> 1) + 1) as usize; + if (op & 1) == 0 { + for _ in 0..len { + let clr = br.read_byte()?; + if !transparent || clr != 0 { + self.pic.set_pixel(x, y, xx, yy, clr); + } + xx += 1; + } + } else { + let clr = br.read_byte()?; + if !transparent || clr != 0 { + for _ in 0..len { + self.pic.set_pixel(x, y, xx, yy, clr); + xx += 1; + } + } else { + xx += len; + } + } + } + validate!(br.tell() == end && xx == w); + } + + Ok(()) + } + #[allow(clippy::verbose_bit_mask)] + fn decode_2(&mut self, br: &mut ByteReader, x: i16, y: i16, _w: usize, _h: usize, len: usize) -> DecoderResult<()> { + + validate!((len & 3) == 0); + let mut xpos = x; + let mut ypos = y; + for _ in 0..len/4 { + let xoff = br.read_u16le()? as i16; + let yoff = i16::from(br.read_byte()?); + let pix = br.read_byte()?; + + xpos += xoff; + ypos += yoff; + self.pic.set_pixel(xpos, ypos, 0, 0, pix); + } + Ok(()) + } + #[allow(clippy::too_many_arguments)] + fn decode_4(&mut self, br: &mut ByteReader, x: i16, y: i16, w: usize, h: usize, mode: u8, add: u16) -> DecoderResult<()> { + self.glyphs.make_glyphs_4(mode); + self.glyphs.read_additional(br, add)?; + + for col in (0..w).step_by(4) { + let mut mask = 0; + let mut bits = 0; + for row in (0..h).step_by(4) { + let bit = if add > 0 { + if bits == 0 { + mask = br.read_byte()?; + bits = 8; + } + let bit = (mask & 0x80) != 0; + mask <<= 1; + bits -= 1; + bit + } else { + false + }; + + let tile_no = br.read_byte()? as usize; + if !bit && (tile_no == 0x80) { + continue; + } + let src = &self.glyphs.data[bit as usize][tile_no]; + for (y1, srow) in src.chunks(4).enumerate() { + for (x1, &pix) in srow.iter().enumerate() { + self.pic.set_pixel(x, y, col + x1, row + y1, pix); + } + } + if !bit { + self.pic.loop_filter(x, y, col, row); + } + } + } + + Ok(()) + } + #[allow(clippy::too_many_arguments)] + fn decode_5(&mut self, br: &mut ByteReader, x: i16, y: i16, w: usize, h: usize, mode: u8, add: u16) -> DecoderResult<()> { + self.glyphs.make_glyphs_5(mode); + self.glyphs.read_additional(br, add)?; + + for col in (0..w).step_by(4) { + let mut mask = 0; + let mut bits = 0; + for row in (0..h).step_by(4) { + let bit = if add > 0 { + if bits == 0 { + mask = br.read_byte()?; + bits = 8; + } + let bit = (mask & 0x80) != 0; + mask <<= 1; + bits -= 1; + bit + } else { + false + }; + + let tile_no = br.read_byte()? as usize; + let src = &self.glyphs.data[bit as usize][tile_no]; + for (y1, srow) in src.chunks(4).enumerate() { + for (x1, &pix) in srow.iter().enumerate() { + self.pic.set_pixel(x, y, col + x1, row + y1, pix); + } + } + if !bit { + self.pic.loop_filter(x, y, col, row); + } + } + } + + Ok(()) + } + fn decode_21(&mut self, br: &mut ByteReader, x: i16, y: i16, w: usize, h: usize, size: usize) -> DecoderResult<()> { + let end = br.tell() + (size as u64); + for yy in 0..h { + if br.tell() >= end { break; } + let len = u64::from(br.read_u16le()?); + let end = br.tell() + len; + let mut xx = 0; + let mut skip = true; + while (br.tell() < end) && (xx <= w) { + let len = br.read_u16le()? as usize; + if !skip { + for _ in 0..=len { + let pix = br.read_byte()?; + self.pic.set_pixel(x, y, xx, yy, pix); + xx += 1; + } + } else { + for _ in 0..len { + self.pic.set_pixel(x, y, xx, yy, 0); + xx += 1; + } + } + skip = !skip; + } + validate!(br.tell() == end && xx == w + 1); + } + + Ok(()) + } + #[allow(clippy::too_many_arguments)] + fn decode_23(&mut self, br: &mut ByteReader, x: i16, y: i16, w: usize, h: usize, bias: u8, add: u16, old: bool) -> DecoderResult<()> { + let mut lut = [0; 256]; + if old { + for (i, el) in lut.iter_mut().enumerate() { + *el = (i as u8).wrapping_add(bias.wrapping_sub(0x30)); + } + } else if add != 256 { + for (i, el) in lut.iter_mut().enumerate() { + *el = (i as u8).wrapping_add(add as u8); + } + } else { + br.read_buf(&mut lut)?; + } + for yy in 0..h { + let len = u64::from(br.read_u16le()?); + let end = br.tell() + len; + let mut xx = 0; + let mut skip = true; + while (br.tell() < end) && (xx <= w) { + let len = br.read_byte()? as usize; + if !skip { + for _ in 0..len { + let pix = self.pic.get_pixel(x, y, xx, yy); + self.pic.set_pixel(x, y, xx, yy, lut[pix as usize]); + xx += 1; + } + } else { + xx += len; + } + skip = !skip; + } + validate!(br.tell() == end && xx == w + 1); + } + + Ok(()) + } + fn decode_37(&mut self, br: &mut ByteReader, x: i16, y: i16, mut w: usize, mut h: usize) -> DecoderResult<()> { + let compr = br.read_byte()?; + let mv_off = br.read_byte()? as usize; + validate!(mv_off <= 2); + let seq = br.read_u16le()?; + let _unp_size = br.read_u32le()?; + let _packed_size = br.read_u32le()?; + let flags = br.read_byte()?; + br.read_skip(3)?; + + w = (w + 3) & !3; + h = (h + 3) & !3; + + validate!(x >= 0 && y >= 0); + let x = x as usize; + let y = y as usize; + validate!((x + w <= self.pic.width) && (y + h <= self.pic.height)); + + if compr == 0 || compr == 2 { + for el in self.pic.frm1.iter_mut() { + *el = 0; + } + for el in self.pic.frm2.iter_mut() { + *el = 0; + } + } else if ((seq & 1) != 0) || ((flags & 1) == 0) { + std::mem::swap(&mut self.pic.frm0, &mut self.pic.frm2); + } + + let stride = self.pic.width; + let dst = &mut self.pic.frm0[x + y * stride..]; + let prv = &self.pic.frm2[x + y * stride..]; + match compr { + 0 => { + for line in dst.chunks_mut(stride).take(h) { + br.read_buf(&mut line[..w])?; + } + }, + 1 => { + let mut len = -1; + let mut run = false; + let mut code = 0; + for (row_no, row) in dst.chunks_mut(stride * 4).take(h / 4).enumerate() { + for col in (0..w).step_by(4) { + let skip_code = if len < 0 { + let op = br.read_byte()?; + len = (op >> 1) as i8; + run = (op & 1) != 0; + false + } else { + run + }; + if !skip_code { + code = br.read_byte()?; + if code == 0xFF { + len -= 1; + for drow in row[col..].chunks_mut(stride) { + for el in drow[..4].iter_mut() { + if len < 0 { + let op = br.read_byte()?; + len = (op >> 1) as i8; + run = (op & 1) != 0; + if run { + code = br.read_byte()?; + } + } + if run { + *el = code; + } else { + *el = br.read_byte()?; + } + len -= 1; + } + } + continue; + } + } + let idx = code as usize; + let mx = C37_MV[mv_off][idx * 2] as isize; + let my = C37_MV[mv_off][idx * 2 + 1] as isize; + do_mc(&mut row[col..], &self.pic.frm2, stride, + (x as isize) + (col as isize) + mx, + (y as isize) + (row_no as isize) * 4 + my, + self.pic.width, self.pic.height); + len -= 1; + } + } + }, + 2 => { + Self::decode_rle(br, dst, w, h, stride)?; + }, + 3 | 4 => { + let has_fills = (flags & 4) != 0; + let has_skips = compr == 4; + let mut skip_run = 0; + for (row_no, row) in dst.chunks_mut(stride * 4).take(h / 4).enumerate() { + for col in (0..w).step_by(4) { + if skip_run > 0 { + for (drow, srow) in row[col..].chunks_mut(stride).zip(prv[col + row_no * 4 * stride..].chunks(stride)) { + drow[..4].copy_from_slice(&srow[..4]); + } + skip_run -= 1; + continue; + } + let opcode = br.read_byte()?; + match opcode { + 0xFF => { + for drow in row[col..].chunks_mut(stride) { + br.read_buf(&mut drow[..4])?; + } + }, + 0xFE if has_fills => { + for drow in row[col..].chunks_mut(stride) { + let clr = br.read_byte()?; + for el in drow[..4].iter_mut() { + *el = clr; + } + } + }, + 0xFD if has_fills => { + let clr = br.read_byte()?; + for drow in row[col..].chunks_mut(stride) { + for el in drow[..4].iter_mut() { + *el = clr; + } + } + }, + 0 if has_skips => { + skip_run = br.read_byte()?; + for (drow, srow) in row[col..].chunks_mut(stride).zip(prv[col + row_no * 4 * stride..].chunks(stride)) { + drow[..4].copy_from_slice(&srow[..4]); + } + }, + _ => { + let idx = opcode as usize; + let mx = C37_MV[mv_off][idx * 2] as isize; + let my = C37_MV[mv_off][idx * 2 + 1] as isize; + do_mc(&mut row[col..], &self.pic.frm2, stride, + (x as isize) + (col as isize) + mx, + (y as isize) + (row_no as isize) * 4 + my, + self.pic.width, self.pic.height); + }, + }; + } + } + }, + _ => return Err(DecoderError::InvalidData), + }; + + Ok(()) + } + fn decode_47(&mut self, br: &mut ByteReader, x: i16, y: i16, w: usize, h: usize) -> DecoderResult<()> { + let seq = br.read_u16le()?; + let compr = br.read_byte()?; + let reorder = br.read_byte()?; + let flags = br.read_byte()?; + br.read_skip(3)?; + let mut clr = [0; 6]; + br.read_buf(&mut clr)?; + let _dec_size = br.read_u32le()?; + br.read_skip(4)?; + br.read_skip(4)?; + + if (flags & 1) != 0 { + for i in 0..256 { + for j in i..256 { + let val = br.read_byte()?; + self.filter[i][j] = val; + self.filter[j][i] = val; + } + } + } + + if compr == 2 && !self.glyphs.glyph8_init { + self.glyphs.make_glyphs_47(); + } + + if seq == 0 { + for el in self.pic.frm1.iter_mut() { + *el = 0; + } + for el in self.pic.frm2.iter_mut() { + *el = 0; + } + } + + validate!(x >= 0 && y >= 0); + let x = x as usize; + let y = y as usize; + validate!((x + w <= self.pic.width) && (y + h <= self.pic.height)); + + let stride = self.pic.width; + let dst = &mut self.pic.frm0[x + y * stride..]; + match compr { + 0 => { + for line in dst.chunks_mut(stride).take(h) { + br.read_buf(&mut line[..w])?; + } + }, + 1 => { + for row in dst.chunks_mut(stride * 2).take((h + 1) / 2) { + for col in (0..w).step_by(2) { + let pix = br.read_byte()?; + row[col] = pix; + row[col + 1] = pix; + row[col + stride] = pix; + row[col + stride + 1] = pix; + } + } + }, + 2 => { + for (row_no, row) in dst.chunks_mut(stride * 8).take((h + 7) / 8).enumerate() { + for col in (0..w).step_by(8) { + do_block47(br, &mut row[col..], &self.pic.frm1, &self.pic.frm2, col, row_no * 8, stride, 8, &clr, &self.glyphs)?; + } + } + }, + 3 => { + self.pic.frm0.copy_from_slice(&self.pic.frm2); + }, + 4 => { + self.pic.frm0.copy_from_slice(&self.pic.frm1); + }, + 5 => { + Self::decode_rle(br, dst, w, h, stride)?; + }, + _ => return Err(DecoderError::InvalidData), + }; + + self.reorder = if seq == 0 || seq == self.prev_seq + 1 { reorder } else { 0 }; + self.prev_seq = seq; + + Ok(()) + } + fn decode_48(&mut self, br: &mut ByteReader, x: i16, y: i16, mut w: usize, mut h: usize) -> DecoderResult<()> { + let compr = br.read_byte()?; + let mvsel = br.read_byte()? as usize; + validate!(mvsel < 2); + let _seq = br.read_u16le()?; + let _packed_size = br.read_u32le()?; + let _unpacked_size = br.read_u32le()?; + let flags = br.read_byte()?; + br.read_skip(3)?; + if (flags & 8) != 0 { + for i in 0..256 { + for j in i..256 { + let val = br.read_byte()?; + self.filter[i][j] = val; + self.filter[j][i] = val; + } + } + } + + w = (w + 7) & !7; + h = (h + 7) & !7; + std::mem::swap(&mut self.pic.frm0, &mut self.pic.frm2); + + validate!(x >= 0 && y >= 0); + let x = x as usize; + let y = y as usize; + validate!((x + w <= self.pic.width) && (y + h <= self.pic.height)); + + let stride = self.pic.width; + let dst = &mut self.pic.frm0[x + y * stride..]; + match compr { + 0 => { + for line in dst.chunks_mut(stride).take(h) { + br.read_buf(&mut line[..w])?; + } + }, + 2 => { + Self::decode_rle(br, dst, w, h, stride)?; + }, + 3 => { + let mut block = [0; 16]; + for (row_no, row) in dst.chunks_mut(stride * 8).take((h + 7) / 8).enumerate() { + for col in (0..w).step_by(8) { + let op = br.read_byte()?; + match op { + 0xFF => { + let val = br.read_byte()?; + + // it should be interpolated which means reading top pixels... + for el in block.iter_mut() { + *el = val; + } + scale2x(&block, &mut row[col..], stride); + }, + 0xFE => { + c48_mv!(offset; &mut row[col..], &self.pic.frm2, br, col, row_no * 8, w, stride, 8); + }, + 0xFD => { + block[ 5] = br.read_byte()?; + block[ 7] = br.read_byte()?; + block[13] = br.read_byte()?; + block[15] = br.read_byte()?; + + // it should be interpolated which means reading top pixels... + block[ 0] = block[ 5]; + block[ 1] = block[ 5]; + block[ 4] = block[ 5]; + block[ 2] = block[ 7]; + block[ 3] = block[ 7]; + block[ 6] = block[ 7]; + block[ 8] = block[13]; + block[ 9] = block[13]; + block[12] = block[13]; + block[10] = block[15]; + block[11] = block[15]; + block[14] = block[15]; + scale2x(&block, &mut row[col..], stride); + }, + 0xFC => { + c48_mv!(index; &mut row[col..], &self.pic.frm2, br, col, row_no * 8, stride, 4, mvsel); + }, + 0xFB => { + c48_mv!(offset; &mut row[col..], &self.pic.frm2, br, col, row_no * 8, w, stride, 4); + }, + 0xFA => { + br.read_buf(&mut block)?; + scale2x(&block, &mut row[col..], stride); + }, + 0xF9 => { + c48_mv!(index; &mut row[col..], &self.pic.frm2, br, col, row_no * 8, stride, 2, mvsel); + }, + 0xF8 => { + c48_mv!(offset; &mut row[col..], &self.pic.frm2, br, col, row_no * 8, w, stride, 2); + }, + 0xF7 => { + for line in row[col..].chunks_mut(stride) { + br.read_buf(&mut line[..8])?; + } + }, + _ => { + br.seek(SeekFrom::Current(-1))?; + c48_mv!(index; &mut row[col..], &self.pic.frm2, br, col, row_no * 8, stride, 8, mvsel); + }, + }; + } + } + }, + 5 => { + for row in dst.chunks_mut(stride * 2).take((h + 1) / 2) { + let mut last = br.read_byte()?; + row[0] = last; + for col in (1..w).step_by(2) { + let new = br.read_byte()?; + row[col] = self.filter[last as usize][new as usize]; + row[col + 1] = new; + last = new; + } + } + let mut off0 = 0; + let mut off1 = stride; + let mut off2 = stride * 2; + for _ in (1..h).step_by(2) { + for i in 0..w { + dst[off1 + i] = self.filter[dst[off0 + i] as usize][dst[off2 + i] as usize]; + } + off0 = off2; + off1 += stride * 2; + off2 += stride * 2; + } + }, + _ => return Err(DecoderError::InvalidData), + }; + Ok(()) + } +} + +impl NADecoder for Smush1Decoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let Some(edata) = info.get_extradata() { + validate!(!edata.is_empty() && edata[0] <= 2); + self.version = edata[0]; + } + self.pic.init(info) + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(src.len() > 8); + + let mut mr = MemoryReader::new_read(&src); + let mut br = ByteReader::new(&mut mr); + + let mut store = false; + while br.left() > 0 { + let tag = br.read_tag()?; + let size = br.read_u32be()? as usize; + validate!((size as i64) <= br.left()); + match &tag { + b"NPAL" => { + validate!((size % 3) == 0); + br.read_buf(&mut self.pic.pal[..size])?; + }, + b"XPAL" => { + let cmd = br.read_u32be()?; + match cmd { + 0 => { + validate!(size == 0x604); + for el in self.pic.pdelta.iter_mut() { + *el = br.read_u16le()?; + } + for (dst, &src) in self.pic.fpal.iter_mut().zip(self.pic.pal.iter()) { + *dst = u16::from(src) << 7; + } + }, + 1 => { + validate!(size == 4 || size == 6); + for i in 0..768 { + self.pic.fpal[i] = self.pic.fpal[i].wrapping_add(self.pic.pdelta[i]); + self.pic.pal[i] = (self.pic.fpal[i] >> 7) as u8; + } + br.read_skip((size as usize) - 4)?; + }, + 2 => { + validate!(size == 0x904); + for el in self.pic.pdelta.iter_mut() { + *el = br.read_u16le()?; + } + br.read_buf(&mut self.pic.pal)?; + }, + _ => return Err(DecoderError::InvalidData), + }; + }, + b"FTCH" => { + br.read_skip(size)?; + self.pic.frm0.copy_from_slice(&self.pic.frm1); + }, + b"STOR" => { + store = true; + br.read_skip(size)?; + }, + b"FOBJ" => { + validate!(size >= 14); + let end = br.tell() + (size as u64); + let compression = br.read_byte()?; + let cparam = br.read_byte()?; + let x = br.read_u16le()? as i16; + let y = br.read_u16le()? as i16; + let w = br.read_u16le()? as usize; + let h = br.read_u16le()? as usize; + let _ = br.read_u16le()?; + let param2 = br.read_u16le()?; + + match compression { + 1 | 3 => self.decode_1(&mut br, x, y, w, h, (compression == 1) ^ (self.version != 1))?, + 2 => self.decode_2(&mut br, x, y, w, h, size - 14)?, + 4 | 33 => self.decode_4(&mut br, x, y, w, h, cparam, param2)?, + 5 | 34 => self.decode_5(&mut br, x, y, w, h, cparam, param2)?, + 21 | 44 => self.decode_21(&mut br, x, y, w, h, size - 14)?, + 23 => self.decode_23(&mut br, x, y, w, h, cparam, param2, self.version == 1)?, + 37 => { + let start = br.tell() as usize; + let end = start + size - 14; + let mut mr = MemoryReader::new_read(&src[start..end]); + let mut br = ByteReader::new(&mut mr); + self.decode_37(&mut br, x, y, w, h)?; + }, + 47 => { + let start = br.tell() as usize; + let end = start + size - 14; + let mut mr = MemoryReader::new_read(&src[start..end]); + let mut br = ByteReader::new(&mut mr); + self.decode_47(&mut br, x, y, w, h)?; + }, + 48 => { + let start = br.tell() as usize; + let end = start + size - 14; + let mut mr = MemoryReader::new_read(&src[start..end]); + let mut br = ByteReader::new(&mut mr); + self.decode_48(&mut br, x, y, w, h)?; + }, + _ => return Err(DecoderError::NotImplemented), + }; + validate!(br.tell() <= end); + let tail = end - br.tell(); + br.read_skip(tail as usize)?; + if store { + self.pic.frm1.copy_from_slice(&self.pic.frm0); + store = false; + } + }, + _ => br.read_skip(size)?, + }; + } + + let ret = self.pic.get_frame(pkt); + + if self.reorder == 2 { + std::mem::swap(&mut self.pic.frm1, &mut self.pic.frm2); + } + if self.reorder != 0 { + std::mem::swap(&mut self.pic.frm0, &mut self.pic.frm2); + } + self.reorder = 0; + + ret + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for Smush1Decoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + + +pub fn get_decoder_video_v1() -> Box { + Box::new(Smush1Decoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::game_register_all_decoders; + use crate::game_register_all_demuxers; + // samples from Rebel Assault + #[test] + fn test_smush_anim_v1() { + let mut dmx_reg = RegisteredDemuxers::new(); + game_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + test_decoding("smush", "smushv1", "assets/Game/smush/c1block.anm", Some(42), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x39339398, 0x7ce83788, 0xaac917d4, 0xaef9d653])); + test_decoding("smush", "smushv1", "assets/Game/smush/c1c3po.anm", Some(42), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x9c1c2422, 0x2121aa7a, 0xc06418bc, 0xd82d704b])); + test_decoding("smush", "smushv1", "assets/Game/smush/o1option.anm", Some(4), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x21ea3ee9, 0x3d88bcee, 0x9b71a87a, 0xc5e0a006])); + } + #[test] + fn test_smush_anim_v2() { + let mut dmx_reg = RegisteredDemuxers::new(); + game_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + // sample from The Dig + test_decoding("smush", "smushv1", "assets/Game/smush/PIGOUT.SAN", Some(42), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x74794559, 0x78a1e484, 0x379a1eec, 0x0609e0b2])); + // sample from Full Throttle + test_decoding("smush", "smushv1", "assets/Game/smush/FIRE.SAN", Some(16), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x442f73b9, 0x0b98d80f, 0xee2f0e19, 0xa555a33d])); + // sample from Mortimer and the Riddles of the Medallion + test_decoding("smush", "smushv1", "assets/Game/smush/FOREST1.SAN", Some(24), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0xd5b71505, 0x0ffe79dd, 0xc274dbaf, 0x8b952271])); + // sample from Curse of Monkey Island + test_decoding("smush", "smushv1", "assets/Game/smush/ZAP010.SAN", None, &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x369839f1, 0x2daab242, 0x23995d80, 0x501fbe09])); + // sample from Jedi Knight: Mysteries of the Sith + test_decoding("smush", "smushv1", "assets/Game/smush/S2L4ECS.SAN", Some(42), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x4525b5f3, 0x9fe5fb23, 0xf5f27980, 0x12589ce1])); + } +} + +const C37_MV: [[i8; 255 * 2]; 3] = [ + [ + 0, 0, 1, 0, 2, 0, 3, 0, 5, 0, 8, 0, 13, 0, 21, + 0, -1, 0, -2, 0, -3, 0, -5, 0, -8, 0, -13, 0, -17, 0, + -21, 0, 0, 1, 1, 1, 2, 1, 3, 1, 5, 1, 8, 1, 13, + 1, 21, 1, -1, 1, -2, 1, -3, 1, -5, 1, -8, 1, -13, 1, + -17, 1, -21, 1, 0, 2, 1, 2, 2, 2, 3, 2, 5, 2, 8, + 2, 13, 2, 21, 2, -1, 2, -2, 2, -3, 2, -5, 2, -8, 2, + -13, 2, -17, 2, -21, 2, 0, 3, 1, 3, 2, 3, 3, 3, 5, + 3, 8, 3, 13, 3, 21, 3, -1, 3, -2, 3, -3, 3, -5, 3, + -8, 3, -13, 3, -17, 3, -21, 3, 0, 5, 1, 5, 2, 5, 3, + 5, 5, 5, 8, 5, 13, 5, 21, 5, -1, 5, -2, 5, -3, 5, + -5, 5, -8, 5, -13, 5, -17, 5, -21, 5, 0, 8, 1, 8, 2, + 8, 3, 8, 5, 8, 8, 8, 13, 8, 21, 8, -1, 8, -2, 8, + -3, 8, -5, 8, -8, 8, -13, 8, -17, 8, -21, 8, 0, 13, 1, + 13, 2, 13, 3, 13, 5, 13, 8, 13, 13, 13, 21, 13, -1, 13, + -2, 13, -3, 13, -5, 13, -8, 13, -13, 13, -17, 13, -21, 13, 0, + 21, 1, 21, 2, 21, 3, 21, 5, 21, 8, 21, 13, 21, 21, 21, + -1, 21, -2, 21, -3, 21, -5, 21, -8, 21, -13, 21, -17, 21, -21, + 21, 0, -1, 1, -1, 2, -1, 3, -1, 5, -1, 8, -1, 13, -1, + 21, -1, -1, -1, -2, -1, -3, -1, -5, -1, -8, -1, -13, -1, -17, + -1, -21, -1, 0, -2, 1, -2, 2, -2, 3, -2, 5, -2, 8, -2, + 13, -2, 21, -2, -1, -2, -2, -2, -3, -2, -5, -2, -8, -2, -13, + -2, -17, -2, -21, -2, 0, -3, 1, -3, 2, -3, 3, -3, 5, -3, + 8, -3, 13, -3, 21, -3, -1, -3, -2, -3, -3, -3, -5, -3, -8, + -3, -13, -3, -17, -3, -21, -3, 0, -5, 1, -5, 2, -5, 3, -5, + 5, -5, 8, -5, 13, -5, 21, -5, -1, -5, -2, -5, -3, -5, -5, + -5, -8, -5, -13, -5, -17, -5, -21, -5, 0, -8, 1, -8, 2, -8, + 3, -8, 5, -8, 8, -8, 13, -8, 21, -8, -1, -8, -2, -8, -3, + -8, -5, -8, -8, -8, -13, -8, -17, -8, -21, -8, 0, -13, 1, -13, + 2, -13, 3, -13, 5, -13, 8, -13, 13, -13, 21, -13, -1, -13, -2, + -13, -3, -13, -5, -13, -8, -13, -13, -13, -17, -13, -21, -13, 0, -17, + 1, -17, 2, -17, 3, -17, 5, -17, 8, -17, 13, -17, 21, -17, -1, + -17, -2, -17, -3, -17, -5, -17, -8, -17, -13, -17, -17, -17, -21, -17, + 0, -21, 1, -21, 2, -21, 3, -21, 5, -21, 8, -21, 13, -21, 21, + -21, -1, -21, -2, -21, -3, -21, -5, -21, -8, -21, -13, -21, -17, -21 + ], [ + 0, 0, -8, -29, 8, -29, -18, -25, 17, -25, 0, -23, -6, -22, 6, + -22, -13, -19, 12, -19, 0, -18, 25, -18, -25, -17, -5, -17, 5, -17, + -10, -15, 10, -15, 0, -14, -4, -13, 4, -13, 19, -13, -19, -12, -8, + -11, -2, -11, 0, -11, 2, -11, 8, -11, -15, -10, -4, -10, 4, -10, + 15, -10, -6, -9, -1, -9, 1, -9, 6, -9, -29, -8, -11, -8, -8, + -8, -3, -8, 3, -8, 8, -8, 11, -8, 29, -8, -5, -7, -2, -7, + 0, -7, 2, -7, 5, -7, -22, -6, -9, -6, -6, -6, -3, -6, -1, + -6, 1, -6, 3, -6, 6, -6, 9, -6, 22, -6, -17, -5, -7, -5, + -4, -5, -2, -5, 0, -5, 2, -5, 4, -5, 7, -5, 17, -5, -13, + -4, -10, -4, -5, -4, -3, -4, -1, -4, 0, -4, 1, -4, 3, -4, + 5, -4, 10, -4, 13, -4, -8, -3, -6, -3, -4, -3, -3, -3, -2, + -3, -1, -3, 0, -3, 1, -3, 2, -3, 4, -3, 6, -3, 8, -3, + -11, -2, -7, -2, -5, -2, -3, -2, -2, -2, -1, -2, 0, -2, 1, + -2, 2, -2, 3, -2, 5, -2, 7, -2, 11, -2, -9, -1, -6, -1, + -4, -1, -3, -1, -2, -1, -1, -1, 0, -1, 1, -1, 2, -1, 3, + -1, 4, -1, 6, -1, 9, -1, -31, 0, -23, 0, -18, 0, -14, 0, + -11, 0, -7, 0, -5, 0, -4, 0, -3, 0, -2, 0, -1, 0, 0, + -31, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 7, 0, 11, 0, + 14, 0, 18, 0, 23, 0, 31, 0, -9, 1, -6, 1, -4, 1, -3, + 1, -2, 1, -1, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, + 6, 1, 9, 1, -11, 2, -7, 2, -5, 2, -3, 2, -2, 2, -1, + 2, 0, 2, 1, 2, 2, 2, 3, 2, 5, 2, 7, 2, 11, 2, + -8, 3, -6, 3, -4, 3, -2, 3, -1, 3, 0, 3, 1, 3, 2, + 3, 3, 3, 4, 3, 6, 3, 8, 3, -13, 4, -10, 4, -5, 4, + -3, 4, -1, 4, 0, 4, 1, 4, 3, 4, 5, 4, 10, 4, 13, + 4, -17, 5, -7, 5, -4, 5, -2, 5, 0, 5, 2, 5, 4, 5, + 7, 5, 17, 5, -22, 6, -9, 6, -6, 6, -3, 6, -1, 6, 1, + 6, 3, 6, 6, 6, 9, 6, 22, 6, -5, 7, -2, 7, 0, 7, + 2, 7, 5, 7, -29, 8, -11, 8, -8, 8, -3, 8, 3, 8, 8, + 8, 11, 8, 29, 8, -6, 9, -1, 9, 1, 9, 6, 9, -15, 10, + -4, 10, 4, 10, 15, 10, -8, 11, -2, 11, 0, 11, 2, 11, 8, + 11, 19, 12, -19, 13, -4, 13, 4, 13, 0, 14, -10, 15, 10, 15, + -5, 17, 5, 17, 25, 17, -25, 18, 0, 18, -12, 19, 13, 19, -6, + 22, 6, 22, 0, 23, -17, 25, 18, 25, -8, 29, 8, 29, 0, 31 + ], [ + 0, 0, -6, -22, 6, -22, -13, -19, 12, -19, 0, -18, -5, -17, 5, + -17, -10, -15, 10, -15, 0, -14, -4, -13, 4, -13, 19, -13, -19, -12, + -8, -11, -2, -11, 0, -11, 2, -11, 8, -11, -15, -10, -4, -10, 4, + -10, 15, -10, -6, -9, -1, -9, 1, -9, 6, -9, -11, -8, -8, -8, + -3, -8, 0, -8, 3, -8, 8, -8, 11, -8, -5, -7, -2, -7, 0, + -7, 2, -7, 5, -7, -22, -6, -9, -6, -6, -6, -3, -6, -1, -6, + 1, -6, 3, -6, 6, -6, 9, -6, 22, -6, -17, -5, -7, -5, -4, + -5, -2, -5, -1, -5, 0, -5, 1, -5, 2, -5, 4, -5, 7, -5, + 17, -5, -13, -4, -10, -4, -5, -4, -3, -4, -2, -4, -1, -4, 0, + -4, 1, -4, 2, -4, 3, -4, 5, -4, 10, -4, 13, -4, -8, -3, + -6, -3, -4, -3, -3, -3, -2, -3, -1, -3, 0, -3, 1, -3, 2, + -3, 3, -3, 4, -3, 6, -3, 8, -3, -11, -2, -7, -2, -5, -2, + -4, -2, -3, -2, -2, -2, -1, -2, 0, -2, 1, -2, 2, -2, 3, + -2, 4, -2, 5, -2, 7, -2, 11, -2, -9, -1, -6, -1, -5, -1, + -4, -1, -3, -1, -2, -1, -1, -1, 0, -1, 1, -1, 2, -1, 3, + -1, 4, -1, 5, -1, 6, -1, 9, -1, -23, 0, -18, 0, -14, 0, + -11, 0, -7, 0, -5, 0, -4, 0, -3, 0, -2, 0, -1, 0, 0, + -23, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 7, 0, 11, 0, + 14, 0, 18, 0, 23, 0, -9, 1, -6, 1, -5, 1, -4, 1, -3, + 1, -2, 1, -1, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, + 5, 1, 6, 1, 9, 1, -11, 2, -7, 2, -5, 2, -4, 2, -3, + 2, -2, 2, -1, 2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 2, + 5, 2, 7, 2, 11, 2, -8, 3, -6, 3, -4, 3, -3, 3, -2, + 3, -1, 3, 0, 3, 1, 3, 2, 3, 3, 3, 4, 3, 6, 3, + 8, 3, -13, 4, -10, 4, -5, 4, -3, 4, -2, 4, -1, 4, 0, + 4, 1, 4, 2, 4, 3, 4, 5, 4, 10, 4, 13, 4, -17, 5, + -7, 5, -4, 5, -2, 5, -1, 5, 0, 5, 1, 5, 2, 5, 4, + 5, 7, 5, 17, 5, -22, 6, -9, 6, -6, 6, -3, 6, -1, 6, + 1, 6, 3, 6, 6, 6, 9, 6, 22, 6, -5, 7, -2, 7, 0, + 7, 2, 7, 5, 7, -11, 8, -8, 8, -3, 8, 0, 8, 3, 8, + 8, 8, 11, 8, -6, 9, -1, 9, 1, 9, 6, 9, -15, 10, -4, + 10, 4, 10, 15, 10, -8, 11, -2, 11, 0, 11, 2, 11, 8, 11, + 19, 12, -19, 13, -4, 13, 4, 13, 0, 14, -10, 15, 10, 15, -5, + 17, 5, 17, 0, 18, -12, 19, 13, 19, -6, 22, 6, 22, 0, 23 + ] +]; + +const C47_MV: [[[i8; 2]; 255]; 2] = [ + [ + [ 0, 0], [ -1, -43], [ 6, -43], [ -9, -42], [ 13, -41], + [-16, -40], [ 19, -39], [-23, -36], [ 26, -34], [ -2, -33], + [ 4, -33], [-29, -32], [ -9, -32], [ 11, -31], [-16, -29], + [ 32, -29], [ 18, -28], [-34, -26], [-22, -25], [ -1, -25], + [ 3, -25], [ -7, -24], [ 8, -24], [ 24, -23], [ 36, -23], + [-12, -22], [ 13, -21], [-38, -20], [ 0, -20], [-27, -19], + [ -4, -19], [ 4, -19], [-17, -18], [ -8, -17], [ 8, -17], + [ 18, -17], [ 28, -17], [ 39, -17], [-12, -15], [ 12, -15], + [-21, -14], [ -1, -14], [ 1, -14], [-41, -13], [ -5, -13], + [ 5, -13], [ 21, -13], [-31, -12], [-15, -11], [ -8, -11], + [ 8, -11], [ 15, -11], [ -2, -10], [ 1, -10], [ 31, -10], + [-23, -9], [-11, -9], [ -5, -9], [ 4, -9], [ 11, -9], + [ 42, -9], [ 6, -8], [ 24, -8], [-18, -7], [ -7, -7], + [ -3, -7], [ -1, -7], [ 2, -7], [ 18, -7], [-43, -6], + [-13, -6], [ -4, -6], [ 4, -6], [ 8, -6], [-33, -5], + [ -9, -5], [ -2, -5], [ 0, -5], [ 2, -5], [ 5, -5], + [ 13, -5], [-25, -4], [ -6, -4], [ -3, -4], [ 3, -4], + [ 9, -4], [-19, -3], [ -7, -3], [ -4, -3], [ -2, -3], + [ -1, -3], [ 0, -3], [ 1, -3], [ 2, -3], [ 4, -3], + [ 6, -3], [ 33, -3], [-14, -2], [-10, -2], [ -5, -2], + [ -3, -2], [ -2, -2], [ -1, -2], [ 0, -2], [ 1, -2], + [ 2, -2], [ 3, -2], [ 5, -2], [ 7, -2], [ 14, -2], + [ 19, -2], [ 25, -2], [ 43, -2], [ -7, -1], [ -3, -1], + [ -2, -1], [ -1, -1], [ 0, -1], [ 1, -1], [ 2, -1], + [ 3, -1], [ 10, -1], [ -5, 0], [ -3, 0], [ -2, 0], + [ -1, 0], [ 1, 0], [ 2, 0], [ 3, 0], [ 5, 0], + [ 7, 0], [-10, 1], [ -7, 1], [ -3, 1], [ -2, 1], + [ -1, 1], [ 0, 1], [ 1, 1], [ 2, 1], [ 3, 1], + [-43, 2], [-25, 2], [-19, 2], [-14, 2], [ -5, 2], + [ -3, 2], [ -2, 2], [ -1, 2], [ 0, 2], [ 1, 2], + [ 2, 2], [ 3, 2], [ 5, 2], [ 7, 2], [ 10, 2], + [ 14, 2], [-33, 3], [ -6, 3], [ -4, 3], [ -2, 3], + [ -1, 3], [ 0, 3], [ 1, 3], [ 2, 3], [ 4, 3], + [ 19, 3], [ -9, 4], [ -3, 4], [ 3, 4], [ 7, 4], + [ 25, 4], [-13, 5], [ -5, 5], [ -2, 5], [ 0, 5], + [ 2, 5], [ 5, 5], [ 9, 5], [ 33, 5], [ -8, 6], + [ -4, 6], [ 4, 6], [ 13, 6], [ 43, 6], [-18, 7], + [ -2, 7], [ 0, 7], [ 2, 7], [ 7, 7], [ 18, 7], + [-24, 8], [ -6, 8], [-42, 9], [-11, 9], [ -4, 9], + [ 5, 9], [ 11, 9], [ 23, 9], [-31, 10], [ -1, 10], + [ 2, 10], [-15, 11], [ -8, 11], [ 8, 11], [ 15, 11], + [ 31, 12], [-21, 13], [ -5, 13], [ 5, 13], [ 41, 13], + [ -1, 14], [ 1, 14], [ 21, 14], [-12, 15], [ 12, 15], + [-39, 17], [-28, 17], [-18, 17], [ -8, 17], [ 8, 17], + [ 17, 18], [ -4, 19], [ 0, 19], [ 4, 19], [ 27, 19], + [ 38, 20], [-13, 21], [ 12, 22], [-36, 23], [-24, 23], + [ -8, 24], [ 7, 24], [ -3, 25], [ 1, 25], [ 22, 25], + [ 34, 26], [-18, 28], [-32, 29], [ 16, 29], [-11, 31], + [ 9, 32], [ 29, 32], [ -4, 33], [ 2, 33], [-26, 34], + [ 23, 36], [-19, 39], [ 16, 40], [-13, 41], [ 9, 42], + [ -6, 43], [ 1, 43], [ 0, 0], [ 0, 0], [ 0, 0], + ], [ + [ 0, 0], [ 1, 0], [ 2, 0], [ 3, 0], [ 5, 0], + [ 8, 0], [ 13, 0], [ 21, 0], [ -1, 0], [ -2, 0], + [ -3, 0], [ -5, 0], [ -8, 0], [-13, 0], [-17, 0], + [-21, 0], [ 0, 1], [ 1, 1], [ 2, 1], [ 3, 1], + [ 5, 1], [ 8, 1], [ 13, 1], [ 21, 1], [ -1, 1], + [ -2, 1], [ -3, 1], [ -5, 1], [ -8, 1], [-13, 1], + [-17, 1], [-21, 1], [ 0, 2], [ 1, 2], [ 2, 2], + [ 3, 2], [ 5, 2], [ 8, 2], [ 13, 2], [ 21, 2], + [ -1, 2], [ -2, 2], [ -3, 2], [ -5, 2], [ -8, 2], + [-13, 2], [-17, 2], [-21, 2], [ 0, 3], [ 1, 3], + [ 2, 3], [ 3, 3], [ 5, 3], [ 8, 3], [ 13, 3], + [ 21, 3], [ -1, 3], [ -2, 3], [ -3, 3], [ -5, 3], + [ -8, 3], [-13, 3], [-17, 3], [-21, 3], [ 0, 5], + [ 1, 5], [ 2, 5], [ 3, 5], [ 5, 5], [ 8, 5], + [ 13, 5], [ 21, 5], [ -1, 5], [ -2, 5], [ -3, 5], + [ -5, 5], [ -8, 5], [-13, 5], [-17, 5], [-21, 5], + [ 0, 8], [ 1, 8], [ 2, 8], [ 3, 8], [ 5, 8], + [ 8, 8], [ 13, 8], [ 21, 8], [ -1, 8], [ -2, 8], + [ -3, 8], [ -5, 8], [ -8, 8], [-13, 8], [-17, 8], + [-21, 8], [ 0, 13], [ 1, 13], [ 2, 13], [ 3, 13], + [ 5, 13], [ 8, 13], [ 13, 13], [ 21, 13], [ -1, 13], + [ -2, 13], [ -3, 13], [ -5, 13], [ -8, 13], [-13, 13], + [-17, 13], [-21, 13], [ 0, 21], [ 1, 21], [ 2, 21], + [ 3, 21], [ 5, 21], [ 8, 21], [ 13, 21], [ 21, 21], + [ -1, 21], [ -2, 21], [ -3, 21], [ -5, 21], [ -8, 21], + [-13, 21], [-17, 21], [-21, 21], [ 0, -1], [ 1, -1], + [ 2, -1], [ 3, -1], [ 5, -1], [ 8, -1], [ 13, -1], + [ 21, -1], [ -1, -1], [ -2, -1], [ -3, -1], [ -5, -1], + [ -8, -1], [-13, -1], [-17, -1], [-21, -1], [ 0, -2], + [ 1, -2], [ 2, -2], [ 3, -2], [ 5, -2], [ 8, -2], + [ 13, -2], [ 21, -2], [ -1, -2], [ -2, -2], [ -3, -2], + [ -5, -2], [ -8, -2], [-13, -2], [-17, -2], [-21, -2], + [ 0, -3], [ 1, -3], [ 2, -3], [ 3, -3], [ 5, -3], + [ 8, -3], [ 13, -3], [ 21, -3], [ -1, -3], [ -2, -3], + [ -3, -3], [ -5, -3], [ -8, -3], [-13, -3], [-17, -3], + [-21, -3], [ 0, -5], [ 1, -5], [ 2, -5], [ 3, -5], + [ 5, -5], [ 8, -5], [ 13, -5], [ 21, -5], [ -1, -5], + [ -2, -5], [ -3, -5], [ -5, -5], [ -8, -5], [-13, -5], + [-17, -5], [-21, -5], [ 0, -8], [ 1, -8], [ 2, -8], + [ 3, -8], [ 5, -8], [ 8, -8], [ 13, -8], [ 21, -8], + [ -1, -8], [ -2, -8], [ -3, -8], [ -5, -8], [ -8, -8], + [-13, -8], [-17, -8], [-21, -8], [ 0, -13], [ 1, -13], + [ 2, -13], [ 3, -13], [ 5, -13], [ 8, -13], [ 13, -13], + [ 21, -13], [ -1, -13], [ -2, -13], [ -3, -13], [ -5, -13], + [ -8, -13], [-13, -13], [-17, -13], [-21, -13], [ 0, -17], + [ 1, -17], [ 2, -17], [ 3, -17], [ 5, -17], [ 8, -17], + [ 13, -17], [ 21, -17], [ -1, -17], [ -2, -17], [ -3, -17], + [ -5, -17], [ -8, -17], [-13, -17], [-17, -17], [-21, -17], + [ 0, -21], [ 1, -21], [ 2, -21], [ 3, -21], [ 5, -21], + [ 8, -21], [ 13, -21], [ 21, -21], [ -1, -21], [ -2, -21], + [ -3, -21], [ -5, -21], [ -8, -21], [-13, -21], [-17, -21] + ] +]; diff --git a/nihav-game/src/codecs/smush/v2.rs b/nihav-game/src/codecs/smush/v2.rs new file mode 100644 index 0000000..ddf8f77 --- /dev/null +++ b/nihav-game/src/codecs/smush/v2.rs @@ -0,0 +1,459 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; +//use std::str::FromStr; + +struct FrameData { + info: NACodecInfoRef, + width: usize, + height: usize, + frm0: Vec, + frm1: Vec, + frm2: Vec, +} + +impl FrameData { + fn new() -> Self { + Self { + info: NACodecInfoRef::default(), + width: 0, + height: 0, + frm0: Vec::new(), + frm1: Vec::new(), + frm2: Vec::new(), + } + } + fn init(&mut self, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + self.width = vinfo.get_width(); + self.height = vinfo.get_height(); + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, RGB565_FORMAT)); + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); + + self.frm0.resize(self.width * self.height, 0); + self.frm1.resize(self.width * self.height, 0); + self.frm2.resize(self.width * self.height, 0); + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn get_frame(&mut self, pkt: &NAPacket) -> DecoderResult { + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?; + if let Some(ref mut vbuf) = bufinfo.get_vbuf16() { + let stride = vbuf.get_stride(0); + let data = vbuf.get_data_mut().unwrap(); + for (dst, src) in data.chunks_mut(stride).zip(self.frm0.chunks(self.width).take(self.height)) { + dst[..self.width].copy_from_slice(src); + } + } else { + return Err(DecoderError::Bug); + } + + let is_intra = pkt.keyframe; + let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo); + frm.set_keyframe(is_intra); + frm.set_frame_type(if is_intra { FrameType::I } else { FrameType::P }); + Ok(frm.into_ref()) + } +} + +fn decode_rle(br: &mut ByteReader, dst: &mut [u8]) -> DecoderResult<()> { + let mut len = 0; + let mut clr = 0; + let mut run = false; + + for el in dst.iter_mut() { + if len == 0 { + let op = br.read_byte()?; + run = (op & 1) != 0; + if run { + clr = br.read_byte()?; + } + len = ((op >> 1) + 1) as usize; + } + *el = if run { clr } else { br.read_byte()? }; + len -= 1; + } + validate!(len == 0); + + Ok(()) +} + +struct Smush2Decoder { + glyphs4: [[u8; 16]; 256], + glyphs8: [[u8; 64]; 256], + pic: FrameData, + rle_buf: Vec, +} + +impl Smush2Decoder { + fn new() -> Self { + let mut glyphs4 = [[0; 16]; 256]; + let mut glyphs8 = [[0; 64]; 256]; + super::make_glyphs_47(&mut glyphs4, &mut glyphs8); + Self { + pic: FrameData::new(), + rle_buf: Vec::new(), + glyphs4, glyphs8, + } + } +} + +struct BlockData<'a> { + glyphs4: &'a [[u8; 16]; 256], + glyphs8: &'a [[u8; 64]; 256], + frm1: &'a [u16], + frm2: &'a [u16], + cb: &'a [u16; 256], + clr4: [u16; 4], + stride: usize, +} + +fn draw_glyph(dst: &mut [u16], stride: usize, bsize: usize, glyph: &[u8], clr2: [u16; 2]) { + for (dst, src) in dst.chunks_mut(stride).zip(glyph.chunks_exact(bsize)) { + for (el, &bit) in dst[..bsize].iter_mut().zip(src.iter()) { + *el = clr2[bit as usize]; + } + } +} + +fn do_block2(br: &mut ByteReader, dst: &mut [u16], x: usize, y: usize, bsize: usize, bdata: &BlockData) -> DecoderResult<()> { + let stride = bdata.stride; + let op = br.read_byte()?; + match op { + 0xFF if bsize > 2 => { + let hsize = bsize / 2; + do_block2(br, dst, x, y, hsize, bdata)?; + do_block2(br, &mut dst[hsize..], x + hsize, y, bsize / 2, bdata)?; + do_block2(br, &mut dst[hsize * stride..], x, y + hsize, hsize, bdata)?; + do_block2(br, &mut dst[hsize * (stride + 1)..], x + hsize, y + hsize, bsize / 2, bdata)?; + }, + 0xFF => { + dst[0] = br.read_u16le()?; + dst[1] = br.read_u16le()?; + dst[stride] = br.read_u16le()?; + dst[stride + 1] = br.read_u16le()?; + }, + 0xFE => { + let pix = br.read_u16le()?; + for dst in dst.chunks_mut(stride).take(bsize) { + for el in dst[..bsize].iter_mut() { + *el = pix; + } + } + }, + 0xFD => { + let idx = br.read_byte()? as usize; + let pix = bdata.cb[idx]; + for dst in dst.chunks_mut(stride).take(bsize) { + for el in dst[..bsize].iter_mut() { + *el = pix; + } + } + }, + 0xF9..=0xFC => { + let pix = bdata.clr4[(op - 0xF9) as usize]; + for dst in dst.chunks_mut(stride).take(bsize) { + for el in dst[..bsize].iter_mut() { + *el = pix; + } + } + }, + 0xF8 if bsize > 2 => { + let idx = br.read_byte()? as usize; + let mut clr2 = [0; 2]; + clr2[1] = br.read_u16le()?; + clr2[0] = br.read_u16le()?; + let glyph: &[u8] = if bsize == 8 { &bdata.glyphs8[idx] } else { &bdata.glyphs4[idx] }; + draw_glyph(dst, stride, bsize, glyph, clr2); + }, + 0xF8 => { + dst[0] = br.read_u16le()?; + dst[1] = br.read_u16le()?; + dst[stride] = br.read_u16le()?; + dst[stride + 1] = br.read_u16le()?; + }, + 0xF7 if bsize > 2 => { + let idx = br.read_byte()? as usize; + let mut clr2 = [0; 2]; + clr2[1] = bdata.cb[br.read_byte()? as usize]; + clr2[0] = bdata.cb[br.read_byte()? as usize]; + let glyph: &[u8] = if bsize == 8 { &bdata.glyphs8[idx] } else { &bdata.glyphs4[idx] }; + draw_glyph(dst, stride, bsize, glyph, clr2); + }, + 0xF7 => { + dst[0] = bdata.cb[br.read_byte()? as usize]; + dst[1] = bdata.cb[br.read_byte()? as usize]; + dst[stride] = bdata.cb[br.read_byte()? as usize]; + dst[stride + 1] = bdata.cb[br.read_byte()? as usize]; + }, + 0xF6 => { + let off = x + y * stride; + let src = &bdata.frm1[off..]; + for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { + dst[..bsize].copy_from_slice(&src[..bsize]); + } + }, + 0xF5 => { + let off = br.read_u16le()? as i16 as isize; + let mx = off % (stride as isize); + let my = off / (stride as isize); + let off = (x as isize) + mx + ((y as isize) + my) * (stride as isize); + validate!(off >= 0); + let src = &bdata.frm2[off as usize..]; + for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { + let size = dst.len().min(src.len()).min(bsize); + dst[..size].copy_from_slice(&src[..size]); + } + }, + _ => { + let mx = C47_MV[op as usize][0] as isize; + let my = C47_MV[op as usize][1] as isize; + let off = (x as isize) + mx + ((y as isize) + my) * (stride as isize); + let src = &bdata.frm2[off as usize..]; + for (dst, src) in dst.chunks_mut(stride).zip(src.chunks(stride)).take(bsize) { + let size = dst.len().min(src.len()).min(bsize); + dst[..size].copy_from_slice(&src[..size]); + } + }, + }; + Ok(()) +} + +impl NADecoder for Smush2Decoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + self.pic.init(info) + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(src.len() > 8); + + let mut mr = MemoryReader::new_read(&src); + let mut br = ByteReader::new(&mut mr); + + let mut reorder = 0; + while br.left() > 0 { + let tag = br.read_tag()?; + let size = br.read_u32be()? as usize; + let tend = br.tell() + (size as u64); + validate!((size as i64) <= br.left()); + match &tag { + b"Bl16" => { + validate!(size >= 8); + br.read_skip(2)?; + let _x = br.read_u16le()? as usize; + let _y = br.read_u16le()? as usize; + br.read_skip(2)?; + validate!(_x <= self.pic.width && _y <= self.pic.height); + if size > 8 { + let w = br.read_u32le()? as usize; + let h = br.read_u32le()? as usize; + validate!(w == self.pic.width && h == self.pic.height); + let seq = br.read_u16le()?; + let compr = br.read_byte()?; + reorder = br.read_byte()?; + br.read_skip(4)?; + let mut clr4 = [0; 4]; + for el in clr4.iter_mut() { + *el = br.read_u16le()?; + } + let bg_clr = br.read_u16le()?; + let _fg_clr = br.read_u16le()?; + let _unp_size = br.read_u32le()?; + let mut cb = [0; 256]; + for el in cb.iter_mut() { + *el = br.read_u16le()?; + } + if size > 0x230 { + br.read_skip(8)?; + } + validate!(br.tell() < tend); + let start = br.tell() as usize; + br.seek(SeekFrom::Start(tend))?; + let mut mr = MemoryReader::new_read(&src[start..(tend as usize)]); + let mut br = ByteReader::new(&mut mr); + + if seq == 0 { + for el in self.pic.frm1.iter_mut() { + *el = bg_clr; + } + for el in self.pic.frm2.iter_mut() { + *el = bg_clr; + } + } + + match compr { + 0 => { + for row in self.pic.frm0.chunks_mut(self.pic.width).take(h) { + for el in row[..w].iter_mut() { + *el = br.read_u16le()?; + } + } + }, + 1 => { unimplemented!(); }, //decode half-res and interpolate + 2 => { + let bdata = BlockData { + glyphs4: &self.glyphs4, + glyphs8: &self.glyphs8, + frm1: &self.pic.frm1, + frm2: &self.pic.frm2, + stride: self.pic.width, + clr4, + cb: &cb, + }; + let dst = &mut self.pic.frm0; + let stride = self.pic.width; + for (row_no, row) in dst.chunks_mut(stride * 8).take((h + 7) / 8).enumerate() { + for col in (0..w).step_by(8) { + do_block2(&mut br, &mut row[col..], col, row_no * 8, 8, &bdata)?; + } + } + }, + 3 => { + self.pic.frm0.copy_from_slice(&self.pic.frm2); + }, + 4 => { + self.pic.frm0.copy_from_slice(&self.pic.frm1); + }, + 5 => { + let size = w * h * 2; + self.rle_buf.resize(size, 0); + decode_rle(&mut br, &mut self.rle_buf)?; + for (drow, srow) in self.pic.frm0.chunks_mut(self.pic.width).zip(self.rle_buf.chunks(w * 2)) { + for (dst, src) in drow.iter_mut().zip(srow.chunks_exact(2)) { + *dst = read_u16le(src)?; + } + } + }, + 6 => { + for row in self.pic.frm0.chunks_mut(self.pic.width).take(h) { + for el in row[..w].iter_mut() { + let idx = br.read_byte()? as usize; + *el = cb[idx]; + } + } + }, + 7 => { unimplemented!(); }, //decode half-res using codebook indices and interpolate + 8 => { + let size = w * h; + self.rle_buf.resize(size, 0); + decode_rle(&mut br, &mut self.rle_buf)?; + for (row, src) in self.pic.frm0.chunks_mut(self.pic.width).zip(self.rle_buf.chunks(w)) { + for (el, &idx) in row.iter_mut().zip(src.iter()) { + *el = cb[idx as usize]; + } + } + }, + _ => return Err(DecoderError::NotImplemented), + }; + } + }, + _ => br.read_skip(size)?, + }; + } + + let ret = self.pic.get_frame(pkt); + + if reorder == 2 { + std::mem::swap(&mut self.pic.frm1, &mut self.pic.frm2); + } + if reorder != 0 { + std::mem::swap(&mut self.pic.frm0, &mut self.pic.frm2); + } + + ret + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for Smush2Decoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + + +pub fn get_decoder_video_v2() -> Box { + Box::new(Smush2Decoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::game_register_all_decoders; + use crate::game_register_all_demuxers; + // sample from Grim Fandango + #[test] + fn test_smush_sanm() { + let mut dmx_reg = RegisteredDemuxers::new(); + game_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + // sample from Grim Fandango + test_decoding("smush", "smushv2", "assets/Game/smush/lol.snm", Some(4), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x408e4dc9, 0x4483d7d8, 0xc9fae314, 0x3bb45ec9], + [0x83548952, 0x0b4a6ccb, 0x42609794, 0x59d3c7d4], + [0x5349f6ca, 0x56361199, 0x7194439f, 0x90df21b8], + [0x0c359bab, 0xed69f862, 0x9c899813, 0x3f6aac2a], + [0x58870617, 0x97c5f3a6, 0x1b2c761c, 0x6ec1cd0e]])); + } +} + +const C47_MV: [[i8; 2]; 255] = [ + [ 0, 0], [ -1, -43], [ 6, -43], [ -9, -42], [ 13, -41], + [-16, -40], [ 19, -39], [-23, -36], [ 26, -34], [ -2, -33], + [ 4, -33], [-29, -32], [ -9, -32], [ 11, -31], [-16, -29], + [ 32, -29], [ 18, -28], [-34, -26], [-22, -25], [ -1, -25], + [ 3, -25], [ -7, -24], [ 8, -24], [ 24, -23], [ 36, -23], + [-12, -22], [ 13, -21], [-38, -20], [ 0, -20], [-27, -19], + [ -4, -19], [ 4, -19], [-17, -18], [ -8, -17], [ 8, -17], + [ 18, -17], [ 28, -17], [ 39, -17], [-12, -15], [ 12, -15], + [-21, -14], [ -1, -14], [ 1, -14], [-41, -13], [ -5, -13], + [ 5, -13], [ 21, -13], [-31, -12], [-15, -11], [ -8, -11], + [ 8, -11], [ 15, -11], [ -2, -10], [ 1, -10], [ 31, -10], + [-23, -9], [-11, -9], [ -5, -9], [ 4, -9], [ 11, -9], + [ 42, -9], [ 6, -8], [ 24, -8], [-18, -7], [ -7, -7], + [ -3, -7], [ -1, -7], [ 2, -7], [ 18, -7], [-43, -6], + [-13, -6], [ -4, -6], [ 4, -6], [ 8, -6], [-33, -5], + [ -9, -5], [ -2, -5], [ 0, -5], [ 2, -5], [ 5, -5], + [ 13, -5], [-25, -4], [ -6, -4], [ -3, -4], [ 3, -4], + [ 9, -4], [-19, -3], [ -7, -3], [ -4, -3], [ -2, -3], + [ -1, -3], [ 0, -3], [ 1, -3], [ 2, -3], [ 4, -3], + [ 6, -3], [ 33, -3], [-14, -2], [-10, -2], [ -5, -2], + [ -3, -2], [ -2, -2], [ -1, -2], [ 0, -2], [ 1, -2], + [ 2, -2], [ 3, -2], [ 5, -2], [ 7, -2], [ 14, -2], + [ 19, -2], [ 25, -2], [ 43, -2], [ -7, -1], [ -3, -1], + [ -2, -1], [ -1, -1], [ 0, -1], [ 1, -1], [ 2, -1], + [ 3, -1], [ 10, -1], [ -5, 0], [ -3, 0], [ -2, 0], + [ -1, 0], [ 1, 0], [ 2, 0], [ 3, 0], [ 5, 0], + [ 7, 0], [-10, 1], [ -7, 1], [ -3, 1], [ -2, 1], + [ -1, 1], [ 0, 1], [ 1, 1], [ 2, 1], [ 3, 1], + [-43, 2], [-25, 2], [-19, 2], [-14, 2], [ -5, 2], + [ -3, 2], [ -2, 2], [ -1, 2], [ 0, 2], [ 1, 2], + [ 2, 2], [ 3, 2], [ 5, 2], [ 7, 2], [ 10, 2], + [ 14, 2], [-33, 3], [ -6, 3], [ -4, 3], [ -2, 3], + [ -1, 3], [ 0, 3], [ 1, 3], [ 2, 3], [ 4, 3], + [ 19, 3], [ -9, 4], [ -3, 4], [ 3, 4], [ 7, 4], + [ 25, 4], [-13, 5], [ -5, 5], [ -2, 5], [ 0, 5], + [ 2, 5], [ 5, 5], [ 9, 5], [ 33, 5], [ -8, 6], + [ -4, 6], [ 4, 6], [ 13, 6], [ 43, 6], [-18, 7], + [ -2, 7], [ 0, 7], [ 2, 7], [ 7, 7], [ 18, 7], + [-24, 8], [ -6, 8], [-42, 9], [-11, 9], [ -4, 9], + [ 5, 9], [ 11, 9], [ 23, 9], [-31, 10], [ -1, 10], + [ 2, 10], [-15, 11], [ -8, 11], [ 8, 11], [ 15, 11], + [ 31, 12], [-21, 13], [ -5, 13], [ 5, 13], [ 41, 13], + [ -1, 14], [ 1, 14], [ 21, 14], [-12, 15], [ 12, 15], + [-39, 17], [-28, 17], [-18, 17], [ -8, 17], [ 8, 17], + [ 17, 18], [ -4, 19], [ 0, 19], [ 4, 19], [ 27, 19], + [ 38, 20], [-13, 21], [ 12, 22], [-36, 23], [-24, 23], + [ -8, 24], [ 7, 24], [ -3, 25], [ 1, 25], [ 22, 25], + [ 34, 26], [-18, 28], [-32, 29], [ 16, 29], [-11, 31], + [ 9, 32], [ 29, 32], [ -4, 33], [ 2, 33], [-26, 34], + [ 23, 36], [-19, 39], [ 16, 40], [-13, 41], [ 9, 42], + [ -6, 43], [ 1, 43], [ 0, 0], [ 0, 0], [ 0, 0], +]; diff --git a/nihav-game/src/codecs/smush/vima.rs b/nihav-game/src/codecs/smush/vima.rs new file mode 100644 index 0000000..aec2d5c --- /dev/null +++ b/nihav-game/src/codecs/smush/vima.rs @@ -0,0 +1,172 @@ +use nihav_core::codecs::*; +use nihav_core::io::bitreader::*; +use nihav_codec_support::codecs::imaadpcm::*; +use std::str::FromStr; + +const VIMA_STEPS: [&[i8]; 4] = [ + /*&[ -1, 4, -1, 4 ], + &[ -1, -1, 2, 6, -1, -1, 2, 6 ],*/ + &[ -1, -1, -1, -1, 1, 2, 4, 6, -1, -1, -1, -1, 1, 2, 4, 6 ], + &[ -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 2, 2, 4, 5, 6, + -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 2, 2, 4, 5, 6 ], + &[ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 5, 5, 6, 6, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 5, 5, 6, 6 ], + &[ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6 ] +]; + +const STEP_TO_BITS: [u8; 89] = [ + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 +]; + +struct VIMADecoder { + ainfo: NAAudioInfo, + chmap: NAChannelMap, +} + +impl VIMADecoder { + fn new() -> Self { + Self { + ainfo: NAAudioInfo::new(0, 1, SND_S16P_FORMAT, 0), + chmap: NAChannelMap::new(), + } + } +} + +impl NADecoder for VIMADecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() { + let channels = ainfo.get_channels(); + validate!(channels == 1 || channels == 2); + self.ainfo = NAAudioInfo::new(ainfo.get_sample_rate(), channels, SND_S16P_FORMAT, 1); + self.chmap = NAChannelMap::from_str(if channels == 1 { "C" } else { "L,R" }).unwrap(); + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let info = pkt.get_stream().get_info(); + if let NACodecTypeInfo::Audio(_) = info.get_properties() { + let src = pkt.get_buffer(); + validate!(src.len() > 4); + + let mut br = BitReader::new(&src, BitReaderMode::BE); + let mut samples = br.read(32)? as usize; + if samples == 0xFFFFFFFF { + br.skip(32)?; + samples = br.read(32)? as usize; + } + + let mut steps = [0; 2]; + steps[0] = br.read(8)? as usize; + let stereo = (steps[0] & 0x80) != 0; + validate!(!stereo || (self.chmap.num_channels() == 2)); + if stereo { + steps[0] ^= 0xFF; + } + validate!(steps[0] <= (IMA_MAX_STEP as usize)); + + let mut predictor = [0; 2]; + predictor[0] = br.read_s(16)?; + if stereo { + steps[1] = br.read(8)? as usize; + validate!(steps[1] <= (IMA_MAX_STEP as usize)); + predictor[1] = br.read_s(16)?; + } + + let abuf = alloc_audio_buffer(self.ainfo, samples, self.chmap.clone())?; + let mut adata = abuf.get_abuf_i16().unwrap(); + let offset = adata.get_offset(1); + let adata = adata.get_data_mut().unwrap(); + let (l, r) = adata.split_at_mut(offset); + + for (ch_no, ch) in [l, r].iter_mut().take(if stereo { 2 } else { 1 }).enumerate() { + let mut step = steps[ch_no]; + let mut sample = predictor[ch_no]; + + for dst in ch.iter_mut().take(samples) { + let bits = STEP_TO_BITS[step]; + let mask = 1 << (bits - 1); + + let idx = br.read(bits)? as u8; + + sample = if (idx & !mask) != (mask - 1) { + let sign = (idx & mask) != 0; + let aidx = idx & !mask; + + let mut diff = (i32::from(2 * aidx + 1) * IMA_STEP_TABLE[step]) >> (bits - 1); + if sign { + diff = -diff; + } + + (sample + diff).max(-32768).min(32767) + } else { + br.read_s(16)? + }; + step = ((step as i8) + VIMA_STEPS[(bits - 4) as usize][idx as usize]).max(0).min(IMA_MAX_STEP as i8) as usize; + *dst = sample as i16; + } + } + if !stereo && self.chmap.num_channels() == 2 { + let (l, r) = adata.split_at_mut(offset); + r[..samples].copy_from_slice(l); + } + + let mut frm = NAFrame::new_from_pkt(pkt, info, abuf); + frm.set_duration(Some(samples as u64)); + frm.set_keyframe(true); + Ok(frm.into_ref()) + } else { + Err(DecoderError::InvalidData) + } + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for VIMADecoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + +pub fn get_decoder_vima() -> Box { + Box::new(VIMADecoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::game_register_all_decoders; + use crate::game_register_all_demuxers; + #[test] + fn test_smush_vima() { + let mut dmx_reg = RegisteredDemuxers::new(); + game_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + // samples from Grim Fandango + test_decoding("smush", "smush-vima", "assets/Game/smush/lol.snm", Some(75000), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0xddd5dce1, 0xd5dc353c, 0xba176be8, 0x5afade63])); + test_decoding("smush", "smush-vima", "assets/Game/smush/ac_bu.snm", None, &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x97a548e7, 0xb22d082b, 0x14c4110b, 0x9723891f])); + } +} + diff --git a/nihav-game/src/demuxers/mod.rs b/nihav-game/src/demuxers/mod.rs index 2377a84..fe35075 100644 --- a/nihav-game/src/demuxers/mod.rs +++ b/nihav-game/src/demuxers/mod.rs @@ -15,6 +15,8 @@ mod gdv; mod imax; #[cfg(feature="demuxer_q")] mod q; +#[cfg(feature="demuxer_smush")] +mod smush; #[cfg(feature="demuxer_vmd")] mod vmd; #[cfg(feature="demuxer_vx")] @@ -35,6 +37,8 @@ const GAME_DEMUXERS: &[&dyn DemuxerCreator] = &[ &imax::IMAXDemuxerCreator {}, #[cfg(feature="demuxer_q")] &q::QDemuxerCreator {}, +#[cfg(feature="demuxer_smush")] + &smush::SmushDemuxerCreator {}, #[cfg(feature="demuxer_vmd")] &vmd::VMDDemuxerCreator {}, #[cfg(feature="demuxer_vx")] diff --git a/nihav-game/src/demuxers/smush.rs b/nihav-game/src/demuxers/smush.rs new file mode 100644 index 0000000..7ed698f --- /dev/null +++ b/nihav-game/src/demuxers/smush.rs @@ -0,0 +1,564 @@ +use nihav_core::frame::*; +use nihav_core::demuxers::*; + +struct SmushDemuxer<'a> { + src: &'a mut ByteReader<'a>, + old: bool, + size: u64, + + nframes: usize, + chunks: Vec, + + keyframe: bool, + cur_frame: usize, + frme_end: u64, + asize: u64, +} + +fn parse_iact(br: &mut ByteReader, end: u64, arate: &mut u32, abits: &mut u8, chans: &mut u8) -> DemuxerResult<()> { + br.read_skip(14)?; + let tag = br.read_tag()?; + if &tag != b"iMUS" { + *arate = 22050; + *abits = 16; + *chans = 2; + return Ok(()); + } + br.read_skip(4)?; + while br.tell() < end { + let tag = br.read_tag()?; + let size = u64::from(br.read_u32be()?); + match &tag { + b"MAP " => { + let cend = br.tell() + size; + while br.tell() < cend { + let tag = br.read_tag()?; + let size = u64::from(br.read_u32be()?); + match &tag { + b"FRMT" => { + validate!(size == 20); + br.read_u32be()?; + br.read_u32be()?; + let bits = br.read_u32be()?; + validate!(bits > 0 && bits <= 16); + *abits = bits as u8; + *arate = br.read_u32be()?; + let c = br.read_u32be()?; + validate!(c == 1 || c == 2); + *chans = c as u8; + return Ok(()); + }, + _ => br.read_skip(size as usize)?, + }; + } + }, + b"DATA" => return Err(DemuxerError::InvalidData), + _ => br.read_skip(size as usize)?, + }; + } + Err(DemuxerError::InvalidData) +} + +impl<'a> SmushDemuxer<'a> { + fn new(io: &'a mut ByteReader<'a>) -> Self { + SmushDemuxer { + src: io, + + old: false, + size: 0, + + nframes: 0, + chunks: Vec::new(), + + keyframe: false, + cur_frame: 0, + frme_end: 0, + asize: 0, + } + } + fn parse_anim_header(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<()> { + let src = &mut self.src; + + let tag = src.read_tag()?; + validate!(&tag == b"AHDR"); + let size = u64::from(src.read_u32be()?); + validate!(size >= 768 + 6); + let end = src.tell() + size; + validate!(end < self.size); + let version = src.read_u16le()?; + validate!(version < 3); + self.nframes = src.read_u16le()? as usize; + validate!(self.nframes != 0); + src.read_skip(2)?; //max FRME size + let mut edata = vec![0; 768 + 1]; + edata[0] = version as u8; + src.read_buf(&mut edata[1..][..768])?; + src.read_skip(size as usize - 768 - 6)?; + + let start = src.tell(); + let mut size = 0; + while size == 0 { + let tag = src.read_tag()?; + validate!(&tag == b"FRME"); + size = u64::from(src.read_u32be()?); + } + + let end = src.tell() + size; + validate!(end <= self.size + 8); // some NUTs feature slightly incorrect total size + + let mut width = 0; + let mut height = 0; + let mut aname = ""; + let mut arate = 0; + let mut abits = 0; + let mut chans = 0; + + while src.tell() < end { + let tag = src.read_tag()?; + let size = u64::from(src.read_u32be()?); + + let tend = src.tell() + size; + validate!(tend <= end); + match &tag { + b"FOBJ" => { + validate!(size >= 10); + let _codec = src.read_u16le()?; + let x = src.read_u16le()? as i16; + let y = src.read_u16le()? as i16; + if x == 0 && y == 0 && width == 0 && height == 0 { + width = src.read_u16le()? as usize; + height = src.read_u16le()? as usize; + } else { + let w = src.read_u16le()? as usize; + let h = src.read_u16le()? as usize; + if x == 0 && y == 0 && w >= width && h >= height { + width = w; + height = h; + } + } + src.read_skip((size - 10) as usize)?; + }, + b"IACT" => { + validate!(size > 8); + let end = src.tell() + size; + let opcode = src.read_u16le()?; + let flags = src.read_u16le()?; + if (opcode == 8) && (flags == 0x2E) { + if parse_iact(src, end, &mut arate, &mut abits, &mut chans).is_ok() { + aname = "smush-iact"; + } + validate!(src.tell() <= end); + } + src.seek(SeekFrom::Start(end))?; + }, + b"PSAD" => { + aname = "pcm"; + arate = 11025; + abits = 8; + chans = 2; + src.read_skip(size as usize)?; + }, + _ => { src.read_skip(size as usize)?; }, + }; + if (src.tell() & 1) != 0 { + if let Ok(0) = src.peek_byte() { + src.read_skip(1)?; + } + } + } + // hack + width = width.max(320); + height = height.max(200); + src.seek(SeekFrom::Start(start))?; + self.frme_end = start; + + let vhdr = NAVideoInfo::new(width, height, false, PAL8_FORMAT); + let vci = NACodecTypeInfo::Video(vhdr); + let vinfo = NACodecInfo::new("smushv1", vci, Some(edata)); + if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 15, self.nframes as u64)).is_none() { + return Err(DemuxerError::MemoryError); + } + + if !aname.is_empty() { + validate!(arate > 0); + let mut fmt = SND_S16_FORMAT; + match aname { + "pcm" => { fmt = SND_U8_FORMAT; }, + "smush-iact" => { fmt.bits = abits; fmt.packed = true; }, + _ => {}, + }; + let ahdr = NAAudioInfo::new(arate, chans, fmt, 0); + let ainfo = NACodecInfo::new(aname, NACodecTypeInfo::Audio(ahdr), None); + if strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, arate, 0)).is_none() { + return Err(DemuxerError::MemoryError); + } + } + + Ok(()) + } + fn parse_sanm_header(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<()> { + let src = &mut self.src; + + let tag = src.read_tag()?; + validate!(&tag == b"SHDR"); + let size = u64::from(src.read_u32be()?); + validate!(src.tell() + size <= self.size); + validate!(size >= 0x426); + + let maj_ver = src.read_byte()?; + let min_ver = src.read_byte()?; + if maj_ver != 1 || min_ver != 0 { + return Err(DemuxerError::NotImplemented); + } + self.nframes = src.read_u16le()? as usize; + let _xoff = src.read_u16le()? as usize; + let _yoff = src.read_u16le()? as usize; + let width = src.read_u16le()? as usize; + let height = src.read_u16le()? as usize; + let _imgtype = src.read_u16le()?; + let frame_delay = src.read_u32le()?; + let _max_frame_size = src.read_u32le()?; + src.read_skip(1024)?; // palette + src.read_skip((size as usize) - 0x416)?; + + let tag = src.read_tag()?; + validate!(&tag == b"FLHD"); + let size = u64::from(src.read_u32be()?); + let end = src.tell() + size; + + let mut arate = 0; + let mut chans = 0; + let mut alen = 0; + while src.tell() < end { + let tag = src.read_tag()?; + if src.tell() == end { break; } + let size = src.read_u32be()?; + match &tag { + b"Wave" => { + validate!(size >= 8); + arate = src.read_u32le()?; + let cc = src.read_u32le()?; + validate!(cc == 1 || cc == 2); + chans = cc as u8; + if size >= 12 { + alen = u64::from(src.read_u32le()? / cc / 2); + src.read_skip((size as usize) - 12)?; + } + }, + _ => src.read_skip(size as usize)?, + }; + } + validate!(src.tell() == end); + + let vhdr = NAVideoInfo::new(width, height, false, RGB565_FORMAT); + let vci = NACodecTypeInfo::Video(vhdr); + let vinfo = NACodecInfo::new("smushv2", vci, None); + if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, frame_delay, 1000000, self.nframes as u64)).is_none() { + return Err(DemuxerError::MemoryError); + } + if arate != 0 { + let ahdr = NAAudioInfo::new(arate, chans, SND_S16P_FORMAT, 0); + let ainfo = NACodecInfo::new("smush-vima", NACodecTypeInfo::Audio(ahdr), None); + if strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, arate, alen)).is_none() { + return Err(DemuxerError::MemoryError); + } + } + + Ok(()) + } + + fn queue_chunk(&mut self, tag: [u8; 4], size: usize) -> DemuxerResult<()> { + self.chunks.extend_from_slice(&tag); + let start = self.chunks.len(); + let nlen = start + size + 4; + self.chunks.resize(nlen, 0); + write_u32be(&mut self.chunks[start..], size as u32).unwrap(); + self.src.read_buf(&mut self.chunks[start + 4..])?; + Ok(()) + } + fn get_frame_anim(&mut self, strmgr: &mut StreamManager) -> DemuxerResult { + loop { + if self.src.tell() >= self.frme_end { + if !self.chunks.is_empty() { + let mut buf = Vec::new(); + std::mem::swap(&mut self.chunks, &mut buf); + let stream = strmgr.get_stream(0).unwrap(); + let (tb_num, tb_den) = stream.get_timebase(); + let ts = NATimeInfo::new(Some(self.cur_frame as u64 - 1), None, None, tb_num, tb_den); + return Ok(NAPacket::new(stream, ts, false, buf)); + } + if self.cur_frame == self.nframes { + return Err(DemuxerError::EOF); + } + let tag = self.src.read_tag()?; + validate!(&tag == b"FRME"); + let size = u64::from(self.src.read_u32be()?); + self.frme_end = self.src.tell() + size; + + self.chunks.clear(); + self.cur_frame += 1; + if size == 0 { + continue; + } + } + let tag = self.src.read_tag()?; + let size = u64::from(self.src.read_u32be()?); + let tend = self.src.tell() + size; + validate!(tend <= self.frme_end); + match &tag { + b"STOR" | b"FTCH" | b"NPAL" | b"XPAL" | b"FOBJ" => { + self.queue_chunk(tag, size as usize)?; + }, + b"IACT" => { + validate!(size >= 4); + let opcode = self.src.read_u16le()?; + let flags = self.src.read_u16le()?; + if (opcode == 8) && (flags == 0x2E) { + if let Some(stream) = strmgr.get_stream(1) { + let (tb_num, tb_den) = stream.get_timebase(); + let ts = NATimeInfo::new(None, None, None, tb_num, tb_den); + + let mut buf = vec![0; size as usize]; + write_u16le(&mut buf[0..2], opcode).unwrap(); + write_u16le(&mut buf[2..4], flags).unwrap(); + self.src.read_buf(&mut buf[4..])?; + + if (self.src.tell() & 1) == 1 { + if let Ok(0) = self.src.peek_byte() { + self.src.read_skip(1)?; + } + } + return Ok(NAPacket::new(stream, ts, true, buf)); + } + } + self.src.read_skip((size as usize) - 4)?; + }, + b"PSAD" => { + if size > 0x30 { + self.src.read_skip(0x30)?; + if let Some(stream) = strmgr.get_stream(1) { + let (tb_num, tb_den) = stream.get_timebase(); + + let audio_size = size - 0x30; + let ts = NATimeInfo::new(Some(self.asize), None, None, tb_num, tb_den); + let pkt = self.src.read_packet(stream, ts, true, audio_size as usize)?; + self.asize += audio_size; + if (self.src.tell() & 1) == 1 { + if let Ok(0) = self.src.peek_byte() { + self.src.read_skip(1)?; + } + } + return Ok(pkt); + } else { + self.src.read_skip((size - 0x30) as usize)?; + } + } else { + self.src.read_skip(size as usize)?; + } + }, + _ => { + self.src.read_skip(size as usize)?; + }, + }; + if (self.src.tell() & 1) == 1 { + if let Ok(0) = self.src.peek_byte() { + self.src.read_skip(1)?; + } + } + } + } + fn get_frame_sanm(&mut self, strmgr: &mut StreamManager) -> DemuxerResult { + loop { + if self.src.tell() >= self.frme_end { + if !self.chunks.is_empty() { + let mut buf = Vec::new(); + std::mem::swap(&mut self.chunks, &mut buf); + let stream = strmgr.get_stream(0).unwrap(); + let (tb_num, tb_den) = stream.get_timebase(); + let ts = NATimeInfo::new(Some(self.cur_frame as u64 - 1), None, None, tb_num, tb_den); + return Ok(NAPacket::new(stream, ts, self.keyframe, buf)); + } + if self.cur_frame == self.nframes { + return Err(DemuxerError::EOF); + } + let tag = self.src.read_tag()?; + let size = u64::from(self.src.read_u32be()?); + self.frme_end = self.src.tell() + size; + match &tag { + b"FLHD" => { self.keyframe = true; }, + b"FRME" => { self.keyframe = false; }, + _ => { + self.src.read_skip(size as usize)?; + continue; + }, + }; + + self.chunks.clear(); + self.cur_frame += 1; + if size == 0 { + continue; + } + } + let tag = self.src.read_tag()?; + if self.src.tell() >= self.frme_end { // happens after some Wave tags + continue; + } + let size = u64::from(self.src.read_u32be()?); + let tend = self.src.tell() + size; + validate!(tend <= self.frme_end); + match &tag { + b"Bl16" => { + self.queue_chunk(tag, size as usize)?; + }, + b"Wave" => { + if let Some(stream) = strmgr.get_stream(1) { + let mut buf = [0; 12]; + let mut nsamples = 0; + if size >= 12 { + self.src.peek_buf(&mut buf)?; + nsamples = read_u32be(&buf[0..])?; + if nsamples == 0xFFFFFFFF { + nsamples = read_u32be(&buf[8..])?; + } + } + + let (tb_num, tb_den) = stream.get_timebase(); + let mut ts = NATimeInfo::new(None, None, None, tb_num, tb_den); + if nsamples != 0 { + ts.pts = Some(self.asize); + self.asize += u64::from(nsamples); + } + let pkt = self.src.read_packet(stream, ts, true, size as usize)?; + return Ok(pkt); + } else { + self.src.read_skip(size as usize)?; + } + }, + _ => { +//println!("unknown tag {}{}{}{} size {:X} @ {:X}", tag[0] as char, tag[1] as char, tag[2] as char, tag[3] as char, size, self.src.tell() - 8); + self.src.read_skip(size as usize)?; + }, + }; + } + } +} + +impl<'a> DemuxCore<'a> for SmushDemuxer<'a> { + fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> { + let magic = self.src.read_tag()?; + match &magic { + b"ANIM" => { + self.old = true; + }, + b"SANM" => { + self.old = false; + }, + _ => return Err(DemuxerError::InvalidData), + }; + self.size = u64::from(self.src.read_u32be()?); + if self.old { + self.parse_anim_header(strmgr)?; + } else { + self.parse_sanm_header(strmgr)?; + } + + self.cur_frame = 0; + Ok(()) + } + + fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult { + if self.cur_frame > self.nframes { return Err(DemuxerError::EOF); } + if self.old { + self.get_frame_anim(strmgr) + } else { + self.get_frame_sanm(strmgr) + } + } + + fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> { + Err(DemuxerError::NotPossible) + } + fn get_duration(&self) -> u64 { 0 } +} + +impl<'a> NAOptionHandler for SmushDemuxer<'a> { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + +pub struct SmushDemuxerCreator { } + +impl DemuxerCreator for SmushDemuxerCreator { + fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box + 'a> { + Box::new(SmushDemuxer::new(br)) + } + fn get_name(&self) -> &'static str { "smush" } +} + +#[cfg(test)] +mod test { + use super::*; + use std::fs::File; + + #[test] + fn test_smush_demux_anim_v1() { + // sample from Rebel Assault game + let mut file = File::open("assets/Game/smush/c1block.anm").unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = SmushDemuxer::new(&mut br); + let mut sm = StreamManager::new(); + let mut si = SeekIndex::new(); + dmx.open(&mut sm, &mut si).unwrap(); + loop { + let pktres = dmx.get_frame(&mut sm); + if let Err(e) = pktres { + if (e as i32) == (DemuxerError::EOF as i32) { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + println!("Got {}", pkt); + } + } + #[test] + fn test_smush_demux_anim_v2() { + // sample from The Dig + let mut file = File::open("assets/Game/smush/PIGOUT.SAN").unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = SmushDemuxer::new(&mut br); + let mut sm = StreamManager::new(); + let mut si = SeekIndex::new(); + dmx.open(&mut sm, &mut si).unwrap(); + loop { + let pktres = dmx.get_frame(&mut sm); + if let Err(e) = pktres { + if (e as i32) == (DemuxerError::EOF as i32) { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + println!("Got {}", pkt); + } + } + #[test] + fn test_smush_demux_sanm() { + // sample from Grim Fandango + let mut file = File::open("assets/Game/smush/lol.snm").unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = SmushDemuxer::new(&mut br); + let mut sm = StreamManager::new(); + let mut si = SeekIndex::new(); + dmx.open(&mut sm, &mut si).unwrap(); + loop { + let pktres = dmx.get_frame(&mut sm); + if let Err(e) = pktres { + if (e as i32) == (DemuxerError::EOF as i32) { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + println!("Got {}", pkt); + } + } +} diff --git a/nihav-registry/src/detect.rs b/nihav-registry/src/detect.rs index 0f5af82..0fb33a2 100644 --- a/nihav-registry/src/detect.rs +++ b/nihav-registry/src/detect.rs @@ -274,6 +274,18 @@ const DETECTORS: &[DetectConditions] = &[ conditions: &[CheckItem{offs: 0, cond: &CC::Eq(Arg::U16LE(0x6839))}, CheckItem{offs: 2, cond: &CC::In(Arg::Byte(3), Arg::Byte(5))}], }, + DetectConditions { + demux_name: "smush", + extensions: ".san", + conditions: &[CheckItem{offs: 0, cond: &CC::Str(b"ANIM")}, + CheckItem{offs: 8, cond: &CC::Str(b"AHDR")}], + }, + DetectConditions { + demux_name: "smush", + extensions: ".snm", + conditions: &[CheckItem{offs: 0, cond: &CC::Str(b"SANM")}, + CheckItem{offs: 8, cond: &CC::Str(b"SHDR")}], + }, DetectConditions { demux_name: "realaudio", extensions: ".ra,.ram", diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index 0e3cc64..222d921 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -255,6 +255,10 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(video; "midivid", "MidiVid"), desc!(video; "midivid3", "MidiVid 3"), desc!(video-ll; "midivid-ll", "MidiVid Lossless"), + desc!(video; "smushv1", "SMUSH Video paletted"), + desc!(video; "smushv2", "SMUSH Video 16-bit"), + desc!(video; "smush-iact", "SMUSH IACT Audio"), + desc!(video; "smush-vima", "SMUSH VIMA Audio"), desc!(video; "vmd-video", "VMD video"), desc!(audio; "vmd-audio", "VMD audio"), desc!(video; "vxvideo", "Actimagine Vx"), -- 2.30.2