spectrusty_formats/
scr.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8/*! **SCR** file format utilities.
9
10Utilities in this module can work with the ULAplus extensions to the **SCR** format:
11
12The type of the file is recognized by its total byte size:
13
14|  size | description                                                                             |
15|-------|-----------------------------------------------------------------------------------------|
16|  6912 | Standard screen data.                                                                   |
17|  6976 | Standard screen data + 64 palette registers.                                            |
18| 12288 | Hi-color screen data as primary screen data followed by the secondary screen data.      |
19| 12352 | Hi-color screen data + 64 palette registers.                                            |
20| 12289 | Hi-res screen data (primary + secondary) followed by the hi-res color information byte. |
21| 12353 | Hi-res screen data (primary + secondary + hi-res color) + 64 palette registers.         |
22
23*/
24use core::slice;
25use std::io::{self, Read, Write, Seek, SeekFrom};
26
27pub use spectrusty_core::memory::ScreenArray;
28
29const PIXELS_SIZE: usize = 6144;
30const SCR_SIZE: u64 = 6912;
31const PALETTE_SIZE: u64 = 64;
32const SCR_PLUS_SIZE: u64 = SCR_SIZE + PALETTE_SIZE;
33const SCR_HICOLOR_SIZE: u64 = PIXELS_SIZE as u64 * 2;
34const SCR_HICOLOR_PLUS_SIZE: u64 = SCR_HICOLOR_SIZE + PALETTE_SIZE;
35const SCR_HIRES_SIZE: u64 = SCR_HICOLOR_SIZE + 1;
36const SCR_HIRES_PLUS_SIZE: u64 = SCR_HICOLOR_SIZE + 1 + PALETTE_SIZE;
37
38/// This enum indicates a current or requested screen mode.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum ScrMode {
41    /// The default ULA screen mode.
42    /// The `bool` should be `true` if the ULAplus palette is or should be supported. Otherwise it's `false`.
43    Classic(bool),
44    /// The high color SCLD screen mode.
45    /// The `bool` should be `true` if the ULAplus palette is or should be supported. Otherwise it's `false`.
46    HighColor(bool),
47    /// The high resultion SCLD screen mode.
48    /// The `u8` is a high resolution color scheme on bits 3 to 5.
49    /// The `bool` should be `true` if the ULAplus palette is or should be supported. Otherwise it's `false`.
50    HighRes(u8, bool),
51}
52
53/// Utilities for loading and saving **SCR** files.
54///
55/// Methods of this trait are implemented automatically for types that implement [ScreenDataProvider].
56pub trait LoadScr: ScreenDataProvider {
57    /// Attempts to read the `SCR` file from the `src` and load it into the underlying implementation.
58    ///
59    /// # Errors
60    /// This function will return an error if a file is not recognized as an `SCR` file
61    /// or the requested screen mode exceeds the capacity of the underlying implementation.
62    /// Other errors may also be returned from attempts to read the file.
63    fn load_scr<R: Read + Seek>(&mut self, scr: R) -> io::Result<()>;
64    /// Attempts to save the screen from the underlying implementation and write as the `SCR`
65    /// file into the `dst`.
66    ///
67    /// # Errors
68    /// This function may return an error from attempts to write the file.
69    fn save_scr<W: Write>(&self, dst: W) -> io::Result<()>;
70}
71
72/// This trait should be implemented by the core chipset emulator types.
73pub trait ScreenDataProvider {
74    /// Should return a currently selected screen mode.
75    fn get_screen_mode(&self) -> ScrMode;
76    /// Should set a requested screen `mode` and return `true`.
77    /// If the provided `mode` can not be set this method should return `false`.
78    fn set_screen_mode(&mut self, mode: ScrMode) -> bool;
79    /// Should return a reference to the primary screen data.
80    fn screen_primary_ref(&self) -> &ScreenArray;
81    /// Should return a mutable reference to the primary screen data.
82    fn screen_primary_mut(&mut self) -> &mut ScreenArray;
83    /// Should return a reference to the secondary screen data.
84    ///
85    /// # Panics
86    /// This method may panic if the secondary screen is not supported by the underlying implementation.
87    fn screen_secondary_ref(&self) -> &ScreenArray {
88        unimplemented!()
89    }
90    /// Should return a mutable reference to the secondary screen data.
91    ///
92    /// # Panics
93    /// This method may panic if the secondary screen is not supported by the underlying implementation.
94    fn screen_secondary_mut(&mut self) -> &mut ScreenArray {
95        unimplemented!()
96    }
97    /// Should return a reference to the ULAplus palette.
98    ///
99    /// # Panics
100    /// This method may panic if ULAplus is not supported by the underlying implementation.
101    fn screen_palette_ref(&self) -> &[u8;PALETTE_SIZE as usize] {
102        unimplemented!()
103    }
104    /// Should return a mutable reference to the ULAplus palette.
105    ///
106    /// # Panics
107    /// This method may panic if ULAplus is not supported by the underlying implementation.
108    fn screen_palette_mut(&mut self) -> &mut [u8;PALETTE_SIZE as usize] {
109        unimplemented!()
110    }
111}
112
113impl<T> LoadScr for T where T: ScreenDataProvider {
114    fn load_scr<R: Read + Seek>(&mut self, mut src: R) -> io::Result<()> {
115        let end = src.seek(SeekFrom::End(0))?;
116        let mode = match end {
117            SCR_SIZE => ScrMode::Classic(false),
118            SCR_PLUS_SIZE => ScrMode::Classic(true),
119            SCR_HICOLOR_SIZE => ScrMode::HighColor(false),
120            SCR_HICOLOR_PLUS_SIZE => ScrMode::HighColor(true),
121            SCR_HIRES_SIZE|SCR_HIRES_PLUS_SIZE => {
122                src.seek(SeekFrom::Current(
123                    if end == SCR_HIRES_PLUS_SIZE {
124                        -(PALETTE_SIZE as i64) - 1
125                    }
126                    else {
127                        -1
128                    }
129                ))?;
130                let mut color: u8 = 0;
131                src.read_exact(slice::from_mut(&mut color))?;
132                ScrMode::HighRes(color, end == SCR_HIRES_PLUS_SIZE)
133            }
134            _ => {
135                return Err(io::Error::new(io::ErrorKind::InvalidData, "Screen format not recognized"));
136            }
137        };
138        if !self.set_screen_mode(mode) {
139            return Err(io::Error::new(io::ErrorKind::InvalidInput, "Screen format not supported"));
140        }
141        src.seek(SeekFrom::Start(0))?;
142        match end {
143            SCR_SIZE|SCR_PLUS_SIZE => {
144                src.read_exact(self.screen_primary_mut())?;
145            }
146            SCR_HICOLOR_SIZE|SCR_HICOLOR_PLUS_SIZE|
147            SCR_HIRES_SIZE|SCR_HIRES_PLUS_SIZE => {
148                src.read_exact(&mut self.screen_primary_mut()[0..PIXELS_SIZE])?;
149                src.read_exact(&mut self.screen_secondary_mut()[0..PIXELS_SIZE])?;
150            }
151            _ => {}
152        }
153        if let SCR_PLUS_SIZE|SCR_HICOLOR_PLUS_SIZE|SCR_HIRES_PLUS_SIZE = end {
154            src.seek(SeekFrom::Start(end - PALETTE_SIZE))?;
155            src.read_exact(self.screen_palette_mut())?;
156        }
157        Ok(())
158    }
159
160    fn save_scr<W: Write>(&self, mut dst: W) -> io::Result<()> {
161        let mode = self.get_screen_mode();
162        match mode {
163            ScrMode::Classic(..) => dst.write_all(self.screen_primary_ref())?,
164            ScrMode::HighColor(..)|ScrMode::HighRes(..) => {
165                dst.write_all(&self.screen_primary_ref()[0..PIXELS_SIZE])?;
166                dst.write_all(&self.screen_secondary_ref()[0..PIXELS_SIZE])?;
167            }
168        }
169        if let ScrMode::HighRes(outmode, ..) = mode {
170            dst.write_all(slice::from_ref(&outmode))?;
171        }
172        if let ScrMode::Classic(true)|
173               ScrMode::HighColor(true)|
174               ScrMode::HighRes(.., true) = mode {
175            dst.write_all(self.screen_palette_ref())?;
176        }
177        Ok(())
178    }
179}