diff --git a/Cargo.lock b/Cargo.lock index 15d4def..9232af2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,7 +613,6 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" name = "gameboy" version = "0.1.5" dependencies = [ - "base64", "console_error_panic_hook", "gl", "glutin", @@ -621,6 +620,7 @@ dependencies = [ "image", "js-sys", "libc", + "linked_list_allocator", "ratatui", "ratatui-image", "wasm-bindgen", @@ -970,6 +970,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" +dependencies = [ + "spinning_top", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1755,6 +1764,15 @@ dependencies = [ "wayland-protocols", ] +[[package]] +name = "spinning_top" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" +dependencies = [ + "lock_api", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index c86639a..3e69b9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,7 @@ crate-type = ["cdylib", "rlib", "staticlib"] [features] default = ["libc", "glutin", "gl" ,"ratatui" ,"icy_sixel", "image", "ratatui-image"] -ffi = [] -ffi-base64 = ["image", "base64"] +ffi = ["linked_list_allocator"] [profile.release] opt-level = "s" @@ -31,6 +30,10 @@ split-debuginfo = "unpacked" lto = false incremental = true opt-level = 0 +panic = 'abort' + +[dependencies] +linked_list_allocator = { version = "0.10", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] libc = { version = "0.2.126", optional = true } @@ -40,7 +43,7 @@ ratatui = { version = "^0.29.0", features = ["crossterm"], optional = true } icy_sixel = { version = "^0.1.1", optional = true } image = { version = "^0.25.1", default-features = false, features = ["jpeg", "png"], optional = true } ratatui-image = { version = "4.2.0", optional = true } -base64 = { version = "0.21.7", optional = true } + [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3.59" diff --git a/Makefile b/Makefile index 078a0e7..4992a4d 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ web-build: ffi-build: # cargo install cbindgen - cargo build --release --no-default-features --features ffi + cargo rustc --release --no-default-features --features ffi --crate-type staticlib cbindgen . -o gameboy.h --lang c ffi-size: diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000..52b5bd1 --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,7 @@ +# cbindgen.toml + +[export] +exclude = ["memcpy", "memmove", "memset", "memcmp", "bzero"] + +[defines] +"feature = ffi" = "FFI" \ No newline at end of file diff --git a/examples/desktop/bin.rs b/examples/desktop/bin.rs index 02db159..216802e 100644 --- a/examples/desktop/bin.rs +++ b/examples/desktop/bin.rs @@ -6,7 +6,7 @@ fn main() { // TODO: Allow receive path by arguments // let gb = Gameboy::new("./../../tests/cpu_instrs/cpu_instrs.gb"); // if let Ok((data, filepath)) = load_rom("./../the-machine.gb") { - if let Ok((data, filepath)) = load_rom("/Users/rapha/Documents/a/boyband/game/SuperWish/game.gb") { + if let Ok((data, filepath)) = load_rom("/Users/rapha/Downloads/dragon-ball.gbc") { // if let Ok((data, filepath)) = load_rom("./../bakery.gb") { let gb = Gameboy::new(data, Some(filepath)); gb.render(Desktop); diff --git a/gameboy.h b/gameboy.h index 8036837..f312b46 100644 --- a/gameboy.h +++ b/gameboy.h @@ -25,6 +25,10 @@ typedef struct ImageBuffer { const uint8_t *data; } ImageBuffer; +#if defined(FFI) +void init_allocator(uint8_t *heap_start, uintptr_t heap_size); +#endif + /** * # Safety * @@ -39,5 +43,3 @@ void keydown(enum KeypadKey key); void keyup(enum KeypadKey key); struct ImageBuffer image(void); - -const char *image_base64(void); diff --git a/src/cpu/core.rs b/src/cpu/core.rs index 012c736..14d7d08 100644 --- a/src/cpu/core.rs +++ b/src/cpu/core.rs @@ -1,6 +1,8 @@ use crate::cpu::registers::Registers; use crate::cpu::{data, ld, misc, stack}; use crate::mmu::MemoryManagementUnit; +#[cfg(feature = "ffi")] +use alloc::vec::Vec; #[allow(dead_code)] pub enum Interrupt { @@ -25,6 +27,7 @@ pub struct Cpu<'a> { } impl Cpu<'_> { + #[cfg(not(feature = "ffi"))] pub fn new(data: Vec, file: Option) -> Self { let memory = MemoryManagementUnit::new_cgb(data, file).unwrap(); let registers = Registers::new(memory.gbmode); @@ -40,6 +43,23 @@ impl Cpu<'_> { _executed_operations: Vec::new(), } } + + #[cfg(feature = "ffi")] + pub fn new(data: Vec, _file: Option<()>) -> Self { + let memory = MemoryManagementUnit::new_cgb(data, None).unwrap(); + let registers = Registers::new(memory.gbmode); + + Cpu { + registers, + memory, + ime: false, + setdi: 0, + setei: 0, + halt: 0, + stop: 0, + _executed_operations: Vec::new(), + } + } fn mut_find_or_insert(vec: &mut Vec, val: T) -> &mut T { if let Some(i) = vec.iter().position(|each| *each == val) { &mut vec[i] @@ -1115,6 +1135,7 @@ impl Cpu<'_> { 4 } _ => { + #[cfg(not(feature = "ffi"))] println!( "Instruction at {:#01x} | {:#06x} | {} not implemented, stopping.", op, op, op diff --git a/src/cpu/registers.rs b/src/cpu/registers.rs index 346dd08..3a8a7d7 100644 --- a/src/cpu/registers.rs +++ b/src/cpu/registers.rs @@ -1,5 +1,8 @@ use crate::mode::GbMode; +#[cfg(not(feature = "ffi"))] use std::fmt; +#[cfg(feature = "ffi")] +use core::fmt; #[derive(Debug)] pub struct Registers { diff --git a/src/gameboy.rs b/src/gameboy.rs index 60eaac6..c0f23e8 100644 --- a/src/gameboy.rs +++ b/src/gameboy.rs @@ -1,5 +1,7 @@ use crate::cpu::core::Cpu; use crate::input::KeypadKey; +#[cfg(feature = "ffi")] +use alloc::vec::Vec; pub struct Gameboy { cpu: Cpu<'static>, @@ -51,6 +53,7 @@ pub fn load_rom(filepath: &str) -> Result<(Vec, std::path::PathBuf), String> pub const CYCLES: u32 = 70224; impl Gameboy { + #[cfg(not(feature = "ffi"))] pub fn new(data: Vec, filepath: Option) -> Gameboy { // let rom = load_rom(); @@ -62,6 +65,17 @@ impl Gameboy { gb } + + #[cfg(feature = "ffi")] + pub fn new(data: Vec, _filepath: Option<()>) -> Gameboy { + let gb = Gameboy { + cpu: Cpu::new(data, None), + width: 160, + height: 144, + }; + + gb + } pub fn render(self, render_mode: RenderMode) { match render_mode { diff --git a/src/gpu/mod.rs b/src/gpu/mod.rs index 14912ae..d1699b2 100644 --- a/src/gpu/mod.rs +++ b/src/gpu/mod.rs @@ -1,5 +1,10 @@ use crate::mode::GbMode; +#[cfg(not(feature = "ffi"))] use std::cmp::Ordering; +#[cfg(feature = "ffi")] +use core::cmp::Ordering; +#[cfg(feature = "ffi")] +use alloc::boxed::Box; const VRAM_SIZE: usize = 0x4000; const VOAM_SIZE: usize = 0xA0; diff --git a/src/lib.rs b/src/lib.rs index d5e57e3..227b0f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,102 @@ +#![cfg_attr(feature = "ffi", no_std)] + +#[cfg(feature = "ffi")] +extern crate alloc; + +#[cfg(feature = "ffi")] +use alloc::vec::Vec; + +#[cfg(not(feature = "ffi"))] use std::sync::Mutex; +#[cfg(not(feature = "ffi"))] use std::sync::OnceLock; +#[cfg(all(feature = "ffi", not(test)))] +#[global_allocator] +static ALLOCATOR: linked_list_allocator::LockedHeap = linked_list_allocator::LockedHeap::empty(); + + + +#[cfg(all(feature = "ffi", not(test)))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // In a no_std environment, we can't do much here + // In a real implementation, you might write to a debug port or similar + loop {} +} + +// Initialize the allocator with some heap memory +#[cfg(all(feature = "ffi", not(test)))] +#[no_mangle] +pub unsafe extern "C" fn init_allocator(heap_start: *mut u8, heap_size: usize) { + ALLOCATOR.lock().init(heap_start, heap_size); +} + +// Required memory functions for no_std +#[cfg(all(feature = "ffi", not(test)))] +#[no_mangle] +pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { + let mut i = 0; + while i < n { + *dest.add(i) = *src.add(i); + i += 1; + } + dest +} + +#[cfg(all(feature = "ffi", not(test)))] +#[no_mangle] +pub unsafe extern "C" fn memmove(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { + if src < dest as *const u8 { + // Copy backwards + let mut i = n; + while i > 0 { + i -= 1; + *dest.add(i) = *src.add(i); + } + } else { + // Copy forwards + let mut i = 0; + while i < n { + *dest.add(i) = *src.add(i); + i += 1; + } + } + dest +} + +#[cfg(all(feature = "ffi", not(test)))] +#[no_mangle] +pub unsafe extern "C" fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 { + let mut i = 0; + while i < n { + *s.add(i) = c as u8; + i += 1; + } + s +} + +#[cfg(all(feature = "ffi", not(test)))] +#[no_mangle] +pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { + let mut i = 0; + while i < n { + let a = *s1.add(i); + let b = *s2.add(i); + if a != b { + return a as i32 - b as i32; + } + i += 1; + } + 0 +} + +#[cfg(all(feature = "ffi", not(test)))] +#[no_mangle] +pub unsafe extern "C" fn bzero(s: *mut u8, n: usize) { + memset(s, 0, n); +} + #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -14,6 +110,17 @@ mod mode; #[cfg(not(feature = "ffi"))] mod screen; +// Prelude for no_std compatibility +#[cfg(feature = "ffi")] +#[allow(dead_code)] +mod prelude { + pub use alloc::boxed::Box; + pub use alloc::string::String; + pub use alloc::vec::Vec; + pub use alloc::vec; + pub use core::fmt; +} + pub use crate::input::KeypadKey; #[cfg(target_arch = "wasm32")] @@ -23,8 +130,12 @@ pub async fn start() { // TODO: Process GL } +#[cfg(not(feature = "ffi"))] static GAMEBOY: OnceLock>> = OnceLock::new(); +#[cfg(feature = "ffi")] +static mut GAMEBOY: Option = None; + #[cfg(target_vendor = "apple")] unsafe impl Send for crate::gameboy::Gameboy {} #[cfg(target_vendor = "apple")] @@ -34,44 +145,83 @@ unsafe impl Sync for crate::gameboy::Gameboy {} /// /// This function is not safe due to from_raw_parts. #[no_mangle] -pub unsafe extern "C" fn load(bytes: *const std::ffi::c_uchar, bytes_length: usize) { - let bytes = std::slice::from_raw_parts(bytes, bytes_length); +pub unsafe extern "C" fn load(bytes: *const core::ffi::c_uchar, bytes_length: usize) { + let bytes = core::slice::from_raw_parts(bytes, bytes_length); let bytes: Vec = Vec::from(bytes); - GAMEBOY - .get_or_init(|| Some(crate::gameboy::Gameboy::new(bytes.to_vec(), None)).into()); + + #[cfg(not(feature = "ffi"))] + { + GAMEBOY + .get_or_init(|| Some(crate::gameboy::Gameboy::new(bytes.to_vec(), None)).into()); + } + + #[cfg(feature = "ffi")] + { + GAMEBOY = Some(crate::gameboy::Gameboy::new(bytes.to_vec(), None)); + } } #[no_mangle] pub extern "C" fn frame() { - if let Some(gb) = GAMEBOY.get() { - if let Ok(mut locked_gb) = gb.lock() { - if let Some(gameboy) = locked_gb.as_mut() { - gameboy.frame(); - }; + #[cfg(not(feature = "ffi"))] + { + if let Some(gb) = GAMEBOY.get() { + if let Ok(mut locked_gb) = gb.lock() { + if let Some(gameboy) = locked_gb.as_mut() { + gameboy.frame(); + }; + } + } + } + + #[cfg(feature = "ffi")] + unsafe { + if let Some(gameboy) = GAMEBOY.as_mut() { + gameboy.frame(); } } } #[no_mangle] pub extern "C" fn keydown(key: KeypadKey) { - if let Some(gb) = GAMEBOY.get() { - if let Ok(mut locked_gb) = gb.lock() { - if let Some(gameboy) = locked_gb.as_mut() { - gameboy.keydown(key); + #[cfg(not(feature = "ffi"))] + { + if let Some(gb) = GAMEBOY.get() { + if let Ok(mut locked_gb) = gb.lock() { + if let Some(gameboy) = locked_gb.as_mut() { + gameboy.keydown(key); + } } } } + + #[cfg(feature = "ffi")] + unsafe { + if let Some(gameboy) = GAMEBOY.as_mut() { + gameboy.keydown(key); + } + } } #[no_mangle] pub extern "C" fn keyup(key: KeypadKey) { - if let Some(gb) = GAMEBOY.get() { - if let Ok(mut locked_gb) = gb.lock() { - if let Some(gameboy) = locked_gb.as_mut() { - gameboy.keyup(key); + #[cfg(not(feature = "ffi"))] + { + if let Some(gb) = GAMEBOY.get() { + if let Ok(mut locked_gb) = gb.lock() { + if let Some(gameboy) = locked_gb.as_mut() { + gameboy.keyup(key); + } } } } + + #[cfg(feature = "ffi")] + unsafe { + if let Some(gameboy) = GAMEBOY.as_mut() { + gameboy.keyup(key); + } + } } #[repr(C)] @@ -82,82 +232,37 @@ pub struct ImageBuffer { #[no_mangle] pub extern "C" fn image() -> ImageBuffer { - if let Some(gb) = GAMEBOY.get() { - if let Ok(locked_gb) = gb.lock() { - if let Some(gameboy) = locked_gb.as_ref() { - let image: &[u8] = gameboy.image(); - let data = image.as_ptr(); - let len = image.len() as i32; - // std::mem::forget(image); - // My guess image will be dropped but let's test - - return ImageBuffer { len, data }; + #[cfg(not(feature = "ffi"))] + { + if let Some(gb) = GAMEBOY.get() { + if let Ok(locked_gb) = gb.lock() { + if let Some(gameboy) = locked_gb.as_ref() { + let image: &[u8] = gameboy.image(); + let data = image.as_ptr(); + let len = image.len() as i32; + // std::mem::forget(image); + // My guess image will be dropped but let's test + + return ImageBuffer { len, data }; + } } } } + + #[cfg(feature = "ffi")] + unsafe { + if let Some(gameboy) = GAMEBOY.as_ref() { + let image: &[u8] = gameboy.image(); + let data = image.as_ptr(); + let len = image.len() as i32; + return ImageBuffer { len, data }; + } + } ImageBuffer { len: 0, - data: std::ptr::null_mut(), + data: core::ptr::null(), } } -#[no_mangle] -#[cfg(feature = "ffi-base64")] -pub extern "C" fn image_base64() -> *const std::os::raw::c_char { - use base64::{engine::general_purpose, Engine}; - use image::DynamicImage; - use std::ffi::CString; - use std::io::Cursor; - - if let Some(gb) = GAMEBOY.get() { - if let Ok(mut locked_gb) = gb.lock() { - let width = 160; - let height = 144; - - let image: &[u8] = locked_gb.as_mut().unwrap().image(); - // std::mem::forget(image); - - // Allocate a new buffer for the RGB image, 3 bytes per pixel - let mut output_data = vec![0u8; width as usize * height as usize * 3]; - - let mut i = 0; - // Iterate through 4-byte chunks of the image data (RGBA bytes) - for chunk in image.chunks(4) { - // ... and copy each of them to output, leaving out the A byte - output_data[i..i + 3].copy_from_slice(&chunk[0..3]); - i += 3; - } - let buffer = - image::ImageBuffer::from_raw(width, height, output_data).unwrap(); - let img: DynamicImage = image::DynamicImage::ImageRgb8(buffer); - // if scale > 1 { - // buffer = image::imageops::resize( - // &buffer, - // width * (scale as u32), - // height * (scale as u32), - // image::imageops::FilterType::Nearest, - // ); - // } - - let mut png: Vec = vec![]; - img.write_to(&mut Cursor::new(&mut png), image::ImageFormat::Png) - .expect("don't fail img write_to"); - let data = general_purpose::STANDARD.encode(&png); - - // let cstring_data = CString::new(img.as_bytes()).expect("don't fail"); - // return cstring_data.into_raw(); - - // let mut png: Vec = vec![]; - // img.write_to(&mut Cursor::new(&mut png), image::ImageFormat::Png).expect("don't fail img write_to"); - // let data = general_purpose::STANDARD.encode(&png); - - let cstring_data = CString::new(data).expect("don't fail"); - return cstring_data.into_raw(); - } - } - - let data = CString::new("").expect("don't fail"); - data.into_raw() -} diff --git a/src/mbc/mbc0.rs b/src/mbc/mbc0.rs index 6ef0626..c2ad58d 100644 --- a/src/mbc/mbc0.rs +++ b/src/mbc/mbc0.rs @@ -1,5 +1,7 @@ pub type StrResult = Result; use crate::mbc::MemoryBankController; +#[cfg(feature = "ffi")] +use alloc::vec::Vec; pub struct MBC0 { rom: Vec, diff --git a/src/mbc/mbc1.rs b/src/mbc/mbc1.rs index f74d20c..9e82d8f 100644 --- a/src/mbc/mbc1.rs +++ b/src/mbc/mbc1.rs @@ -1,5 +1,11 @@ +#[cfg(not(feature = "ffi"))] use std::io::prelude::*; +#[cfg(not(feature = "ffi"))] use std::{fs, io, path}; +#[cfg(feature = "ffi")] +use alloc::vec::Vec; +#[cfg(feature = "ffi")] +use alloc::vec; use crate::mbc::{ram_banks, rom_banks, MemoryBankController}; pub type StrResult = Result; @@ -11,12 +17,16 @@ pub struct MBC1 { banking_mode: u8, rombank: usize, rambank: usize, + #[cfg(not(feature = "ffi"))] savepath: Option, + #[cfg(feature = "ffi")] + savepath: Option<()>, rombanks: usize, rambanks: usize, } impl MBC1 { + #[cfg(not(feature = "ffi"))] pub fn new(data: Vec, file: path::PathBuf) -> StrResult { let (svpath, rambanks) = match data[0x147] { 0x02 => (None, ram_banks(data[0x149])), @@ -28,7 +38,7 @@ impl MBC1 { let mut res = MBC1 { rom: data, - ram: ::std::iter::repeat(0u8).take(ramsize).collect(), + ram: ::core::iter::repeat(0u8).take(ramsize).collect(), ram_on: false, banking_mode: 0, rombank: 1, @@ -39,6 +49,28 @@ impl MBC1 { }; res.loadram().map(|_| res) } + + #[cfg(feature = "ffi")] + pub fn new(data: Vec, _file: ()) -> StrResult { + let rambanks = match data[0x147] { + 0x02 | 0x03 => ram_banks(data[0x149]), + _ => 0, + }; + let rombanks = rom_banks(data[0x148]); + let ramsize = rambanks * 0x2000; + + Ok(MBC1 { + rom: data, + ram: vec![0u8; ramsize], + ram_on: false, + banking_mode: 0, + rombank: 1, + rambank: 0, + savepath: None, + rombanks, + rambanks, + }) + } #[allow(dead_code)] pub fn new_without_save(data: Vec) -> StrResult { @@ -52,7 +84,7 @@ impl MBC1 { let res = MBC1 { rom: data, - ram: ::std::iter::repeat(0u8).take(ramsize).collect(), + ram: ::core::iter::repeat(0u8).take(ramsize).collect(), ram_on: false, banking_mode: 0, rombank: 1, @@ -65,6 +97,7 @@ impl MBC1 { Ok(res) } + #[cfg(not(feature = "ffi"))] fn loadram(&mut self) -> StrResult<()> { match self.savepath { None => Ok(()), @@ -82,8 +115,14 @@ impl MBC1 { } } } + + #[cfg(feature = "ffi")] + fn loadram(&mut self) -> StrResult<()> { + Ok(()) + } } +#[cfg(not(feature = "ffi"))] impl Drop for MBC1 { fn drop(&mut self) { match self.savepath { @@ -95,6 +134,13 @@ impl Drop for MBC1 { } } +#[cfg(feature = "ffi")] +impl Drop for MBC1 { + fn drop(&mut self) { + // No-op for no_std + } +} + impl MemoryBankController for MBC1 { fn readrom(&self, a: u16) -> u8 { let bank = if a < 0x4000 { diff --git a/src/mbc/mbc2.rs b/src/mbc/mbc2.rs index 3348061..3a2cd32 100644 --- a/src/mbc/mbc2.rs +++ b/src/mbc/mbc2.rs @@ -1,5 +1,11 @@ +#[cfg(not(feature = "ffi"))] use std::io::prelude::*; +#[cfg(not(feature = "ffi"))] use std::{fs, io, path}; +#[cfg(feature = "ffi")] +use alloc::vec::Vec; +#[cfg(feature = "ffi")] +use alloc::vec; use crate::mbc::{rom_banks, MemoryBankController}; pub type StrResult = Result; @@ -9,11 +15,15 @@ pub struct MBC2 { ram: Vec, ram_on: bool, rombank: usize, + #[cfg(not(feature = "ffi"))] savepath: Option, + #[cfg(feature = "ffi")] + savepath: Option<()>, rombanks: usize, } impl MBC2 { + #[cfg(not(feature = "ffi"))] pub fn new(data: Vec, file: path::PathBuf) -> StrResult { let svpath = match data[0x147] { 0x05 => None, @@ -32,7 +42,22 @@ impl MBC2 { }; res.loadram().map(|_| res) } + + #[cfg(feature = "ffi")] + pub fn new(data: Vec, _file: ()) -> StrResult { + let rombanks = rom_banks(data[0x148]); + + Ok(MBC2 { + rom: data, + ram: vec![0; 512], + ram_on: false, + rombank: 1, + savepath: None, + rombanks, + }) + } + #[cfg(not(feature = "ffi"))] fn loadram(&mut self) -> StrResult<()> { match self.savepath { None => Ok(()), @@ -50,8 +75,14 @@ impl MBC2 { } } } + + #[cfg(feature = "ffi")] + fn loadram(&mut self) -> StrResult<()> { + Ok(()) + } } +#[cfg(not(feature = "ffi"))] impl Drop for MBC2 { fn drop(&mut self) { match self.savepath { @@ -63,6 +94,13 @@ impl Drop for MBC2 { } } +#[cfg(feature = "ffi")] +impl Drop for MBC2 { + fn drop(&mut self) { + // No-op for no_std + } +} + impl MemoryBankController for MBC2 { fn readrom(&self, a: u16) -> u8 { let bank = if a < 0x4000 { 0 } else { self.rombank }; diff --git a/src/mbc/mbc3.rs b/src/mbc/mbc3.rs index f1c18d6..eace2c4 100644 --- a/src/mbc/mbc3.rs +++ b/src/mbc/mbc3.rs @@ -1,9 +1,16 @@ use crate::mbc::{ram_banks, MemoryBankController}; pub type StrResult = Result; +#[cfg(not(feature = "ffi"))] use std::io::prelude::*; +#[cfg(not(feature = "ffi"))] use std::path; +#[cfg(not(feature = "ffi"))] use std::{fs, io, time}; +#[cfg(feature = "ffi")] +use alloc::vec::Vec; +#[cfg(feature = "ffi")] +use alloc::vec; pub struct MBC3 { rom: Vec, @@ -13,13 +20,20 @@ pub struct MBC3 { rambanks: usize, selectrtc: bool, ram_on: bool, + #[cfg(not(feature = "ffi"))] savepath: Option, + #[cfg(feature = "ffi")] + savepath: Option<()>, rtc_ram: [u8; 5], rtc_ram_latch: [u8; 5], + #[cfg(not(feature = "ffi"))] + rtc_zero: Option, + #[cfg(feature = "ffi")] rtc_zero: Option, } impl MBC3 { + #[cfg(not(feature = "ffi"))] pub fn new(data: Vec, file: path::PathBuf) -> StrResult { let subtype = data[0x147]; let svpath = match subtype { @@ -38,7 +52,7 @@ impl MBC3 { let mut res = MBC3 { rom: data, - ram: ::std::iter::repeat(0u8).take(ramsize).collect(), + ram: ::core::iter::repeat(0u8).take(ramsize).collect(), rombank: 1, rambank: 0, rambanks, @@ -68,7 +82,7 @@ impl MBC3 { let mut res = MBC3 { rom: data, - ram: ::std::iter::repeat(0u8).take(ramsize).collect(), + ram: ::core::iter::repeat(0u8).take(ramsize).collect(), rombank: 1, rambank: 0, rambanks, @@ -81,7 +95,13 @@ impl MBC3 { }; res.loadram().map(|_| res) } + + #[cfg(feature = "ffi")] + pub fn new(data: Vec, _file: ()) -> StrResult { + Self::new_without_save(data) + } + #[cfg(not(feature = "ffi"))] fn loadram(&mut self) -> StrResult<()> { match self.savepath { None => Ok(()), @@ -109,12 +129,18 @@ impl MBC3 { } } } + + #[cfg(feature = "ffi")] + fn loadram(&mut self) -> StrResult<()> { + Ok(()) + } fn latch_rtc_reg(&mut self) { self.calc_rtc_reg(); self.rtc_ram_latch.clone_from_slice(&self.rtc_ram); } + #[cfg(not(feature = "ffi"))] fn calc_rtc_reg(&mut self) { // Do not modify regs when halted if self.rtc_ram[4] & 0x40 == 0x40 { @@ -146,7 +172,13 @@ impl MBC3 { self.calc_rtc_zero(); } } + + #[cfg(feature = "ffi")] + fn calc_rtc_reg(&mut self) { + // No-op for no_std + } + #[cfg(not(feature = "ffi"))] fn compute_difftime(&self) -> Option { self.rtc_zero?; let mut difftime = match time::SystemTime::now().duration_since(time::UNIX_EPOCH) @@ -163,12 +195,18 @@ impl MBC3 { difftime -= days * 3600 * 24; Some(difftime) } + + #[cfg(feature = "ffi")] + fn compute_difftime(&self) -> Option { + None + } fn calc_rtc_zero(&mut self) { self.rtc_zero = self.compute_difftime(); } } +#[cfg(not(feature = "ffi"))] impl Drop for MBC3 { fn drop(&mut self) { match self.savepath { @@ -192,6 +230,13 @@ impl Drop for MBC3 { } } +#[cfg(feature = "ffi")] +impl Drop for MBC3 { + fn drop(&mut self) { + // No-op for no_std + } +} + impl MemoryBankController for MBC3 { fn readrom(&self, a: u16) -> u8 { let idx = if a < 0x4000 { diff --git a/src/mbc/mbc5.rs b/src/mbc/mbc5.rs index fb66d5a..199a9a7 100644 --- a/src/mbc/mbc5.rs +++ b/src/mbc/mbc5.rs @@ -1,9 +1,16 @@ use crate::mbc::{ram_banks, rom_banks, MemoryBankController}; pub type StrResult = Result; +#[cfg(not(feature = "ffi"))] use std::fs::File; +#[cfg(not(feature = "ffi"))] use std::io::prelude::*; +#[cfg(not(feature = "ffi"))] use std::{io, path}; +#[cfg(feature = "ffi")] +use alloc::vec::Vec; +#[cfg(feature = "ffi")] +use alloc::vec; pub struct MBC5 { rom: Vec, @@ -11,12 +18,16 @@ pub struct MBC5 { rombank: usize, rambank: usize, ram_on: bool, + #[cfg(not(feature = "ffi"))] savepath: Option, + #[cfg(feature = "ffi")] + savepath: Option<()>, rombanks: usize, rambanks: usize, } impl MBC5 { + #[cfg(not(feature = "ffi"))] pub fn new(data: Vec, file: path::PathBuf) -> StrResult { let subtype = data[0x147]; let svpath = match subtype { @@ -32,7 +43,7 @@ impl MBC5 { let mut res = MBC5 { rom: data, - ram: ::std::iter::repeat(0u8).take(ramsize).collect(), + ram: ::core::iter::repeat(0u8).take(ramsize).collect(), rombank: 1, rambank: 0, ram_on: false, @@ -42,7 +53,30 @@ impl MBC5 { }; res.loadram().map(|_| res) } + + #[cfg(feature = "ffi")] + pub fn new(data: Vec, _file: ()) -> StrResult { + let subtype = data[0x147]; + let rambanks = match subtype { + 0x1A | 0x1B | 0x1D | 0x1E => ram_banks(data[0x149]), + _ => 0, + }; + let ramsize = 0x2000 * rambanks; + let rombanks = rom_banks(data[0x148]); + + Ok(MBC5 { + rom: data, + ram: vec![0u8; ramsize], + rombank: 1, + rambank: 0, + ram_on: false, + savepath: None, + rombanks, + rambanks, + }) + } + #[cfg(not(feature = "ffi"))] fn loadram(&mut self) -> StrResult<()> { match self.savepath { None => Ok(()), @@ -59,8 +93,14 @@ impl MBC5 { } } } + + #[cfg(feature = "ffi")] + fn loadram(&mut self) -> StrResult<()> { + Ok(()) + } } +#[cfg(not(feature = "ffi"))] impl Drop for MBC5 { fn drop(&mut self) { match self.savepath { @@ -72,6 +112,13 @@ impl Drop for MBC5 { } } +#[cfg(feature = "ffi")] +impl Drop for MBC5 { + fn drop(&mut self) { + // No-op for no_std + } +} + impl MemoryBankController for MBC5 { fn readrom(&self, a: u16) -> u8 { let idx = if a < 0x4000 { diff --git a/src/mbc/mod.rs b/src/mbc/mod.rs index 11f40a0..114b355 100644 --- a/src/mbc/mod.rs +++ b/src/mbc/mod.rs @@ -1,6 +1,9 @@ pub type StrResult = Result; +#[cfg(not(feature = "ffi"))] use std::path; +#[cfg(feature = "ffi")] +use alloc::{boxed::Box, string::String, vec::Vec}; mod mbc0; mod mbc1; @@ -36,6 +39,7 @@ pub trait MemoryBankController: Send { } } +#[cfg(not(feature = "ffi"))] pub fn get_mbc( data: Vec, filepath: Option, @@ -63,6 +67,27 @@ pub fn get_mbc( } } +#[cfg(feature = "ffi")] +pub fn get_mbc( + data: Vec, + _filepath: Option<()>, +) -> StrResult> { + match data[0x147] { + 0x00 => { + mbc0::MBC0::new(data).map(|v| Box::new(v) as Box) + } + 0x01..=0x03 => mbc1::MBC1::new(data, ()) + .map(|v| Box::new(v) as Box), + 0x05..=0x06 => mbc2::MBC2::new(data, ()) + .map(|v| Box::new(v) as Box), + 0x0F..=0x13 => mbc3::MBC3::new(data, ()) + .map(|v| Box::new(v) as Box), + 0x19..=0x1E => mbc5::MBC5::new(data, ()) + .map(|v| Box::new(v) as Box), + _ => Err("Unsupported MBC type"), + } +} + fn ram_banks(v: u8) -> usize { match v { 1 => diff --git a/src/mmu/mod.rs b/src/mmu/mod.rs index 79648e9..487e909 100644 --- a/src/mmu/mod.rs +++ b/src/mmu/mod.rs @@ -8,7 +8,10 @@ use crate::mmu::timer::Timer; // use crate::sound::Sound; use crate::mbc; use crate::mode::{GbMode, GbSpeed}; +#[cfg(not(feature = "ffi"))] use std::path; +#[cfg(feature = "ffi")] +use alloc::{boxed::Box, vec::Vec}; pub type StrResult = Result; @@ -59,63 +62,77 @@ fn fill_random(slice: &mut [u8], start: u32) { } impl<'a> MemoryManagementUnit<'a> { + #[cfg(not(feature = "ffi"))] pub fn new( data: Vec, file: Option, ) -> StrResult> { - let mmu_mbc = mbc::get_mbc(data, file)?; - - let serial = Serial::default(); - let mut res = MemoryManagementUnit { - wram: [0; WRAM_SIZE], - zram: [0; ZRAM_SIZE], - hdma: [0; 4], - wrambank: 1, - inte: 0, - intf: 0, - serial, - timer: Timer::default(), - keypad: Keypad::default(), - gpu: Gpu::new(), - // sound: None, - mbc: mmu_mbc, - gbmode: GbMode::Classic, - gbspeed: GbSpeed::Single, - speed_switch_req: false, - hdma_src: 0, - hdma_dst: 0, - hdma_status: DMAType::NoDma, - hdma_len: 0xFF, - undocumented_cgb_regs: [0; 3], - }; - fill_random(&mut res.wram, 42); - if res.rb(0x0143) == 0xC0 { - return Err("This game does not work in Classic mode"); - } - res.set_initial(); - Ok(res) + Self::new_impl(data, file, GbMode::Classic) + } + + #[cfg(feature = "ffi")] + pub fn new( + data: Vec, + _file: Option<()>, + ) -> StrResult> { + Self::new_impl(data, None, GbMode::Classic) } + #[cfg(not(feature = "ffi"))] pub fn new_cgb( data: Vec, file: Option, + ) -> StrResult> { + Self::new_impl(data, file, GbMode::Color) + } + + #[cfg(feature = "ffi")] + pub fn new_cgb( + data: Vec, + _file: Option<()>, + ) -> StrResult> { + Self::new_impl(data, None, GbMode::Color) + } + + #[cfg(not(feature = "ffi"))] + fn new_impl( + data: Vec, + file: Option, + mode: GbMode, ) -> StrResult> { let mmu_mbc = mbc::get_mbc(data, file)?; + Self::new_with_mbc(mmu_mbc, mode) + } + + #[cfg(feature = "ffi")] + fn new_impl( + data: Vec, + _file: Option<()>, + mode: GbMode, + ) -> StrResult> { + let mmu_mbc = mbc::get_mbc(data, None)?; + Self::new_with_mbc(mmu_mbc, mode) + } + + fn new_with_mbc( + mmu_mbc: Box, + mode: GbMode, + ) -> StrResult> { let serial = Serial::default(); let mut res = MemoryManagementUnit { wram: [0; WRAM_SIZE], zram: [0; ZRAM_SIZE], - wrambank: 1, hdma: [0; 4], + wrambank: 1, inte: 0, intf: 0, serial, timer: Timer::default(), keypad: Keypad::default(), - gpu: Gpu::new_cgb(), + gpu: if mode == GbMode::Color { Gpu::new_cgb() } else { Gpu::new() }, // sound: None, mbc: mmu_mbc, - gbmode: GbMode::Color, + gbmode: mode, gbspeed: GbSpeed::Single, speed_switch_req: false, hdma_src: 0, @@ -125,11 +142,17 @@ impl<'a> MemoryManagementUnit<'a> { undocumented_cgb_regs: [0; 3], }; fill_random(&mut res.wram, 42); - res.determine_mode(); + if mode == GbMode::Classic && res.rb(0x0143) == 0xC0 { + return Err("This game does not work in Classic mode"); + } + if mode == GbMode::Color { + res.determine_mode(); + } res.set_initial(); Ok(res) } + fn set_initial(&mut self) { self.wb(0xFF05, 0); self.wb(0xFF06, 0); diff --git a/src/mmu/serial.rs b/src/mmu/serial.rs index f0108a8..a3c8283 100644 --- a/src/mmu/serial.rs +++ b/src/mmu/serial.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "ffi")] +use alloc::boxed::Box; + pub type SerialCallback<'a> = Box Option + Send + 'a>; fn noop(_: u8) -> Option {