Merge pull request #1 from Kim-Dewelski/main

Hopefully this will fix stuff 📦
This commit is contained in:
2022-02-07 20:09:50 -05:00
committed by GitHub
10 changed files with 286 additions and 29 deletions

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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
View 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();
}
});
}

View File

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

View File

@@ -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
View 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;
}
}
}

View File

@@ -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],
}
}

View File

@@ -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
View 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;