config!!!

This commit is contained in:
Alex Janka 2023-10-05 10:14:54 +11:00
parent 71bb9f8e6d
commit d3d2de183c
6 changed files with 338 additions and 135 deletions

44
Cargo.lock generated
View file

@ -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"

View file

@ -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<String>,
/// Shader path
#[arg(long)]
shader: Option<String>,
/// 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<usize>,
/// 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::<EmulatorMessage>();
{
@ -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<WindowRenderer> = 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<WindowRenderer> = 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<dyn EmulatorCoreTrait> = if args.camera {
// Box::new(EmulatorCore::init(receiver, options, Webcam::new()))
@ -181,7 +181,7 @@ impl EmulatorHandler {
#[cfg(not(feature = "camera"))]
let core: Box<dyn EmulatorCoreTrait> =
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()));

113
lib/src/config/mod.rs Normal file
View file

@ -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<Self> {
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<C>(&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<C>(&self, name: &str) -> Option<C>
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<C>(&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<String>,
pub cgb_bootrom: Option<String>,
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<String>,
pub cgb_shader_path: Option<String>,
}
#[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,
}
}
}

View file

@ -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<u8>),
}
impl RomFile {
#[allow(clippy::type_complexity)]
pub fn load<C>(
self,
save: SramType,
camera: C,
) -> Result<(Rom<C>, Arc<Mutex<CameraWrapper<C>>>), std::io::Error>
where
C: PocketCamera + Send + 'static,
{
let camera: CameraWrapperRef<C> = 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<Vec<u8>, std::io::Error> {
match self {
RomFile::Path(path) => std::fs::read(path),
RomFile::Raw(data) => Ok(data),
}
}
}
pub trait Renderer<Format: From<Colour>> {
fn prepare(&mut self, width: usize, height: usize);
@ -112,7 +161,7 @@ impl PocketCamera for NoCamera {
pub(crate) type CameraWrapperRef<C> = Arc<Mutex<CameraWrapper<C>>>;
pub(crate) struct CameraWrapper<C>
pub struct CameraWrapper<C>
where
C: PocketCamera,
{
@ -163,35 +212,40 @@ where
pub enum SramType {
File(String),
RawBuffer(Arc<RwLock<Vec<u8>>>),
Auto,
None,
}
#[non_exhaustive]
pub struct EmulatorOptions<ColourFormat, R>
pub struct EmulatorOptions<ColourFormat, R, C>
where
ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static,
{
pub(crate) window: R,
pub(crate) tile_window: Option<R>,
pub(crate) layer_window: Option<R>,
pub(crate) rom: RomFile,
pub(crate) rom: Rom<C>,
pub(crate) output: AudioOutput,
pub(crate) save: Option<SramType>,
pub(crate) no_save: bool,
pub(crate) bootrom: Option<RomFile>,
pub(crate) dmg_bootrom: Option<RomFile>,
pub(crate) cgb_bootrom: Option<RomFile>,
pub(crate) show_bootrom: bool,
pub(crate) serial_target: SerialTarget,
pub(crate) cgb_mode: bool,
spooky: PhantomData<ColourFormat>,
_spooky: PhantomData<ColourFormat>,
}
impl<ColourFormat, R> EmulatorOptions<ColourFormat, R>
impl<ColourFormat, R, C> EmulatorOptions<ColourFormat, R, C>
where
ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static,
{
pub fn new(window: R, rom: RomFile, output: AudioOutput) -> Self {
pub fn new(window: R, rom: Rom<C>, 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<String>) -> 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<C>,
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<RwLock<Vec<u8>>>) -> Self {
@ -228,8 +309,17 @@ where
self
}
pub fn with_bootrom(mut self, bootrom: Option<RomFile>, show_bootrom: bool) -> Self {
self.bootrom = bootrom;
pub fn with_dmg_bootrom(mut self, dmg_bootrom: Option<RomFile>) -> Self {
self.dmg_bootrom = dmg_bootrom;
self
}
pub fn with_cgb_bootrom(mut self, cgb_bootrom: Option<RomFile>) -> Self {
self.cgb_bootrom = cgb_bootrom;
self
}
pub fn with_show_bootrom(mut self, show_bootrom: bool) -> Self {
self.show_bootrom = show_bootrom;
self
}

View file

@ -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<EmulatorMessage>,
mut options: EmulatorOptions<ColourFormat, R>,
camera: C,
mut options: EmulatorOptions<ColourFormat, R, C>,
camera: Arc<Mutex<CameraWrapper<C>>>,
) -> Self {
let camera: CameraWrapperRef<C> = 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(

View file

@ -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<dyn Mbc>,
pub(crate) rom_type: CgbRomType,
pub rom_type: CgbRomType,
spooky: PhantomData<C>,
}