aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs142
1 files changed, 142 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..3d18789
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,142 @@
+pub mod audio;
+pub mod consts;
+pub mod display;
+pub mod gamepad;
+pub mod interrupts_timers;
+pub mod io;
+pub mod opcodes;
+pub mod state;
+
+use crate::gamepad::Gamepad;
+use crate::state::{GBState, MemError};
+use clap::Parser;
+use std::time::SystemTime;
+use std::{thread, time};
+
+pub fn exec_opcode(state: &mut GBState) -> Result<u64, MemError> {
+ let opcode = state.mem.r(state.cpu.pc)?;
+
+ if state.is_debug {
+ println!(
+ "{:02x}:{:04x} = {:02x} (IME: {})",
+ state.mem.rom_bank, state.cpu.pc, opcode, state.mem.ime
+ );
+ }
+
+ state.cpu.pc += 1;
+
+ let n1 = (opcode >> 3) & 0b111;
+ let n2 = opcode & 0b111;
+
+ match opcode >> 6 {
+ 0b00 => opcodes::op00(state, n1, n2),
+ 0b01 => opcodes::op01(state, n1, n2),
+ 0b10 => opcodes::op10(state, n1, n2),
+ 0b11 => opcodes::op11(state, n1, n2),
+ _ => panic!(),
+ }
+}
+
+#[derive(Parser)]
+#[command(author, version, about, long_about = None)]
+struct Cli {
+ /// The gameboy rom file
+ rom: String,
+
+ /// Setting this saves battery by using thread::sleep instead of spin_sleeping. It can result in lag and inconsistent timing.
+ #[arg(long)]
+ thread_sleep: bool,
+
+ #[arg(short, long, default_value_t = 1.0)]
+ speed: f32,
+}
+
+fn main() {
+ let cli = Cli::parse();
+
+ println!("Initializing Gamepad...");
+
+ let mut gamepad = Gamepad::new();
+
+ println!("Starting {:?}...", &cli.rom);
+
+ let mut state = GBState::new();
+
+ let save_file = format!("{}.sav", &cli.rom);
+
+ state.mem.load_rom(&cli.rom).unwrap();
+
+ if let Err(_) = state.mem.load_external_ram(&save_file) {
+ println!(
+ "\"{}\" not found. Initializing new external ram.",
+ save_file
+ );
+ }
+
+ let mut nanos_sleep: i128 = 0;
+ let mut halt_time = 0;
+ let mut was_previously_halted = false;
+
+ let mut last_ram_bank_enabled = false;
+
+ loop {
+ if was_previously_halted && !state.mem.halt {
+ println!("Halt cycles {}", halt_time);
+ halt_time = 0;
+ }
+ was_previously_halted = state.mem.halt;
+ let now = SystemTime::now();
+ let c = if !state.mem.halt {
+ exec_opcode(&mut state).unwrap()
+ } else {
+ halt_time += 4;
+ 4
+ };
+
+ state.div_timer(c);
+ state.tima_timer(c);
+ state.update_display_interrupts(c);
+ state.check_interrupts().unwrap();
+
+ nanos_sleep += c as i128 * (consts::CPU_CYCLE_LENGTH_NANOS as f32 / cli.speed) as i128;
+ if nanos_sleep > 0 {
+ gamepad.update_events();
+
+ let action_button_reg = gamepad.get_action_gamepad_reg();
+ let direction_button_reg = gamepad.get_direction_gamepad_reg();
+ gamepad.check_special_actions(&mut state);
+
+ if state.mem.joypad_is_action
+ && (action_button_reg & (state.mem.joypad_reg >> 4))
+ != (state.mem.joypad_reg >> 4)
+ || (!state.mem.joypad_is_action
+ && (direction_button_reg & state.mem.joypad_reg & 0b1111)
+ != (state.mem.joypad_reg & 0b1111))
+ {
+ state.mem.io[0x0f] |= 0b10000;
+ }
+
+ state.mem.joypad_reg = direction_button_reg | (action_button_reg << 4);
+
+ if cli.thread_sleep {
+ thread::sleep(time::Duration::from_nanos(nanos_sleep as u64 / 10));
+ } else {
+ while SystemTime::now().duration_since(now).unwrap().as_nanos()
+ < nanos_sleep as u128
+ {}
+ }
+
+ nanos_sleep =
+ nanos_sleep - SystemTime::now().duration_since(now).unwrap().as_nanos() as i128;
+
+ if last_ram_bank_enabled && !state.mem.ram_bank_enabled {
+ println!("Saving to \"{}\"...", save_file);
+
+ if let Err(_) = state.mem.save_external_ram(&save_file) {
+ println!("Failed to save external RAM");
+ }
+ }
+ last_ram_bank_enabled = state.mem.ram_bank_enabled;
+ }
+ }
+}