rsmsx/
rsmsx.rs

1use arg::Args;
2use log::{Level, LevelFilter, Metadata, Record};
3use std::{cell::RefCell, ops::ControlFlow, rc::Rc};
4
5use librsmsx::{
6    dev::{api::ret_no_hook, util::peek_stack},
7    prelude::*,
8};
9
10const SYSTEM_ROM_FILE: &str = "cbios_main_msx1.rom";
11
12#[derive(Args, Debug)]
13///rsmsx 0.1.0
14///MSX emulator written in rust
15struct MyArgs {
16    #[arg(long)]
17    ///ROM in SLOT 1
18    cart: String,
19
20    ///System file
21    #[arg(long = "sys")]
22    system_rom: String,
23
24    #[arg(long, default_value = "true")]
25    ///Best quality rendering
26    quality: bool,
27
28    #[arg(long = "fint", default_value = "16")]
29    ///Frame interval in milliseconds
30    /// The `frame_interval` variable in the code is used to specify the interval in milliseconds
31    /// between frames in the main game loop. This interval determines how often the game logic and
32    /// rendering are updated. In this case, the `frame_interval` value is provided as a command-line
33    /// argument when running the program, allowing the user to customize the frame rate of the game.
34    frame_interval: u32,
35
36    #[arg(long)]
37    ///Mapper type (KONAMI4...)
38    mtype: String,
39}
40
41static MY_LOGGER: MyLogger = MyLogger;
42
43struct MyLogger;
44
45impl log::Log for MyLogger {
46    fn enabled(&self, metadata: &Metadata) -> bool {
47        metadata.level() <= Level::Debug
48    }
49
50    fn log(&self, record: &Record) {
51        if self.enabled(record.metadata()) {
52            println!("{} - {}", record.level(), record.args());
53        }
54        // die on error
55        if record.metadata().level() == Level::Error {
56            panic!();
57        }
58    }
59    fn flush(&self) {}
60}
61
62pub struct SampleHook {
63    calls: u64,
64}
65impl Default for SampleHook {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl SampleHook {
72    pub(crate) fn new() -> Self {
73        Self { calls: 0 }
74    }
75}
76
77impl FuncHook for SampleHook {
78    fn handle_generic_hook(
79        &mut self,
80        data: &mut Z80Data,
81        memory: &mut Memory,
82        _ports: &mut Ports,
83    ) -> ControlFlow<()> {
84        if data.pc() == 0x89c7 {
85            println!("HGH: pc:0x{:04x} hl:0x{:04x}", data.pc(), data.hl());
86            let mut addr = data.hl();
87            loop {
88                let ch = memory.read_byte(addr);
89                if ch == 0 {
90                    break;
91                }
92                print!("{}", ch as char);
93                addr += 1;
94            }
95            println!();
96            peek_stack(data, memory, 10);
97            ret_no_hook(data, memory);
98            // assert!(false);
99            ControlFlow::Continue(()) // otherwise
100        } else {
101            ControlFlow::Break(()) // it means that this pc cannot be handled
102        }
103    }
104    fn handle_call_hook(
105        &mut self,
106        _data: &mut Z80Data,
107        _memory: &mut Memory,
108        _ports: &mut Ports,
109        new_pc: u16,
110        _old_pc: u16,
111    ) -> ControlFlow<()> {
112        if new_pc == 0xb181 {
113            println!("regs before: {:?}", _data);
114        }
115        self.calls += 1;
116        if new_pc == 0xb181 {
117            println!("new call hook #:{} 0x{:04x}", self.calls, new_pc);
118            println!("regs after: {:?}", _data);
119            panic!();
120        }
121        // ControlFlow::Break(()) // override call
122        ControlFlow::Continue(())
123    }
124    fn handle_ret_hook(&mut self, _data: &mut Z80Data, _memory: &mut Memory, _next_pc: u16) {}
125    fn handle_command_hook(
126        &mut self,
127        _data: &mut Z80Data,
128        _memory: &mut Memory,
129        _ports: &mut Ports,
130        mode: u64,
131    ) {
132        println!("debug mode #:{}", mode);
133    }
134}
135
136fn main() -> Result<(), Box<dyn std::error::Error>> {
137    log::set_logger(&MY_LOGGER).unwrap();
138    log::set_max_level(LevelFilter::Debug);
139
140    let args: std::vec::Vec<_> = std::env::args().skip(1).collect();
141    match MyArgs::from_args(args.iter().map(|x| x.as_str())) {
142        Ok(mut args) => {
143            if args.system_rom.is_empty() {
144                args.system_rom = SYSTEM_ROM_FILE.to_string();
145            }
146            let base_scale = 2.0;
147            let scale_magnifier = 2.0;
148            let mut sys = BaseSystem::new(base_scale * scale_magnifier);
149            let ppi = Rc::new(RefCell::new(PPI::new()));
150            let mut memory = Memory::new(ppi.clone());
151            memory.load_bios_basic(&args.system_rom);
152            if !args.cart.is_empty() {
153                memory.load_rom(&args.cart, 1, &args.mtype);
154            }
155            let psg = PSG::new(SoundType::Normal, Some(&mut sys));
156            let vdp = Rc::new(RefCell::new(Vdp::new(GraphicsType::Normal, args.quality)));
157            let ports = Ports::new(vdp.clone(), ppi.clone(), psg);
158            let mut cpu_z80 = Z80::new(memory, ports);
159            cpu_z80.set_hook(Rc::new(RefCell::new(SampleHook::new())));
160            cpu_z80.reboot();
161            let mut msx = MSX::new(
162                cpu_z80,
163                vdp.clone(),
164                // memory.clone(),
165                // ppi.clone(),
166                // psg.clone(),
167            );
168
169            let avg_fps = msx.main_loop(args.frame_interval as isize, &mut sys);
170            // let avg_fps = msx.main_loop(args.frame_interval as isize).await;
171            log::info!("Avg FPS: {:.2}", avg_fps);
172        }
173        Err(err) => println!("err={:?}", err),
174    }
175    Ok(())
176}