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}