--- /dev/null
+use nihav_core::codecs::*;
+use nihav_core::io::bitreader::*;
+use nihav_core::io::byteio::*;
+use std::cmp::Ordering;
+
+const START_BITS: u8 = 9;
+const MAX_BITS: u8 = 16;
+const DICT_SIZE: usize = 1 << MAX_BITS;
+const START_POS: usize = 257;
+const INVALID_POS: usize = 65536;
+
+struct LZWState {
+ dict_sym: [u8; DICT_SIZE],
+ dict_prev: [u16; DICT_SIZE],
+ dict_pos: usize,
+ dict_lim: usize,
+ idx_bits: u8,
+}
+
+impl LZWState {
+ fn new() -> Self {
+ Self {
+ dict_sym: [0; DICT_SIZE],
+ dict_prev: [0; DICT_SIZE],
+ dict_pos: START_POS,
+ dict_lim: 1 << START_BITS,
+ idx_bits: START_BITS,
+ }
+ }
+ fn reset(&mut self) {
+ self.dict_pos = START_POS;
+ self.dict_lim = 1 << START_BITS;
+ self.idx_bits = START_BITS;
+ }
+ fn add(&mut self, prev: usize, sym: u8) {
+ if self.dict_pos < self.dict_lim {
+ self.dict_sym [self.dict_pos] = sym;
+ self.dict_prev[self.dict_pos] = prev as u16;
+ self.dict_pos += 1;
+ }
+ }
+ fn decode_idx(&self, dst: &mut [u8], pos: usize, idx: usize) -> DecoderResult<usize> {
+ let mut tot_len = 1;
+ let mut tidx = idx;
+ while tidx > 256 {
+ tidx = self.dict_prev[tidx] as usize;
+ tot_len += 1;
+ }
+ validate!(pos + tot_len <= dst.len());
+
+ let mut end = pos + tot_len - 1;
+ let mut tidx = idx;
+ while tidx > 256 {
+ dst[end] = self.dict_sym[tidx];
+ end -= 1;
+ tidx = self.dict_prev[tidx] as usize;
+ }
+ dst[end] = tidx as u8;
+
+ Ok(tot_len)
+ }
+ fn decode(&mut self, br: &mut BitReader, dst: &mut [u8]) -> DecoderResult<()> {
+ self.reset();
+
+ let mut pos = 0;
+ let mut lastidx = INVALID_POS;
+ br.skip(9)?;
+ loop {
+ let idx = br.read(self.idx_bits)? as usize;
+ if idx == 256 {
+ break;
+ }
+ match idx.cmp(&self.dict_pos) {
+ Ordering::Less => {
+ let len = self.decode_idx(dst, pos, idx)?;
+ if lastidx != INVALID_POS {
+ self.add(lastidx, dst[pos]);
+ }
+ pos += len;
+ },
+ Ordering::Equal => {
+ validate!(lastidx != INVALID_POS);
+ let len = self.decode_idx(dst, pos, lastidx)?;
+ let lastsym = dst[pos];
+ pos += len;
+ validate!(pos < dst.len());
+ dst[pos] = lastsym;
+ pos += 1;
+ self.add(lastidx, lastsym);
+ },
+ Ordering::Greater => return Err(DecoderError::InvalidData),
+ }
+ lastidx = idx;
+ if self.dict_pos == self.dict_lim && self.idx_bits < MAX_BITS {
+ self.dict_lim <<= 1;
+ self.idx_bits += 1;
+ }
+ }
+ validate!(pos == dst.len());
+ Ok(())
+ }
+}
+
+struct EuclidDecoder {
+ info: NACodecInfoRef,
+ width: usize,
+ height: usize,
+ lzw: LZWState,
+ pal: [u8; 768],
+ frame: Vec<u8>,
+ mode: u32,
+ update: bool,
+ buf: Vec<u8>,
+}
+
+impl EuclidDecoder {
+ fn new() -> Self {
+ Self {
+ info: NACodecInfoRef::default(),
+ width: 0,
+ height: 0,
+ lzw: LZWState::new(),
+ pal: [0; 768],
+ frame: Vec::new(),
+ mode: 0,
+ update: false,
+ buf: Vec::new(),
+ }
+ }
+ fn output_frame(&self, dst: &mut [u8], stride: usize, paloff: usize) {
+ match self.mode {
+ 12 => {
+ for (dlines, sline) in dst.chunks_exact_mut(stride * 2)
+ .zip(self.frame.chunks_exact(self.width / 2)).take(self.height / 2) {
+ let (dline0, dline1) = dlines.split_at_mut(stride);
+ for (pair, &b) in dline0.chunks_exact_mut(2).zip(sline.iter()) {
+ pair[0] = b & 0xF;
+ pair[1] = b >> 4;
+ }
+ dline1.copy_from_slice(dline0);
+ }
+ },
+ 15 | 40 => {
+ for (dlines, sline) in dst.chunks_exact_mut(stride * 2)
+ .zip(self.frame.chunks_exact(self.width)).take(self.height / 2) {
+ let (dline0, dline1) = dlines.split_at_mut(stride);
+ dline0[..sline.len()].copy_from_slice(sline);
+ dline1[..sline.len()].copy_from_slice(sline);
+ }
+ },
+ 27 => {
+ for (dline, sline) in dst.chunks_exact_mut(stride)
+ .zip(self.frame.chunks_exact(self.width / 2)).take(self.height) {
+ for (pair, &b) in dline.chunks_exact_mut(2).zip(sline.iter()) {
+ pair[0] = b & 0xF;
+ pair[1] = b >> 4;
+ }
+ }
+ },
+ _ => {
+ for (dline, sline) in dst.chunks_exact_mut(stride)
+ .zip(self.frame.chunks_exact(self.width)).take(self.height) {
+ dline[..sline.len()].copy_from_slice(sline);
+ }
+ },
+ }
+
+ dst[paloff..][..768].copy_from_slice(&self.pal);
+ }
+}
+
+impl NADecoder for EuclidDecoder {
+ 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();
+ if let Some(edata) = info.get_extradata() {
+ validate!(edata.len() >= 0x34);
+ let hdr_size = read_u32le(&edata)? as usize;
+ validate!(hdr_size >= 0x34 && hdr_size <= edata.len());
+ self.mode = read_u32le(&edata[0x10..])?;
+
+ self.update = (read_u32le(&edata[0x18..])? & 1) != 0;
+
+ if edata.len() > hdr_size {
+ let src_pal = &edata[hdr_size..];
+ let copy_size = src_pal.len().min(self.pal.len());
+ self.pal[..copy_size].copy_from_slice(&src_pal[..copy_size]);
+ } else {
+ //self.pal[..DEFAULT_PAL.len()].copy_from_slice(&DEFAULT_PAL);
+ for (i, clr) in self.pal.chunks_exact_mut(3).enumerate() {
+ clr[0] = i as u8;
+ clr[1] = i as u8;
+ clr[2] = i as u8;
+ }
+ }
+ } else {
+ return Err(DecoderError::InvalidData);
+ }
+ let frm_size = match self.mode {
+ 12 => {
+ validate!((self.width & 1) == 0);
+ validate!((self.height & 1) == 0);
+ (self.width / 2) * (self.height / 2)
+ },
+ 15 | 40 => {
+ validate!((self.height & 1) == 0);
+ self.width * (self.height / 2)
+ },
+ 27 => {
+ validate!((self.width & 1) == 0);
+ (self.width / 2) * self.height
+ },
+ 28 => {
+ self.width * self.height
+ },
+ _ => return Err(DecoderError::NotImplemented),
+ };
+ self.frame = vec![0; frm_size];
+ self.buf = vec![0; frm_size];
+
+ 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() > 1);
+
+ let mut br = BitReader::new(&src, BitReaderMode::LE);
+
+ let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?;
+
+ let mut buf = bufinfo.get_vbuf().unwrap();
+ let paloff = buf.get_offset(1);
+ let stride = buf.get_stride(0);
+ let data = buf.get_data_mut().unwrap();
+
+ self.lzw.decode(&mut br, if !self.update { &mut self.frame } else { &mut self.buf })?;
+ if self.update {
+ for (pix, &b) in self.frame.iter_mut().zip(self.buf.iter()) {
+ *pix ^= b;
+ }
+ }
+
+ self.output_frame(data, stride, paloff);
+
+ let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo);
+ frm.set_keyframe(self.update);
+ frm.set_frame_type(if !self.update { FrameType::I } else { FrameType::P });
+ Ok(frm.into_ref())
+ }
+ fn flush(&mut self) {}
+}
+
+impl NAOptionHandler for EuclidDecoder {
+ 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(EuclidDecoder::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_euclid() {
+ let mut dmx_reg = RegisteredDemuxers::new();
+ acorn_register_all_demuxers(&mut dmx_reg);
+ let mut dec_reg = RegisteredDecoders::new();
+ acorn_register_all_decoders(&mut dec_reg);
+
+ // a sample from RISC DISC 3
+ test_decoding("tca", "euclid", "assets/Acorn/JUMPER", Some(1),
+ &dmx_reg, &dec_reg,
+ ExpectedTestResult::MD5Frames(vec![
+ [0x5ce22d08, 0x5f3cbd1b, 0x61a8b85e, 0x58197ba6],
+ [0x1fc3cc2e, 0xdc107b42, 0xada9f7b5, 0x0882f3d6]]));
+ }
+ #[test]
+ fn test_euclid_inter() {
+ let mut dmx_reg = RegisteredDemuxers::new();
+ acorn_register_all_demuxers(&mut dmx_reg);
+ let mut dec_reg = RegisteredDecoders::new();
+ acorn_register_all_decoders(&mut dec_reg);
+
+ // a sample from RISC DISC 3
+ test_decoding("tca", "euclid", "assets/Acorn/WIGGLFACE", Some(1),
+ &dmx_reg, &dec_reg,
+ ExpectedTestResult::MD5Frames(vec![
+ [0xd1362b20, 0x986be0ec, 0x59aae46e, 0x11123e70],
+ [0xc07ab19c, 0xc518aca6, 0xc1f632b3, 0x9baa9cb5]]));
+ }
+}
+
+#[allow(dead_code)]
+const DEFAULT_PAL: [u8; 16 * 3] = [
+ 0xFF, 0xFF, 0xFF,
+ 0xDD, 0xDD, 0xDD,
+ 0xBB, 0xBB, 0xBB,
+ 0x99, 0x99, 0x99,
+ 0x77, 0x77, 0x77,
+ 0x55, 0x55, 0x55,
+ 0x33, 0x33, 0x33,
+ 0x00, 0x00, 0x00,
+ 0x00, 0x44, 0x99,
+ 0xEE, 0xEE, 0x00,
+ 0x00, 0xCC, 0x00,
+ 0xDD, 0x00, 0x00,
+ 0xEE, 0xEE, 0xBB,
+ 0x55, 0x88, 0x00,
+ 0xFF, 0xBB, 0x00,
+ 0x00, 0xBB, 0xFF
+];
--- /dev/null
+use nihav_core::demuxers::*;
+
+struct TCADemuxer<'a> {
+ src: &'a mut ByteReader<'a>,
+ frameno: u64,
+ data_end: u64,
+}
+
+impl<'a> TCADemuxer<'a> {
+ fn new(src: &'a mut ByteReader<'a>) -> Self {
+ Self {
+ src,
+ frameno: 0,
+ data_end: 0,
+ }
+ }
+}
+
+impl<'a> DemuxCore<'a> for TCADemuxer<'a> {
+ fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> {
+ let tag = self.src.peek_tag()?;
+ let is_acef = &tag == b"ACEF";
+ let acef_size = if is_acef {
+ self.src.read_skip(4)?;
+ self.src.read_u32le()?
+ } else { 0 };
+ let size2 = self.src.read_u32le()?;
+ if is_acef {
+ validate!(acef_size > 0x44 && size2 + 8 <= acef_size);
+ }
+ self.data_end = u64::from(size2 + 8);
+
+ self.src.read_skip(12)?;
+
+ const HDR_SIZE: usize = 0x30;
+
+ let mut hdr = vec![0; HDR_SIZE + 4];
+ write_u32le(&mut hdr, HDR_SIZE as u32 + 4)?;
+ self.src.read_buf(&mut hdr[4..])?;
+
+ let width = read_u32le(&hdr[8..])? as usize;
+ let height = read_u32le(&hdr[12..])? as usize;
+ validate!(width > 0 && height > 0);
+ validate!((width | height) & 1 == 0);
+
+ if is_acef {
+ let data_start = self.src.tell();
+
+ // scan tail for palette and such
+ if self.src.seek(SeekFrom::Start(u64::from(acef_size))).is_ok() {
+ while let Ok(tag) = self.src.read_tag() {
+ let size = self.src.read_u32le()? as usize;
+ validate!(size >= 8);
+ if &tag == b"PALE" {
+ validate!((0x28..=0x428).contains(&size) && (size & 3) == 0);
+ self.src.read_skip(0x1C)?;
+ let nclrs = (size - 0x24) / 4;
+ hdr.resize(HDR_SIZE + 4 + 256 * 3, 0);
+ for _ in 0..nclrs {
+ let idx = usize::from(self.src.read_byte()?);
+ self.src.read_buf(&mut hdr[HDR_SIZE + 4 + idx * 3..][..3])?;
+ }
+ } else {
+ self.src.read_skip(size - 8)?;
+ }
+ }
+ }
+ self.src.seek(SeekFrom::Start(data_start))?;
+ }
+
+ let vci = NACodecTypeInfo::Video(NAVideoInfo::new(width / 2, height / 2, false, PAL8_FORMAT));
+ let vinfo = NACodecInfo::new("euclid", vci, Some(hdr));
+ let ret = strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 10, 0));
+ if ret.is_none() {
+ return Err(DemuxerError::MemoryError);
+ }
+
+ Ok(())
+ }
+
+ fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
+ if self.src.tell() >= self.data_end {
+ return Err(DemuxerError::EOF);
+ }
+ let fsize = self.src.read_u32le()? as usize;
+ if fsize == 0 {
+ return Err(DemuxerError::EOF);
+ }
+ validate!((9..=1048576).contains(&fsize));
+ if let Some(stream) = strmgr.get_stream(0) {
+ let ts = stream.make_ts(Some(self.frameno), None, None);
+ self.frameno += 1;
+ // last word is the current packet size for backwards seeking so it can be omitted
+ self.src.read_packet(stream, ts, false, fsize - 4)
+ } else {
+ Err(DemuxerError::InvalidData)
+ }
+ }
+
+ fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> {
+ Err(DemuxerError::NotPossible)
+ }
+ fn get_duration(&self) -> u64 { 0 }
+}
+
+impl<'a> NAOptionHandler for TCADemuxer<'a> {
+ fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+ fn set_options(&mut self, _options: &[NAOption]) { }
+ fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub struct TCADemuxerCreator { }
+
+impl DemuxerCreator for TCADemuxerCreator {
+ fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> {
+ Box::new(TCADemuxer::new(br))
+ }
+ fn get_name(&self) -> &'static str { "tca" }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::fs::File;
+
+ #[test]
+ fn test_tca_demux() {
+ // a sample from RISC DISC 3
+ let mut file = File::open("assets/Acorn/JUMPER").unwrap();
+ let mut fr = FileReader::new_read(&mut file);
+ let mut br = ByteReader::new(&mut fr);
+ let mut dmx = TCADemuxer::new(&mut br);
+ let mut sm = StreamManager::new();
+ let mut si = SeekIndex::new();
+ dmx.open(&mut sm, &mut si).unwrap();
+
+ loop {
+ match dmx.get_frame(&mut sm) {
+ Ok(pkt) => {
+ println!("Got {pkt}");
+ },
+ Err(DemuxerError::EOF) => return,
+ Err(_) => panic!("error"),
+ }
+ }
+ }
+}