add Highlander FMV support
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 15 Oct 2022 08:39:24 +0000 (10:39 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 15 Oct 2022 08:39:24 +0000 (10:39 +0200)
nihav-game/Cargo.toml
nihav-game/src/codecs/hl_fmv.rs [new file with mode: 0644]
nihav-game/src/codecs/mod.rs
nihav-game/src/demuxers/hl_fmv.rs [new file with mode: 0644]
nihav-game/src/demuxers/mod.rs
nihav-registry/src/detect.rs
nihav-registry/src/register.rs

index 3811364670bebf5d9810fa030892aad3b297df4d..eb0f1fd51317fc42dd2efe03bda6b47a73adf9ca 100644 (file)
@@ -18,12 +18,13 @@ 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_smush", "demuxer_vmd", "demuxer_vx"]
+all_demuxers = ["demuxer_bmv", "demuxer_bmv3", "demuxer_fcmp", "demuxer_fst", "demuxer_gdv", "demuxer_hl_fmv", "demuxer_imax", "demuxer_q", "demuxer_smush", "demuxer_vmd", "demuxer_vx"]
 demuxer_bmv = ["demuxers"]
 demuxer_bmv3 = ["demuxers"]
 demuxer_fcmp = ["demuxers"]
 demuxer_fst = ["demuxers"]
 demuxer_gdv = ["demuxers"]
+demuxer_hl_fmv = ["demuxers"]
 demuxer_imax = ["demuxers"]
 demuxer_q = ["demuxers"]
 demuxer_smush = ["demuxers"]
@@ -33,11 +34,12 @@ 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_smush_video", "decoder_vmd", "decoder_vx"]
+all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_hl_fmv", "decoder_imax", "decoder_ipma", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_smush_video", "decoder_vmd", "decoder_vx"]
 decoder_bmv = ["decoders"]
 decoder_bmv3 = ["decoders"]
 decoder_fstvid = ["decoders"]
 decoder_gdvvid = ["decoders"]
+decoder_hl_fmv = ["decoders"]
 decoder_imax = ["decoders"]
 decoder_ipma = ["decoders"]
 decoder_midivid = ["decoders"]
diff --git a/nihav-game/src/codecs/hl_fmv.rs b/nihav-game/src/codecs/hl_fmv.rs
new file mode 100644 (file)
index 0000000..426d144
--- /dev/null
@@ -0,0 +1,516 @@
+use nihav_core::codecs::*;
+use nihav_core::io::byteio::*;
+
+const FRAME_W: usize = 320;
+const FRAME_H: usize = 240;
+const HIST_SIZE: usize = 32768;
+
+struct Pattern {
+    len:        u8,
+    pattern:    [u8; 16],
+}
+
+struct HighlanderDecoder {
+    info:       NACodecInfoRef,
+    hist:       [u8; HIST_SIZE],
+    tmp1:       [u8; FRAME_W * FRAME_H],
+    tmp2:       [u8; FRAME_W * FRAME_H * 17 / 16],
+}
+
+fn unpack(src: &[u8], dst: &mut [u8], hist: &mut [u8; HIST_SIZE]) -> DecoderResult<usize> {
+    let mut mr = MemoryReader::new_read(src);
+    let mut br = ByteReader::new(&mut mr);
+
+    let mut mw = MemoryWriter::new_write(dst);
+    let mut bw = ByteWriter::new(&mut mw);
+
+    *hist = [0; HIST_SIZE];
+
+    let mut pprev = 0;
+    let mut prev  = 0;
+    while br.left() > 0 {
+        let mut flags               = br.read_byte()?;
+        for _ in 0..8 {
+            let idx = (usize::from(pprev) << 7) ^ usize::from(prev);
+            if (flags & 1) == 0 {
+                if br.left() == 0 {
+                    break;
+                }
+                hist[idx]           = br.read_byte()?;
+            }
+            let val = hist[idx];
+            bw.write_byte(val)?;
+
+            flags >>= 1;
+            pprev = prev;
+            prev  = val;
+        }
+    }
+
+    Ok(bw.tell() as usize)
+}
+
+fn paint_frame(dst: &mut [u8], stride: usize, src: &[u8]) -> DecoderResult<()> {
+    let mut mr = MemoryReader::new_read(src);
+    let mut br = ByteReader::new(&mut mr);
+
+    let mut blk_offs = [0; 16];
+    for (y, offs) in blk_offs.chunks_mut(4).enumerate() {
+        offs[0] = stride * y;
+        offs[1] = stride * y + 1;
+        offs[2] = stride * y + 2;
+        offs[3] = stride * y + 3;
+    }
+
+    for row in dst.chunks_mut(stride * 4).take(FRAME_H / 4) {
+        for xoff in (0..FRAME_W).step_by(4) {
+            let idx = br.read_byte()? as usize;
+            validate!(idx < PAINT_MODE.len());
+            let mode = &PAINT_MODE[idx];
+            validate!(i64::from(mode.len) <= br.left());
+
+            for (&blk_off, &idx) in blk_offs.iter().zip(mode.pattern.iter()) {
+                if idx == 0xFF {
+                    row[xoff + blk_off] = br.read_byte()?;
+                }
+            }
+            for (&blk_off, &idx) in blk_offs.iter().zip(mode.pattern.iter()) {
+                if idx != 0xFF {
+                    row[xoff + blk_off] = row[xoff + blk_offs[idx as usize]];
+                }
+            }
+        }
+    }
+    Ok(())
+}
+
+impl HighlanderDecoder {
+    fn new() -> Self {
+        Self {
+            info:   NACodecInfoRef::default(),
+            hist:   [0; HIST_SIZE],
+            tmp1:   [0; FRAME_W * FRAME_H],
+            tmp2:   [0; FRAME_W * FRAME_H * 17 / 16],
+        }
+    }
+}
+
+impl NADecoder for HighlanderDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(_vinfo) = info.get_properties() {
+            let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(FRAME_W, FRAME_H, false, PAL8_FORMAT));
+            self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref();
+
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let src = pkt.get_buffer();
+        validate!(src.len() > 4);
+
+        let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?;
+        let bufo = bufinfo.get_vbuf();
+        let mut buf = bufo.unwrap();
+        let paloff = buf.get_offset(1);
+        let stride = buf.get_stride(0);
+        let data = buf.get_data_mut().unwrap();
+        let dst = data.as_mut_slice();
+
+        validate!(src.len() > 4);
+
+        let size = read_u32le(&src)? as usize;
+        validate!(size <= src.len() - 4);
+
+        let size2 = unpack(&src[4..][..size],   &mut self.tmp1, &mut self.hist)?;
+        let size3 = unpack(&self.tmp1[..size2], &mut self.tmp2, &mut self.hist)?;
+        paint_frame(dst, stride, &self.tmp2[..size3])?;
+
+        let dpal = &mut dst[paloff..][..768];
+        for (dst, &src) in dpal.iter_mut().zip(DEFAULT_PAL.iter()) {
+            *dst = (src << 2) | (src >> 4);
+        }
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo);
+        frm.set_keyframe(true);
+        frm.set_frame_type(FrameType::I);
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {
+    }
+}
+
+impl NAOptionHandler for HighlanderDecoder {
+    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() -> Box<dyn NADecoder + Send> {
+    Box::new(HighlanderDecoder::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 extracted from Highlander: The Last of the MacLeods unpublished game
+    #[test]
+    fn test_hl_fmv_video() {
+        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("hl-fmv", "hl-fmv-video", "assets/Game/0260.fmv", Some(10), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5([0x369659f0, 0x417ad3a7, 0xc62dfc6f, 0x6e5fe871]));
+    }
+}
+
+const PAINT_MODE: [Pattern; 9] = [
+    Pattern {
+        len: 1,
+        pattern: [
+            0xFF, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00
+        ],
+    },
+    Pattern {
+        len: 2,
+        pattern: [
+            0x05, 0x09, 0x05, 0x09,
+            0x09, 0xFF, 0x09, 0x05,
+            0x05, 0xFF, 0x05, 0x09,
+            0x09, 0x05, 0x09, 0x05
+        ],
+    },
+    Pattern {
+        len: 2,
+        pattern: [
+            0xFF, 0xFF, 0x00, 0x01,
+            0x01, 0x01, 0x01, 0x01,
+            0x00, 0x01, 0x00, 0x01,
+            0x01, 0x01, 0x01, 0x01
+        ]
+    },
+    Pattern {
+        len: 2,
+        pattern: [
+            0xFF, 0xFF, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x00
+        ],
+    },
+    Pattern {
+        len: 2,
+        pattern: [
+            0x0E, 0x0E, 0x0E, 0x0E,
+            0x0E, 0x0F, 0x0E, 0x0F,
+            0x0E, 0x0E, 0x0E, 0x0E,
+            0x0E, 0x0F, 0xFF, 0xFF
+        ],
+    },
+    Pattern {
+        len: 2,
+        pattern: [
+            0x0F, 0x0F, 0x0F, 0x0F,
+            0x0E, 0x0F, 0x0E, 0x0F,
+            0x0F, 0x0F, 0x0F, 0x0F,
+            0x0E, 0x0F, 0xFF, 0xFF
+        ],
+    },
+    Pattern {
+        len: 5,
+        pattern: [
+            0xFF, 0xFF, 0x00, 0xFF,
+            0x01, 0xFF, 0x01, 0xFF,
+            0x00, 0x01, 0x00, 0x03,
+            0x03, 0x07, 0x03, 0x07
+        ],
+    },
+    Pattern {
+        len: 8,
+        pattern: [
+            0xFF, 0xFF, 0xFF, 0xFF,
+            0x01, 0x00, 0x03, 0x02,
+            0xFF, 0xFF, 0xFF, 0xFF,
+            0x09, 0x08, 0x0B, 0x0A
+        ],
+    },
+    Pattern {
+        len: 16,
+        pattern: [
+            0xFF, 0xFF, 0xFF, 0xFF,
+            0xFF, 0xFF, 0xFF, 0xFF,
+            0xFF, 0xFF, 0xFF, 0xFF,
+            0xFF, 0xFF, 0xFF, 0xFF
+        ],
+    }
+];
+
+const DEFAULT_PAL: [u8; 768] = [
+    0x00, 0x00, 0x00,
+    0x00, 0x00, 0x20,
+    0x00, 0x20, 0x00,
+    0x00, 0x20, 0x20,
+    0x20, 0x00, 0x00,
+    0x20, 0x00, 0x20,
+    0x20, 0x20, 0x00,
+    0x30, 0x30, 0x30,
+    0x30, 0x37, 0x30,
+    0x3C, 0x32, 0x29,
+    0x01, 0x01, 0x01,
+    0x02, 0x02, 0x02,
+    0x03, 0x03, 0x03,
+    0x04, 0x04, 0x04,
+    0x05, 0x05, 0x05,
+    0x07, 0x07, 0x07,
+    0x08, 0x08, 0x08,
+    0x0A, 0x0A, 0x0A,
+    0x15, 0x15, 0x15,
+    0x13, 0x13, 0x13,
+    0x10, 0x10, 0x10,
+    0x0E, 0x0E, 0x0E,
+    0x20, 0x20, 0x20,
+    0x00, 0x00, 0x20,
+    0x00, 0x20, 0x00,
+    0x00, 0x20, 0x20,
+    0x20, 0x00, 0x00,
+    0x20, 0x00, 0x20,
+    0x20, 0x20, 0x00,
+    0x00, 0x00, 0x0C,
+    0x00, 0x00, 0x19,
+    0x00, 0x00, 0x26,
+    0x00, 0x00, 0x33,
+    0x00, 0x0C, 0x00,
+    0x00, 0x0C, 0x0C,
+    0x00, 0x0C, 0x19,
+    0x00, 0x0C, 0x26,
+    0x00, 0x0C, 0x33,
+    0x00, 0x0C, 0x3F,
+    0x00, 0x19, 0x00,
+    0x00, 0x19, 0x0C,
+    0x00, 0x19, 0x19,
+    0x00, 0x19, 0x26,
+    0x00, 0x19, 0x33,
+    0x00, 0x19, 0x3F,
+    0x00, 0x26, 0x00,
+    0x00, 0x26, 0x0C,
+    0x00, 0x26, 0x19,
+    0x00, 0x26, 0x26,
+    0x00, 0x26, 0x33,
+    0x00, 0x26, 0x3F,
+    0x00, 0x33, 0x00,
+    0x00, 0x33, 0x0C,
+    0x00, 0x33, 0x19,
+    0x00, 0x33, 0x26,
+    0x00, 0x33, 0x33,
+    0x00, 0x33, 0x3F,
+    0x00, 0x3F, 0x19,
+    0x00, 0x3F, 0x26,
+    0x00, 0x3F, 0x33,
+    0x0C, 0x00, 0x00,
+    0x0C, 0x00, 0x0C,
+    0x0C, 0x00, 0x19,
+    0x0C, 0x00, 0x26,
+    0x0C, 0x00, 0x33,
+    0x0C, 0x00, 0x3F,
+    0x0C, 0x0C, 0x00,
+    0x0C, 0x0C, 0x0C,
+    0x0C, 0x0C, 0x19,
+    0x0C, 0x0C, 0x26,
+    0x0C, 0x0C, 0x33,
+    0x0C, 0x0C, 0x3F,
+    0x0C, 0x19, 0x00,
+    0x0C, 0x19, 0x0C,
+    0x0C, 0x19, 0x19,
+    0x0C, 0x19, 0x26,
+    0x0C, 0x19, 0x33,
+    0x0C, 0x19, 0x3F,
+    0x0C, 0x26, 0x00,
+    0x0C, 0x26, 0x0C,
+    0x0C, 0x26, 0x19,
+    0x0C, 0x26, 0x26,
+    0x0C, 0x26, 0x33,
+    0x0C, 0x26, 0x3F,
+    0x0C, 0x33, 0x00,
+    0x0C, 0x33, 0x0C,
+    0x0C, 0x33, 0x19,
+    0x0C, 0x33, 0x26,
+    0x0C, 0x33, 0x33,
+    0x0C, 0x33, 0x3F,
+    0x0C, 0x3F, 0x0C,
+    0x0C, 0x3F, 0x19,
+    0x0C, 0x3F, 0x26,
+    0x0C, 0x3F, 0x33,
+    0x0C, 0x3F, 0x3F,
+    0x19, 0x00, 0x00,
+    0x19, 0x00, 0x0C,
+    0x19, 0x00, 0x19,
+    0x19, 0x00, 0x26,
+    0x19, 0x00, 0x33,
+    0x19, 0x00, 0x3F,
+    0x19, 0x0C, 0x00,
+    0x19, 0x0C, 0x0C,
+    0x19, 0x0C, 0x19,
+    0x19, 0x0C, 0x26,
+    0x19, 0x0C, 0x33,
+    0x19, 0x0C, 0x3F,
+    0x19, 0x19, 0x00,
+    0x19, 0x19, 0x0C,
+    0x19, 0x19, 0x19,
+    0x19, 0x19, 0x26,
+    0x19, 0x19, 0x33,
+    0x19, 0x26, 0x00,
+    0x19, 0x26, 0x0C,
+    0x19, 0x26, 0x19,
+    0x19, 0x26, 0x26,
+    0x19, 0x26, 0x33,
+    0x19, 0x26, 0x3F,
+    0x19, 0x33, 0x00,
+    0x19, 0x33, 0x0C,
+    0x19, 0x33, 0x26,
+    0x19, 0x33, 0x33,
+    0x19, 0x33, 0x3F,
+    0x19, 0x3F, 0x00,
+    0x19, 0x3F, 0x0C,
+    0x19, 0x3F, 0x26,
+    0x19, 0x3F, 0x33,
+    0x33, 0x00, 0x3F,
+    0x3F, 0x00, 0x33,
+    0x26, 0x26, 0x00,
+    0x26, 0x0C, 0x26,
+    0x26, 0x00, 0x26,
+    0x26, 0x00, 0x33,
+    0x26, 0x00, 0x00,
+    0x26, 0x0C, 0x0C,
+    0x26, 0x00, 0x19,
+    0x26, 0x0C, 0x33,
+    0x26, 0x00, 0x3F,
+    0x26, 0x19, 0x00,
+    0x26, 0x19, 0x0C,
+    0x26, 0x0C, 0x19,
+    0x26, 0x19, 0x26,
+    0x26, 0x19, 0x33,
+    0x26, 0x0C, 0x3F,
+    0x26, 0x26, 0x0C,
+    0x26, 0x26, 0x19,
+    0x26, 0x26, 0x26,
+    0x26, 0x26, 0x33,
+    0x26, 0x26, 0x3F,
+    0x26, 0x33, 0x00,
+    0x26, 0x33, 0x0C,
+    0x19, 0x33, 0x19,
+    0x26, 0x33, 0x26,
+    0x26, 0x33, 0x33,
+    0x26, 0x33, 0x3F,
+    0x26, 0x3F, 0x00,
+    0x26, 0x3F, 0x0C,
+    0x26, 0x33, 0x19,
+    0x26, 0x3F, 0x26,
+    0x26, 0x3F, 0x33,
+    0x26, 0x3F, 0x3F,
+    0x33, 0x00, 0x00,
+    0x26, 0x00, 0x0C,
+    0x33, 0x00, 0x19,
+    0x33, 0x00, 0x26,
+    0x33, 0x00, 0x33,
+    0x26, 0x0C, 0x00,
+    0x33, 0x0C, 0x0C,
+    0x33, 0x0C, 0x19,
+    0x33, 0x0C, 0x26,
+    0x33, 0x0C, 0x33,
+    0x33, 0x0C, 0x3F,
+    0x33, 0x19, 0x00,
+    0x33, 0x19, 0x0C,
+    0x26, 0x19, 0x19,
+    0x33, 0x19, 0x26,
+    0x33, 0x19, 0x33,
+    0x26, 0x19, 0x3F,
+    0x33, 0x26, 0x00,
+    0x33, 0x26, 0x0C,
+    0x33, 0x26, 0x19,
+    0x33, 0x26, 0x26,
+    0x33, 0x26, 0x33,
+    0x33, 0x26, 0x3F,
+    0x33, 0x33, 0x00,
+    0x33, 0x33, 0x0C,
+    0x33, 0x33, 0x19,
+    0x33, 0x33, 0x26,
+    0x33, 0x33, 0x33,
+    0x33, 0x33, 0x3F,
+    0x33, 0x3F, 0x00,
+    0x33, 0x3F, 0x0C,
+    0x26, 0x3F, 0x19,
+    0x33, 0x3F, 0x26,
+    0x33, 0x3F, 0x33,
+    0x33, 0x3F, 0x3F,
+    0x33, 0x00, 0x0C,
+    0x3F, 0x00, 0x19,
+    0x3F, 0x00, 0x26,
+    0x33, 0x0C, 0x00,
+    0x3F, 0x0C, 0x0C,
+    0x3F, 0x0C, 0x19,
+    0x3F, 0x0C, 0x26,
+    0x3F, 0x0C, 0x33,
+    0x3F, 0x0C, 0x3F,
+    0x3F, 0x19, 0x00,
+    0x3F, 0x19, 0x0C,
+    0x33, 0x19, 0x19,
+    0x3F, 0x19, 0x26,
+    0x3F, 0x19, 0x33,
+    0x33, 0x19, 0x3F,
+    0x3F, 0x26, 0x00,
+    0x3F, 0x26, 0x0C,
+    0x3F, 0x26, 0x19,
+    0x3F, 0x26, 0x26,
+    0x3F, 0x26, 0x33,
+    0x3F, 0x26, 0x3F,
+    0x3F, 0x33, 0x00,
+    0x3F, 0x33, 0x0C,
+    0x3F, 0x33, 0x19,
+    0x3F, 0x33, 0x26,
+    0x3F, 0x33, 0x33,
+    0x3F, 0x33, 0x3F,
+    0x3F, 0x3F, 0x0C,
+    0x33, 0x3F, 0x19,
+    0x3F, 0x3F, 0x26,
+    0x3F, 0x3F, 0x33,
+    0x19, 0x19, 0x3F,
+    0x19, 0x3F, 0x19,
+    0x19, 0x3F, 0x3F,
+    0x3F, 0x19, 0x19,
+    0x3F, 0x19, 0x3F,
+    0x3F, 0x3F, 0x19,
+    0x30, 0x30, 0x30,
+    0x17, 0x17, 0x17,
+    0x1D, 0x1D, 0x1D,
+    0x21, 0x21, 0x21,
+    0x25, 0x25, 0x25,
+    0x32, 0x32, 0x32,
+    0x2C, 0x2C, 0x2C,
+    0x35, 0x35, 0x35,
+    0x37, 0x37, 0x37,
+    0x38, 0x38, 0x38,
+    0x3A, 0x3A, 0x3A,
+    0x3C, 0x3C, 0x3C,
+    0x3E, 0x3E, 0x3E,
+    0x3C, 0x3E, 0x3F,
+    0x29, 0x28, 0x28,
+    0x20, 0x20, 0x20,
+    0x00, 0x00, 0x3F,
+    0x00, 0x3F, 0x00,
+    0x00, 0x3F, 0x3F,
+    0x3F, 0x00, 0x00,
+    0x3F, 0x00, 0x3F,
+    0x3F, 0x3F, 0x00,
+    0x3F, 0x3F, 0x3F
+];
index c75e26324cd78fcd1267058f531abe3d23a09d4b..ff87adf7242e1086530c8df0a9ba2f4f34933d86 100644 (file)
@@ -13,6 +13,8 @@ pub mod bmv3;
 pub mod futurevision;
 #[cfg(feature="decoder_gdvvid")]
 pub mod gremlinvideo;
+#[cfg(feature="decoder_hl_fmv")]
+pub mod hl_fmv;
 #[cfg(feature="decoder_imax")]
 pub mod imax;
 #[cfg(feature="decoder_ipma")]
@@ -51,6 +53,8 @@ const GAME_CODECS: &[DecoderInfo] = &[
     DecoderInfo { name: "fst-audio", get_decoder: futurevision::get_decoder_audio },
 #[cfg(feature="decoder_fstvid")]
     DecoderInfo { name: "fst-video", get_decoder: futurevision::get_decoder_video },
+#[cfg(feature="decoder_hl_fmv")]
+    DecoderInfo { name: "hl-fmv-video", get_decoder: hl_fmv::get_decoder },
 #[cfg(feature="decoder_imax")]
     DecoderInfo { name: "fable-imax", get_decoder: imax::get_decoder },
 #[cfg(feature="decoder_ipma")]
diff --git a/nihav-game/src/demuxers/hl_fmv.rs b/nihav-game/src/demuxers/hl_fmv.rs
new file mode 100644 (file)
index 0000000..b511bb9
--- /dev/null
@@ -0,0 +1,115 @@
+use nihav_core::frame::*;
+use nihav_core::demuxers::*;
+
+#[allow(dead_code)]
+struct HighlanderFMVDemuxer<'a> {
+    src:        &'a mut ByteReader<'a>,
+    vpts:       u64,
+    apts:       u64,
+}
+
+impl<'a> DemuxCore<'a> for HighlanderFMVDemuxer<'a> {
+    #[allow(unused_variables)]
+    fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> {
+        let src = &mut self.src;
+
+        let tag                         = src.read_tag()?;
+        validate!(&tag == b"FMV*");
+        let size                        = src.read_u32le()?;
+        validate!(size == 0);
+
+        let vhdr = NAVideoInfo::new(320, 240, false, PAL8_FORMAT);
+        let vci = NACodecTypeInfo::Video(vhdr);
+        let vinfo = NACodecInfo::new("hl-fmv-video", vci, None);
+        if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 2, 25, 0)).is_none() {
+            return Err(DemuxerError::MemoryError);
+        }
+        let ahdr = NAAudioInfo::new(22050, 1, SND_U8_FORMAT, 1);
+        let ainfo = NACodecInfo::new("pcm", NACodecTypeInfo::Audio(ahdr), None);
+        if strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, 22050, 0)).is_none() {
+            return Err(DemuxerError::MemoryError);
+        }
+
+        self.apts = 0;
+        self.vpts = 0;
+        Ok(())
+    }
+
+    fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
+        let tag                         = self.src.read_tag()?;
+        let size                        = self.src.read_u32le()? as usize;
+        match &tag {
+            b"AUD1" => {
+                let stream = strmgr.get_stream_by_id(1).unwrap();
+                let (tb_num, tb_den) = stream.get_timebase();
+                let ts = NATimeInfo::new(Some(self.apts), None, None, tb_num, tb_den);
+                self.apts += size as u64;
+                self.src.read_packet(stream, ts, true, size)
+            },
+            b"VID3" => {
+                let stream = strmgr.get_stream_by_id(0).unwrap();
+                let (tb_num, tb_den) = stream.get_timebase();
+                let ts = NATimeInfo::new(Some(self.vpts), None, None, tb_num, tb_den);
+                self.vpts += 1;
+                self.src.read_packet(stream, ts, true, size)
+            },
+            b"END*" => Err(DemuxerError::EOF),
+            _ => Err(DemuxerError::InvalidData),
+        }
+    }
+
+    fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> {
+        Err(DemuxerError::NotPossible)
+    }
+    fn get_duration(&self) -> u64 { 0 }
+}
+impl<'a> NAOptionHandler for HighlanderFMVDemuxer<'a> {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+impl<'a> HighlanderFMVDemuxer<'a> {
+    fn new(io: &'a mut ByteReader<'a>) -> Self {
+        HighlanderFMVDemuxer {
+            src:        io,
+            vpts:       0,
+            apts:       0,
+        }
+    }
+}
+
+pub struct HighlanderFMVDemuxerCreator { }
+
+impl DemuxerCreator for HighlanderFMVDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> {
+        Box::new(HighlanderFMVDemuxer::new(br))
+    }
+    fn get_name(&self) -> &'static str { "hl-fmv" }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    // sample extracted from Highlander: The Last of the MacLeods unpublished game
+    #[test]
+    fn test_highlander_fmv_demux() {
+        let mut file = File::open("assets/Game/0010.fmv").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = HighlanderFMVDemuxer::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 943ca5463bd4b00f6f82fa55aceb91338bf59506..ca85790ed0a8bf242392d4c20319ee74cbee0268 100644 (file)
@@ -11,6 +11,8 @@ mod bmv;
 mod fst;
 #[cfg(feature="demuxer_gdv")]
 mod gdv;
+#[cfg(feature="demuxer_hl_fmv")]
+mod hl_fmv;
 #[cfg(feature="demuxer_imax")]
 mod imax;
 #[cfg(feature="demuxer_q")]
@@ -33,6 +35,8 @@ const GAME_DEMUXERS: &[&dyn DemuxerCreator] = &[
     &fst::FSTDemuxerCreator {},
 #[cfg(feature="demuxer_gdv")]
     &gdv::GDVDemuxerCreator {},
+#[cfg(feature="demuxer_hl_fmv")]
+    &hl_fmv::HighlanderFMVDemuxerCreator {},
 #[cfg(feature="demuxer_imax")]
     &imax::IMAXDemuxerCreator {},
 #[cfg(feature="demuxer_q")]
index 3e1d26cfb842b6698bc7fb8905c9f347c7f6244c..e73ae4b2924cf1db80877a64529594e70caaa416 100644 (file)
@@ -274,6 +274,12 @@ const DETECTORS: &[DetectConditions] = &[
         conditions: &[CheckItem{offs:  0, cond: &CC::Str(b"IMAX") },
                       CheckItem{offs: 10, cond: &CC::Eq(Arg::U16LE(0x102)) }],
     },
+    DetectConditions {
+        demux_name: "hl-fmv",
+        extensions: ".fmv",
+        conditions: &[CheckItem{offs:  0, cond: &CC::Str(b"FMV*") },
+                      CheckItem{offs:  4, cond: &CC::Eq(Arg::U32LE(0)) }],
+    },
     DetectConditions {
         demux_name: "legend-q",
         extensions: ".q",
index 3a37892c2cd78af578830c3ce33b3ada364f1022..21e61653a1209b82c8735a3e9b821efc74160da7 100644 (file)
@@ -250,6 +250,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[
     desc!(video;    "fable-imax",    "Fable IMAX video"),
     desc!(video;    "fst-video",     "FutureVision video"),
     desc!(audio;    "fst-audio",     "FutureVision audio"),
+    desc!(video;    "hl-fmv-video",  "Highlander FMV video"),
     desc!(video-llp; "ipma",         "Imagination Pilots Matte Animation"),
     desc!(video-llp; "ipma2",        "Imagination Pilots Matte Animation v2"),
     desc!(video;    "legend-q-video", "Legend Entertainment Q video"),