From fead60e32f35ca21262769174a23f6b4735bf28e Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Sat, 2 Sep 2023 17:45:27 +0200 Subject: [PATCH] add (limited) support for Digital Pictures SGA format --- nihav-game/Cargo.toml | 6 +- nihav-game/src/codecs/mod.rs | 4 + nihav-game/src/codecs/sga.rs | 773 +++++++++++++++++++++++++++++++++ nihav-game/src/demuxers/mod.rs | 4 + nihav-game/src/demuxers/sga.rs | 408 +++++++++++++++++ nihav-registry/src/detect.rs | 5 + nihav-registry/src/register.rs | 1 + 7 files changed, 1199 insertions(+), 2 deletions(-) create mode 100644 nihav-game/src/codecs/sga.rs create mode 100644 nihav-game/src/demuxers/sga.rs diff --git a/nihav-game/Cargo.toml b/nihav-game/Cargo.toml index 24ccc06..652fd67 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"] demuxers = [] -all_demuxers = ["demuxer_bmv", "demuxer_bmv3", "demuxer_cnm", "demuxer_fcmp", "demuxer_fst", "demuxer_gdv", "demuxer_hl_fmv", "demuxer_imax", "demuxer_q", "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_sga", "demuxer_siff", "demuxer_smush", "demuxer_vmd", "demuxer_vx"] demuxer_bmv = ["demuxers"] demuxer_bmv3 = ["demuxers"] demuxer_cnm = ["demuxers"] @@ -28,6 +28,7 @@ demuxer_gdv = ["demuxers"] demuxer_hl_fmv = ["demuxers"] demuxer_imax = ["demuxers"] demuxer_q = ["demuxers"] +demuxer_sga = ["demuxers"] demuxer_siff = ["demuxers"] demuxer_smush = ["demuxers"] demuxer_vmd = ["demuxers"] @@ -36,7 +37,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_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_sga", "decoder_smush_video", "decoder_vmd", "decoder_vx"] decoder_arxel_vid = ["decoders"] decoder_beam_fcp = ["decoders"] decoder_beam_vbv = ["decoders"] @@ -50,6 +51,7 @@ decoder_ipma = ["decoders"] decoder_midivid = ["decoders"] decoder_midivid3 = ["decoders"] decoder_q = ["decoders"] +decoder_sga = ["decoders"] decoder_smush_video = ["decoders"] decoder_vmd = ["decoders"] decoder_vx = ["decoders"] diff --git a/nihav-game/src/codecs/mod.rs b/nihav-game/src/codecs/mod.rs index 14c6c87..c8d1a38 100644 --- a/nihav-game/src/codecs/mod.rs +++ b/nihav-game/src/codecs/mod.rs @@ -36,6 +36,8 @@ pub mod midivid; pub mod midivid3; #[cfg(feature="decoder_q")] pub mod q; +#[cfg(feature="decoder_sga")] +pub mod sga; #[cfg(any(feature="decoder_smush_video", feature="decoder_smush_audio"))] pub mod smush; #[cfg(feature="decoder_vmd")] @@ -78,6 +80,8 @@ 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_sga")] + DecoderInfo { name: "dp-sga", get_decoder: sga::get_decoder }, #[cfg(feature="decoder_smush_video")] DecoderInfo { name: "smushv1", get_decoder: smush::get_decoder_video_v1 }, #[cfg(feature="decoder_smush_video")] diff --git a/nihav-game/src/codecs/sga.rs b/nihav-game/src/codecs/sga.rs new file mode 100644 index 0000000..f8cff3e --- /dev/null +++ b/nihav-game/src/codecs/sga.rs @@ -0,0 +1,773 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; + +const RGB555_FORMAT: NAPixelFormaton = NAPixelFormaton { model: ColorModel::RGB(RGBSubmodel::RGB), components: 3, + comp_info: [ + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 5, shift: 10, comp_offs: 0, next_elem: 2 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 5, shift: 5, comp_offs: 1, next_elem: 2 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 5, shift: 0, comp_offs: 2, next_elem: 2 }), + None, None], + elem_size: 2, be: false, alpha: false, palette: false }; + +const OPCODE_SKIP: usize = 91; + +struct DPVideoDecoder { + info: NACodecInfoRef, + pal: [u16; 256], + subtype: u8, + frame: Vec, + width: usize, + height: usize, +} + +impl DPVideoDecoder { + fn new() -> Self { + Self { + info: NACodecInfoRef::default(), + pal: [0; 256], + subtype: 0, + frame: Vec::new(), + width: 0, + height: 0, + } + } + fn decode_81(&mut self, br: &mut ByteReader) -> DecoderResult<()> { + let mut pat = [0; 8]; + let mut cur_clr = [0; 16]; + let mut off = 0; + let mut rle_len = 0; + let mut src_off = 0; + let mut had_skips = false; + for _y in (0..self.height).step_by(8) { + for x in (0..self.width).step_by(8) { + let off = off + x; + if rle_len > 0 { + copy_block(&mut self.frame, self.width, src_off, off, if had_skips { 15 } else { 0 }); + rle_len -= 1; + continue; + } + + let mut op = usize::from(br.read_byte()?); + if (op & 0x80) != 0 { + rle_len = (op & 0x7F) + 1; + op = usize::from(br.read_byte()?); + src_off = off; + had_skips = false; + } + validate!(op < 0x80); + match OPCODE_TYPE[op] { + 4 => { + if op < OPCODE_SKIP { + let nclrs = usize::from(PATTERN_COLOURS[op]); + assert_eq!(nclrs, 2); + br.read_buf(&mut cur_clr[..nclrs])?; + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let psrc = if pattern == 0 { + br.read_buf(&mut pat)?; + &pat + } else { + &PATTERN_8X8[pattern] + }; + paint_8x8(&mut self.frame[off..], self.width, psrc, &cur_clr); + } else { + had_skips = true; + } + }, + 2 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + br.read_buf(&mut cur_clr[..nclrs])?; + let mask = br.read_u32le()?; + paint_8x4_old(&mut self.frame[off..], self.width, &PATTERN_8X4_OLD[pattern], &cur_clr, mask); + } + let op = usize::from(br.read_byte()?); + validate!(op < 0x80); + match OPCODE_TYPE[op] { + 2 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + br.read_buf(&mut cur_clr[..nclrs])?; + let mask = br.read_u32le()?; + paint_8x4_old(&mut self.frame[off + self.width * 4..], self.width, &PATTERN_8X4_OLD[pattern], &cur_clr, mask); + } + }, + 0 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + br.read_buf(&mut cur_clr[..nclrs])?; + let mask = br.read_u16le()?; + paint_4x4_old(&mut self.frame[off + self.width * 4..], self.width, &PATTERN_4X4_OLD[pattern], &cur_clr, mask); + } + let op = usize::from(br.read_byte()?); + validate!(op < 0x80 && OPCODE_TYPE[op] == 0); + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + br.read_buf(&mut cur_clr[..nclrs])?; + let mask = br.read_u16le()?; + paint_4x4_old(&mut self.frame[off + self.width * 4 + 4..], self.width, &PATTERN_4X4_OLD[pattern], &cur_clr, mask); + } + }, + _ => return Err(DecoderError::InvalidData), + }; + }, + _ => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + br.read_buf(&mut cur_clr[..nclrs])?; + let mask = br.read_u16le()?; + paint_4x4_old(&mut self.frame[off..], self.width, &PATTERN_4X4_OLD[pattern], &cur_clr, mask); + } + let op = usize::from(br.read_byte()?); + validate!(op < 0x80 && OPCODE_TYPE[op] == 0); + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + br.read_buf(&mut cur_clr[..nclrs])?; + let mask = br.read_u16le()?; + paint_4x4_old(&mut self.frame[off + 4..], self.width, &PATTERN_4X4_OLD[pattern], &cur_clr, mask); + } + let op = usize::from(br.read_byte()?); + validate!(op < 0x80); + match OPCODE_TYPE[op] { + 2 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + br.read_buf(&mut cur_clr[..nclrs])?; + let mask = br.read_u32le()?; + paint_8x4_old(&mut self.frame[off + self.width * 4..], self.width, &PATTERN_8X4_OLD[pattern], &cur_clr, mask); + } + }, + 0 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + br.read_buf(&mut cur_clr[..nclrs])?; + let mask = br.read_u16le()?; + paint_4x4_old(&mut self.frame[off + self.width * 4..], self.width, &PATTERN_4X4_OLD[pattern], &cur_clr, mask); + } + let op = usize::from(br.read_byte()?); + validate!(op < 0x80 && OPCODE_TYPE[op] == 0); + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + br.read_buf(&mut cur_clr[..nclrs])?; + let mask = br.read_u16le()?; + paint_4x4_old(&mut self.frame[off + self.width * 4 + 4..], self.width, &PATTERN_4X4_OLD[pattern], &cur_clr, mask); + } + }, + _ => return Err(DecoderError::InvalidData), + }; + }, + }; + } + off += self.width * 8; + } + Ok(()) + } + fn decode_85(&mut self, clr_src: &[u8], op_src: &[u8]) -> DecoderResult<()> { + let mut mr = MemoryReader::new_read(clr_src); + let mut clrs = ByteReader::new(&mut mr); + let mut mr = MemoryReader::new_read(op_src); + let mut ops = ByteReader::new(&mut mr); + + let mut pat = [0; 8]; + let mut cur_clr = [0; 16]; + + let mut off = 0; + let mut rle_len = 0; + let mut rle_mode = 0; + let mut src_off = 0; + for _y in (0..self.height).step_by(8) { + for x in (0..self.width).step_by(8) { + let off = off + x; + if rle_len > 0 { + copy_block(&mut self.frame, self.width, src_off, off, rle_mode); + rle_len -= 1; + continue; + } + let op = usize::from(ops.read_byte()?); + if op < 0x80 { + src_off = off; + match OPCODE_TYPE[op] { + 4 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let psrc = if pattern == 0 { + ops.read_buf(&mut pat)?; + &pat + } else { + &PATTERN_8X8[pattern] + }; + let nclrs = usize::from(PATTERN_COLOURS[op]); + assert_eq!(nclrs, 2); + clrs.read_buf(&mut cur_clr[..nclrs])?; + if pattern == 0 { + cur_clr.swap(0, 1); + } + paint_8x8(&mut self.frame[off..], self.width, psrc, &cur_clr); + } + }, + 2 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + clrs.read_buf(&mut cur_clr[..nclrs])?; + let mask = ops.read_u32le()?; + paint_8x4(&mut self.frame[off..], self.width, &PATTERN_8X4[pattern], &cur_clr, mask); + } + let op = usize::from(ops.read_byte()?); + validate!(op < 0x80); + match OPCODE_TYPE[op] { + 2 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + clrs.read_buf(&mut cur_clr[..nclrs])?; + let mask = ops.read_u32le()?; + paint_8x4(&mut self.frame[off + self.width * 4..], self.width, &PATTERN_8X4[pattern], &cur_clr, mask); + } + }, + 0 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + clrs.read_buf(&mut cur_clr[..nclrs])?; + let mask = ops.read_u16le()?; + paint_4x4(&mut self.frame[off + self.width * 4..], self.width, &PATTERN_4X4[pattern], &cur_clr, mask); + } + let op = usize::from(ops.read_byte()?); + validate!(op < 0x80 && OPCODE_TYPE[op] == 0); + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + clrs.read_buf(&mut cur_clr[..nclrs])?; + let mask = ops.read_u16le()?; + paint_4x4(&mut self.frame[off + self.width * 4 + 4..], self.width, &PATTERN_4X4[pattern], &cur_clr, mask); + } + }, + _ => return Err(DecoderError::InvalidData), + }; + }, + _ => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + clrs.read_buf(&mut cur_clr[..nclrs])?; + let mask = ops.read_u16le()?; + paint_4x4(&mut self.frame[off..], self.width, &PATTERN_4X4[pattern], &cur_clr, mask); + } + let op = usize::from(ops.read_byte()?); + validate!(op < 0x80 && OPCODE_TYPE[op] == 0); + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + clrs.read_buf(&mut cur_clr[..nclrs])?; + let mask = ops.read_u16le()?; + paint_4x4(&mut self.frame[off + 4..], self.width, &PATTERN_4X4[pattern], &cur_clr, mask); + } + let op = usize::from(ops.read_byte()?); + validate!(op < 0x80); + match OPCODE_TYPE[op] { + 2 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + clrs.read_buf(&mut cur_clr[..nclrs])?; + let mask = ops.read_u32le()?; + paint_8x4(&mut self.frame[off + self.width * 4..], self.width, &PATTERN_8X4[pattern], &cur_clr, mask); + } + }, + 0 => { + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + clrs.read_buf(&mut cur_clr[..nclrs])?; + let mask = ops.read_u16le()?; + paint_4x4(&mut self.frame[off + self.width * 4..], self.width, &PATTERN_4X4[pattern], &cur_clr, mask); + } + let op = usize::from(ops.read_byte()?); + validate!(op < 0x80 && OPCODE_TYPE[op] == 0); + if op < OPCODE_SKIP { + let pattern = usize::from(OPCODE_TO_PATTERN[op]); + let nclrs = usize::from(PATTERN_COLOURS[op]); + clrs.read_buf(&mut cur_clr[..nclrs])?; + let mask = ops.read_u16le()?; + paint_4x4(&mut self.frame[off + self.width * 4 + 4..], self.width, &PATTERN_4X4[pattern], &cur_clr, mask); + } + }, + _ => return Err(DecoderError::InvalidData), + }; + }, + }; + } else { + rle_mode = ((op >> 3) & 0xF) as u8; + rle_len = op & 7; + if rle_len == 7 { + rle_len = usize::from(ops.read_byte()?); + } +if rle_mode !=0 && rle_mode !=15 { +println!(" RLE len {} mode {}", rle_len, rle_mode); +} + copy_block(&mut self.frame, self.width, src_off, off, rle_mode); + } + } + off += 8 * self.width; + } + Ok(()) + } +} + +fn copy_block(frame: &mut [u8], stride: usize, mut src_off: usize, mut dst_off: usize, mode: u8) { + match mode { + 0 => { + for _ in 0..8 { + for x in 0..8 { + frame[dst_off + x] = frame[src_off + x]; + } + src_off += stride; + dst_off += stride; + } + }, +// 1 -> copy 4x4 from src+4 to dst+1, copy 8x4 from src+1 to dst+1 +// 2 -> copy 4x4 from src to dst, copy 8x4 from src+1 to dst+1 +// 3 -> copy 8x4 from src+1 to dst+1 +// 4 -> copy 8x4 from src to dst, copy 4x4 from src+1 to dst+1 +// 5 -> copy 4x4 from src+4 to dst+1, copy 4x4 from src+1 to dst+1 +// 6 -> ??? copy 8x4 from src to dst, copy 4x4 from src+1 to dst+1 +// 7 -> copy 4x4 from src+1 to dst+1 +// 8 -> copy 8x4 from src to dst, copy 4x4 from src+2 to dst+2 +// 9 -> copy 4x4 from src+4 to dst+1, copy 4x4 from src+2 to dst+2 +//10 -> copy 4x8 from src+2 to dst+2 +//11 -> copy 4x4 from src+2 to dst+2 +//12 -> copy 8x4 block +//13 -> copy 4x4 block + 14 | 15 => {}, // skip + _ => unimplemented!(), + }; +} + +fn paint_8x8(dst: &mut [u8], stride: usize, pat: &[u8; 8], clrs: &[u8; 16]) { + for (line, &lpat) in dst.chunks_mut(stride).zip(pat.iter()) { + let mut mask = usize::from(lpat); + for dst in line[..8].iter_mut() { + *dst = clrs[mask & 1]; + mask >>= 1; + } + } +} + +fn paint_8x4_old(dst: &mut [u8], stride: usize, pat: &[u8; 8], clrs: &[u8; 16], mut mask: u32) { + for (y, line) in dst.chunks_mut(stride).take(4).enumerate() { + for (x, dst) in line[..8].iter_mut().enumerate() { + *dst = clrs[usize::from(pat[x / 2 + (y / 2) * 4] + ((mask & 1) as u8))]; + mask >>= 1; + } + } +} + +fn paint_8x4(dst: &mut [u8], stride: usize, pat: &[u8; 8], clrs: &[u8; 16], mut mask: u32) { + for (y, line) in dst.chunks_mut(stride).take(4).enumerate() { + for (x, dst) in line[..8].iter_mut().enumerate() { + *dst = clrs[usize::from(pat[x / 2 + (y / 2) * 4] - ((mask & 1) as u8))]; + mask >>= 1; + } + } +} + +fn paint_4x4_old(dst: &mut [u8], stride: usize, pat: &[u8; 8], clrs: &[u8; 16], mut mask: u16) { + for (line, pat) in dst.chunks_mut(stride).zip(pat.chunks_exact(2)) { + for (x, dst) in line[..4].iter_mut().enumerate() { + *dst = clrs[usize::from(pat[x / 2] + ((mask & 1) as u8))]; + mask >>= 1; + } + } +} + +fn paint_4x4(dst: &mut [u8], stride: usize, pat: &[u8; 8], clrs: &[u8; 16], mut mask: u16) { + for (line, pat) in dst.chunks_mut(stride).zip(pat.chunks_exact(2)) { + for (x, dst) in line[..4].iter_mut().enumerate() { + *dst = clrs[usize::from(pat[x / 2] - ((mask & 1) as u8))]; + mask >>= 1; + } + } +} + +impl NADecoder for DPVideoDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + let width = vinfo.get_width(); + let height = vinfo.get_height(); + validate!((width & 7) == 0 && (height & 7) == 0); + if let Some(ref edata) = info.get_extradata() { + validate!(!edata.is_empty()); + self.subtype = edata[0]; + validate!(self.subtype >= 0x80); + if !matches!(self.subtype, 0x81 | 0x85 | 0x86 | 0x89 | 0x8A) { + return Err(DecoderError::NotImplemented); + } + match self.subtype { + 0x81 | 0x8A => { + }, + 0x85 | 0x86 => { + validate!(edata.len() > 256 * 2); + for (dst, src) in self.pal.iter_mut().zip(edata[1..].chunks_exact(2)) { + *dst = read_u16be(src)?; + } + }, + 0x89 => { + }, + _ => return Err(DecoderError::NotImplemented), + }; + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(vinfo.get_width(), vinfo.get_height(), false, RGB555_FORMAT)); + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); + self.width = vinfo.get_width(); + self.height = vinfo.get_height(); + self.frame.resize(self.width * self.height, 0); + + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + + match self.subtype { + 0x81 | 0x8A => { + validate!(src.len() > 12); + let mut mr = MemoryReader::new_read(&src); + let mut br = ByteReader::new(&mut mr); + let stype = br.read_byte()?; + validate!(stype == 0x81 || stype == 0x8A); + br.read_byte()?; + let size = usize::from(br.read_u16le()?); + if size + 4 < src.len() { + return Err(DecoderError::ShortData); + } + br.read_u32be()?; // timestamp + br.read_byte()?; + let pal_size = usize::from(br.read_byte()?); + let width = usize::from(br.read_byte()?) * 8; + let height = usize::from(br.read_byte()?) * 8; + validate!(width == self.width); + validate!(height == self.height); + + for dst in self.pal.iter_mut().take(pal_size) { + *dst = br.read_u16le()?; + } + + self.decode_81(&mut br)?; + }, + 0x85 | 0x86 | 0x89 => { + validate!(src.len() > 6); + let mut mr = MemoryReader::new_read(&src); + let mut br = ByteReader::new(&mut mr); + + let pal_offset = usize::from(br.read_u16be()?); + let pal_size = usize::from(br.read_u16be()?); + let offset = usize::from(br.read_u16be()?); + validate!(offset <= src.len()); + validate!(pal_offset + pal_size <= 256); + validate!(pal_size * 2 <= src.len()); + + for (dst, src) in self.pal[pal_offset..].iter_mut().zip(src[6..][..pal_size * 2].chunks_exact(2)) { + *dst = read_u16be(src)?; + } + + self.decode_85(&src[6 + pal_size * 2..], &src[6 + offset..])?; + }, + _ => unreachable!(), + }; + + let mut bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?; + if let NABufferType::Video16(ref mut vbuf16) = bufinfo { + let stride = vbuf16.get_stride(0); + let dst = vbuf16.get_data_mut().unwrap(); + for (dline, sline) in dst.chunks_exact_mut(stride).zip(self.frame.chunks_exact(self.width)) { + for (dst, &src) in dline.iter_mut().zip(sline.iter()) { + *dst = self.pal[usize::from(src)]; + } + } + } else { + return Err(DecoderError::Bug); + } + + let frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo); + Ok(frm.into_ref()) + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for DPVideoDecoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + +pub fn get_decoder() -> Box { + Box::new(DPVideoDecoder::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; + #[test] + fn test_sga_dec_81() { + 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 Double Switch + test_decoding("sga", "dp-sga", "assets/Game/sga/ALEXSTIL.AVC", None, &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x8ae53caf, 0x9bd04a58, 0xf08f3ea9, 0x72b6fd05])); + } + #[test] + fn test_sga_dec_85() { + 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 Night Trap + test_decoding("sga", "dp-sga", "assets/Game/sga/CRMOVIE", Some(32), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0xa408375d, 0x3fc131f5, 0xd9ce5172, 0x30043774], + [0xa408375d, 0x3fc131f5, 0xd9ce5172, 0x30043774], + [0xa408375d, 0x3fc131f5, 0xd9ce5172, 0x30043774], + [0xa408375d, 0x3fc131f5, 0xd9ce5172, 0x30043774], + [0xa408375d, 0x3fc131f5, 0xd9ce5172, 0x30043774], + [0xa408375d, 0x3fc131f5, 0xd9ce5172, 0x30043774], + [0xa408375d, 0x3fc131f5, 0xd9ce5172, 0x30043774], + [0xa408375d, 0x3fc131f5, 0xd9ce5172, 0x30043774], + [0xa408375d, 0x3fc131f5, 0xd9ce5172, 0x30043774], + [0xa408375d, 0x3fc131f5, 0xd9ce5172, 0x30043774], + [0x2c18a4b2, 0x5771e98b, 0x90373b23, 0xc38b820d], + [0x2112a384, 0x8af91c70, 0xa8ba0a10, 0x166b754e], + [0x8824d580, 0xaba31634, 0x005f0c5e, 0xe45ac6c5], + [0x3f5551ba, 0xff75d014, 0xc7d22554, 0x2567f49f], + [0xf6257fd0, 0x457f6ff2, 0x0f5975bb, 0x85457c46], + [0xb7302db3, 0x5d384875, 0x8bce4edf, 0x9b25b176]])); + } + #[test] + fn test_sga_dec_86() { + 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_decode_images("sga", "assets/Game/sga/dplogo.dtv", "sga86", None, &dmx_reg, &dec_reg); +//panic!("end"); + + // sample from Corpse Killer + test_decoding("sga", "dp-sga", "assets/Game/sga/dplogo.dtv", Some(10), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x6dec0bbc, 0x9e81995b, 0x660a899b, 0xbc7954ca], + [0xce33d3e5, 0xcba3398d, 0x63a6ca73, 0xc3967861], + [0x55d3506a, 0x4b43c8e9, 0xac878ff2, 0xfd538d50], + [0x5c2abd0a, 0x0fc59df8, 0xfce6f84d, 0x577a6f0a], + [0x922e5500, 0xe16d523d, 0x44122da3, 0xf8d74675]])); + } +} + +const OPCODE_TYPE: [u8; 128] = [ + 0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, + 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +]; +const OPCODE_TO_PATTERN: [u8; 96] = [ + 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x00, 0x00, 0x01, 0x02, 0x06, 0x07, 0x08, 0x09, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x03, 0x06, 0x04, + 0x07, 0x08, 0x05, 0x06, 0x09, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 +]; +const PATTERN_COLOURS: [u8; 96] = [ + 0x00, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x08, 0x08, 0x0a, 0x02, 0x02, 0x02, 0x02, + 0x04, 0x06, 0x06, 0x08, 0x08, 0x0a, 0x0a, 0x08, + 0x06, 0x04, 0x0a, 0x08, 0x02, 0x08, 0x08, 0x06, + 0x10, 0x0e, 0x0e, 0x0c, 0x0e, 0x0c, 0x0c, 0x0a, + 0x0e, 0x0c, 0x0c, 0x0a, 0x0c, 0x0a, 0x0a, 0x08, + 0x10, 0x0e, 0x0e, 0x0c, 0x0e, 0x0c, 0x0c, 0x0a, + 0x0e, 0x0c, 0x0c, 0x0a, 0x0c, 0x0a, 0x0a, 0x08, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 +]; +const PATTERN_4X4: [[u8; 8]; 26] = [ + [ 0x01, 0x03, 0x05, 0x03, 0x07, 0x07, 0x07, 0x07 ], + [ 0x01, 0x03, 0x01, 0x05, 0x07, 0x07, 0x07, 0x07 ], + [ 0x01, 0x03, 0x05, 0x07, 0x09, 0x09, 0x09, 0x09 ], + [ 0x01, 0x03, 0x01, 0x03, 0x05, 0x05, 0x05, 0x05 ], + [ 0x01, 0x01, 0x01, 0x01, 0x03, 0x05, 0x07, 0x05 ], + [ 0x01, 0x01, 0x01, 0x01, 0x03, 0x05, 0x03, 0x07 ], + [ 0x01, 0x01, 0x01, 0x01, 0x03, 0x05, 0x07, 0x09 ], + [ 0x01, 0x01, 0x01, 0x01, 0x03, 0x05, 0x03, 0x05 ], + [ 0x01, 0x01, 0x01, 0x01, 0x03, 0x03, 0x03, 0x03 ], + [ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 ], + [ 0x01, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x0d, 0x0f ], + [ 0x01, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x0d, 0x0b ], + [ 0x01, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x09, 0x0d ], + [ 0x01, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x09, 0x0b ], + [ 0x01, 0x03, 0x05, 0x03, 0x07, 0x09, 0x0b, 0x0d ], + [ 0x01, 0x03, 0x05, 0x03, 0x07, 0x09, 0x0b, 0x09 ], + [ 0x01, 0x03, 0x05, 0x03, 0x07, 0x09, 0x07, 0x0b ], + [ 0x01, 0x03, 0x05, 0x03, 0x07, 0x09, 0x07, 0x09 ], + [ 0x01, 0x03, 0x01, 0x05, 0x07, 0x09, 0x0b, 0x0d ], + [ 0x01, 0x03, 0x01, 0x05, 0x07, 0x09, 0x0b, 0x09 ], + [ 0x01, 0x03, 0x01, 0x05, 0x07, 0x09, 0x07, 0x0b ], + [ 0x01, 0x03, 0x01, 0x05, 0x07, 0x09, 0x07, 0x09 ], + [ 0x01, 0x03, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0b ], + [ 0x01, 0x03, 0x01, 0x03, 0x05, 0x07, 0x09, 0x07 ], + [ 0x01, 0x03, 0x01, 0x03, 0x05, 0x07, 0x05, 0x09 ], + [ 0x01, 0x03, 0x01, 0x03, 0x05, 0x07, 0x05, 0x07 ] +]; +const PATTERN_8X4: [[u8; 8]; 26] = [ + [ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 ], + [ 0x01, 0x01, 0x03, 0x03, 0x01, 0x01, 0x03, 0x03 ], + [ 0x01, 0x01, 0x03, 0x03, 0x01, 0x01, 0x05, 0x05 ], + [ 0x01, 0x03, 0x05, 0x05, 0x07, 0x09, 0x05, 0x05 ], + [ 0x01, 0x03, 0x05, 0x05, 0x07, 0x07, 0x05, 0x05 ], + [ 0x01, 0x01, 0x03, 0x05, 0x01, 0x01, 0x07, 0x09 ], + [ 0x01, 0x01, 0x03, 0x05, 0x01, 0x01, 0x07, 0x07 ], + [ 0x01, 0x01, 0x03, 0x03, 0x05, 0x07, 0x03, 0x03 ], + [ 0x01, 0x01, 0x03, 0x03, 0x01, 0x01, 0x05, 0x07 ], + [ 0x01, 0x01, 0x03, 0x03, 0x05, 0x05, 0x03, 0x03 ], + [ 0x01, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x0d, 0x0f ], + [ 0x01, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x0d, 0x0d ], + [ 0x01, 0x03, 0x05, 0x07, 0x09, 0x09, 0x0b, 0x0d ], + [ 0x01, 0x03, 0x05, 0x07, 0x09, 0x09, 0x0b, 0x0b ], + [ 0x01, 0x03, 0x05, 0x05, 0x07, 0x09, 0x0b, 0x0d ], + [ 0x01, 0x03, 0x05, 0x05, 0x07, 0x09, 0x0b, 0x0b ], + [ 0x01, 0x03, 0x05, 0x05, 0x07, 0x07, 0x09, 0x0b ], + [ 0x01, 0x03, 0x05, 0x05, 0x07, 0x07, 0x09, 0x09 ], + [ 0x01, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x0d ], + [ 0x01, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x0b ], + [ 0x01, 0x01, 0x03, 0x05, 0x07, 0x07, 0x09, 0x0b ], + [ 0x01, 0x01, 0x03, 0x05, 0x07, 0x07, 0x09, 0x09 ], + [ 0x01, 0x01, 0x03, 0x03, 0x05, 0x07, 0x09, 0x0b ], + [ 0x01, 0x01, 0x03, 0x03, 0x05, 0x07, 0x09, 0x09 ], + [ 0x01, 0x01, 0x03, 0x03, 0x05, 0x05, 0x07, 0x09 ], + [ 0x01, 0x01, 0x03, 0x03, 0x05, 0x05, 0x07, 0x07 ] +]; +const PATTERN_8X8: [[u8; 8]; 26] = [ + [ 0; 8 ], + [ 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa ], + [ 0xff, 0xff, 0x55, 0xaa, 0x55, 0xaa, 0x00, 0x00 ], + [ 0x5f, 0xaf, 0x57, 0xab, 0x15, 0x2a, 0x05, 0x0a ], + [ 0x17, 0x2b, 0x17, 0x2b, 0x17, 0x2b, 0x17, 0x2b ], + [ 0x05, 0x2a, 0x55, 0x2a, 0x55, 0xab, 0x57, 0xaf ], + [ 0x00, 0x00, 0x55, 0xaa, 0x55, 0xaa, 0xff, 0xff ], + [ 0x50, 0xa0, 0x54, 0xa8, 0xd5, 0xea, 0xf5, 0xfa ], + [ 0xd4, 0xe8, 0xd4, 0xe8, 0xd4, 0xe8, 0xd4, 0xe8 ], + [ 0xf5, 0xea, 0xd5, 0xaa, 0x54, 0xaa, 0x54, 0xa0 ], + [ 0x55, 0xaa, 0x55, 0xaa, 0x00, 0x00, 0x00, 0x00 ], + [ 0x55, 0x2a, 0x15, 0x0a, 0x05, 0x02, 0x01, 0x00 ], + [ 0x05, 0x0a, 0x05, 0x0a, 0x05, 0x0a, 0x05, 0x0a ], + [ 0x01, 0x00, 0x05, 0x02, 0x15, 0x0a, 0x55, 0x2a ], + [ 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa, 0x55, 0xaa ], + [ 0x00, 0x80, 0x40, 0xa0, 0x50, 0xa8, 0x54, 0xaa ], + [ 0x50, 0xa0, 0x50, 0xa0, 0x50, 0xa0, 0x50, 0xa0 ], + [ 0x54, 0xaa, 0x50, 0xa8, 0x40, 0xa0, 0x00, 0x80 ], + [ 0xff, 0xff, 0xff, 0xff, 0x55, 0xaa, 0x55, 0xaa ], + [ 0x7f, 0xff, 0x5f, 0xbf, 0x57, 0xaf, 0x55, 0xab ], + [ 0x5f, 0xaf, 0x5f, 0xaf, 0x5f, 0xaf, 0x5f, 0xaf ], + [ 0x55, 0xab, 0x57, 0xaf, 0x5f, 0xbf, 0x7f, 0xff ], + [ 0x55, 0xaa, 0x55, 0xaa, 0xff, 0xff, 0xff, 0xff ], + [ 0xd5, 0xaa, 0xf5, 0xea, 0xfd, 0xfa, 0xff, 0xfe ], + [ 0xf5, 0xfa, 0xf5, 0xfa, 0xf5, 0xfa, 0xf5, 0xfa ], + [ 0xff, 0xfe, 0xfd, 0xfa, 0xf5, 0xea, 0xd5, 0xaa ] +]; + +const PATTERN_4X4_OLD: [[u8; 8]; 26] = [ + [ 0x00, 0x02, 0x04, 0x02, 0x06, 0x06, 0x06, 0x06 ], + [ 0x00, 0x02, 0x00, 0x04, 0x06, 0x06, 0x06, 0x06 ], + [ 0x00, 0x02, 0x04, 0x06, 0x08, 0x08, 0x08, 0x08 ], + [ 0x00, 0x02, 0x00, 0x02, 0x04, 0x04, 0x04, 0x04 ], + [ 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x06, 0x04 ], + [ 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x02, 0x06 ], + [ 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x06, 0x08 ], + [ 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x02, 0x04 ], + [ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02 ], + [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], + [ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e ], + [ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0a ], + [ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x08, 0x0c ], + [ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x08, 0x0a ], + [ 0x00, 0x02, 0x04, 0x02, 0x06, 0x08, 0x0a, 0x0c ], + [ 0x00, 0x02, 0x04, 0x02, 0x06, 0x08, 0x0a, 0x08 ], + [ 0x00, 0x02, 0x04, 0x02, 0x06, 0x08, 0x06, 0x0a ], + [ 0x00, 0x02, 0x04, 0x02, 0x06, 0x08, 0x06, 0x08 ], + [ 0x00, 0x02, 0x00, 0x04, 0x06, 0x08, 0x0a, 0x0c ], + [ 0x00, 0x02, 0x00, 0x04, 0x06, 0x08, 0x0a, 0x08 ], + [ 0x00, 0x02, 0x00, 0x04, 0x06, 0x08, 0x06, 0x0a ], + [ 0x00, 0x02, 0x00, 0x04, 0x06, 0x08, 0x06, 0x08 ], + [ 0x00, 0x02, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a ], + [ 0x00, 0x02, 0x00, 0x02, 0x04, 0x06, 0x08, 0x06 ], + [ 0x00, 0x02, 0x00, 0x02, 0x04, 0x06, 0x04, 0x08 ], + [ 0x00, 0x02, 0x00, 0x02, 0x04, 0x06, 0x04, 0x06 ] +]; +const PATTERN_8X4_OLD: [[u8; 8]; 26] = [ + [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], + [ 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0x02 ], + [ 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x04, 0x04 ], + [ 0x00, 0x02, 0x04, 0x04, 0x06, 0x08, 0x04, 0x04 ], + [ 0x00, 0x02, 0x04, 0x04, 0x06, 0x06, 0x04, 0x04 ], + [ 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x06, 0x08 ], + [ 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x06, 0x06 ], + [ 0x00, 0x00, 0x02, 0x02, 0x04, 0x06, 0x02, 0x02 ], + [ 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x04, 0x06 ], + [ 0x00, 0x00, 0x02, 0x02, 0x04, 0x04, 0x02, 0x02 ], + [ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e ], + [ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0c ], + [ 0x00, 0x02, 0x04, 0x06, 0x08, 0x08, 0x0a, 0x0c ], + [ 0x00, 0x02, 0x04, 0x06, 0x08, 0x08, 0x0a, 0x0a ], + [ 0x00, 0x02, 0x04, 0x04, 0x06, 0x08, 0x0a, 0x0c ], + [ 0x00, 0x02, 0x04, 0x04, 0x06, 0x08, 0x0a, 0x0a ], + [ 0x00, 0x02, 0x04, 0x04, 0x06, 0x06, 0x08, 0x0a ], + [ 0x00, 0x02, 0x04, 0x04, 0x06, 0x06, 0x08, 0x08 ], + [ 0x00, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c ], + [ 0x00, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0a ], + [ 0x00, 0x00, 0x02, 0x04, 0x06, 0x06, 0x08, 0x0a ], + [ 0x00, 0x00, 0x02, 0x04, 0x06, 0x06, 0x08, 0x08 ], + [ 0x00, 0x00, 0x02, 0x02, 0x04, 0x06, 0x08, 0x0a ], + [ 0x00, 0x00, 0x02, 0x02, 0x04, 0x06, 0x08, 0x08 ], + [ 0x00, 0x00, 0x02, 0x02, 0x04, 0x04, 0x06, 0x08 ], + [ 0x00, 0x00, 0x02, 0x02, 0x04, 0x04, 0x06, 0x06 ] +]; diff --git a/nihav-game/src/demuxers/mod.rs b/nihav-game/src/demuxers/mod.rs index 8ee7a6c..7895afd 100644 --- a/nihav-game/src/demuxers/mod.rs +++ b/nihav-game/src/demuxers/mod.rs @@ -24,6 +24,8 @@ mod hl_fmv; mod imax; #[cfg(feature="demuxer_q")] mod q; +#[cfg(feature="demuxer_sga")] +mod sga; #[cfg(feature="demuxer_siff")] mod siff; #[cfg(feature="demuxer_smush")] @@ -52,6 +54,8 @@ const GAME_DEMUXERS: &[&dyn DemuxerCreator] = &[ &imax::IMAXDemuxerCreator {}, #[cfg(feature="demuxer_q")] &q::QDemuxerCreator {}, +#[cfg(feature="demuxer_sga")] + &sga::SGADemuxerCreator {}, #[cfg(feature="demuxer_siff")] &siff::SIFFDemuxerCreator {}, #[cfg(feature="demuxer_smush")] diff --git a/nihav-game/src/demuxers/sga.rs b/nihav-game/src/demuxers/sga.rs new file mode 100644 index 0000000..b5d7f7b --- /dev/null +++ b/nihav-game/src/demuxers/sga.rs @@ -0,0 +1,408 @@ +use nihav_core::frame::*; +use nihav_core::demuxers::*; + +const RGB555_FORMAT: NAPixelFormaton = NAPixelFormaton { model: ColorModel::RGB(RGBSubmodel::RGB), components: 3, + comp_info: [ + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 5, shift: 10, comp_offs: 0, next_elem: 2 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 5, shift: 5, comp_offs: 1, next_elem: 2 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 5, shift: 0, comp_offs: 2, next_elem: 2 }), + None, None], + elem_size: 2, be: false, alpha: false, palette: false }; +struct SGADemuxer<'a> { + src: &'a mut ByteReader<'a>, + subtype: u8, + apts: u64, + abuf: Vec, + abuf2: Vec, + asize: usize, + no_ts_in_f9: bool, + ntsc: bool, +} + +impl<'a> SGADemuxer<'a> { + fn new(io: &'a mut ByteReader<'a>) -> Self { + Self { + src: io, + subtype: 0, + apts: 0, + abuf: Vec::new(), + abuf2: Vec::new(), + asize: 0, + no_ts_in_f9: false, + ntsc: false, + } + } +} + +fn parse_smpte_time(src: &[u8], ntsc: bool) -> DemuxerResult { + validate!(src.len() >= 4); + let hours = src[0]; + let minutes = src[1]; + validate!(minutes < 60); + let seconds = src[2]; + validate!(seconds < 60); + let frame = src[3]; + if ntsc { + validate!(frame < 60); + } else { + validate!(frame < 30); + } + + let tot_min = u64::from(hours) * 60 + u64::from(minutes); + let tot_sec = tot_min * 60 + u64::from(seconds); + Ok(tot_sec * if ntsc { 60 } else { 30 } + u64::from(frame)) +} + +fn get_smpte_time(src: &mut ByteReader, ntsc: bool) -> DemuxerResult { + let mut buf = [0; 4]; + src.read_buf(&mut buf)?; + parse_smpte_time(&buf, ntsc) +} + +impl<'a> DemuxCore<'a> for SGADemuxer<'a> { + fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> { + let mut subtype = self.src.read_byte()?; + match subtype { + 0xF1 => { + self.src.read_skip(3)?; + subtype = self.src.read_byte()?; + }, + 0xF4 => { + self.src.read_skip(1)?; + let csize = self.src.read_u16be()?; + self.src.read_skip(usize::from(csize))?; + subtype = self.src.read_byte()?; + }, + 0xF9 => { + self.src.read_skip(3)?; + if (self.src.peek_byte()? & 0x80) == 0 { + self.src.read_skip(4)?; + } else { + self.no_ts_in_f9 = true; + } + subtype = self.src.read_byte()?; + }, + _ => {}, + }; + validate!(subtype >= 0x80); + if !matches!(subtype, 0x81 | 0x85 | 0x86 | 0x89 | 0x8A) { + return Err(DemuxerError::NotImplemented); + } + self.subtype = subtype; + match subtype { + 0x81 | 0x8A => { + self.src.read_skip(9)?; + let tile_w = self.src.read_byte()?; + let tile_h = self.src.read_byte()?; + validate!(tile_w > 0 && tile_h > 0); + self.src.seek(SeekFrom::Start(0))?; + let vhdr = NAVideoInfo::new(usize::from(tile_w) * 8, usize::from(tile_h) * 8, false, RGB555_FORMAT); + let vci = NACodecTypeInfo::Video(vhdr); + let vinfo = NACodecInfo::new("dp-sga", vci, Some(vec![subtype])); + if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 30, 0)).is_none() { + return Err(DemuxerError::MemoryError); + } + }, + 0x85 | 0x86 => { + let mut edata = vec![0; 0x201]; + edata[0] = subtype; + self.src.read_byte()?; + let tile_w = self.src.read_byte()?; + let tile_h = self.src.read_byte()?; + validate!(tile_w > 0 && tile_h > 0); + self.src.read_skip(8)?; + self.src.read_buf(&mut edata[1..])?; + if self.subtype == 0x85 { + self.asize = usize::from(self.src.read_u16be()?); + } + + let vhdr = NAVideoInfo::new(usize::from(tile_w) * 8, usize::from(tile_h) * 8, false, RGB555_FORMAT); + let vci = NACodecTypeInfo::Video(vhdr); + let vinfo = NACodecInfo::new("dp-sga", vci, Some(edata)); + if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 30, 0)).is_none() { + return Err(DemuxerError::MemoryError); + } + + let srate = 16000; + let ahdr = NAAudioInfo::new(srate, 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, srate, 0)).is_none() { + return Err(DemuxerError::MemoryError); + } + }, + 0x89 => { + self.src.seek(SeekFrom::Start(0))?; + let vhdr = NAVideoInfo::new(256, 160, false, RGB555_FORMAT); + let vci = NACodecTypeInfo::Video(vhdr); + let vinfo = NACodecInfo::new("dp-sga", vci, Some(vec![subtype])); + if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 30, 0)).is_none() { + return Err(DemuxerError::MemoryError); + } + + let srate = 22050; + let ahdr = NAAudioInfo::new(srate, 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, srate, 0)).is_none() { + return Err(DemuxerError::MemoryError); + } + let ainfo = NACodecInfo::new("pcm", NACodecTypeInfo::Audio(ahdr), None); + if strmgr.add_stream(NAStream::new(StreamType::Audio, 2, ainfo, 1, srate, 0)).is_none() { + return Err(DemuxerError::MemoryError); + } + }, + _ => unreachable!(), + }; + + Ok(()) + } + + fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult { + if !self.abuf.is_empty() { + let mut buf = Vec::new(); + std::mem::swap(&mut buf, &mut self.abuf); + + if let Some(stream) = strmgr.get_stream(1) { + let ts = stream.make_ts(Some(self.apts), None, None); + self.apts += buf.len() as u64; + return Ok(NAPacket::new(stream, ts, true, buf)); + } + } + if !self.abuf2.is_empty() { + let mut buf = Vec::new(); + std::mem::swap(&mut buf, &mut self.abuf2); + + if let Some(stream) = strmgr.get_stream(2) { + let ts = stream.make_ts(Some(self.apts), None, None); + self.apts += buf.len() as u64; + return Ok(NAPacket::new(stream, ts, true, buf)); + } + } + match self.subtype { + 0x81 | 0x8A => { + let mut hdr = [0; 4]; + loop { + match self.src.read_buf(&mut hdr) { + Ok(_) => {}, + Err(ByteIOError::ReadError) | + Err(ByteIOError::EOF) => return Err(DemuxerError::EOF), + Err(err) => return Err(err.into()), + }; + let chunk_size = usize::from(read_u16le(&hdr[2..])?); + validate!(chunk_size > 8); + match hdr[0] { + 0x81 | 0x8A => { + let mut buf = vec![0; chunk_size + 4]; + buf[..4].copy_from_slice(&hdr); + self.src.read_buf(&mut buf[4..])?; + let ts = parse_smpte_time(&buf[4..], self.ntsc)?; + let stream = strmgr.get_stream(0).unwrap(); + let ts = NATimeInfo::new(Some(ts), None, None, stream.tb_num, stream.tb_den); + return Ok(NAPacket::new(stream, ts, false, buf)); + }, + 0xF1 => {}, + 0xF9 => { + if !self.no_ts_in_f9 { + self.src.read_skip(4)?; + } + }, + _ => self.src.read_skip(chunk_size)?, + }; + if (self.src.tell() & 1) != 0 { + self.src.read_skip(1)?; + } + } + }, + 0x85 => { + let ts = match get_smpte_time(self.src, self.ntsc) { + Ok(val) => val, + Err(DemuxerError::IOError) => return Err(DemuxerError::EOF), + Err(err) => return Err(err), + }; + let pal_size = self.src.read_u16be()?; + let asize = usize::from(self.src.read_u16be()?); + validate!(asize >= self.asize); + let full_size = usize::from(self.src.read_u16be()?); + validate!(full_size >= asize); + let pal_off = self.src.read_u16be()?; + let vsize = full_size - self.asize; + let offset = (asize - self.asize) as u16; + let mut buf = vec![0; vsize + 6]; + if asize > 0 { + self.abuf.resize(self.asize - 1, 0); + self.src.read_buf(&mut self.abuf)?; + self.src.read_byte()?; + } + write_u16be(&mut buf, pal_off)?; + write_u16be(&mut buf[2..], pal_size)?; + write_u16be(&mut buf[4..], offset)?; + self.src.read_buf(&mut buf[6..])?; + + let stream = strmgr.get_stream(0).unwrap(); + let ts = NATimeInfo::new(Some(ts), None, None, stream.tb_num, stream.tb_den); + Ok(NAPacket::new(stream, ts, false, buf)) + }, + 0x86 => { + let ts = match get_smpte_time(self.src, self.ntsc) { + Ok(val) => val, + Err(DemuxerError::IOError) => return Err(DemuxerError::EOF), + Err(err) => return Err(err), + }; + let asize = usize::from(self.src.read_u16be()?); + let vsize = usize::from(self.src.read_u16be()?); + let pal_off = self.src.read_u16be()?; + let pal_size = self.src.read_u16be()?; + let offset = self.src.read_u16be()?; + let mut buf = vec![0; vsize + 6]; + if asize > 0 { + self.abuf.resize(asize, 0); + self.src.read_buf(&mut self.abuf)?; + } + write_u16be(&mut buf, pal_off)?; + write_u16be(&mut buf[2..], pal_size)?; + write_u16be(&mut buf[4..], offset)?; + self.src.read_buf(&mut buf[6..])?; + + let stream = strmgr.get_stream(0).unwrap(); + let ts = NATimeInfo::new(Some(ts), None, None, stream.tb_num, stream.tb_den); + Ok(NAPacket::new(stream, ts, false, buf)) + }, + 0x89 => { + let mut hdr = [0; 4]; + loop { + match self.src.read_buf(&mut hdr) { + Ok(_) => {}, + Err(ByteIOError::ReadError) | + Err(ByteIOError::EOF) => return Err(DemuxerError::EOF), + Err(err) => return Err(err.into()), + }; + let chunk_size = usize::from(read_u16be(&hdr[2..])?); + validate!((hdr[0] & 0x80) != 0); + validate!(chunk_size > 8); + let end = self.src.tell() + (chunk_size as u64); + match hdr[0] { + 0x89 => { + let ts = get_smpte_time(self.src, self.ntsc)?; + let asize = usize::from(self.src.read_u16be()?); + let vsize = usize::from(self.src.read_u16be()?); + validate!((asize & 0x7FFF) + vsize + 16 <= chunk_size); + let pal_size = self.src.read_u16be()?; + let offset = self.src.read_u16be()?; + validate!(usize::from(offset) <= vsize); + let mut buf = vec![0; vsize + 6]; + if asize > 0 { + if (asize & 0x8000) == 0 { + self.abuf.resize(asize, 0); + self.src.read_buf(&mut self.abuf)?; + self.abuf2.resize(asize, 0); + self.abuf2.copy_from_slice(&self.abuf); + } else { + let asize = asize & 0x7FFF; + validate!((asize & 1) == 0); + self.abuf.resize(asize / 2, 0); + self.abuf2.resize(asize / 2, 0); + self.src.read_buf(&mut self.abuf)?; + self.src.read_buf(&mut self.abuf2)?; + } + } + write_u16be(&mut buf, 1/*pal_off*/)?; + write_u16be(&mut buf[2..], pal_size)?; + write_u16be(&mut buf[4..], offset)?; + self.src.read_buf(&mut buf[6..])?; + validate!(self.src.tell() <= end); + self.src.seek(SeekFrom::Start(end))?; + + let stream = strmgr.get_stream(0).unwrap(); + let ts = NATimeInfo::new(Some(ts), None, None, stream.tb_num, stream.tb_den); + return Ok(NAPacket::new(stream, ts, false, buf)); + }, + _ => self.src.read_skip(chunk_size)?, + }; + } + }, + _ => unreachable!(), + } + } + + fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> { + Err(DemuxerError::NotPossible) + } + fn get_duration(&self) -> u64 { 0 } +} + +const DEMUXER_OPTS: &[NAOptionDefinition] = &[ + NAOptionDefinition { + name: "ntsc", description: "timestamps are 60fps instead of 30fps", + opt_type: NAOptionDefinitionType::Bool }, +]; + +impl<'a> NAOptionHandler for SGADemuxer<'a> { + fn get_supported_options(&self) -> &[NAOptionDefinition] { DEMUXER_OPTS } + fn set_options(&mut self, options: &[NAOption]) { + for option in options.iter() { + for opt_def in DEMUXER_OPTS.iter() { + if opt_def.check(option).is_ok() { + if let ("name", NAValue::Bool(ref bval)) = (option.name, &option.value) { + self.ntsc = *bval; + } + } + } + } + } + fn query_option_value(&self, name: &str) -> Option { + match name { + "ntsc" => Some(NAValue::Bool(self.ntsc)), + _ => None, + } + } +} + +pub struct SGADemuxerCreator { } + +impl DemuxerCreator for SGADemuxerCreator { + fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box + 'a> { + Box::new(SGADemuxer::new(br)) + } + fn get_name(&self) -> &'static str { "sga" } +} + +#[cfg(test)] +mod test { + use super::*; + use std::fs::File; + + fn test_sga_demux(name: &str) { + let mut file = File::open(name).unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = SGADemuxer::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 == DemuxerError::EOF { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + println!("Got {}", pkt); + } + } + + #[test] + fn test_sga_demux_81() { + // samples from Double Switch game + test_sga_demux("assets/Game/sga/ALEXSTIL.AVC"); + test_sga_demux("assets/Game/sga/DPLOGO.AVC"); + } + #[test] + fn test_sga_demux_85() { + // sample from Night Trap game + test_sga_demux("assets/Game/sga/CRMOVIE"); + } + #[test] + fn test_sga_demux_86() { + // sample from Corpse Killer game + test_sga_demux("assets/Game/sga/dplogo.dtv"); + } +} diff --git a/nihav-registry/src/detect.rs b/nihav-registry/src/detect.rs index 3c5a60a..6603c0d 100644 --- a/nihav-registry/src/detect.rs +++ b/nihav-registry/src/detect.rs @@ -403,6 +403,11 @@ const DETECTORS: &[DetectConditions] = &[ conditions: &[CheckItem{offs: 0, cond: &CC::Str(b"BMVi") }, CheckItem{offs: 32, cond: &CC::Str(b"DATA")}], }, + DetectConditions { + demux_name: "sga", + extensions: ".dtv,.avc", + conditions: &[], + }, DetectConditions { demux_name: "vmd", extensions: ".vmd", diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index 7bfd172..ac6feca 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -245,6 +245,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(audio; "bmv-audio", "BMV audio"), desc!(video; "bmv3-video", "DW Noir BMV video"), desc!(audio; "bmv3-audio", "DW Noir BMV audio"), + desc!(video; "dp-sga", "Digital Pictures SGA video"), desc!(video; "fable-imax", "Fable IMAX video"), desc!(video; "fst-video", "FutureVision video"), desc!(audio; "fst-audio", "FutureVision audio"), -- 2.39.5