LucasArts SMUSH formats support
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sun, 9 Jan 2022 10:16:46 +0000 (11:16 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sun, 9 Jan 2022 10:16:46 +0000 (11:16 +0100)
nihav-game/Cargo.toml
nihav-game/src/codecs/mod.rs
nihav-game/src/codecs/smush/iact.rs [new file with mode: 0644]
nihav-game/src/codecs/smush/mod.rs [new file with mode: 0644]
nihav-game/src/codecs/smush/v1.rs [new file with mode: 0644]
nihav-game/src/codecs/smush/v2.rs [new file with mode: 0644]
nihav-game/src/codecs/smush/vima.rs [new file with mode: 0644]
nihav-game/src/demuxers/mod.rs
nihav-game/src/demuxers/smush.rs [new file with mode: 0644]
nihav-registry/src/detect.rs
nihav-registry/src/register.rs

index 8079a21..24a846e 100644 (file)
@@ -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"]
 
index f5e9532..b65bcc8 100644 (file)
@@ -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 (file)
index 0000000..7dccff4
--- /dev/null
@@ -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<u8>,
+}
+
+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<NAFrameRef> {
+        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<NAValue> { None }
+}
+
+pub fn get_decoder_iact() -> Box<dyn NADecoder + Send> {
+    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 (file)
index 0000000..9274cba
--- /dev/null
@@ -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 (file)
index 0000000..6731a29
--- /dev/null
@@ -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<u8>,
+    frm1:       Vec<u8>,
+    frm2:       Vec<u8>,
+}
+
+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<NAFrameRef> {
+        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<NAFrameRef> {
+        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<NAValue> { None }
+}
+
+
+pub fn get_decoder_video_v1() -> Box<dyn NADecoder + Send> {
+    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 (file)
index 0000000..ddf8f77
--- /dev/null
@@ -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<u16>,
+    frm1:       Vec<u16>,
+    frm2:       Vec<u16>,
+}
+
+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<NAFrameRef> {
+        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<u8>,
+}
+
+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<NAFrameRef> {
+        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<NAValue> { None }
+}
+
+
+pub fn get_decoder_video_v2() -> Box<dyn NADecoder + Send> {
+    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 (file)
index 0000000..aec2d5c
--- /dev/null
@@ -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<NAFrameRef> {
+        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<NAValue> { None }
+}
+
+pub fn get_decoder_vima() -> Box<dyn NADecoder + Send> {
+    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]));
+    }
+}
+
index 2377a84..fe35075 100644 (file)
@@ -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 (file)
index 0000000..7ed698f
--- /dev/null
@@ -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<u8>,
+
+    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<NAPacket> {
+        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<NAPacket> {
+        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<NAPacket> {
+        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<NAValue> { None }
+}
+
+pub struct SmushDemuxerCreator { }
+
+impl DemuxerCreator for SmushDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + '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);
+        }
+    }
+}
index 0f5af82..0fb33a2 100644 (file)
@@ -275,6 +275,18 @@ const DETECTORS: &[DetectConditions] = &[
                       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",
         conditions: &[CheckItem{offs: 0, cond: &CC::Str(b".ra\xFD")}],
index 0e3cc64..222d921 100644 (file)
@@ -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"),