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]])); } }