mirror of
https://github.com/FranLMSP/rultra64.git
synced 2026-01-01 07:51:34 -05:00
Good stuff!
This commit is contained in:
7
bin/main.rs
Normal file
7
bin/main.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use rultra64::gui::EmulatorApp;
|
||||
|
||||
fn main() {
|
||||
let app = EmulatorApp::default();
|
||||
let native_options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(app), native_options);
|
||||
}
|
||||
45
emulator.rs
Normal file
45
emulator.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use crate::mmu::MMU;
|
||||
use crate::cpu::CPU;
|
||||
|
||||
pub struct Emulator {
|
||||
cpu: CPU,
|
||||
mmu: MMU,
|
||||
}
|
||||
|
||||
impl Emulator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cpu: CPU::new(),
|
||||
mmu: MMU::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_hle() -> Self {
|
||||
Self {
|
||||
cpu: CPU::new_hle(),
|
||||
mmu: MMU::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
self.cpu = CPU::new();
|
||||
self.mmu = MMU::new();
|
||||
}
|
||||
|
||||
pub fn reload_hle(&mut self) {
|
||||
self.cpu = CPU::new_hle();
|
||||
self.mmu = MMU::new();
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
self.cpu.fetch_and_exec_opcode(&mut self.mmu);
|
||||
}
|
||||
|
||||
pub fn cpu(&self) -> &CPU {
|
||||
&self.cpu
|
||||
}
|
||||
|
||||
pub fn mut_mmu(&mut self) -> &mut MMU {
|
||||
&mut self.mmu
|
||||
}
|
||||
}
|
||||
147
gui.rs
Normal file
147
gui.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use eframe::{egui, epi};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::emulator::Emulator;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum Register {
|
||||
CPU,
|
||||
CP0,
|
||||
}
|
||||
|
||||
impl Default for Register {
|
||||
fn default() -> Self {
|
||||
Self::CPU
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EmulatorApp {
|
||||
core: Emulator,
|
||||
selected_register: Register,
|
||||
}
|
||||
|
||||
impl Default for EmulatorApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
core: Emulator::new_hle(),
|
||||
selected_register: Register::CPU,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::App for EmulatorApp {
|
||||
fn name(&self) -> &str {
|
||||
"Rultra64"
|
||||
}
|
||||
|
||||
/// Called once before the first frame.
|
||||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::CtxRef,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
) {
|
||||
// Load previous app state (if any).
|
||||
// Note that you must enable the `persistence` feature for this to work.
|
||||
#[cfg(feature = "persistence")]
|
||||
if let Some(storage) = _storage {
|
||||
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by the frame work to save state before shutdown.
|
||||
/// Note that you must enable the `persistence` feature for this to work.
|
||||
#[cfg(feature = "persistence")]
|
||||
fn save(&mut self, storage: &mut dyn epi::Storage) {
|
||||
epi::set_value(storage, epi::APP_KEY, self);
|
||||
}
|
||||
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
let Self { core: emulator_core, selected_register } = self;
|
||||
|
||||
let emulator_core = Rc::new(RefCell::new(emulator_core));
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
// The top panel is often a good place for a menu bar:
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Load ROM").clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
||||
let picked_path = path.display().to_string();
|
||||
if let Ok(rom) = crate::rom::ROM::new_from_filename(&picked_path) {
|
||||
let mut emulator_core = emulator_core.borrow_mut();
|
||||
emulator_core.reload_hle();
|
||||
emulator_core.mut_mmu().set_rom(rom);
|
||||
emulator_core.mut_mmu().hle_ipl();
|
||||
println!("ROM loaded!");
|
||||
}
|
||||
}
|
||||
}
|
||||
if ui.button("Quit").clicked() {
|
||||
frame.quit();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
build_registers_window(ctx, selected_register, emulator_core.clone());
|
||||
build_emulator_controls_window(ctx, emulator_core.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn build_registers_window(ctx: &egui::CtxRef, selected_register: &mut Register, emulator_core: Rc<RefCell<&mut Emulator>>) {
|
||||
egui::Window::new("Registers").vscroll(true).show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(selected_register, Register::CPU, "CPU");
|
||||
ui.selectable_value(selected_register, Register::CP0, "CP0");
|
||||
});
|
||||
ui.separator();
|
||||
match selected_register {
|
||||
Register::CPU => build_cpu_registers(ui, emulator_core),
|
||||
Register::CP0 => {ui.label("CP0 registers");},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fn build_cpu_registers(ui: &mut egui::Ui, emulator_core: Rc<RefCell<&mut Emulator>>) {
|
||||
let emulator_core = emulator_core.borrow();
|
||||
ui.columns(3, |cols| {
|
||||
cols[0].label("#");
|
||||
cols[1].label("Name");
|
||||
cols[2].label("Value");
|
||||
});
|
||||
ui.separator();
|
||||
ui.columns(3, |cols| {
|
||||
cols[0].label("-");
|
||||
cols[1].label("PC");
|
||||
cols[2].label(format!("{:64X}", emulator_core.cpu().registers().get_program_counter()));
|
||||
});
|
||||
ui.columns(3, |cols| {
|
||||
cols[0].label("-");
|
||||
cols[1].label("hi");
|
||||
cols[2].label(format!("{}", emulator_core.cpu().registers().get_hi()));
|
||||
});
|
||||
ui.columns(3, |cols| {
|
||||
cols[0].label("-");
|
||||
cols[1].label("lo");
|
||||
cols[2].label(format!("{}", emulator_core.cpu().registers().get_lo()));
|
||||
});
|
||||
for (index, name) in crate::registers::CPU_REGISTER_NAMES.into_iter().enumerate() {
|
||||
let val = emulator_core.cpu().registers().get_by_name(name);
|
||||
ui.columns(3, |cols| {
|
||||
cols[0].label(format!("r{}", index));
|
||||
cols[1].label(format!("{}", name));
|
||||
cols[2].label(format!("{}", val));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn build_emulator_controls_window(ctx: &egui::CtxRef, emulator_core: Rc<RefCell<&mut Emulator>>) {
|
||||
egui::Window::new("Controls").vscroll(true).show(ctx, |ui| {
|
||||
if ui.button("Tick").clicked() {
|
||||
emulator_core.borrow_mut().tick();
|
||||
}
|
||||
});
|
||||
}
|
||||
9
lib.rs
Normal file
9
lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod registers;
|
||||
pub mod cpu;
|
||||
pub mod mmu;
|
||||
pub mod rom;
|
||||
pub mod rdram;
|
||||
pub mod emulator;
|
||||
pub mod rcp;
|
||||
pub mod utils;
|
||||
pub mod gui;
|
||||
203
mmu.rs
Normal file
203
mmu.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use crate::rdram::RDRAM;
|
||||
use crate::rom::ROM;
|
||||
use crate::rcp::RCP;
|
||||
|
||||
pub const KUSEG: RangeInclusive<i64> = 0x00000000..=0x7FFFFFFF;
|
||||
pub const KSEG0: RangeInclusive<i64> = 0x80000000..=0x9FFFFFFF;
|
||||
pub const KSEG1: RangeInclusive<i64> = 0xA0000000..=0xBFFFFFFF;
|
||||
pub const KSSEG: RangeInclusive<i64> = 0xC0000000..=0xDFFFFFFF;
|
||||
pub const KSEG3: RangeInclusive<i64> = 0xE0000000..=0xFFFFFFFF;
|
||||
|
||||
pub const RDRAM1: RangeInclusive<i64> = 0x00000000..=0x003FFFFF;
|
||||
pub const RDRAM2: RangeInclusive<i64> = 0x00400000..=0x007FFFFF;
|
||||
pub const RESERVED1: RangeInclusive<i64> = 0x00800000..=0x03EFFFFF;
|
||||
pub const RDRAM_REGISTERS: RangeInclusive<i64> = 0x03F00000..=0x03FFFFFF;
|
||||
pub const RSP_DMEM: RangeInclusive<i64> = 0x04000000..=0x04000FFF;
|
||||
pub const RSP_IMEM: RangeInclusive<i64> = 0x04001000..=0x04001FFF;
|
||||
pub const UNKNOWN: RangeInclusive<i64> = 0x04002000..=0x0403FFFF;
|
||||
pub const RSP_REGISTERS: RangeInclusive<i64> = 0x04040000..=0x040FFFFF;
|
||||
pub const RDP_COMMAND_REGISTERS: RangeInclusive<i64> = 0x04100000..=0x041FFFFF;
|
||||
pub const RDP_SPAN_REGISTERS: RangeInclusive<i64> = 0x04200000..=0x042FFFFF;
|
||||
pub const MIPS_INTERFACE: RangeInclusive<i64> = 0x04300000..=0x043FFFFF;
|
||||
pub const VIDEO_INTERFACE: RangeInclusive<i64> = 0x04400000..=0x044FFFFF;
|
||||
pub const AUDIO_INTERFACE: RangeInclusive<i64> = 0x04500000..=0x045FFFFF;
|
||||
pub const PERIPHERAL_INTERFACE: RangeInclusive<i64> = 0x04600000..=0x046FFFFF;
|
||||
pub const RDRAM_INTERFACE: RangeInclusive<i64> = 0x04700000..=0x047FFFFF;
|
||||
pub const SERIAL_INTERFACE: RangeInclusive<i64> = 0x04800000..=0x048FFFFF;
|
||||
pub const UNUSED: RangeInclusive<i64> = 0x04900000..=0x04FFFFFF;
|
||||
pub const CARTRIDGE_DOMAIN_2_ADDRESS_1: RangeInclusive<i64> = 0x05000000..=0x05FFFFFF;
|
||||
pub const CARTRIDGE_DOMAIN_1_ADDRESS_1: RangeInclusive<i64> = 0x06000000..=0x07FFFFFF;
|
||||
pub const CARTRIDGE_DOMAIN_2_ADDRESS_2: RangeInclusive<i64> = 0x08000000..=0x0FFFFFFF;
|
||||
pub const CARTRIDGE_DOMAIN_1_ADDRESS_2: RangeInclusive<i64> = 0x10000000..=0x1FBFFFFF;
|
||||
pub const PIF_ROM: RangeInclusive<i64> = 0x1FC00000..=0x1FC007BF;
|
||||
pub const PIF_RAM: RangeInclusive<i64> = 0x1FC007C0..=0x1FC007FF;
|
||||
pub const RESERVED2: RangeInclusive<i64> = 0x1FC00800..=0x1FCFFFFF;
|
||||
pub const CARTRIDGE_DOMAIN_1_ADDRESS_3: RangeInclusive<i64> = 0x1FD00000..=0x7FFFFFFF;
|
||||
pub const EXTERNAL_SYSAD_DEVICE_BUS: RangeInclusive<i64> = 0x80000000..=0xFFFFFFFF;
|
||||
|
||||
pub struct MMU {
|
||||
rdram: RDRAM,
|
||||
rom: ROM,
|
||||
rcp: RCP,
|
||||
}
|
||||
|
||||
impl MMU {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
rdram: RDRAM::new(),
|
||||
rcp: RCP::new(),
|
||||
rom: ROM::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hle_ipl(&mut self) {
|
||||
// Skip IPL1 and IPL2
|
||||
for i in 0..0x1000 {
|
||||
let byte = self.read_virtual(0xB0000000 + i, 1);
|
||||
self.write_virtual(0xA4000000 + i, &byte);
|
||||
}
|
||||
// Skip IPL3
|
||||
for i in 0..0x100000 {
|
||||
let byte = self.read_physical_byte(0x10001000 + i);
|
||||
self.write_physical_byte(0x00001000 + i, byte);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_rom(&mut self, rom: ROM) {
|
||||
self.rom = rom;
|
||||
}
|
||||
|
||||
pub fn convert(address: i64) -> i64 {
|
||||
let address = address & 0x00000000FFFFFFFF;
|
||||
if KUSEG.contains(&address) {
|
||||
return address - KUSEG.min().unwrap();
|
||||
} else if KSEG0.contains(&address) {
|
||||
return address - KSEG0.min().unwrap();
|
||||
} else if KSEG1.contains(&address) {
|
||||
return address - KSEG1.min().unwrap();
|
||||
} else if KSSEG.contains(&address) {
|
||||
return address - KSSEG.min().unwrap();
|
||||
} else if KSEG3.contains(&address) {
|
||||
return address - KSEG3.min().unwrap();
|
||||
}
|
||||
unreachable!("Invalid virtual memory address {:08X}", address);
|
||||
}
|
||||
|
||||
pub fn read_virtual(&self, address: i64, bytes: usize) -> Vec<u8> {
|
||||
let converted_address = MMU::convert(address);
|
||||
self.read_physical(converted_address, bytes)
|
||||
}
|
||||
|
||||
pub fn write_virtual(&mut self, address: i64, data: &[u8]) {
|
||||
let converted_address = MMU::convert(address);
|
||||
self.write_physical(converted_address, data)
|
||||
}
|
||||
|
||||
pub fn read_physical(&self, address: i64, bytes: usize) -> Vec<u8> {
|
||||
let mut data = Vec::new();
|
||||
for _ in 0..bytes {
|
||||
data.push(self.read_physical_byte(address));
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
pub fn write_physical(&mut self, address: i64, data: &[u8]) {
|
||||
for byte in data {
|
||||
self.write_physical_byte(address, *byte);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_physical_byte(&self, address: i64) -> u8 {
|
||||
if RDRAM1.contains(&address) {
|
||||
return self.rdram.read8(address);
|
||||
} else if RDRAM2.contains(&address) {
|
||||
return self.rdram.read8(address);
|
||||
} else if RESERVED1.contains(&address) {
|
||||
return 0xFF;
|
||||
} else if RDRAM_REGISTERS.contains(&address) {
|
||||
return 0;
|
||||
} else if RSP_DMEM.contains(&address) {
|
||||
return 0;
|
||||
} else if RSP_IMEM.contains(&address) {
|
||||
return 0;
|
||||
} else if UNKNOWN.contains(&address) {
|
||||
return 0;
|
||||
} else if RSP_REGISTERS.contains(&address) {
|
||||
return 0;
|
||||
} else if RDP_COMMAND_REGISTERS.contains(&address) {
|
||||
return 0;
|
||||
} else if RDP_SPAN_REGISTERS.contains(&address) {
|
||||
return 0;
|
||||
} else if MIPS_INTERFACE.contains(&address) {
|
||||
return 0;
|
||||
} else if VIDEO_INTERFACE.contains(&address) {
|
||||
return self.rcp.video_interface.get_register(address);
|
||||
} else if AUDIO_INTERFACE.contains(&address) {
|
||||
return 0;
|
||||
} else if PERIPHERAL_INTERFACE.contains(&address) {
|
||||
return 0;
|
||||
} else if RDRAM_INTERFACE.contains(&address) {
|
||||
return 0;
|
||||
} else if SERIAL_INTERFACE.contains(&address) {
|
||||
return 0;
|
||||
} else if UNUSED.contains(&address) {
|
||||
return 0xFF;
|
||||
} else if CARTRIDGE_DOMAIN_2_ADDRESS_1.contains(&address) {
|
||||
return 0;
|
||||
} else if CARTRIDGE_DOMAIN_1_ADDRESS_1.contains(&address) {
|
||||
return 0;
|
||||
} else if CARTRIDGE_DOMAIN_2_ADDRESS_2.contains(&address) {
|
||||
return self.rom.read(address);
|
||||
} else if CARTRIDGE_DOMAIN_1_ADDRESS_2.contains(&address) {
|
||||
return self.rom.read(address);
|
||||
} else if PIF_ROM.contains(&address) {
|
||||
return 0;
|
||||
} else if PIF_RAM.contains(&address) {
|
||||
return 0;
|
||||
} else if RESERVED2.contains(&address) {
|
||||
return 0;
|
||||
} else if CARTRIDGE_DOMAIN_1_ADDRESS_3.contains(&address) {
|
||||
return 0;
|
||||
} else if EXTERNAL_SYSAD_DEVICE_BUS.contains(&address) {
|
||||
return 0;
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
pub fn write_physical_byte(&mut self, address: i64, data: u8) {
|
||||
if RDRAM1.contains(&address) {
|
||||
self.rdram.write8(address, data);
|
||||
} else if RDRAM2.contains(&address) {
|
||||
self.rdram.write8(address, data);
|
||||
} else if RESERVED1.contains(&address) {
|
||||
} else if RDRAM_REGISTERS.contains(&address) {
|
||||
} else if RSP_DMEM.contains(&address) {
|
||||
} else if RSP_IMEM.contains(&address) {
|
||||
} else if UNKNOWN.contains(&address) {
|
||||
} else if RSP_REGISTERS.contains(&address) {
|
||||
} else if RDP_COMMAND_REGISTERS.contains(&address) {
|
||||
} else if RDP_SPAN_REGISTERS.contains(&address) {
|
||||
} else if MIPS_INTERFACE.contains(&address) {
|
||||
} else if VIDEO_INTERFACE.contains(&address) {
|
||||
self.rcp.video_interface.set_register(address, data);
|
||||
} else if AUDIO_INTERFACE.contains(&address) {
|
||||
} else if PERIPHERAL_INTERFACE.contains(&address) {
|
||||
} else if RDRAM_INTERFACE.contains(&address) {
|
||||
} else if SERIAL_INTERFACE.contains(&address) {
|
||||
} else if UNUSED.contains(&address) {
|
||||
} else if CARTRIDGE_DOMAIN_2_ADDRESS_1.contains(&address) {
|
||||
} else if CARTRIDGE_DOMAIN_1_ADDRESS_1.contains(&address) {
|
||||
} else if CARTRIDGE_DOMAIN_2_ADDRESS_2.contains(&address) {
|
||||
self.rom.write(address, data);
|
||||
} else if CARTRIDGE_DOMAIN_1_ADDRESS_2.contains(&address) {
|
||||
self.rom.write(address, data);
|
||||
} else if PIF_ROM.contains(&address) {
|
||||
} else if PIF_RAM.contains(&address) {
|
||||
} else if RESERVED2.contains(&address) {
|
||||
} else if CARTRIDGE_DOMAIN_1_ADDRESS_3.contains(&address) {
|
||||
} else if EXTERNAL_SYSAD_DEVICE_BUS.contains(&address) {
|
||||
}
|
||||
}
|
||||
}
|
||||
73
rcp.rs
Normal file
73
rcp.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use crate::rdram::RDRAM;
|
||||
use crate::utils::box_array;
|
||||
|
||||
pub struct VideoInterface {
|
||||
registers: Box<[u8; 0x100000]>,
|
||||
}
|
||||
|
||||
impl VideoInterface {
|
||||
pub fn new() -> Self {
|
||||
let mut registers = box_array![0; 0x100000];
|
||||
// Initialize VI_V_INTR 0x0440 000C: https://n64brew.dev/wiki/Video_Interface#0x0440_000C_-_VI_V_INTR
|
||||
registers[0x0440000C - 0x04400000] = 0xFF;
|
||||
registers[0x0440000B - 0x04400000] = 0x03;
|
||||
// Initialize VI_BURST 0x0440 0014: https://n64brew.dev/wiki/Video_Interface#0x0440_0014_-_VI_BURST
|
||||
registers[0x04400014 - 0x04400000] = 0x01;
|
||||
// Initialize VI_H_SYNC 0x0440 001C: https://n64brew.dev/wiki/Video_Interface#0x0440_001C_-_VI_H_SYNC
|
||||
registers[0x0440001C - 0x04400000] = 0xFF;
|
||||
registers[0x0440001B - 0x04400000] = 0x07;
|
||||
Self {
|
||||
registers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_register(&self, address: i64) -> u8 {
|
||||
self.registers[(address - 0x04400000) as usize]
|
||||
}
|
||||
|
||||
pub fn set_register(&mut self, address: i64, data: u8) {
|
||||
self.registers[(address - 0x04400000) as usize] = data;
|
||||
}
|
||||
|
||||
/*
|
||||
RDRAM base address of the video output Frame Buffer. This can be changed as needed to implement double or triple buffering.
|
||||
https://n64brew.dev/wiki/Video_Interface#0x0440_0004_-_VI_ORIGIN
|
||||
*/
|
||||
pub fn get_vi_origin(&self) -> u32 {
|
||||
((self.get_register(0x04400005) as u32) << 16) | ((self.get_register(0x04400006) as u32) << 8) | (self.get_register(0x04400007) as u32)
|
||||
}
|
||||
|
||||
/*
|
||||
This is the width in pixels of the frame buffer if you draw to the frame buffer based on a different width than what
|
||||
is given here the image will drift with each line to the left or right. The common values are 320 and 640,
|
||||
the maximum value is 640. The minimum value depends on the TV set, 160 would probably be a safe minimum but no guarantee.
|
||||
The same value would also be used on drawing commands for clipping or scissors.
|
||||
This can also be used with High Res interlacing modes to change the odd and even lines of the frame buffer to be drawn
|
||||
to screen by doubling the width of this value and changing the VI_ORIGIN register to the odd or even field being displayed.
|
||||
RDRAM base address of the video output Frame Buffer. This can be changed as needed to implement double or triple buffering.
|
||||
https://n64brew.dev/wiki/Video_Interface#0x0440_0008_-_VI_WIDTH
|
||||
*/
|
||||
pub fn get_vi_width(&self) -> u16 {
|
||||
(((self.get_register(0x04400010) as u16) << 8) & 0b1111) | (self.get_register(0x04400011) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RCP {
|
||||
pub video_interface: VideoInterface,
|
||||
}
|
||||
|
||||
impl RCP {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
video_interface: VideoInterface::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_framebuffer(&self, rdram: &RDRAM, dest: &mut [u8]) {
|
||||
let mut addr = self.video_interface.get_vi_origin() as i64;
|
||||
for elem in dest {
|
||||
*elem = rdram.read8(addr);
|
||||
addr += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
58
rdram.rs
Normal file
58
rdram.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use crate::utils::box_array;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Byte {
|
||||
data: u16,
|
||||
}
|
||||
|
||||
impl Byte {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&self) -> u16 {
|
||||
self.data & 0x1FF
|
||||
}
|
||||
|
||||
pub fn write(&mut self, data: u16) {
|
||||
self.data = data & 0x1FF;
|
||||
}
|
||||
|
||||
pub fn read8(&self) -> u8 {
|
||||
self.data.to_be_bytes()[1]
|
||||
}
|
||||
|
||||
pub fn write8(&mut self, data: u8) {
|
||||
self.data = (self.data & 0x100) | (data as u16);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RDRAM {
|
||||
data: Box<[Byte; 0x400000]>,
|
||||
}
|
||||
|
||||
impl RDRAM {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: box_array![Byte::new(); 0x400000],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&self, address: i64) -> u16 {
|
||||
self.data[address as usize].read()
|
||||
}
|
||||
|
||||
pub fn write(&mut self, address: i64, data: u16) {
|
||||
self.data[address as usize].write(data);
|
||||
}
|
||||
|
||||
pub fn read8(&self, address: i64) -> u8 {
|
||||
self.data[address as usize].read8()
|
||||
}
|
||||
|
||||
pub fn write8(&mut self, address: i64, data: u8) {
|
||||
self.data[address as usize].write8(data);
|
||||
}
|
||||
}
|
||||
449
registers.rs
Normal file
449
registers.rs
Normal file
@@ -0,0 +1,449 @@
|
||||
pub trait Register<T: PartialOrd + Copy> {
|
||||
fn get(&self) -> T;
|
||||
fn set(&mut self, val: T);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Fixed<T>(T);
|
||||
impl<T: PartialOrd + Copy> Register<T> for Fixed<T> {
|
||||
fn get(&self) -> T {self.0}
|
||||
fn set(&mut self, _: T) {}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Generic<T>(T);
|
||||
impl<T: PartialOrd + Copy> Register<T> for Generic<T> {
|
||||
fn get(&self) -> T {self.0}
|
||||
fn set(&mut self, val: T) {self.0 = val}
|
||||
}
|
||||
|
||||
|
||||
pub const CPU_REGISTER_NAMES: [&'static str; 32] = [
|
||||
"zero", "at", "v0", "v1", "a0", "a1", "a2", "a3",
|
||||
"t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
|
||||
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
|
||||
"t8", "t9", "k0", "k1", "gp", "sp", "s8", "ra"
|
||||
];
|
||||
|
||||
pub struct CPURegisters {
|
||||
registers: [Box<dyn Register<i64>>; 32],
|
||||
program_counter: Generic<i64>,
|
||||
next_program_counter: Generic<i64>,
|
||||
hi: Generic<i64>,
|
||||
lo: Generic<i64>,
|
||||
load_link: bool,
|
||||
}
|
||||
|
||||
impl CPURegisters {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
registers: [
|
||||
Box::new(Fixed(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
Box::new(Generic(0_i64)),
|
||||
],
|
||||
program_counter: Generic(0xBFC00000),
|
||||
next_program_counter: Generic(0xBFC00004),
|
||||
hi: Generic(0_i64),
|
||||
lo: Generic(0_i64),
|
||||
load_link: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_hle() -> Self {
|
||||
let mut registers = Self::new();
|
||||
registers.set_by_name("t3", 0xFFFFFFFFA4000040_u64 as i64);
|
||||
registers.set_by_name("s4", 0x0000000000000001);
|
||||
registers.set_by_name("s6", 0x000000000000003F);
|
||||
registers.set_by_name("sp", 0xFFFFFFFFA4001FF0_u64 as i64);
|
||||
|
||||
registers.set_program_counter(0x80001000);
|
||||
registers.set_next_program_counter(0x80001000 + 4);
|
||||
/* registers.set_program_counter(0xA4000040);
|
||||
registers.set_next_program_counter(0xA4000040 + 4); */
|
||||
|
||||
registers
|
||||
}
|
||||
|
||||
pub fn set_load_link(&mut self, val: bool) {
|
||||
self.load_link = val;
|
||||
}
|
||||
|
||||
pub fn get_load_link(&self) -> bool {
|
||||
self.load_link
|
||||
}
|
||||
|
||||
fn find_index(name: &'static str) -> usize {
|
||||
CPU_REGISTER_NAMES.iter().position(|v| *v == name).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_by_number(&self, index: usize) -> i64 {
|
||||
if index > 31 {
|
||||
unreachable!("Register number {} not valid", index);
|
||||
}
|
||||
self.registers[index].get()
|
||||
}
|
||||
|
||||
pub fn get_by_name(&self, name: &'static str) -> i64 {
|
||||
let index = CPURegisters::find_index(name);
|
||||
self.registers[index].get()
|
||||
}
|
||||
|
||||
pub fn set_by_number(&mut self, index: usize, val: i64) {
|
||||
if index > 31 {
|
||||
unreachable!("Register number {} not valid", index);
|
||||
}
|
||||
self.registers[index].set(val);
|
||||
}
|
||||
|
||||
pub fn set_by_name(&mut self, name: &'static str, val: i64) {
|
||||
let index = CPURegisters::find_index(name);
|
||||
self.registers[index].set(val);
|
||||
}
|
||||
|
||||
pub fn get_program_counter(&self) -> i64 {
|
||||
self.program_counter.get()
|
||||
}
|
||||
|
||||
pub fn set_program_counter(&mut self, val: i64) {
|
||||
self.program_counter.set(val);
|
||||
}
|
||||
|
||||
pub fn increment_program_counter(&mut self, val: i64) {
|
||||
let pc: i64 = self.program_counter.get();
|
||||
self.program_counter.set(pc.wrapping_add(val));
|
||||
}
|
||||
|
||||
pub fn get_next_program_counter(&self) -> i64 {
|
||||
self.next_program_counter.get()
|
||||
}
|
||||
|
||||
pub fn set_next_program_counter(&mut self, val: i64) {
|
||||
self.next_program_counter.set(val);
|
||||
}
|
||||
|
||||
pub fn increment_next_program_counter(&mut self, val: i64) {
|
||||
let pc: i64 = self.next_program_counter.get();
|
||||
self.next_program_counter.set(pc.wrapping_add(val));
|
||||
}
|
||||
|
||||
pub fn set_hi(&mut self, val: i64) {
|
||||
self.hi.set(val);
|
||||
}
|
||||
|
||||
pub fn set_lo(&mut self, val: i64) {
|
||||
self.lo.set(val);
|
||||
}
|
||||
|
||||
pub fn get_hi(&self) -> i64 {
|
||||
self.hi.get()
|
||||
}
|
||||
|
||||
pub fn get_lo(&self) -> i64 {
|
||||
self.lo.get()
|
||||
}
|
||||
}
|
||||
|
||||
pub const CP0_REGISTER_NAMES: [&'static str; 32] = [
|
||||
"index", "random", "EntryLo0", "EntryLo1", "context", "PageMask", "wired", "7",
|
||||
"BadVAddr", "count", "EntryHi", "compare", "status", "cause", "epc", "PRId",
|
||||
"config", "LLAddr", "WatchLo", "WatchHi", "XContext", "21", "22", "23",
|
||||
"24", "25", "ParityError", "CacheError", "TagLo", "TagHi", "ErrorEPC", "31"
|
||||
];
|
||||
|
||||
pub struct CP0Registers {
|
||||
index: Generic<i32>,
|
||||
random: Generic<i32>,
|
||||
entry_lo_0: Generic<i64>,
|
||||
entry_lo_1: Generic<i64>,
|
||||
context: Generic<i64>,
|
||||
page_mask: Generic<i32>,
|
||||
wired: Generic<i32>,
|
||||
r7: Generic<i64>,
|
||||
bad_v_addr: Generic<i64>,
|
||||
count: Generic<i32>,
|
||||
entry_hi: Generic<i64>,
|
||||
compare: Generic<i32>,
|
||||
status: Generic<i32>,
|
||||
cause: Generic<i32>,
|
||||
epc: Generic<i64>,
|
||||
prid: Generic<i32>,
|
||||
config: Generic<i32>,
|
||||
lladdr: Generic<i32>,
|
||||
watch_lo: Generic<i32>,
|
||||
watch_hi: Generic<i32>,
|
||||
xcontext: Generic<i64>,
|
||||
r21: Generic<i64>,
|
||||
r22: Generic<i64>,
|
||||
r23: Generic<i64>,
|
||||
r24: Generic<i64>,
|
||||
r25: Generic<i64>,
|
||||
parity_error: Generic<i32>,
|
||||
cache_error: Generic<i32>,
|
||||
tag_lo: Generic<i32>,
|
||||
tag_hi: Generic<i32>,
|
||||
error_epc: Generic<i64>,
|
||||
r31: Generic<i64>,
|
||||
}
|
||||
|
||||
impl CP0Registers {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
index: Generic(0),
|
||||
random: Generic(0),
|
||||
entry_lo_0: Generic(0),
|
||||
entry_lo_1: Generic(0),
|
||||
context: Generic(0),
|
||||
page_mask: Generic(0),
|
||||
wired: Generic(0),
|
||||
r7: Generic(0),
|
||||
bad_v_addr: Generic(0),
|
||||
count: Generic(0),
|
||||
entry_hi: Generic(0),
|
||||
compare: Generic(0),
|
||||
status: Generic(0),
|
||||
cause: Generic(0),
|
||||
epc: Generic(0),
|
||||
prid: Generic(0),
|
||||
config: Generic(0),
|
||||
lladdr: Generic(0),
|
||||
watch_lo: Generic(0),
|
||||
watch_hi: Generic(0),
|
||||
xcontext: Generic(0),
|
||||
r21: Generic(0),
|
||||
r22: Generic(0),
|
||||
r23: Generic(0),
|
||||
r24: Generic(0),
|
||||
r25: Generic(0),
|
||||
parity_error: Generic(0),
|
||||
cache_error: Generic(0),
|
||||
tag_lo: Generic(0),
|
||||
tag_hi: Generic(0),
|
||||
error_epc: Generic(0),
|
||||
r31: Generic(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_hle() -> Self {
|
||||
let mut cp0 = Self::new();
|
||||
cp0.set_by_name_32("random", 0x0000001F);
|
||||
cp0.set_by_name_32("status", 0x70400004);
|
||||
cp0.set_by_name_32("PRId", 0x00000B00);
|
||||
cp0.set_by_name_32("config", 0x0006E463);
|
||||
|
||||
cp0
|
||||
}
|
||||
|
||||
fn find_index(name: &'static str) -> usize {
|
||||
CP0_REGISTER_NAMES.iter().position(|v| *v == name).unwrap()
|
||||
}
|
||||
|
||||
pub fn is_32bits(index: usize) -> bool {
|
||||
match index {
|
||||
0 | 1 | 5 | 6 | 9 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 => true,
|
||||
2 | 3 | 4 | 8 | 10 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 30 => false,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_64bits(index: usize) -> bool {
|
||||
!CP0Registers::is_32bits(index)
|
||||
}
|
||||
|
||||
pub fn get_by_number_32(&self, index: usize) -> i32 {
|
||||
if index > 31 {
|
||||
unreachable!("Register number {} not valid", index);
|
||||
}
|
||||
match index {
|
||||
0 => self.index.get(),
|
||||
1 => self.random.get(),
|
||||
5 => self.page_mask.get(),
|
||||
6 => self.wired.get(),
|
||||
9 => self.count.get(),
|
||||
11 => self.compare.get(),
|
||||
12 => self.status.get(),
|
||||
13 => self.cause.get(),
|
||||
15 => self.prid.get(),
|
||||
16 => self.config.get(),
|
||||
17 => self.lladdr.get(),
|
||||
18 => self.watch_lo.get(),
|
||||
19 => self.watch_hi.get(),
|
||||
26 => self.parity_error.get(),
|
||||
27 => self.cache_error.get(),
|
||||
28 => self.tag_lo.get(),
|
||||
29 => self.tag_hi.get(),
|
||||
_ => unreachable!("Register {} is not 32bit", index),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_by_number_32(&mut self, index: usize, val: i32) {
|
||||
if index > 31 {
|
||||
unreachable!("Register number {} not valid", index);
|
||||
}
|
||||
match index {
|
||||
0 => self.index.set(val),
|
||||
1 => self.random.set(val),
|
||||
5 => self.page_mask.set(val),
|
||||
6 => self.wired.set(val),
|
||||
9 => self.count.set(val),
|
||||
11 => self.compare.set(val),
|
||||
12 => self.status.set(val),
|
||||
13 => self.cause.set(val),
|
||||
15 => self.prid.set(val),
|
||||
16 => self.config.set(val),
|
||||
17 => self.lladdr.set(val),
|
||||
18 => self.watch_lo.set(val),
|
||||
19 => self.watch_hi.set(val),
|
||||
26 => self.parity_error.set(val),
|
||||
27 => self.cache_error.set(val),
|
||||
28 => self.tag_lo.set(val),
|
||||
29 => self.tag_hi.set(val),
|
||||
_ => unreachable!("Register {} is not 32bit", index),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_by_number_64(&self, index: usize) -> i64 {
|
||||
if index > 31 {
|
||||
unreachable!("Register number {} not valid", index);
|
||||
}
|
||||
match index {
|
||||
2 => self.entry_lo_0.get(),
|
||||
3 => self.entry_lo_1.get(),
|
||||
4 => self.context.get(),
|
||||
7 => self.r7.get(),
|
||||
8 => self.bad_v_addr.get(),
|
||||
10 => self.entry_hi.get(),
|
||||
14 => self.epc.get(),
|
||||
20 => self.xcontext.get(),
|
||||
21 => self.r21.get(),
|
||||
22 => self.r22.get(),
|
||||
23 => self.r23.get(),
|
||||
24 => self.r24.get(),
|
||||
25 => self.r25.get(),
|
||||
30 => self.error_epc.get(),
|
||||
31 => self.r31.get(),
|
||||
_ => unreachable!("Register {} is not 64bit", index),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_by_number_64(&mut self, index: usize, val: i64) {
|
||||
if index > 31 {
|
||||
unreachable!("Register number {} not valid", index);
|
||||
}
|
||||
match index {
|
||||
2 => self.entry_lo_0.set(val),
|
||||
3 => self.entry_lo_1.set(val),
|
||||
4 => self.context.set(val),
|
||||
7 => self.r7.set(val),
|
||||
8 => self.bad_v_addr.set(val),
|
||||
10 => self.entry_hi.set(val),
|
||||
14 => self.epc.set(val),
|
||||
20 => self.xcontext.set(val),
|
||||
21 => self.r21.set(val),
|
||||
22 => self.r22.set(val),
|
||||
23 => self.r23.set(val),
|
||||
24 => self.r24.set(val),
|
||||
25 => self.r25.set(val),
|
||||
30 => self.error_epc.set(val),
|
||||
31 => self.r31.set(val),
|
||||
_ => unreachable!("Register {} is not 64bit", index),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_by_name_32(&self, name: &'static str) -> i32 {
|
||||
let index = CP0Registers::find_index(name);
|
||||
self.get_by_number_32(index)
|
||||
}
|
||||
|
||||
pub fn set_by_name_32(&mut self, name: &'static str, val: i32) {
|
||||
let index = CP0Registers::find_index(name);
|
||||
self.set_by_number_32(index, val);
|
||||
}
|
||||
|
||||
pub fn get_by_name_64(&self, name: &'static str) -> i64 {
|
||||
let index = CP0Registers::find_index(name);
|
||||
self.get_by_number_64(index)
|
||||
}
|
||||
|
||||
pub fn set_by_name_64(&mut self, name: &'static str, val: i64) {
|
||||
let index = CP0Registers::find_index(name);
|
||||
self.set_by_number_64(index, val);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod cpu_registers_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_set_by_number() {
|
||||
let mut registers = CPURegisters::new();
|
||||
registers.set_by_number(0, 20);
|
||||
assert_eq!(registers.get_by_number(0), 0);
|
||||
registers.set_by_number(5, 20);
|
||||
assert_eq!(registers.get_by_number(5), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_by_name() {
|
||||
let mut registers = CPURegisters::new();
|
||||
registers.set_by_name("zero", 20);
|
||||
assert_eq!(registers.get_by_name("zero"), 0);
|
||||
registers.set_by_name("a0", 20);
|
||||
assert_eq!(registers.get_by_name("a0"), 20);
|
||||
assert_eq!(registers.get_by_number(4), 20);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod cp0_registers_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_set_by_number() {
|
||||
let mut registers = CP0Registers::new();
|
||||
registers.set_by_number_32(0, 20);
|
||||
assert_eq!(registers.get_by_number_32(0), 20);
|
||||
registers.set_by_number_64(4, 20);
|
||||
assert_eq!(registers.get_by_number_64(4), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_by_name() {
|
||||
let mut registers = CP0Registers::new();
|
||||
registers.set_by_name_32("index", 0);
|
||||
assert_eq!(registers.get_by_name_32("index"), 0);
|
||||
registers.set_by_name_64("context", 20);
|
||||
assert_eq!(registers.get_by_name_64("context"), 20);
|
||||
assert_eq!(registers.get_by_number_64(4), 20);
|
||||
}
|
||||
}
|
||||
50
rom.rs
Normal file
50
rom.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use crate::mmu::CARTRIDGE_DOMAIN_2_ADDRESS_2;
|
||||
use crate::mmu::CARTRIDGE_DOMAIN_1_ADDRESS_2;
|
||||
|
||||
pub struct ROM {
|
||||
data: Vec<u8>,
|
||||
ram: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ROM {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: Vec::new(),
|
||||
ram: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_filename(filename: &str) -> std::io::Result<Self> {
|
||||
let mut file = File::open(filename)?;
|
||||
let mut data = vec![];
|
||||
file.read_to_end(&mut data)?;
|
||||
Ok(Self {
|
||||
data,
|
||||
ram: vec![0; 0xFC00000],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read(&self, address: i64) -> u8 {
|
||||
if CARTRIDGE_DOMAIN_2_ADDRESS_2.contains(&address) {
|
||||
return match self.ram.get((address - CARTRIDGE_DOMAIN_2_ADDRESS_2.min().unwrap()) as usize) {
|
||||
Some(byte) => *byte,
|
||||
None => 0xFF,
|
||||
};
|
||||
} else if CARTRIDGE_DOMAIN_1_ADDRESS_2.contains(&address) {
|
||||
return match self.data.get((address - CARTRIDGE_DOMAIN_1_ADDRESS_2.min().unwrap()) as usize) {
|
||||
Some(byte) => *byte,
|
||||
None => 0xFF,
|
||||
};
|
||||
}
|
||||
unreachable!("Invalid ROM access");
|
||||
}
|
||||
|
||||
pub fn write(&mut self, address: i64, data: u8) {
|
||||
if let Some(elem) = self.ram.get_mut((address - CARTRIDGE_DOMAIN_2_ADDRESS_2.min().unwrap()) as usize) {
|
||||
*elem = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
use rultra64::gui::EmulatorApp;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
let app = EmulatorApp::default();
|
||||
let native_options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(app), native_options);
|
||||
}
|
||||
@@ -99,6 +99,10 @@ impl CPU {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registers(&self) -> &CPURegisters {
|
||||
&self.registers
|
||||
}
|
||||
|
||||
pub fn fetch_opcode(address: i64, mmu: &MMU) -> u32 {
|
||||
let data = mmu.read_virtual(address, 4);
|
||||
let opcode = ((data[0] as u32) << 24) | ((data[1] as u32) << 16) | ((data[2] as u32) << 8) | ((data[3] as u32) << 8);
|
||||
|
||||
@@ -17,11 +17,29 @@ impl Emulator {
|
||||
pub fn new_hle() -> Self {
|
||||
Self {
|
||||
cpu: CPU::new_hle(),
|
||||
mmu: MMU::new_hle(),
|
||||
mmu: MMU::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
self.cpu = CPU::new();
|
||||
self.mmu = MMU::new();
|
||||
}
|
||||
|
||||
pub fn reload_hle(&mut self) {
|
||||
self.cpu = CPU::new_hle();
|
||||
self.mmu = MMU::new();
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
self.cpu.fetch_and_exec_opcode(&mut self.mmu);
|
||||
}
|
||||
|
||||
pub fn cpu(&self) -> &CPU {
|
||||
&self.cpu
|
||||
}
|
||||
|
||||
pub fn mut_mmu(&mut self) -> &mut MMU {
|
||||
&mut self.mmu
|
||||
}
|
||||
}
|
||||
147
src/gui.rs
Normal file
147
src/gui.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use eframe::{egui, epi};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::emulator::Emulator;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum Register {
|
||||
CPU,
|
||||
CP0,
|
||||
}
|
||||
|
||||
impl Default for Register {
|
||||
fn default() -> Self {
|
||||
Self::CPU
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EmulatorApp {
|
||||
core: Emulator,
|
||||
selected_register: Register,
|
||||
}
|
||||
|
||||
impl Default for EmulatorApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
core: Emulator::new_hle(),
|
||||
selected_register: Register::CPU,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::App for EmulatorApp {
|
||||
fn name(&self) -> &str {
|
||||
"Rultra64"
|
||||
}
|
||||
|
||||
/// Called once before the first frame.
|
||||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::CtxRef,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
) {
|
||||
// Load previous app state (if any).
|
||||
// Note that you must enable the `persistence` feature for this to work.
|
||||
#[cfg(feature = "persistence")]
|
||||
if let Some(storage) = _storage {
|
||||
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by the frame work to save state before shutdown.
|
||||
/// Note that you must enable the `persistence` feature for this to work.
|
||||
#[cfg(feature = "persistence")]
|
||||
fn save(&mut self, storage: &mut dyn epi::Storage) {
|
||||
epi::set_value(storage, epi::APP_KEY, self);
|
||||
}
|
||||
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
let Self { core: emulator_core, selected_register } = self;
|
||||
|
||||
let emulator_core = Rc::new(RefCell::new(emulator_core));
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
// The top panel is often a good place for a menu bar:
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Load ROM").clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
||||
let picked_path = path.display().to_string();
|
||||
if let Ok(rom) = crate::rom::ROM::new_from_filename(&picked_path) {
|
||||
let mut emulator_core = emulator_core.borrow_mut();
|
||||
emulator_core.reload_hle();
|
||||
emulator_core.mut_mmu().set_rom(rom);
|
||||
emulator_core.mut_mmu().hle_ipl();
|
||||
println!("ROM loaded!");
|
||||
}
|
||||
}
|
||||
}
|
||||
if ui.button("Quit").clicked() {
|
||||
frame.quit();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
build_registers_window(ctx, selected_register, emulator_core.clone());
|
||||
build_emulator_controls_window(ctx, emulator_core.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn build_registers_window(ctx: &egui::CtxRef, selected_register: &mut Register, emulator_core: Rc<RefCell<&mut Emulator>>) {
|
||||
egui::Window::new("Registers").vscroll(true).show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(selected_register, Register::CPU, "CPU");
|
||||
ui.selectable_value(selected_register, Register::CP0, "CP0");
|
||||
});
|
||||
ui.separator();
|
||||
match selected_register {
|
||||
Register::CPU => build_cpu_registers(ui, emulator_core),
|
||||
Register::CP0 => {ui.label("CP0 registers");},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fn build_cpu_registers(ui: &mut egui::Ui, emulator_core: Rc<RefCell<&mut Emulator>>) {
|
||||
let emulator_core = emulator_core.borrow();
|
||||
ui.columns(3, |cols| {
|
||||
cols[0].label("#");
|
||||
cols[1].label("Name");
|
||||
cols[2].label("Value");
|
||||
});
|
||||
ui.separator();
|
||||
ui.columns(3, |cols| {
|
||||
cols[0].label("-");
|
||||
cols[1].label("PC");
|
||||
cols[2].label(format!("{:64X}", emulator_core.cpu().registers().get_program_counter()));
|
||||
});
|
||||
ui.columns(3, |cols| {
|
||||
cols[0].label("-");
|
||||
cols[1].label("hi");
|
||||
cols[2].label(format!("{}", emulator_core.cpu().registers().get_hi()));
|
||||
});
|
||||
ui.columns(3, |cols| {
|
||||
cols[0].label("-");
|
||||
cols[1].label("lo");
|
||||
cols[2].label(format!("{}", emulator_core.cpu().registers().get_lo()));
|
||||
});
|
||||
for (index, name) in crate::registers::CPU_REGISTER_NAMES.into_iter().enumerate() {
|
||||
let val = emulator_core.cpu().registers().get_by_name(name);
|
||||
ui.columns(3, |cols| {
|
||||
cols[0].label(format!("r{}", index));
|
||||
cols[1].label(format!("{}", name));
|
||||
cols[2].label(format!("{}", val));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn build_emulator_controls_window(ctx: &egui::CtxRef, emulator_core: Rc<RefCell<&mut Emulator>>) {
|
||||
egui::Window::new("Controls").vscroll(true).show(ctx, |ui| {
|
||||
if ui.button("Tick").clicked() {
|
||||
emulator_core.borrow_mut().tick();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4,3 +4,6 @@ pub mod mmu;
|
||||
pub mod rom;
|
||||
pub mod rdram;
|
||||
pub mod emulator;
|
||||
pub mod rcp;
|
||||
pub mod utils;
|
||||
pub mod gui;
|
||||
39
src/mmu.rs
39
src/mmu.rs
@@ -2,6 +2,7 @@ use std::ops::RangeInclusive;
|
||||
|
||||
use crate::rdram::RDRAM;
|
||||
use crate::rom::ROM;
|
||||
use crate::rcp::RCP;
|
||||
|
||||
pub const KUSEG: RangeInclusive<i64> = 0x00000000..=0x7FFFFFFF;
|
||||
pub const KSEG0: RangeInclusive<i64> = 0x80000000..=0x9FFFFFFF;
|
||||
@@ -39,46 +40,37 @@ pub const EXTERNAL_SYSAD_DEVICE_BUS: RangeInclusive<i64> = 0x80000000..=0xFFF
|
||||
pub struct MMU {
|
||||
rdram: RDRAM,
|
||||
rom: ROM,
|
||||
rcp: RCP,
|
||||
}
|
||||
|
||||
impl MMU {
|
||||
pub fn new() -> Self {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
#[cfg(not(test))]
|
||||
if args.len() < 2 {
|
||||
eprintln!("Please, specify a ROM file");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let rom = match ROM::load_file(&args.get(1).unwrap_or(&"".to_string())) {
|
||||
Ok(rom) => rom,
|
||||
Err(err) => {
|
||||
eprintln!("Could not read ROM: {}", err);
|
||||
std::process::exit(1);
|
||||
},
|
||||
};
|
||||
Self {
|
||||
rdram: RDRAM::new(),
|
||||
rom,
|
||||
rcp: RCP::new(),
|
||||
rom: ROM::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_hle() -> Self {
|
||||
let mut mmu = Self::new();
|
||||
pub fn hle_ipl(&mut self) {
|
||||
// Skip IPL1 and IPL2
|
||||
for i in 0..0x1000 {
|
||||
let byte = mmu.read_virtual(0x10001000 + i, 1);
|
||||
mmu.write_virtual(0x00001000 + i, vec![byte]);
|
||||
let byte = self.read_virtual(0xB0000000 + i, 1);
|
||||
self.write_virtual(0xA4000000 + i, &byte);
|
||||
}
|
||||
// Skip IPL3
|
||||
for i in 0..0x100000 {
|
||||
let byte = mmu.read_physical_byte(0x10001000 + i);
|
||||
mmu.write_physical_byte(0x00001000 + i, byte);
|
||||
let byte = self.read_physical_byte(0x10001000 + i);
|
||||
self.write_physical_byte(0x00001000 + i, byte);
|
||||
}
|
||||
}
|
||||
|
||||
mmu
|
||||
pub fn set_rom(&mut self, rom: ROM) {
|
||||
self.rom = rom;
|
||||
}
|
||||
|
||||
pub fn convert(address: i64) -> i64 {
|
||||
let address = address & 0x00000000FFFFFFFF;
|
||||
if KUSEG.contains(&address) {
|
||||
return address - KUSEG.min().unwrap();
|
||||
} else if KSEG0.contains(&address) {
|
||||
@@ -141,7 +133,7 @@ impl MMU {
|
||||
} else if MIPS_INTERFACE.contains(&address) {
|
||||
return 0;
|
||||
} else if VIDEO_INTERFACE.contains(&address) {
|
||||
return 0;
|
||||
return self.rcp.video_interface.get_register(address);
|
||||
} else if AUDIO_INTERFACE.contains(&address) {
|
||||
return 0;
|
||||
} else if PERIPHERAL_INTERFACE.contains(&address) {
|
||||
@@ -151,7 +143,7 @@ impl MMU {
|
||||
} else if SERIAL_INTERFACE.contains(&address) {
|
||||
return 0;
|
||||
} else if UNUSED.contains(&address) {
|
||||
return 0;
|
||||
return 0xFF;
|
||||
} else if CARTRIDGE_DOMAIN_2_ADDRESS_1.contains(&address) {
|
||||
return 0;
|
||||
} else if CARTRIDGE_DOMAIN_1_ADDRESS_1.contains(&address) {
|
||||
@@ -189,6 +181,7 @@ impl MMU {
|
||||
} else if RDP_SPAN_REGISTERS.contains(&address) {
|
||||
} else if MIPS_INTERFACE.contains(&address) {
|
||||
} else if VIDEO_INTERFACE.contains(&address) {
|
||||
self.rcp.video_interface.set_register(address, data);
|
||||
} else if AUDIO_INTERFACE.contains(&address) {
|
||||
} else if PERIPHERAL_INTERFACE.contains(&address) {
|
||||
} else if RDRAM_INTERFACE.contains(&address) {
|
||||
|
||||
73
src/rcp.rs
Normal file
73
src/rcp.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use crate::rdram::RDRAM;
|
||||
use crate::utils::box_array;
|
||||
|
||||
pub struct VideoInterface {
|
||||
registers: Box<[u8; 0x100000]>,
|
||||
}
|
||||
|
||||
impl VideoInterface {
|
||||
pub fn new() -> Self {
|
||||
let mut registers = box_array![0; 0x100000];
|
||||
// Initialize VI_V_INTR 0x0440 000C: https://n64brew.dev/wiki/Video_Interface#0x0440_000C_-_VI_V_INTR
|
||||
registers[0x0440000C - 0x04400000] = 0xFF;
|
||||
registers[0x0440000B - 0x04400000] = 0x03;
|
||||
// Initialize VI_BURST 0x0440 0014: https://n64brew.dev/wiki/Video_Interface#0x0440_0014_-_VI_BURST
|
||||
registers[0x04400014 - 0x04400000] = 0x01;
|
||||
// Initialize VI_H_SYNC 0x0440 001C: https://n64brew.dev/wiki/Video_Interface#0x0440_001C_-_VI_H_SYNC
|
||||
registers[0x0440001C - 0x04400000] = 0xFF;
|
||||
registers[0x0440001B - 0x04400000] = 0x07;
|
||||
Self {
|
||||
registers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_register(&self, address: i64) -> u8 {
|
||||
self.registers[(address - 0x04400000) as usize]
|
||||
}
|
||||
|
||||
pub fn set_register(&mut self, address: i64, data: u8) {
|
||||
self.registers[(address - 0x04400000) as usize] = data;
|
||||
}
|
||||
|
||||
/*
|
||||
RDRAM base address of the video output Frame Buffer. This can be changed as needed to implement double or triple buffering.
|
||||
https://n64brew.dev/wiki/Video_Interface#0x0440_0004_-_VI_ORIGIN
|
||||
*/
|
||||
pub fn get_vi_origin(&self) -> u32 {
|
||||
((self.get_register(0x04400005) as u32) << 16) | ((self.get_register(0x04400006) as u32) << 8) | (self.get_register(0x04400007) as u32)
|
||||
}
|
||||
|
||||
/*
|
||||
This is the width in pixels of the frame buffer if you draw to the frame buffer based on a different width than what
|
||||
is given here the image will drift with each line to the left or right. The common values are 320 and 640,
|
||||
the maximum value is 640. The minimum value depends on the TV set, 160 would probably be a safe minimum but no guarantee.
|
||||
The same value would also be used on drawing commands for clipping or scissors.
|
||||
This can also be used with High Res interlacing modes to change the odd and even lines of the frame buffer to be drawn
|
||||
to screen by doubling the width of this value and changing the VI_ORIGIN register to the odd or even field being displayed.
|
||||
RDRAM base address of the video output Frame Buffer. This can be changed as needed to implement double or triple buffering.
|
||||
https://n64brew.dev/wiki/Video_Interface#0x0440_0008_-_VI_WIDTH
|
||||
*/
|
||||
pub fn get_vi_width(&self) -> u16 {
|
||||
(((self.get_register(0x04400010) as u16) << 8) & 0b1111) | (self.get_register(0x04400011) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RCP {
|
||||
pub video_interface: VideoInterface,
|
||||
}
|
||||
|
||||
impl RCP {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
video_interface: VideoInterface::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_framebuffer(&self, rdram: &RDRAM, dest: &mut [u8]) {
|
||||
let mut addr = self.video_interface.get_vi_origin() as i64;
|
||||
for elem in dest {
|
||||
*elem = rdram.read8(addr);
|
||||
addr += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::utils::box_array;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Byte {
|
||||
data: u16,
|
||||
@@ -28,13 +30,13 @@ impl Byte {
|
||||
}
|
||||
|
||||
pub struct RDRAM {
|
||||
data: [Byte; 0x400000],
|
||||
data: Box<[Byte; 0x400000]>,
|
||||
}
|
||||
|
||||
impl RDRAM {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: [Byte::new(); 0x400000],
|
||||
data: box_array![Byte::new(); 0x400000],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ impl ROM {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_file(filename: &str) -> std::io::Result<Self> {
|
||||
pub fn new_from_filename(filename: &str) -> std::io::Result<Self> {
|
||||
let mut file = File::open(filename)?;
|
||||
let mut data = vec![];
|
||||
file.read_to_end(&mut data)?;
|
||||
|
||||
13
src/utils.rs
Normal file
13
src/utils.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
#[macro_export]
|
||||
macro_rules! box_array {
|
||||
($val:expr ; $len:expr) => {{
|
||||
fn vec_to_boxed_array<T>(vec: Vec<T>) -> Box<[T; $len]> {
|
||||
let boxed_slice = vec.into_boxed_slice();
|
||||
let ptr = ::std::boxed::Box::into_raw(boxed_slice) as *mut [T; $len];
|
||||
unsafe { Box::from_raw(ptr) }
|
||||
}
|
||||
vec_to_boxed_array(vec![$val; $len])
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use box_array;
|
||||
13
utils.rs
Normal file
13
utils.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
#[macro_export]
|
||||
macro_rules! box_array {
|
||||
($val:expr ; $len:expr) => {{
|
||||
fn vec_to_boxed_array<T>(vec: Vec<T>) -> Box<[T; $len]> {
|
||||
let boxed_slice = vec.into_boxed_slice();
|
||||
let ptr = ::std::boxed::Box::into_raw(boxed_slice) as *mut [T; $len];
|
||||
unsafe { Box::from_raw(ptr) }
|
||||
}
|
||||
vec_to_boxed_array(vec![$val; $len])
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use box_array;
|
||||
Reference in New Issue
Block a user