]> git.nihav.org Git - nihav.git/commitdiff
Sierra RBT and SEQ formats support
authorKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 27 Jun 2024 15:47:11 +0000 (17:47 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 27 Jun 2024 15:47:11 +0000 (17:47 +0200)
nihav-game/Cargo.toml
nihav-game/src/codecs/mod.rs
nihav-game/src/codecs/rbt.rs [new file with mode: 0644]
nihav-game/src/codecs/seq.rs [new file with mode: 0644]
nihav-game/src/demuxers/mod.rs
nihav-game/src/demuxers/rbt.rs [new file with mode: 0644]
nihav-game/src/demuxers/seq.rs [new file with mode: 0644]
nihav-registry/src/detect.rs
nihav-registry/src/register.rs

index 9111b9a053fdee015173c7e8c9661eb32e979853..af489f36aa876eb420041248ca31d1ac8c0e236a 100644 (file)
@@ -18,7 +18,7 @@ nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, feature
 [features]
 default = ["all_decoders", "all_demuxers", "all_muxers"]
 demuxers = []
-all_demuxers = ["demuxer_bmv", "demuxer_bmv3", "demuxer_cnm", "demuxer_fcmp", "demuxer_fst", "demuxer_gdv", "demuxer_hl_fmv", "demuxer_imax", "demuxer_q", "demuxer_sga", "demuxer_siff", "demuxer_smush", "demuxer_vmd", "demuxer_vx"]
+all_demuxers = ["demuxer_bmv", "demuxer_bmv3", "demuxer_cnm", "demuxer_fcmp", "demuxer_fst", "demuxer_gdv", "demuxer_hl_fmv", "demuxer_imax", "demuxer_q", "demuxer_rbt", "demuxer_seq", "demuxer_sga", "demuxer_siff", "demuxer_smush", "demuxer_vmd", "demuxer_vx"]
 demuxer_bmv = ["demuxers"]
 demuxer_bmv3 = ["demuxers"]
 demuxer_cnm = ["demuxers"]
@@ -28,6 +28,8 @@ demuxer_gdv = ["demuxers"]
 demuxer_hl_fmv = ["demuxers"]
 demuxer_imax = ["demuxers"]
 demuxer_q = ["demuxers"]
+demuxer_rbt = ["demuxers"]
+demuxer_seq = ["demuxers"]
 demuxer_sga = ["demuxers"]
 demuxer_siff = ["demuxers"]
 demuxer_smush = ["demuxers"]
@@ -37,7 +39,7 @@ demuxer_vx = ["demuxers"]
 all_decoders = ["all_video_decoders", "all_audio_decoders"]
 decoders = []
 
-all_video_decoders = ["decoder_arxel_vid", "decoder_beam_fcp", "decoder_beam_vbv", "decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_hl_fmv", "decoder_imax", "decoder_ipma", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_sga", "decoder_smush_video", "decoder_vmd", "decoder_vx"]
+all_video_decoders = ["decoder_arxel_vid", "decoder_beam_fcp", "decoder_beam_vbv", "decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_hl_fmv", "decoder_imax", "decoder_ipma", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_rbt", "decoder_seq", "decoder_sga", "decoder_smush_video", "decoder_vmd", "decoder_vx"]
 decoder_arxel_vid = ["decoders"]
 decoder_beam_fcp = ["decoders"]
 decoder_beam_vbv = ["decoders"]
@@ -51,6 +53,8 @@ decoder_ipma = ["decoders"]
 decoder_midivid = ["decoders"]
 decoder_midivid3 = ["decoders"]
 decoder_q = ["decoders"]
+decoder_rbt = ["decoders"]
+decoder_seq = ["decoders"]
 decoder_sga = ["decoders"]
 decoder_smush_video = ["decoders"]
 decoder_vmd = ["decoders"]
index c8d1a38729b505043f10b8e871134262adae269f..af23b68bf7c4fc51917dc45f81d7402977429205 100644 (file)
@@ -36,6 +36,10 @@ pub mod midivid;
 pub mod midivid3;
 #[cfg(feature="decoder_q")]
 pub mod q;
+#[cfg(feature="decoder_rbt")]
+pub mod rbt;
+#[cfg(feature="decoder_seq")]
+pub mod seq;
 #[cfg(feature="decoder_sga")]
 pub mod sga;
 #[cfg(any(feature="decoder_smush_video", feature="decoder_smush_audio"))]
@@ -80,6 +84,12 @@ 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_rbt")]
+    DecoderInfo { name: "rbt-video", get_decoder: rbt::get_decoder },
+#[cfg(feature="decoder_rbt")]
+    DecoderInfo { name: "rbt-audio", get_decoder: rbt::get_decoder_audio },
+#[cfg(feature="decoder_seq")]
+    DecoderInfo { name: "seq-video", get_decoder: seq::get_decoder },
 #[cfg(feature="decoder_sga")]
     DecoderInfo { name: "dp-sga", get_decoder: sga::get_decoder },
 #[cfg(feature="decoder_smush_video")]
diff --git a/nihav-game/src/codecs/rbt.rs b/nihav-game/src/codecs/rbt.rs
new file mode 100644 (file)
index 0000000..cc64bfb
--- /dev/null
@@ -0,0 +1,453 @@
+use nihav_core::codecs::*;
+use nihav_core::io::byteio::*;
+use nihav_core::compr::lz_copy;
+
+const FRAME_HEADER: usize = 24;
+
+struct RobotVideoDecoder {
+    info:       NACodecInfoRef,
+    pal:        [u8; 768],
+    frame:      Vec<u8>,
+    cell_buf:   Vec<u8>,
+    width:      usize,
+    height:     usize,
+    version:    u8,
+}
+
+impl RobotVideoDecoder {
+    fn new() -> Self {
+        Self {
+            info:       NACodecInfoRef::default(),
+            pal:        [0; 768],
+            frame:      Vec::new(),
+            cell_buf:   Vec::new(),
+            width:      0,
+            height:     0,
+            version:    0,
+        }
+    }
+}
+
+struct BitReader<'a, 'b> {
+    br:     &'a mut ByteReader<'b>,
+    end:    u64,
+    bbuf:   u32,
+    bits:   u8,
+}
+
+impl<'a, 'b> BitReader<'a, 'b> {
+    fn new(br: &'a mut ByteReader<'b>, size: usize) -> Self {
+        let end = br.tell() + (size as u64);
+        Self {
+            br, end,
+            bbuf:   0,
+            bits:   0,
+        }
+    }
+    fn refill(&mut self) -> DecoderResult<()> {
+        while self.bits <= 24 && self.br.tell() < self.end {
+            self.bbuf |= u32::from(self.br.read_byte()?) << (24 - self.bits);
+            self.bits += 8;
+        }
+        Ok(())
+    }
+    fn read_bit(&mut self) -> DecoderResult<bool> {
+        self.refill()?;
+        if self.bits == 0 { return Err(DecoderError::ShortData); }
+        let bit = (self.bbuf >> 31) != 0;
+        self.bbuf <<= 1;
+        self.bits  -= 1;
+        Ok(bit)
+    }
+    fn read(&mut self, nbits: u8) -> DecoderResult<u32> {
+        self.refill()?;
+        if self.bits < nbits { return Err(DecoderError::ShortData); }
+        let ret = self.bbuf >> (32 - nbits);
+        self.bbuf <<= nbits;
+        self.bits  -= nbits;
+        Ok(ret)
+    }
+}
+
+fn lzs_unpack(br: &mut ByteReader, csize: usize, dst: &mut [u8]) -> DecoderResult<()> {
+    let mut br = BitReader::new(br, csize);
+
+    let mut dpos = 0;
+    loop {
+        if br.read_bit()? {
+            let offset = (if br.read_bit()? {
+                    let off = br.read(7)?;
+                    if off == 0 {
+                        validate!(dpos == dst.len());
+                        return Ok(());
+                    }
+                    off
+                } else {
+                    br.read(11)?
+                }) as usize;
+
+            let mut len = br.read(2)?;
+            if len < 3 {
+                len += 2;
+            } else {
+                len = br.read(2)?;
+                if len < 3 {
+                    len += 5;
+                } else {
+                    len = 8;
+                    loop {
+                        let t = br.read(4)?;
+                        len += t;
+                        if t != 0xF {
+                            break;
+                        }
+                    }
+                }
+            }
+            let len = len as usize;
+
+            validate!(offset <= dpos);
+            validate!(dpos + len <= dst.len());
+            lz_copy(dst, dpos, offset, len);
+            dpos += len;
+        } else {
+            dst[dpos] = br.read(8)? as u8;
+            dpos += 1;
+        }
+    }
+}
+
+fn unpack_cell(br: &mut ByteReader, cell_size: usize, nchunks: usize, dst: &mut Vec<u8>, limit: usize) -> DecoderResult<()> {
+    let mut data_left = cell_size;
+    dst.clear();
+    dst.reserve(limit);
+    for _ in 0..nchunks {
+        validate!(data_left >= 10);
+        let csize       = br.read_u32le()? as usize;
+        validate!(csize <= data_left);
+        let rsize       = br.read_u32le()? as usize;
+        validate!(rsize + dst.len() <= limit);
+        let method      = br.read_u16le()?;
+
+        data_left -= 10;
+
+        let cur_size = dst.len();
+        dst.resize(cur_size + rsize, 0);
+        match method {
+            0 => { lzs_unpack(br, csize, &mut dst[cur_size..])?; },
+            2 => {
+                validate!(rsize == csize);
+                br.read_buf(&mut dst[cur_size..])?;
+            },
+            _ => return Err(DecoderError::NotImplemented),
+        }
+        data_left -= csize;
+    }
+    Ok(())
+}
+
+fn blit(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize) {
+    for (dline, sline) in dst.chunks_mut(dstride).zip(src.chunks(sstride)) {
+        dline[..sstride].copy_from_slice(sline);
+    }
+}
+
+fn blit_scaled(dst: &mut [u8], dstride: usize, src: &[u8], sstride: usize, scale: u8) {
+    let mut slines = src.chunks(sstride);
+    let mut acc = 0;
+
+    let mut cur_line = slines.next().unwrap();
+
+    for dline in dst.chunks_mut(dstride) {
+        dline[..sstride].copy_from_slice(cur_line);
+        acc += scale;
+        if acc >= 100 {
+            acc -= 100;
+            if let Some(line) = slines.next() {
+                cur_line = line;
+            } else {
+                break;
+            }
+        }
+    }
+}
+
+impl NADecoder for RobotVideoDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(vinfo) = info.get_properties() {
+            self.width  = vinfo.get_width();
+            self.height = vinfo.get_height();
+            self.frame.resize(self.width * self.height, 0);
+            if let Some(ref edata) = info.get_extradata() {
+                validate!(edata.len() > 2);
+                self.version = edata[0];
+                validate!(self.version >= 4 && self.version <= 6);
+                if edata[1] != 0 {
+                    validate!(edata.len() > 39);
+                    let pal_start = read_u16le(&edata[25+2..])? as usize;
+                    let pal_len   = read_u16le(&edata[29+2..])? as usize;
+                    validate!(pal_len > 0 && pal_start + pal_len <= 256);
+                    match edata[32+2] {
+                        0 => {
+                            let dpal = self.pal[pal_start * 3..].chunks_exact_mut(3);
+                            for (dst, quad) in dpal.zip(edata[37+2..].chunks_exact(4)) {
+                                dst.copy_from_slice(&quad[1..]);
+                            }
+                        },
+                        1 => self.pal[pal_start * 3..][..pal_len * 3].copy_from_slice(&edata[37+2..][..pal_len * 3]),
+                        _ => return Err(DecoderError::NotImplemented),
+                    }
+                } else {
+                    for (i, entry) in self.pal.chunks_exact_mut(3).enumerate() {
+                        entry[0] = i as u8;
+                        entry[1] = i as u8;
+                        entry[2] = i as u8;
+                    }
+                }
+            } else {
+                return Err(DecoderError::InvalidData);
+            }
+            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();
+
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let src = pkt.get_buffer();
+        validate!(src.len() > FRAME_HEADER);
+
+        let mut mr = MemoryReader::new_read(&src);
+        let mut br = ByteReader::new(&mut mr);
+
+        let ncells              = br.read_u16le()? as usize;
+        validate!(ncells > 0 && ncells <= 10);
+        for el in self.frame.iter_mut() { *el = 0xFF; }
+        for _ in 0..ncells {
+                                  br.read_byte()?;
+            let scale           = br.read_byte()?;
+            let width           = br.read_u16le()? as usize;
+            let height          = br.read_u16le()? as usize;
+                                  br.read_skip(4)?;
+            let xoff            = br.read_u16le()? as usize;
+            let yoff            = br.read_u16le()? as usize;
+            validate!(xoff + width <= self.width && yoff + height <= self.height);
+            let mut cell_size   = br.read_u16le()? as usize;
+            // hack
+            if self.version == 6 && ncells == 1 && (src.len() - 18 >= 0x10000) {
+                cell_size += (src.len() - 18) & !0xFFFF;
+            }
+            if self.version > 4 {
+                let nchunks     = br.read_u16le()? as usize;
+                validate!(nchunks > 0);
+                                  br.read_skip(4)?;
+
+                unpack_cell(&mut br, cell_size, nchunks, &mut self.cell_buf, width * height)?;
+            } else {
+                                  br.read_skip(6)?;
+                self.cell_buf.resize(width * height, 0);
+                lzs_unpack(&mut br, cell_size, &mut self.cell_buf)?;
+            }
+            if scale == 100 {
+                blit(&mut self.frame[xoff + yoff * self.width..], self.width, &self.cell_buf, width);
+            } else {
+                blit_scaled(&mut self.frame[xoff + yoff * self.width..], self.width, &self.cell_buf, width, scale);
+            }
+        }
+
+        let buf = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?;
+        let mut vbuf = buf.get_vbuf().unwrap();
+        let paloff = vbuf.get_offset(1);
+        let stride = vbuf.get_stride(0);
+        let data = vbuf.get_data_mut().unwrap();
+
+        blit(data, stride, &self.frame, self.width);
+        data[paloff..][..768].copy_from_slice(&self.pal);
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), buf);
+        let ftype = if pkt.keyframe { FrameType::I } else { FrameType::P };
+        frm.set_frame_type(ftype);
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {
+    }
+}
+
+impl NAOptionHandler for RobotVideoDecoder {
+    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(RobotVideoDecoder::new())
+}
+
+struct RobotAudioDecoder {
+    info:       NACodecInfoRef,
+    ainfo:      NAAudioInfo,
+    chmap:      NAChannelMap,
+}
+
+impl RobotAudioDecoder {
+    fn new() -> Self {
+        Self {
+            ainfo:  NAAudioInfo::new(11025, 1, SND_S16_FORMAT, 1),
+            info:   NACodecInfo::new_dummy(),
+            chmap:  NAChannelMap::from_str("C").unwrap(),
+        }
+    }
+    fn pred16(pred: i32, val: u8) -> i32 {
+        if (val & 0x80) != 0 {
+            pred - i32::from(SOL_AUD_STEPS16[(val & 0x7F) as usize])
+        } else {
+            pred + i32::from(SOL_AUD_STEPS16[(val & 0x7F) as usize])
+        }
+    }
+}
+
+impl NADecoder for RobotAudioDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Audio(_ainfo) = info.get_properties() {
+            self.info = info.replace_info(NACodecTypeInfo::Audio(self.ainfo));
+
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let src = pkt.get_buffer();
+        validate!(src.len() > 1);
+
+        let samples = match src[0] {
+            0 => src.len() - 1,
+            1 => src.len() - 1 - 8,
+            _ => return Err(DecoderError::InvalidData),
+        };
+
+        let abuf = alloc_audio_buffer(self.ainfo, samples / 2, self.chmap.clone())?;
+        let mut adata = abuf.get_abuf_i16().unwrap();
+        let dst = adata.get_data_mut().unwrap();
+
+        match src[0] {
+            0 => {
+                let mut pred = 0;
+                for (dst, &b) in dst.iter_mut().zip(src[1..][..src.len()/2].iter()) {
+                    pred = Self::pred16(pred, b);
+                    *dst = pred as i16;
+                }
+            },
+            1 => {
+                validate!(src.len() > 8);
+                let mut pred = 0;
+                for &b in src[1..9].iter() {
+                    pred = Self::pred16(pred, b);
+                }
+                for (dst, &b) in dst.iter_mut().zip(src[9..].iter()) {
+                    pred = Self::pred16(pred, b);
+                    *dst = pred as i16;
+                }
+            },
+            _ => unreachable!(),
+        }
+
+        let frm = NAFrame::new_from_pkt(pkt, self.info.clone(), abuf);
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {
+    }
+}
+
+impl NAOptionHandler for RobotAudioDecoder {
+    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_audio() -> Box<dyn NADecoder + Send> {
+    Box::new(RobotAudioDecoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::*;
+    #[test]
+    fn test_rbt_v4() {
+        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 SWAT demo
+        test_decoding("sierra-rbt", "rbt-video", "assets/Game/sierra/12.rbt",
+                      Some(2), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5Frames(vec![
+                            [0x2a00775d, 0xef8da06a, 0x015b6f06, 0xa22d0158],
+                            [0xf2acb558, 0x0d9c5c54, 0x32c43af4, 0xd9776b68],
+                            [0x386e02e9, 0x76dbd5a6, 0x4e9da3d7, 0xa47fdca3]]));
+    }
+    #[test]
+    fn test_rbt_v5() {
+        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 Phantasmagora (with scaling)
+        test_decoding("sierra-rbt", "rbt-video", "assets/Game/sierra/162.RBT",
+                      None, &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5([0x4912fa8f, 0xae201d9e, 0x59707ea0, 0xc50bf0e2]));
+    }
+    #[test]
+    fn test_rbt_v6() {
+        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 Rama
+        test_decoding("sierra-rbt", "rbt-video", "assets/Game/sierra/7531.RBT",
+                      Some(2), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5Frames(vec![
+                            [0x49db87f3, 0x57881095, 0x676d1600, 0x5ddaa50b],
+                            [0xa75ff558, 0xb6815b27, 0x5f9d872f, 0xd7f56470],
+                            [0x60bca745, 0xc47d6882, 0xc193fe70, 0x7b8738c9]]));
+    }
+    #[test]
+    fn test_rbt_audio() {
+        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 Space Quest 6
+        test_decoding("sierra-rbt", "rbt-audio", "assets/Game/sierra/410.rbt",
+                      Some(2), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5([0xdd44e3ca, 0x6cfc1bc1, 0xdcd4214a, 0x443cf5ed]));
+    }
+}
+
+const SOL_AUD_STEPS16: [i16; 128] = [
+     0x00,   0x08,   0x10,   0x20,   0x30,   0x40,   0x50,   0x60,
+     0x70,   0x80,   0x90,   0xA0,   0xB0,   0xC0,   0xD0,   0xE0,
+     0xF0,  0x100,  0x110,  0x120,  0x130,  0x140,  0x150,  0x160,
+    0x170,  0x180,  0x190,  0x1A0,  0x1B0,  0x1C0,  0x1D0,  0x1E0,
+    0x1F0,  0x200,  0x208,  0x210,  0x218,  0x220,  0x228,  0x230,
+    0x238,  0x240,  0x248,  0x250,  0x258,  0x260,  0x268,  0x270,
+    0x278,  0x280,  0x288,  0x290,  0x298,  0x2A0,  0x2A8,  0x2B0,
+    0x2B8,  0x2C0,  0x2C8,  0x2D0,  0x2D8,  0x2E0,  0x2E8,  0x2F0,
+    0x2F8,  0x300,  0x308,  0x310,  0x318,  0x320,  0x328,  0x330,
+    0x338,  0x340,  0x348,  0x350,  0x358,  0x360,  0x368,  0x370,
+    0x378,  0x380,  0x388,  0x390,  0x398,  0x3A0,  0x3A8,  0x3B0,
+    0x3B8,  0x3C0,  0x3C8,  0x3D0,  0x3D8,  0x3E0,  0x3E8,  0x3F0,
+    0x3F8,  0x400,  0x440,  0x480,  0x4C0,  0x500,  0x540,  0x580,
+    0x5C0,  0x600,  0x640,  0x680,  0x6C0,  0x700,  0x740,  0x780,
+    0x7C0,  0x800,  0x900,  0xA00,  0xB00,  0xC00,  0xD00,  0xE00,
+    0xF00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
+];
diff --git a/nihav-game/src/codecs/seq.rs b/nihav-game/src/codecs/seq.rs
new file mode 100644 (file)
index 0000000..5968423
--- /dev/null
@@ -0,0 +1,219 @@
+use nihav_core::codecs::*;
+use nihav_core::io::byteio::*;
+
+const WIDTH:  usize = 320;
+const HEIGHT: usize = 200;
+const FRAME_HEADER: usize = 24;
+
+struct SequenceDecoder {
+    info:       NACodecInfoRef,
+    pal:        [u8; 768],
+    frame:      [u8; WIDTH * HEIGHT],
+}
+
+impl SequenceDecoder {
+    fn new() -> Self {
+        Self {
+            info:       NACodecInfoRef::default(),
+            pal:        [0; 768],
+            frame:      [0; WIDTH * HEIGHT],
+        }
+    }
+}
+
+impl NADecoder for SequenceDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(_vinfo) = info.get_properties() {
+            if let Some(ref edata) = info.get_extradata() {
+                validate!(edata.len() > 32);
+                let pal_start = read_u16le(&edata[25..])? as usize;
+                let pal_len   = read_u16le(&edata[29..])? as usize;
+                validate!(pal_len > 0 && pal_start + pal_len <= 256);
+                match edata[32] {
+                    0 => {
+                        let dpal = self.pal[pal_start * 3..].chunks_exact_mut(3);
+                        for (dst, quad) in dpal.zip(edata[37..].chunks_exact(4)) {
+                            dst.copy_from_slice(&quad[1..]);
+                        }
+                    },
+                    1 => self.pal[pal_start * 3..][..pal_len * 3].copy_from_slice(&edata[37..][..pal_len * 3]),
+                    _ => return Err(DecoderError::NotImplemented),
+                }
+            } else {
+                return Err(DecoderError::InvalidData);
+            }
+            let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(WIDTH, HEIGHT, 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() > FRAME_HEADER);
+
+        let mut mr = MemoryReader::new_read(&src);
+        let mut br = ByteReader::new(&mut mr);
+
+        let width       = br.read_u16le()? as usize;
+        let height      = br.read_u16le()? as usize;
+        let xoff        = br.read_u16le()? as usize;
+        let yoff        = br.read_u16le()? as usize;
+        validate!(width + xoff <= WIDTH && height + yoff <= HEIGHT);
+
+        let _ckey       = br.read_byte()?;
+        let frm_type    = br.read_byte()?;
+                          br.read_skip(6)?;
+        let opcode_size = br.read_u16le()? as usize;
+        validate!(opcode_size <= src.len() - FRAME_HEADER);
+
+        let opcodes = &src[FRAME_HEADER..][..opcode_size];
+        let clr_data = &src[FRAME_HEADER + opcode_size..];
+        match frm_type {
+            0 => {
+                validate!(opcodes.is_empty());
+                validate!(clr_data.len() == width * height);
+
+                let dst = &mut self.frame[xoff + yoff * WIDTH..];
+                for (dline, sline) in dst.chunks_mut(WIDTH).zip(clr_data.chunks(width)) {
+                    dline[..width].copy_from_slice(sline);
+                }
+            },
+            1 | 11 => {
+                validate!(!opcodes.is_empty());
+                let mut mr = MemoryReader::new_read(opcodes);
+                let mut ops = ByteReader::new(&mut mr);
+                let mut mr = MemoryReader::new_read(clr_data);
+                let mut clr = ByteReader::new(&mut mr);
+
+                let mut x = xoff;
+                let mut y = yoff;
+                while y < yoff + height {
+                    let op = ops.read_byte()?;
+                    let mut len = (op & 0x3F) as usize;
+                    if len == 0 {
+                        len = width + xoff - x;
+                    }
+                    match op >> 6 {
+                        3 => x += len,
+                        2 => {
+                            clr.read_buf(&mut self.frame[x + y * WIDTH..][..len])?;
+                            x += len;
+                        },
+                        _ => {
+                            let len = ((u16::from(op & 0x7) << 8) | u16::from(ops.read_byte()?)) as usize;
+                            match op >> 3 {
+                                2 => x += len,
+                                3 => {
+                                    for _ in 0..len {
+                                        validate!(y < height + yoff);
+                                        self.frame[x + y * WIDTH] = clr.read_byte()?;
+                                        x += 1;
+                                        if x == width + xoff {
+                                            y += 1;
+                                            x = xoff;
+                                        }
+                                    }
+                                },
+                                6 => {
+                                    let len = if len != 0 { len } else { height + yoff - y };
+                                    for _ in 0..(len * width) {
+                                        validate!(y < height + yoff);
+                                        self.frame[x + y * WIDTH] = clr.read_byte()?;
+                                        x += 1;
+                                        if x == width + xoff {
+                                            y += 1;
+                                            x = xoff;
+                                        }
+                                    }
+                                },
+                                7 => {
+                                    if len > 0 {
+                                        y += len;
+                                    } else {
+                                        y = height + yoff;
+                                    }
+                                },
+                                _ => return Err(DecoderError::NotImplemented),
+                            }
+                        },
+                    }
+                    if x == width + xoff {
+                        x = xoff;
+                        y += 1;
+                    }
+                }
+            },
+            _ => return Err(DecoderError::InvalidData),
+        }
+
+
+        let buf = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?;
+        let mut vbuf = buf.get_vbuf().unwrap();
+        let paloff = vbuf.get_offset(1);
+        let stride = vbuf.get_stride(0);
+        let data = vbuf.get_data_mut().unwrap();
+
+        for (drow, srow) in data.chunks_mut(stride).zip(self.frame.chunks_exact(WIDTH)) {
+            drow[..WIDTH].copy_from_slice(srow);
+        }
+        data[paloff..][..768].copy_from_slice(&self.pal);
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), buf);
+        let ftype = if pkt.keyframe { FrameType::I } else { FrameType::P };
+        frm.set_frame_type(ftype);
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {
+    }
+}
+
+impl NAOptionHandler for SequenceDecoder {
+    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(SequenceDecoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::*;
+    #[test]
+    fn test_seq1() {
+        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 King's Quest VI
+        test_decoding("sierra-seq", "seq-video", "assets/Game/sierra/FS1.SEQ",
+                      Some(2), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5Frames(vec![
+                            [0x5a7472da, 0xa9e242fd, 0x867efa52, 0x9625f05c],
+                            [0x720ab982, 0x704970a0, 0xf854af8b, 0x3b86bed9],
+                            [0xaa1426e1, 0x79750652, 0x87b7a727, 0xc582a561]]));
+    }
+    #[test]
+    fn test_seq2() {
+        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 Gabriel Knight
+        test_decoding("sierra-seq", "seq-video", "assets/Game/sierra/blood.seq",
+                      Some(2), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5Frames(vec![
+                            [0x989422ee, 0x5892beae, 0x0ca9db17, 0xe25ab710],
+                            [0x0d5f395e, 0x2eeac229, 0x1504ece0, 0xa7d3401e],
+                            [0x988d3fa6, 0x68be4639, 0x7ab7137c, 0x72e69e26]]));
+    }
+}
index 7895afd619b75348ce814e3cb03382a7c3654ca2..3e3f84269f3fc8bd7a4de5ff18d534a192184c33 100644 (file)
@@ -24,6 +24,10 @@ mod hl_fmv;
 mod imax;
 #[cfg(feature="demuxer_q")]
 mod q;
+#[cfg(feature="demuxer_rbt")]
+mod rbt;
+#[cfg(feature="demuxer_seq")]
+mod seq;
 #[cfg(feature="demuxer_sga")]
 mod sga;
 #[cfg(feature="demuxer_siff")]
@@ -54,6 +58,10 @@ const GAME_DEMUXERS: &[&dyn DemuxerCreator] = &[
     &imax::IMAXDemuxerCreator {},
 #[cfg(feature="demuxer_q")]
     &q::QDemuxerCreator {},
+#[cfg(feature="demuxer_rbt")]
+    &rbt::RobotDemuxerCreator {},
+#[cfg(feature="demuxer_seq")]
+    &seq::SequenceDemuxerCreator {},
 #[cfg(feature="demuxer_sga")]
     &sga::SGADemuxerCreator {},
 #[cfg(feature="demuxer_siff")]
diff --git a/nihav-game/src/demuxers/rbt.rs b/nihav-game/src/demuxers/rbt.rs
new file mode 100644 (file)
index 0000000..9f6da60
--- /dev/null
@@ -0,0 +1,283 @@
+use nihav_core::frame::*;
+use nihav_core::demuxers::*;
+
+const AFRAME_HDR_SIZE: usize = 16;
+
+#[allow(dead_code)]
+struct RobotDemuxer<'a> {
+    src:        &'a mut ByteReader<'a>,
+    version:    u16,
+    vpts:       u64,
+    apts:       u64,
+    pkt_no:     usize,
+    audio:      bool,
+    has_audio:  bool,
+    initial:    Vec<u8>,
+    a_id:       Option<usize>,
+    v_id:       Option<usize>,
+    nframes:    usize,
+    vframe_len: Vec<u32>,
+    pkt_len:    Vec<u32>,
+}
+
+impl<'a> DemuxCore<'a> for RobotDemuxer<'a> {
+    #[allow(unused_variables)]
+    fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> {
+        let mut hdr = [0; 60];
+                                          self.src.read_buf(&mut hdr)?;
+        validate!(hdr[0] == 0x16 || hdr[0] == 0x3D);
+        validate!(&hdr[2..6] == b"SOL\0");
+        self.version = read_u16le(&hdr[6..])?;
+        let aframe_size = read_u16le(&hdr[8..])? as usize;
+        validate!(self.version >= 4 && self.version <= 6);
+
+        self.nframes = read_u16le(&hdr[14..])? as usize;
+        validate!(self.nframes > 0);
+        let pal_size = read_u16le(&hdr[16..])? as usize;
+        let audio_pre_size = read_u16le(&hdr[18..])? as usize;
+
+        let mut width = read_u16le(&hdr[20..])? as usize;
+        if width == 0 { width = 640; }
+        let mut height = read_u16le(&hdr[22..])? as usize;
+        if height == 0 { height = 480; }
+        let has_pal = hdr[24] != 0;
+        self.has_audio = hdr[25] != 0;
+        let fps = read_u16le(&hdr[28..])?;
+        if !self.has_audio || audio_pre_size == 0 {
+                                          self.src.read_skip(audio_pre_size)?;
+        } else {
+            let end_pos = self.src.tell() + (audio_pre_size as u64);
+            validate!(audio_pre_size >= 12);
+            validate!(aframe_size > AFRAME_HDR_SIZE);
+            let pre_size                = self.src.read_u32le()? as usize;
+            validate!(pre_size <= audio_pre_size - 14);
+            let method                  = self.src.read_u16le()?;
+            validate!(method == 0);
+            let size1                   = self.src.read_u32le()? as usize;
+            let size2                   = self.src.read_u32le()? as usize;
+            validate!(size1 + size2 <= pre_size);
+            let to_skip = (aframe_size - AFRAME_HDR_SIZE) / 2;
+            if size1 + size2 > to_skip {
+                self.initial.resize(size1 + size2 + 1 - to_skip, 0);
+                self.initial[0] = 0;
+                                          self.src.read_buf(&mut self.initial[1..])?;
+            }
+                                          self.src.seek(SeekFrom::Start(end_pos))?;
+        }
+        let mut pal = vec![0; pal_size + 2];
+        pal[0] = self.version as u8;
+        pal[1] = has_pal as u8;
+                                          self.src.read_buf(&mut pal[2..])?;
+        self.vframe_len.clear();
+        self.vframe_len.reserve(self.nframes);
+        if self.version < 6 {
+            for _ in 0..self.nframes {
+                let size                = self.src.read_u16le()?;
+                self.vframe_len.push(u32::from(size));
+            }
+        } else {
+            for _ in 0..self.nframes {
+                let size                = self.src.read_u32le()?;
+                self.vframe_len.push(size);
+            }
+        }
+
+        self.pkt_len.clear();
+        self.pkt_len.reserve(self.nframes);
+        if self.version < 6 {
+            for _ in 0..self.nframes {
+                let size                = self.src.read_u16le()?;
+                self.pkt_len.push(u32::from(size));
+            }
+        } else {
+            for _ in 0..self.nframes {
+                let size                = self.src.read_u32le()?;
+                self.pkt_len.push(size);
+            }
+        }
+                                          self.src.read_skip(256 * 4)?; // cues
+                                          self.src.read_skip(256 * 2)?; // smth
+        let pos = (self.src.tell() & 0x7FF) as usize;
+        if pos != 0 {
+                                          self.src.read_skip(0x800 - pos)?;
+        }
+
+        let vhdr = NAVideoInfo::new(width, height, false, PAL8_FORMAT);
+        let vci = NACodecTypeInfo::Video(vhdr);
+        let vinfo = NACodecInfo::new("rbt-video", vci, Some(pal));
+        self.v_id = strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, fps.into(), self.nframes as u64));
+        if self.has_audio {
+            let ahdr = NAAudioInfo::new(11025, 2, SND_S16_FORMAT, 1);
+            let ainfo = NACodecInfo::new("rbt-audio", NACodecTypeInfo::Audio(ahdr), None);
+            self.a_id = strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, 22050, 2));
+        }
+        self.apts = 0;
+        self.vpts = 0;
+        self.pkt_no = 0;
+        self.audio = false;
+
+        Ok(())
+    }
+
+    fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
+        if self.has_audio && !self.initial.is_empty() {
+            let mut buf = Vec::new();
+            std::mem::swap(&mut self.initial, &mut buf);
+            if let Some(a_id) = self.a_id {
+                let stream = strmgr.get_stream(a_id).unwrap();
+                let ts = stream.make_ts(Some(0), None, None);
+                self.apts += (buf.len() - 1) as u64;
+                return Ok(NAPacket::new(stream, ts, true, buf));
+            }
+        }
+        loop {
+            if self.pkt_no >= self.nframes {
+                return Err(DemuxerError::EOF);
+            }
+
+            if !self.audio {
+                let stream = strmgr.get_stream(self.v_id.unwrap_or(0)).unwrap();
+                let ts = stream.make_ts(Some(self.vpts), None, None);
+                self.vpts += 1;
+
+                let mut buf = vec![0; self.vframe_len[self.pkt_no] as usize];
+                                      self.src.read_buf(&mut buf)?;
+
+                if self.has_audio {
+                    self.audio = true;
+                } else {
+                                      self.src.read_skip((self.pkt_len[self.pkt_no] - self.vframe_len[self.pkt_no]) as usize)?;
+                    self.pkt_no += 1;
+                }
+
+                return Ok(NAPacket::new(stream, ts, self.vpts == 1, buf));
+            } else {
+                let asize = (self.pkt_len[self.pkt_no] - self.vframe_len[self.pkt_no]) as usize;
+                validate!(asize >= AFRAME_HDR_SIZE);
+                self.audio = false;
+                self.pkt_no += 1;
+
+                let _ref_apts           = u64::from(self.src.read_u32le()?);
+                let ref_asize           = self.src.read_u32le()? as usize;
+                validate!(asize == ref_asize + 8);
+
+                if let Some(a_id) = self.a_id {
+                    let stream = strmgr.get_stream(a_id).unwrap();
+                    let ts = stream.make_ts(Some(self.apts), None, None);
+                    self.apts += (ref_asize - 8) as u64;
+                    let mut buf = vec![0; ref_asize + 1];
+                    buf[0] = 1;
+                                          self.src.read_buf(&mut buf[1..])?;
+                    return Ok(NAPacket::new(stream, ts, true, buf));
+                } else {
+                                          self.src.read_skip(ref_asize)?;
+                }
+            }
+        }
+    }
+
+    fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> {
+        Err(DemuxerError::NotPossible)
+    }
+    fn get_duration(&self) -> u64 { 0 }
+}
+impl<'a> NAOptionHandler for RobotDemuxer<'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> RobotDemuxer<'a> {
+    fn new(io: &'a mut ByteReader<'a>) -> Self {
+        RobotDemuxer {
+            src:        io,
+            vpts:       0,
+            apts:       0,
+            a_id:       None,
+            v_id:       None,
+            nframes:    0,
+            pkt_no:     0,
+            audio:      false,
+            has_audio:  false,
+            initial:    Vec::new(),
+            version:    0,
+            vframe_len: Vec::new(),
+            pkt_len:    Vec::new(),
+        }
+    }
+}
+
+pub struct RobotDemuxerCreator { }
+
+impl DemuxerCreator for RobotDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> {
+        Box::new(RobotDemuxer::new(br))
+    }
+    fn get_name(&self) -> &'static str { "sierra-rbt" }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    #[test]
+    fn test_rbt_v4() {
+        // sample from SWAT demo
+        let mut file = File::open("assets/Game/sierra/12.rbt").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = RobotDemuxer::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_rbt_v5() {
+        // sample from Space Quest 6
+        let mut file = File::open("assets/Game/sierra/410.rbt").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = RobotDemuxer::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_rbt_v6() {
+        // sample from Rama
+        let mut file = File::open("assets/Game/sierra/7531.RBT").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = RobotDemuxer::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/seq.rs b/nihav-game/src/demuxers/seq.rs
new file mode 100644 (file)
index 0000000..ae5b099
--- /dev/null
@@ -0,0 +1,111 @@
+use nihav_core::frame::*;
+use nihav_core::demuxers::*;
+
+struct SequenceDemuxer<'a> {
+    src:        &'a mut ByteReader<'a>,
+    nframes:    usize,
+    frame_no:   usize,
+}
+
+impl<'a> DemuxCore<'a> for SequenceDemuxer<'a> {
+    #[allow(unused_variables)]
+    fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> {
+        let src = &mut self.src;
+
+        self.nframes                    = src.read_u16le()? as usize;
+        validate!(self.nframes > 0);
+        let pal_size                    = src.read_u32le()? as usize;
+        validate!(pal_size > 37 && pal_size <= 1024 + 37);
+        let mut pal_chunk = vec![0; pal_size];
+                                          src.read_buf(&mut pal_chunk)?;
+
+        let vhdr = NAVideoInfo::new(320, 200, false, PAL8_FORMAT);
+        let vci = NACodecTypeInfo::Video(vhdr);
+        let vinfo = NACodecInfo::new("seq-video", vci, Some(pal_chunk));
+        if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 10, self.nframes as u64)).is_none() {
+            return Err(DemuxerError::MemoryError);
+        }
+
+        self.frame_no = 0;
+        Ok(())
+    }
+
+    fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
+        const FRAME_HEADER: usize = 24;
+
+        if self.frame_no == self.nframes {
+            return Err(DemuxerError::EOF);
+        }
+
+        let stream = strmgr.get_stream(0).unwrap();
+        let ts = stream.make_ts(Some(self.frame_no as u64), None, None);
+
+        let mut buf = vec![0; FRAME_HEADER];
+                                  self.src.read_buf(&mut buf)?;
+        let frm_size = read_u16le(&buf[12..])? as usize;
+        let offset              = u64::from(self.src.read_u32le()?);
+        validate!(offset >= self.src.tell());
+                                  self.src.seek(SeekFrom::Start(offset))?;
+        buf.resize(FRAME_HEADER + frm_size, 0);
+                                  self.src.read_buf(&mut buf[FRAME_HEADER..])?;
+
+        self.frame_no += 1;
+
+        Ok(NAPacket::new(stream, ts, self.frame_no == 1, buf))
+    }
+
+    fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> {
+        Err(DemuxerError::NotPossible)
+    }
+    fn get_duration(&self) -> u64 { 0 }
+}
+impl<'a> NAOptionHandler for SequenceDemuxer<'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> SequenceDemuxer<'a> {
+    fn new(io: &'a mut ByteReader<'a>) -> Self {
+        SequenceDemuxer {
+            src:        io,
+            frame_no:   0,
+            nframes:    0,
+        }
+    }
+}
+
+pub struct SequenceDemuxerCreator { }
+
+impl DemuxerCreator for SequenceDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> {
+        Box::new(SequenceDemuxer::new(br))
+    }
+    fn get_name(&self) -> &'static str { "sierra-seq" }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    // sample from King's Quest VI
+    #[test]
+    fn test_seq() {
+        let mut file = File::open("assets/Game/sierra/FS1.SEQ").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = SequenceDemuxer::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 87226a730ccea6a18c7e41c167c89e9c3b103f95..a7f521dc4471f553751235ede44bd08ca624196c 100644 (file)
@@ -419,6 +419,18 @@ const DETECTORS: &[DetectConditions] = &[
         extensions: ".dtv,.avc",
         conditions: &[],
     },
+    DetectConditions {
+        demux_name: "sierra-rbt",
+        extensions: ".rbt",
+        conditions: &[CheckItem{offs: 0, cond: &CC::Eq(Arg::Byte(0x16)) },
+                      CheckItem{offs: 2, cond: &CC::Str(b"SOL\0")},
+                      CheckItem{offs: 6, cond: &CC::In(Arg::U16LE(4), Arg::U16LE(6))}],
+    },
+    DetectConditions {
+        demux_name: "sierra-seq",
+        extensions: ".seq",
+        conditions: &[],
+    },
     DetectConditions {
         demux_name: "vmd",
         extensions: ".vmd",
index 15a3f65b8ed52c8d979446260fd416a459580e5c..b2a7ca6cfad168bd23533aa746b774dfc137ef78 100644 (file)
@@ -270,6 +270,9 @@ static CODEC_REGISTER: &[CodecDescription] = &[
     desc!(video;    "midivid",       "MidiVid"),
     desc!(video;    "midivid3",      "MidiVid 3"),
     desc!(video-ll; "midivid-ll",    "MidiVid Lossless"),
+    desc!(video-ll; "rbt-video",     "Sierra Robot video"),
+    desc!(audio;    "rbt-audio",     "Sierra Robot audio"),
+    desc!(video;    "seq-video",     "Sierra Sequence video"),
     desc!(video;    "smushv1",       "SMUSH Video paletted"),
     desc!(video;    "smushv2",       "SMUSH Video 16-bit"),
     desc!(video;    "smush-iact",    "SMUSH IACT Audio"),