diff options
Diffstat (limited to 'src/app')
-rw-r--r-- | src/app/check_events.rs | 52 | ||||
-rw-r--r-- | src/app/draw_video.rs | 71 | ||||
-rw-r--r-- | src/app/init.rs | 88 | ||||
-rw-r--r-- | src/app/load.rs | 96 | ||||
-rw-r--r-- | src/app/main.rs | 69 | ||||
-rw-r--r-- | src/app/run.rs | 58 | ||||
-rw-r--r-- | src/app/sync_video.rs | 44 |
7 files changed, 478 insertions, 0 deletions
diff --git a/src/app/check_events.rs b/src/app/check_events.rs new file mode 100644 index 0000000..1dd9551 --- /dev/null +++ b/src/app/check_events.rs @@ -0,0 +1,52 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::app::App; + +use sdl2::event::Event; +use std::sync::atomic::Ordering; + +impl App { + pub fn check_events(&mut self) -> Result<bool, String> { + // Return true if we should quit. + + let mut event_pump = match self.sdl.event_pump() { + Ok(pump) => pump, + _ => return Err("unable to get event pump".to_string()), + }; + + if self.got_terminate.load(Ordering::Relaxed) { + eprintln!("got terminate"); + return Ok(true) + }; + + for event in event_pump.poll_iter() { + match event { + Event::Quit {..} => return Ok(true), + _ => {}, + }; + } + + return Ok(false); + } +} diff --git a/src/app/draw_video.rs b/src/app/draw_video.rs new file mode 100644 index 0000000..a0d2016 --- /dev/null +++ b/src/app/draw_video.rs @@ -0,0 +1,71 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::SCREEN_SIZE; +use crate::app::App; + +use sdl2::pixels::Color; +use sdl2::rect::Rect; + +impl App { + pub fn draw_video(&mut self, video: &[u8], agb_palette: &[u16]) { + // TO-DO: Honour video mode. + + let mut palette: [Color; 0x100] = [Color::RGB(0x00, 0x00, 0x00); 0x100]; + + for (index, element) in palette.iter_mut().enumerate() { + let value = unsafe { *agb_palette.get_unchecked(index) }; + + let colour = decode_colour(value); + *element = colour; + } + + for pixel_y in 0x0..SCREEN_SIZE.1 { + for pixel_x in 0x0..SCREEN_SIZE.0 { + let pixel = pixel_y as usize * SCREEN_SIZE.0 as usize + pixel_x as usize; + + let value = video[pixel]; + let colour = palette[value as usize]; + self.canvas.set_draw_color(colour); + + let square = Rect::new( + (pixel_x as u32 * self.scale) as i32, + (pixel_y as u32 * self.scale) as i32, + self.scale, + self.scale, + ); + self.canvas.fill_rect(square).unwrap(); + } + } + + self.canvas.present(); + } +} + +fn decode_colour(colour: u16) -> Color { + let red = ((colour & 0b0000000000011111) as f32 / 31.0 * 255.0) as u8; + let green = ((colour & 0b0000001111100000) as f32 / 992.0 * 255.0) as u8; + let blue = ((colour & 0b0111110000000000) as f32 / 31744.0 * 255.0) as u8; + + return Color::RGB(red, green, blue); +} diff --git a/src/app/init.rs b/src/app/init.rs new file mode 100644 index 0000000..c7a8653 --- /dev/null +++ b/src/app/init.rs @@ -0,0 +1,88 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + +This file is part of Luma. +Luma is free software: you can redistribute it +and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::{SCREEN_SIZE, VERSION}; +use crate::app::App; +use crate::configuration::Configuration; + +use sdl2::pixels::Color; +use sdl2::render::BlendMode; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; + +impl App { + pub fn init(configuration: Configuration) -> Result<Self, String> { + let got_terminate = Arc::new(AtomicBool::new(false)); + + match ctrlc::set_handler({ + let got_terminate = got_terminate.clone(); + move || got_terminate.store(true, Ordering::Relaxed) + }) { + Err(..) => return Err("unable to set signal handler".to_string()), + _ => {}, + }; + + let sdl = match sdl2::init() { + Ok( sdl) => sdl, + Err(..) => return Err("unable to initialise sdl2".to_string()), + }; + + let sdl_video = match sdl.video() { + Ok( video) => video, + Err(..) => return Err("unable to initialise video".to_string()), + }; + + let window_title = format!("Luma {:X}.{:X}", VERSION.0, VERSION.1); + + let mut window_builder = sdl_video.window(&window_title, SCREEN_SIZE.0 as u32 * configuration.scale, SCREEN_SIZE.1 as u32 * configuration.scale); + window_builder.position_centered(); + + let window = match window_builder.build() { + Ok( window) => window, + Err(..) => return Err("unable to open window".to_string()), + }; + + let mut canvas = match window.into_canvas().build() { + Ok( canvas) => canvas, + Err(..) => return Err("unable to build canvas".to_string()), + }; + + canvas.set_blend_mode(BlendMode::Blend); + + let clear_colour = Color::RGB(0x00, 0x00, 0x00); + canvas.set_draw_color(clear_colour); + canvas.clear(); + canvas.present(); + + return Ok(App { + bootloader: configuration.bootloader, + image: configuration.image, + + scale: configuration.scale, + + got_terminate: got_terminate, + + sdl: sdl, + canvas: canvas, + }); + } +} diff --git a/src/app/load.rs b/src/app/load.rs new file mode 100644 index 0000000..24a2385 --- /dev/null +++ b/src/app/load.rs @@ -0,0 +1,96 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::VERSION; +use crate::app::App; +use crate::state::State; + +use std::fs::File; +use std::io::Read; + +impl App { + pub fn load(&mut self, state: &mut State) -> Result<(), String> { + eprintln!("loading booatloader \"{}\"", self.bootloader); + + let mut bootloader = match File::open(&self.bootloader) { + Ok(file) => file, + _ => return Err("unable to open bootloader".to_string()), + }; + + if let Err(..) = bootloader.read(state.bootloader_buffer()) { return Err("unable to read bootloader".to_string()) }; + + eprintln!("loading image \"{}\"", self.image); + + let mut image = match File::open(&self.image) { + Ok(file) => file, + _ => return Err("unable to open image".to_string()), + }; + + match image.read(state.image_buffer()) { + Err(..) => return Err("unable to read image".to_string()), + _ => {}, + }; + + let title = get_title(&state.image_buffer()[0xA0..0xAC]); + let id = get_id(&state.image_buffer()[0xAC..0xB0]); + let version = state.image_buffer()[0xBC]; + + eprintln!("loaded image \"{title}\" ({id}) v.{version}"); + + self.canvas.window_mut().set_title(&format!("Luma {:X}.{:X} - {title}", VERSION.0, VERSION.1)).unwrap(); + + return Ok(()); + } +} + +fn get_title(data: &[u8]) -> String { + let mut title = String::with_capacity(0xC); + + for raw in data { + let character = match char::from_u32(*raw as u32) { + Some('\u{0000}') => break, + Some(character) => character, + None => '?', + }; + + title.push(character); + } + + return title; +} + +fn get_id(data: &[u8]) -> String { + let mut id = String::with_capacity(0xC); + + for raw in data { + let character = match char::from_u32(*raw as u32) { + Some('\u{0000}') => break, + Some(character) => character, + None => '?', + }; + + id.push(character); + } + + return id; +} diff --git a/src/app/main.rs b/src/app/main.rs new file mode 100644 index 0000000..6391716 --- /dev/null +++ b/src/app/main.rs @@ -0,0 +1,69 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::VERSION; +use crate::app::App; +use crate::configuration::Configuration; + +use std::env::{args, var}; +use std::process::exit; + +impl App { + pub fn main() { + println!("\u{1B}[1mluma\u{1B}[0m {:X}.{:X}", VERSION.0, VERSION.1); + println!("Copyright 2021-2023 \u{1B}[1mGabriel Bj\u{F8}rnager Jensen\u{1B}[0m."); + println!(); + + let path = if let Some(path) = args().nth(0x1) { path } + else { default_configuration_path() }; + + let configuration = match Configuration::load(&path) { + Ok( configuration) => configuration, + Err(message) => panic!("unable to load configuration: {message}"), + }; + + let app = match App::init(configuration) { + Ok( app) => app, + Err(message) => panic!("unable to initialise application: {message}"), + }; + + let result = app.run(); + + if let Err(ref message) = result { eprintln!("\u{1B}[1m\u{1B}[91merror\u{1B}[0m: {message}") }; + + exit(match result { + Ok( ..) => 0x0, + Err(..) => 0x1, + }); + } +} + +fn default_configuration_path() -> String { + let home = match var("HOME") { + Ok( path) => path, + Err(..) => "/".to_string(), + }; + + let path = home + "/.luma.toml"; + return path; +} diff --git a/src/app/run.rs b/src/app/run.rs new file mode 100644 index 0000000..5bc8dc7 --- /dev/null +++ b/src/app/run.rs @@ -0,0 +1,58 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::{PALETTE_LENGTH, VIDEO_LENGTH}; +use crate::app::App; +use crate::cpu::Cpu; +use crate::state::State; + +use std::time::Instant; + +impl App { + pub fn run(mut self) -> Result<(), String> { + let mut state = State::new(); + + self.load(&mut state)?; + + let cpu = Cpu::new(state); + + let mut cpu = cpu.boot(); + + let mut video_buffer: Vec::<u8> = vec![0x0; VIDEO_LENGTH as usize]; + let mut palette_buffer: Vec::<u16> = vec![0x0; (PALETTE_LENGTH / 0x2) as usize]; + + 'main_loop: loop { + let frame_start = Instant::now(); + + if self.check_events()? { break 'main_loop }; + + cpu.dump_video( &mut video_buffer[..]); + cpu.dump_palette(&mut palette_buffer[..]); + self.draw_video(&video_buffer[..], &palette_buffer[..]); + + self.sync_video(frame_start); + } + + return Ok(()); + } +} diff --git a/src/app/sync_video.rs b/src/app/sync_video.rs new file mode 100644 index 0000000..bb863d4 --- /dev/null +++ b/src/app/sync_video.rs @@ -0,0 +1,44 @@ +/* + Copyright 2021-2023 Gabriel Jensen. + + This file is part of Luma. + + Luma is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Luma is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Luma. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::app::App; + +use std::thread::sleep; +use std::time::{Duration, Instant}; + +impl App { + pub fn sync_video(&self, frame_start: Instant) { + // Courtesy of TASVideos: <https://tasvideos.org/PlatformFramerates> + // 59.7275005696058 Hz + + const FRAME_DURATION: u64 = 0xFF7932; + let frame_duration = Duration::from_nanos(FRAME_DURATION); + + let remaining = match frame_duration.checked_sub(frame_start.elapsed()) { + Some(value) => value, + None => Duration::from_secs(0x0), + }; + + sleep(remaining); + } +} |