aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKostya Shishkov <kostya.shishkov@gmail.com>2024-06-27 17:47:11 +0200
committerKostya Shishkov <kostya.shishkov@gmail.com>2024-06-27 17:47:11 +0200
commitf9fc73be53d5226f12dc8f566b082f0758e87a87 (patch)
treec99c0a35568b1e1f589008b91c4cf21215edf6c5
parent3900194bcd88993d828dd56a30018e4b47bce6e0 (diff)
downloadnihav-f9fc73be53d5226f12dc8f566b082f0758e87a87.tar.gz
Sierra RBT and SEQ formats support
-rw-r--r--nihav-game/Cargo.toml8
-rw-r--r--nihav-game/src/codecs/mod.rs10
-rw-r--r--nihav-game/src/codecs/rbt.rs453
-rw-r--r--nihav-game/src/codecs/seq.rs219
-rw-r--r--nihav-game/src/demuxers/mod.rs8
-rw-r--r--nihav-game/src/demuxers/rbt.rs283
-rw-r--r--nihav-game/src/demuxers/seq.rs111
-rw-r--r--nihav-registry/src/detect.rs12
-rw-r--r--nihav-registry/src/register.rs3
9 files changed, 1105 insertions, 2 deletions
diff --git a/nihav-game/Cargo.toml b/nihav-game/Cargo.toml
index 9111b9a..af489f3 100644
--- a/nihav-game/Cargo.toml
+++ b/nihav-game/Cargo.toml
@@ -18,7 +18,7 @@ nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, feature
[features]
default = ["all_decoders", "all_demuxers", "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"]
diff --git a/nihav-game/src/codecs/mod.rs b/nihav-game/src/codecs/mod.rs
index c8d1a38..af23b68 100644
--- a/nihav-game/src/codecs/mod.rs
+++ b/nihav-game/src/codecs/mod.rs
@@ -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
index 0000000..cc64bfb
--- /dev/null
+++ b/nihav-game/src/codecs/rbt.rs
@@ -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
index 0000000..5968423
--- /dev/null
+++ b/nihav-game/src/codecs/seq.rs
@@ -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]]));
+ }
+}
diff --git a/nihav-game/src/demuxers/mod.rs b/nihav-game/src/demuxers/mod.rs
index 7895afd..3e3f842 100644
--- a/nihav-game/src/demuxers/mod.rs
+++ b/nihav-game/src/demuxers/mod.rs
@@ -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
index 0000000..9f6da60
--- /dev/null
+++ b/nihav-game/src/demuxers/rbt.rs
@@ -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
index 0000000..ae5b099
--- /dev/null
+++ b/nihav-game/src/demuxers/seq.rs
@@ -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);
+ }
+ }
+}
diff --git a/nihav-registry/src/detect.rs b/nihav-registry/src/detect.rs
index 87226a7..a7f521d 100644
--- a/nihav-registry/src/detect.rs
+++ b/nihav-registry/src/detect.rs
@@ -420,6 +420,18 @@ const DETECTORS: &[DetectConditions] = &[
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",
conditions: &[],
diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs
index 15a3f65..b2a7ca6 100644
--- a/nihav-registry/src/register.rs
+++ b/nihav-registry/src/register.rs
@@ -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"),