diff --git a/src/device.rs b/src/device.rs index a7160f6..8bbf9a2 100644 --- a/src/device.rs +++ b/src/device.rs @@ -42,7 +42,10 @@ pub mod tlb; pub mod unmapped; pub mod vi; -pub fn run_game(rom_contents: Vec, device: &mut Device) { +pub fn run_game(device: &mut Device, rom_contents: Vec, fullscreen: bool, overclock: bool) { + device.ui.fullscreen = fullscreen; + device.cpu.overclock = overclock; + cart::rom::init(device, rom_contents); // cart needs to come before rdram // rdram pointer is shared with parallel-rdp @@ -270,6 +273,7 @@ impl Device { pc: 0xBFC00000, pc_phys: 0, llbit: false, + overclock: false, lo: 0, hi: 0, running: false, diff --git a/src/device/cpu.rs b/src/device/cpu.rs index a72ddd9..085bda8 100644 --- a/src/device/cpu.rs +++ b/src/device/cpu.rs @@ -30,6 +30,7 @@ pub struct Cpu { pub hi: u64, pub running: bool, pub llbit: bool, + pub overclock: bool, pub clock_rate: u64, #[serde(skip, default = "savestates::default_instructions")] pub instrs: [fn(&mut device::Device, u32); 64], @@ -223,7 +224,11 @@ pub fn map_instructions(device: &mut device::Device) { } pub fn init(device: &mut device::Device) { - device.cpu.clock_rate = 93750000; + device.cpu.clock_rate = if !device.cpu.overclock { + 93750000 + } else { + 125000000 + }; map_instructions(device); diff --git a/src/main.rs b/src/main.rs index 1315364..27fab5c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -161,8 +161,8 @@ async fn main() { .stack_size(env!("N64_STACK_SIZE").parse().unwrap()) .spawn(move || { let mut device = device::Device::new(); - device.ui.fullscreen = args.fullscreen; - device::run_game(rom_contents, &mut device); + let overclock = device.ui.config.emulation.overclock; + device::run_game(&mut device, rom_contents, args.fullscreen, overclock); }) .unwrap(); diff --git a/src/ui/config.rs b/src/ui/config.rs index a5fdb12..cd831ac 100644 --- a/src/ui/config.rs +++ b/src/ui/config.rs @@ -40,6 +40,7 @@ pub struct Input { pub transfer_pak: [bool; 4], pub emulate_vru: bool, } + #[derive(serde::Serialize, serde::Deserialize)] pub struct Video { pub upscale: u32, @@ -47,10 +48,17 @@ pub struct Video { pub fullscreen: bool, pub widescreen: bool, } + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Emulation { + pub overclock: bool, +} + #[derive(serde::Serialize, serde::Deserialize)] pub struct Config { pub input: Input, pub video: Video, + pub emulation: Emulation, } impl Drop for Config { @@ -99,6 +107,7 @@ impl Config { fullscreen: false, widescreen: false, }, + emulation: Emulation { overclock: false }, } } } diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 36121da..db7dae9 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -18,6 +18,7 @@ pub struct GopherEguiApp { integer_scaling: bool, fullscreen: bool, widescreen: bool, + overclock: bool, emulate_vru: bool, dinput: bool, show_vru_dialog: bool, @@ -44,6 +45,7 @@ struct SaveConfig { fullscreen: bool, widescreen: bool, emulate_vru: bool, + overclock: bool, } fn get_input_profiles(config: &ui::config::Config) -> Vec { @@ -120,6 +122,7 @@ impl GopherEguiApp { fullscreen: config.video.fullscreen, widescreen: config.video.widescreen, emulate_vru: config.input.emulate_vru, + overclock: config.emulation.overclock, show_vru_dialog: false, dinput: false, controller_paths, @@ -157,6 +160,8 @@ fn save_config( config.video.fullscreen = save_config_items.fullscreen; config.video.widescreen = save_config_items.widescreen; config.input.emulate_vru = save_config_items.emulate_vru; + + config.emulation.overclock = save_config_items.overclock; } impl Drop for GopherEguiApp { @@ -171,6 +176,7 @@ impl Drop for GopherEguiApp { fullscreen: self.fullscreen, widescreen: self.widescreen, emulate_vru: self.emulate_vru, + overclock: self.overclock, }; let mut config = ui::config::Config::new(); save_config( @@ -295,7 +301,7 @@ fn get_latest_version(app: &mut GopherEguiApp, ctx: &egui::Context) { } } -pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { +pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context, enable_overclock: bool) { let netplay; let selected_controller = app.selected_controller; @@ -307,6 +313,7 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { let fullscreen = app.fullscreen; let widescreen = app.widescreen; let emulate_vru = app.emulate_vru; + let overclock = app.overclock; let peer_addr; let session; let player_number; @@ -402,6 +409,7 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { fullscreen, widescreen, emulate_vru, + overclock, }; if file.is_some() || netplay { @@ -424,8 +432,7 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { session.unwrap(), player_number.unwrap(), )); - device.ui.fullscreen = fullscreen; - device::run_game(rom_contents, &mut device); + device::run_game(&mut device, rom_contents, fullscreen, enable_overclock); netplay::close(&mut device); } else { for i in 0..4 { @@ -448,8 +455,12 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { if rom_contents.is_empty() { println!("Could not read rom file"); } else { - device.ui.fullscreen = fullscreen; - device::run_game(rom_contents, &mut device); + device::run_game( + &mut device, + rom_contents, + fullscreen, + enable_overclock, + ); } } let result = std::fs::remove_file(running_file); @@ -492,7 +503,7 @@ impl eframe::App for GopherEguiApp { .min_col_width(200.0) .show(ui, |ui| { if ui.button("Open ROM").clicked() { - open_rom(self, ctx); + open_rom(self, ctx, self.overclock); } if ui.button("Netplay: Create Session").clicked() && !self.dirs.cache_dir.join("game_running").exists() @@ -616,6 +627,9 @@ impl eframe::App for GopherEguiApp { ui.checkbox(&mut self.widescreen, "Widescreen (stretch)"); ui.add_space(16.0); + ui.checkbox(&mut self.overclock, "Overclock N64 CPU (may cause bugs)"); + ui.add_space(16.0); + ui.hyperlink_to("Wiki", "https://github.com/gopher64/gopher64/wiki"); ui.hyperlink_to("Discord Server", "https://discord.gg/9RGXq8W8JQ"); ui.add_space(16.0); diff --git a/src/ui/gui/gui_netplay.rs b/src/ui/gui/gui_netplay.rs index 0c20a80..486668e 100644 --- a/src/ui/gui/gui_netplay.rs +++ b/src/ui/gui/gui_netplay.rs @@ -68,6 +68,7 @@ pub struct NetplayRoom { md5: Option, game_name: Option, pub port: Option, + features: Option>, } #[derive(serde::Serialize, serde::Deserialize)] @@ -310,6 +311,8 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) { // If the ROM doesn't report a name, use the filename game_name = app.netplay.create_rom_label.clone(); } + let mut features = std::collections::HashMap::new(); + features.insert("overclock".to_string(), app.overclock.to_string()); let netplay_message = NetplayMessage { message_type: "request_create_room".to_string(), player_name: Some(app.netplay.player_name.clone()), @@ -329,6 +332,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) { md5: Some(app.netplay.game_info.md5.clone()), protected: None, port: None, + features: Some(features), }), }; let (mut socket, _response) = @@ -502,6 +506,7 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) { md5: Some(app.netplay.game_info.md5.clone()), protected: None, port: app.netplay.selected_session.as_ref().unwrap().port, + features: None, }), }; let socket = app.netplay.socket.as_mut().unwrap(); @@ -651,6 +656,17 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) { } pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) { + let overclock = app + .netplay + .waiting_session + .as_ref() + .unwrap() + .features + .as_ref() + .unwrap() + .get("overclock") + .unwrap(); + let motd_message = NetplayMessage { message_type: "request_motd".to_string(), player_name: None, @@ -684,6 +700,7 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) { md5: None, protected: None, port: app.netplay.waiting_session.as_ref().unwrap().port, + features: None, }), }; @@ -722,6 +739,7 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) { md5: None, protected: None, port: app.netplay.waiting_session.as_ref().unwrap().port, + features: None, }), }; let socket = app.netplay.socket.as_mut().unwrap(); @@ -753,6 +771,7 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) { md5: None, protected: None, port: app.netplay.waiting_session.as_ref().unwrap().port, + features: None, }), }; app.netplay.chat_message.clear(); @@ -801,7 +820,7 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) { }; } - gui::open_rom(app, ctx); + gui::open_rom(app, ctx, *overclock == "true"); app.netplay = Default::default(); return; } else {