aboutsummaryrefslogtreecommitdiffstats
path: root/nihav-game/src
diff options
context:
space:
mode:
authorKostya Shishkov <kostya.shishkov@gmail.com>2022-10-15 10:39:24 +0200
committerKostya Shishkov <kostya.shishkov@gmail.com>2022-10-15 10:39:24 +0200
commitff6a914f5b07a9c62d7624fb6dd9f1fc093e82ca (patch)
tree424cfaa98b5d4a3310b8bc87a2694a6e8cc60e5a /nihav-game/src
parentbf30ce1bcf061cf59d0dbd149997892ad4fdf7ed (diff)
downloadnihav-ff6a914f5b07a9c62d7624fb6dd9f1fc093e82ca.tar.gz
add Highlander FMV support
Diffstat (limited to 'nihav-game/src')
-rw-r--r--nihav-game/src/codecs/hl_fmv.rs516
-rw-r--r--nihav-game/src/codecs/mod.rs4
-rw-r--r--nihav-game/src/demuxers/hl_fmv.rs115
-rw-r--r--nihav-game/src/demuxers/mod.rs4
4 files changed, 639 insertions, 0 deletions
diff --git a/nihav-game/src/codecs/hl_fmv.rs b/nihav-game/src/codecs/hl_fmv.rs
new file mode 100644
index 0000000..426d144
--- /dev/null
+++ b/nihav-game/src/codecs/hl_fmv.rs
@@ -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
+];
diff --git a/nihav-game/src/codecs/mod.rs b/nihav-game/src/codecs/mod.rs
index c75e263..ff87adf 100644
--- a/nihav-game/src/codecs/mod.rs
+++ b/nihav-game/src/codecs/mod.rs
@@ -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
index 0000000..b511bb9
--- /dev/null
+++ b/nihav-game/src/demuxers/hl_fmv.rs
@@ -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);
+ }
+ }
+}
diff --git a/nihav-game/src/demuxers/mod.rs b/nihav-game/src/demuxers/mod.rs
index 943ca54..ca85790 100644
--- a/nihav-game/src/demuxers/mod.rs
+++ b/nihav-game/src/demuxers/mod.rs
@@ -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")]