From d3d2de183cfeeb73423dd70aed2438eaae5d1c5a Mon Sep 17 00:00:00 2001 From: Alex Janka Date: Thu, 5 Oct 2023 10:14:54 +1100 Subject: [PATCH] config!!! --- Cargo.lock | 44 ++++++++++++ gb-emu/src/main.rs | 106 ++++++++++++++-------------- lib/src/config/mod.rs | 113 ++++++++++++++++++++++++++++++ lib/src/connect/mod.rs | 120 ++++++++++++++++++++++++++++---- lib/src/lib.rs | 86 ++++++----------------- lib/src/processor/memory/rom.rs | 4 +- 6 files changed, 338 insertions(+), 135 deletions(-) create mode 100644 lib/src/config/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 953d838..e8aaa46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -364,6 +364,9 @@ name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] [[package]] name = "blake3" @@ -975,6 +978,15 @@ dependencies = [ "serde", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "1.0.2" @@ -985,6 +997,18 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -1255,6 +1279,7 @@ dependencies = [ "ash-window", "async-ringbuf", "bytemuck", + "directories", "futures", "itertools", "librashader", @@ -1263,6 +1288,7 @@ dependencies = [ "pixels", "rand", "raw-window-handle", + "ron", "serde", "serde_with", ] @@ -2514,6 +2540,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "orbclient" version = "0.3.46" @@ -2887,6 +2919,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64", + "bitflags 2.4.0", + "serde", + "serde_derive", +] + [[package]] name = "roxmltree" version = "0.14.1" diff --git a/gb-emu/src/main.rs b/gb-emu/src/main.rs index b98edd9..2d4322b 100644 --- a/gb-emu/src/main.rs +++ b/gb-emu/src/main.rs @@ -5,13 +5,15 @@ use camera::Webcam; use clap::{ArgGroup, Parser}; use debug::Debugger; use gb_emu_lib::{ + config::ConfigManager, connect::{ - EmulatorCoreTrait, EmulatorMessage, EmulatorOptions, NoCamera, RomFile, SerialTarget, - StdoutType, + CgbRomType, EmulatorCoreTrait, EmulatorMessage, EmulatorOptions, NoCamera, RomFile, + SerialTarget, SramType, StdoutType, }, EmulatorCore, }; use gilrs::Gilrs; +use serde::{Deserialize, Serialize}; use std::{ path::PathBuf, sync::mpsc::channel, @@ -47,14 +49,6 @@ struct Args { #[arg(long)] no_save: bool, - /// BootROM path - #[arg(short, long)] - bootrom: Option, - - /// Shader path - #[arg(long)] - shader: Option, - /// Output link port to stdout as ASCII #[arg(long)] ascii: bool, @@ -71,10 +65,6 @@ struct Args { #[arg(long)] layer_window: bool, - /// Scale display by... - #[arg(short, long)] - scale_factor: Option, - /// Mute audio #[arg(long)] mute: bool, @@ -82,19 +72,23 @@ struct Args { /// Run debug console #[arg(long)] debug: bool, - - /// Prefer DMG mode - #[arg(short, long)] - dmg: bool, - - /// Show BootROM - #[arg(long)] - show_bootrom: bool, // /// Use webcam as Pocket Camera emulation // #[arg(short, long)] // camera: bool, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(default)] +pub struct StandaloneConfig { + scale_factor: usize, +} + +impl Default for StandaloneConfig { + fn default() -> Self { + Self { scale_factor: 3 } + } +} + fn main() { let args = Args::parse(); @@ -114,8 +108,6 @@ struct EmulatorHandler { impl EmulatorHandler { fn run(args: Args) -> ! { - let factor = args.scale_factor.unwrap_or(3); - let (sender, receiver) = channel::(); { @@ -126,52 +118,60 @@ impl EmulatorHandler { .unwrap(); } + let config_manager = ConfigManager::get().expect("Could not open config folder"); + let config = config_manager.load_or_create_base_config(); + let standalone_config: StandaloneConfig = + config_manager.load_or_create_config("standalone"); + let (output, _stream) = audio::create_output(args.mute); - let rom = RomFile::Path(args.rom); + let rom_file = RomFile::Path(PathBuf::from(args.rom)); + + let (rom, camera) = rom_file + .load( + args.save.map(SramType::File).unwrap_or(SramType::Auto), + NoCamera::default(), + ) + .expect("Error parsing rom"); + + let shader_path = if rom.rom_type == CgbRomType::CgbOnly || config.prefer_cgb { + config.vulkan_config.cgb_shader_path.as_ref() + } else { + config.vulkan_config.dmg_shader_path.as_ref() + } + .map(|v| config_manager.dir().join(v)); let mut window_manager = WindowManager::new(sender); let window = window_manager.add( - factor, + standalone_config.scale_factor, Some(Gilrs::new().unwrap()), - args.shader.map(PathBuf::from), + shader_path, ); let tile_window: Option = if args.tile_window { - Some(window_manager.add(factor, None, None)) + Some(window_manager.add(standalone_config.scale_factor, None, None)) } else { None }; let layer_window: Option = if args.layer_window { - Some(window_manager.add(factor.min(2), None, None)) + Some(window_manager.add(standalone_config.scale_factor.min(2), None, None)) } else { None }; - let options = EmulatorOptions::new(window, rom, output) - .with_save_path(args.save) - .with_serial_target(if args.ascii { - SerialTarget::Stdout(StdoutType::Ascii) - } else if args.hex { - SerialTarget::Stdout(StdoutType::Hex) - } else { - SerialTarget::None - }) - .with_bootrom( - args.bootrom - .or(if args.dmg { - std::env::var("DMG_BOOTROM").ok() - } else { - std::env::var("CGB_BOOTROM").ok() - }) - .map(RomFile::Path), - args.show_bootrom, - ) - .with_no_save(args.no_save) - .with_tile_window(tile_window) - .with_layer_window(layer_window) - .with_cgb_mode(!args.dmg); + let options = + EmulatorOptions::new_with_config(config, config_manager.dir(), window, rom, output) + .with_serial_target(if args.ascii { + SerialTarget::Stdout(StdoutType::Ascii) + } else if args.hex { + SerialTarget::Stdout(StdoutType::Hex) + } else { + SerialTarget::None + }) + .with_no_save(args.no_save) + .with_tile_window(tile_window) + .with_layer_window(layer_window); // let core: Box = if args.camera { // Box::new(EmulatorCore::init(receiver, options, Webcam::new())) @@ -181,7 +181,7 @@ impl EmulatorHandler { #[cfg(not(feature = "camera"))] let core: Box = - Box::new(EmulatorCore::init(receiver, options, NoCamera::default())); + Box::new(EmulatorCore::init(receiver, options, camera)); #[cfg(feature = "camera")] let core = Box::new(EmulatorCore::init(receiver, options, Webcam::new())); diff --git a/lib/src/config/mod.rs b/lib/src/config/mod.rs new file mode 100644 index 0000000..8076454 --- /dev/null +++ b/lib/src/config/mod.rs @@ -0,0 +1,113 @@ +use std::{ + fs, + io::{BufReader, BufWriter}, + path::PathBuf, +}; + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +pub struct ConfigManager { + path: PathBuf, +} + +impl ConfigManager { + pub fn get() -> Option { + directories::ProjectDirs::from("com", "alexjanka", "TWINC") + .map(|v| v.config_dir().to_path_buf()) + .map(|path| { + if let Ok(false) = path.try_exists() { + fs::create_dir_all(path.clone()).expect("Failed to create config dir"); + }; + Self { path } + }) + } + + pub fn dir(&self) -> PathBuf { + self.path.clone() + } + + pub fn load_or_create_base_config(&self) -> Config { + self.load_or_create_config("base") + } + + pub fn load_or_create_config(&self, name: &str) -> C + where + C: Serialize + DeserializeOwned + Default + Clone, + { + match self.load_custom_config(name) { + Some(v) => v, + None => { + let config = C::default(); + if let Ok(true) = self.path.join(name).try_exists() { + eprintln!("Failed to load \"{name}\" config, but it exists on disk"); + } else { + let result = self.save_custom_config(name, config.clone()); + if let Err(e) = result { + eprintln!("Failed to save \"{name}\" config: {e:#?}"); + } + } + config + } + } + } + + pub fn load_custom_config(&self, name: &str) -> Option + where + C: DeserializeOwned + Default, + { + let path = self.path.join(name); + ron::de::from_reader(BufReader::new(fs::File::open(path).ok()?)).ok() + } + + pub fn save_custom_config(&self, name: &str, config: C) -> Result<(), ron::Error> + where + C: Serialize, + { + let path = self.path.join(name); + ron::ser::to_writer_pretty( + BufWriter::new(fs::File::create(path)?), + &config, + Default::default(), + ) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(default)] +pub struct Config { + pub dmg_bootrom: Option, + pub cgb_bootrom: Option, + pub show_bootrom: bool, + pub prefer_cgb: bool, + pub vulkan_config: VulkanRendererConfig, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(default)] +pub struct VulkanRendererConfig { + pub dmg_shader_path: Option, + pub cgb_shader_path: Option, +} + +#[allow(clippy::derivable_impls)] +impl Default for Config { + fn default() -> Self { + Self { + dmg_bootrom: None, + cgb_bootrom: None, + show_bootrom: false, + prefer_cgb: true, + vulkan_config: Default::default(), + } + } +} + +#[allow(clippy::derivable_impls)] +impl Default for VulkanRendererConfig { + fn default() -> Self { + Self { + dmg_shader_path: None, + cgb_shader_path: None, + } + } +} diff --git a/lib/src/connect/mod.rs b/lib/src/connect/mod.rs index bc5cce6..978103e 100644 --- a/lib/src/connect/mod.rs +++ b/lib/src/connect/mod.rs @@ -1,9 +1,14 @@ +use std::fs; use std::marker::PhantomData; +use std::path::PathBuf; use std::sync::{Arc, Mutex, RwLock}; pub use crate::processor::memory::mmio::gpu::Colour; pub use crate::processor::memory::mmio::joypad::{JoypadButtons, JoypadState}; pub use crate::processor::memory::mmio::serial::{SerialTarget, StdoutType}; +use crate::processor::memory::rom::sram_save::SaveDataLocation; +pub use crate::processor::memory::rom::CgbRomType; +use crate::processor::memory::Rom; pub use crate::{HEIGHT, WIDTH}; use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb}; @@ -18,10 +23,54 @@ pub enum DownsampleType { } pub enum RomFile { - Path(String), + Path(PathBuf), Raw(Vec), } +impl RomFile { + #[allow(clippy::type_complexity)] + pub fn load( + self, + save: SramType, + camera: C, + ) -> Result<(Rom, Arc>>), std::io::Error> + where + C: PocketCamera + Send + 'static, + { + let camera: CameraWrapperRef = Arc::new(Mutex::new(CameraWrapper::new(camera))); + + match self { + RomFile::Path(path) => { + let save_location = match save { + SramType::File(path) => Some(SaveDataLocation::File(PathBuf::from(path))), + SramType::RawBuffer(buf) => Some(SaveDataLocation::Raw(buf)), + SramType::Auto => Some(SaveDataLocation::File(path.with_extension("sav"))), + SramType::None => None, + }; + + fs::read(path).map(|data| Rom::load(data, save_location, camera.clone())) + } + RomFile::Raw(data) => { + let save_location = match save { + SramType::File(path) => Some(SaveDataLocation::File(PathBuf::from(path))), + SramType::RawBuffer(buf) => Some(SaveDataLocation::Raw(buf)), + SramType::Auto => None, + SramType::None => None, + }; + Ok(Rom::load(data, save_location, camera.clone())) + } + } + .map(|v| (v, camera)) + } + + pub fn load_data(self) -> Result, std::io::Error> { + match self { + RomFile::Path(path) => std::fs::read(path), + RomFile::Raw(data) => Ok(data), + } + } +} + pub trait Renderer> { fn prepare(&mut self, width: usize, height: usize); @@ -112,7 +161,7 @@ impl PocketCamera for NoCamera { pub(crate) type CameraWrapperRef = Arc>>; -pub(crate) struct CameraWrapper +pub struct CameraWrapper where C: PocketCamera, { @@ -163,35 +212,40 @@ where pub enum SramType { File(String), RawBuffer(Arc>>), + Auto, + None, } #[non_exhaustive] -pub struct EmulatorOptions +pub struct EmulatorOptions where ColourFormat: From + Copy, R: Renderer, + C: PocketCamera + Send + 'static, { pub(crate) window: R, pub(crate) tile_window: Option, pub(crate) layer_window: Option, - pub(crate) rom: RomFile, + pub(crate) rom: Rom, pub(crate) output: AudioOutput, pub(crate) save: Option, pub(crate) no_save: bool, - pub(crate) bootrom: Option, + pub(crate) dmg_bootrom: Option, + pub(crate) cgb_bootrom: Option, pub(crate) show_bootrom: bool, pub(crate) serial_target: SerialTarget, pub(crate) cgb_mode: bool, - spooky: PhantomData, + _spooky: PhantomData, } -impl EmulatorOptions +impl EmulatorOptions where ColourFormat: From + Copy, R: Renderer, + C: PocketCamera + Send + 'static, { - pub fn new(window: R, rom: RomFile, output: AudioOutput) -> Self { + pub fn new(window: R, rom: Rom, output: AudioOutput) -> Self { Self { window, tile_window: None, @@ -200,17 +254,44 @@ where output, save: None, no_save: false, - bootrom: None, + dmg_bootrom: None, + cgb_bootrom: None, show_bootrom: false, serial_target: SerialTarget::None, cgb_mode: true, - spooky: PhantomData, + _spooky: PhantomData, } } - pub fn with_save_path(mut self, path: Option) -> Self { - self.save = path.map(SramType::File); - self + #[cfg(feature = "config")] + pub fn new_with_config( + config: crate::config::Config, + config_dir: PathBuf, + window: R, + rom: Rom, + output: AudioOutput, + ) -> Self { + Self { + window, + tile_window: None, + layer_window: None, + rom, + output, + save: None, + no_save: false, + dmg_bootrom: config + .dmg_bootrom + .map(|v| config_dir.join(v)) + .map(RomFile::Path), + cgb_bootrom: config + .cgb_bootrom + .map(|v| config_dir.join(v)) + .map(RomFile::Path), + show_bootrom: config.show_bootrom, + serial_target: SerialTarget::None, + cgb_mode: config.prefer_cgb, + _spooky: Default::default(), + } } pub fn with_sram_buffer(mut self, buffer: Arc>>) -> Self { @@ -228,8 +309,17 @@ where self } - pub fn with_bootrom(mut self, bootrom: Option, show_bootrom: bool) -> Self { - self.bootrom = bootrom; + pub fn with_dmg_bootrom(mut self, dmg_bootrom: Option) -> Self { + self.dmg_bootrom = dmg_bootrom; + self + } + + pub fn with_cgb_bootrom(mut self, cgb_bootrom: Option) -> Self { + self.cgb_bootrom = cgb_bootrom; + self + } + + pub fn with_show_bootrom(mut self, show_bootrom: bool) -> Self { self.show_bootrom = show_bootrom; self } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index f46ead8..703dc57 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -5,24 +5,17 @@ use crate::{ util::pause, }; use connect::{ - AudioOutput, CameraWrapper, CameraWrapperRef, EmulatorCoreTrait, EmulatorMessage, - EmulatorOptions, PocketCamera, Renderer, RomFile, SramType, + AudioOutput, CameraWrapper, EmulatorCoreTrait, EmulatorMessage, EmulatorOptions, PocketCamera, + Renderer, RomFile, }; use processor::{ - memory::{ - mmio::gpu::Colour, - rom::{sram_save::SaveDataLocation, CgbRomType}, - OutputTargets, Rom, - }, + memory::{mmio::gpu::Colour, rom::CgbRomType, OutputTargets}, Cpu, }; use std::{ - fs::{self}, io::{stdout, Write}, marker::PhantomData, - path::PathBuf, process::exit, - str::FromStr, sync::{mpsc::Receiver, Arc, Mutex}, }; @@ -35,6 +28,8 @@ compile_error!("select only one rendering backend!"); #[cfg_attr(feature = "vulkan-renderer", path = "renderer/vulkan/vulkan.rs")] pub mod renderer; +#[cfg(feature = "config")] +pub mod config; pub mod connect; mod constants; mod processor; @@ -62,76 +57,37 @@ where { pub fn init( receiver: Receiver, - mut options: EmulatorOptions, - camera: C, + mut options: EmulatorOptions, + camera: Arc>>, ) -> Self { - let camera: CameraWrapperRef = Arc::new(Mutex::new(CameraWrapper::new(camera))); + let rom = options.rom; - let maybe_save = options.save.map(|i| match i { - SramType::File(sram_path) => { - SaveDataLocation::File(PathBuf::from_str(&sram_path).unwrap()) - } - SramType::RawBuffer(buffer) => SaveDataLocation::Raw(buffer), - }); - - let rom = match options.rom { - RomFile::Path(path) => { - let maybe_save = if options.no_save { - None - } else { - Some(maybe_save.unwrap_or(SaveDataLocation::File( - PathBuf::from_str(&path).unwrap().with_extension("sav"), - ))) - }; - - match fs::read(path) { - Ok(data) => Rom::load(data, maybe_save, camera.clone()), - Err(e) => { - eprintln!("Error reading ROM: {e}"); - exit(1); - } - } - } - RomFile::Raw(data) => Rom::load(data, maybe_save, camera.clone()), - }; - - let (bootrom, cgb) = if let Some(b) = options.bootrom { - let bootrom = match b { - RomFile::Path(path) => match fs::read(path) { - Ok(data) => data, - Err(e) => { - eprintln!("Error reading bootROM: {e}"); - exit(1); - } - }, - RomFile::Raw(data) => data, - }; - let cgb = - rom.rom_type == CgbRomType::CgbOnly || options.cgb_mode || bootrom.len() > 256; - (bootrom, cgb) + let is_cgb_mode = rom.rom_type == CgbRomType::CgbOnly || options.cgb_mode; + let bootrom = if is_cgb_mode { + options.cgb_bootrom.unwrap_or(RomFile::Raw( + include_bytes!("../../sameboy-bootroms/cgb_boot.bin").to_vec(), + )) } else { - let cgb = rom.rom_type == CgbRomType::CgbOnly || options.cgb_mode; - let bootrom = if cgb { - include_bytes!("../../sameboy-bootroms/cgb_boot.bin").to_vec() - } else { - include_bytes!("../../sameboy-bootroms/dmg_boot.bin").to_vec() - }; - (bootrom, cgb) - }; + options.dmg_bootrom.unwrap_or(RomFile::Raw( + include_bytes!("../../sameboy-bootroms/dmg_boot.bin").to_vec(), + )) + } + .load_data() + .expect("Error loading bootrom!"); options.window.prepare(WIDTH, HEIGHT); options.window.set_title(format!( "{} on {} on {}", rom.get_title(), rom.mbc_type(), - if cgb { "CGB" } else { "DMG" } + if is_cgb_mode { "CGB" } else { "DMG" } )); Self::new( receiver, Cpu::new( Memory::init( - cgb, + is_cgb_mode, bootrom, rom, OutputTargets::new( diff --git a/lib/src/processor/memory/rom.rs b/lib/src/processor/memory/rom.rs index c4dd491..72a0ca3 100644 --- a/lib/src/processor/memory/rom.rs +++ b/lib/src/processor/memory/rom.rs @@ -12,7 +12,7 @@ mod mbcs; pub mod sram_save; #[derive(Serialize, Deserialize, Debug, PartialEq)] -pub(crate) enum CgbRomType { +pub enum CgbRomType { Dmg, CgbOptional, CgbOnly, @@ -24,7 +24,7 @@ where { title: String, mbc: Box, - pub(crate) rom_type: CgbRomType, + pub rom_type: CgbRomType, spooky: PhantomData, }