* gui

* name

* more

* more

* fix

* warn

* more

* remove dnd

* more

* more

* more

* more

* more

* more

* more

* more

* more

* linter

* more

* more

* more

* more

* linter

* more

* linter

* fix

* more
This commit is contained in:
Logan McNaughton 2023-11-02 10:45:11 -06:00 committed by GitHub
parent 268048f76c
commit cce163921e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 404 additions and 151 deletions

View File

@ -13,9 +13,11 @@ sevenz-rust = "0.5"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
egui = "0.23"
eframe = "0.23"
rfd = {version = "0.12", default-features = false, features = ["xdg-portal"] }
sha2 = "0.10"
rusttype = "0.9"
futures = "0.3"
sdl2-sys = { git = "https://github.com/Rust-SDL2/rust-sdl2.git" }
clap = { version = "4.4", features = ["derive"] }

View File

@ -1,4 +1,6 @@
use crate::ui;
use std::fs;
use std::io::Read;
pub mod ai;
pub mod cache;
@ -32,6 +34,109 @@ pub mod tlb;
pub mod unmapped;
pub mod vi;
pub fn run_game(file_path: &std::path::Path, device: &mut Device, fullscreen: bool) {
let rom_contents = get_rom_contents(file_path);
cart_rom::init(device, rom_contents); // cart needs to come before rdram
// rdram pointer is shared with parallel-rdp
let (rdram_ptr, rdram_size) = rdram::init(device);
ui::audio::init(&mut device.ui, 33600);
ui::video::init(&mut device.ui, rdram_ptr, rdram_size, fullscreen);
ui::input::init(&mut device.ui);
mi::init(device);
pif::init(device);
memory::init(device);
rsp_interface::init(device);
rdp::init(device);
vi::init(device);
cpu::init(device);
ui::storage::init(&mut device.ui);
ui::storage::load_saves(&mut device.ui);
cart_rom::load_rom_save(device);
cpu::run(device);
}
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
let test = u32::from_be_bytes(contents[0..4].try_into().unwrap());
if test == 0x80371240 {
// z64
contents
} else if test == 0x37804012 {
// v64
let mut data: Vec<u8> = vec![0; contents.len()];
for i in (0..contents.len()).step_by(2) {
let temp = u16::from_ne_bytes(contents[i..i + 2].try_into().unwrap());
data[i..i + 2].copy_from_slice(&temp.to_be_bytes());
}
return data;
} else if test == 0x40123780 {
// n64
let mut data: Vec<u8> = vec![0; contents.len()];
for i in (0..contents.len()).step_by(4) {
let temp = u32::from_ne_bytes(contents[i..i + 4].try_into().unwrap());
data[i..i + 4].copy_from_slice(&temp.to_be_bytes());
}
return data;
} else {
panic!("unknown rom format")
}
}
fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
let mut contents = vec![];
if file_path.extension().unwrap().to_ascii_lowercase() == "zip" {
let zip_file = fs::File::open(file_path).unwrap();
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let extension = file
.enclosed_name()
.unwrap()
.extension()
.unwrap()
.to_ascii_lowercase();
if extension == "z64" || extension == "n64" || extension == "v64" {
file.read_to_end(&mut contents)
.expect("could not read zip file");
break;
}
}
} else if file_path.extension().unwrap().to_ascii_lowercase() == "7z" {
let mut archive =
sevenz_rust::SevenZReader::open(file_path, sevenz_rust::Password::empty()).unwrap();
let mut found = false;
archive
.for_each_entries(
&mut |entry: &sevenz_rust::SevenZArchiveEntry, reader: &mut dyn std::io::Read| {
let name = entry.name().to_ascii_lowercase();
if !found
&& (name.ends_with("z64") || name.ends_with("n64") || name.ends_with("v64"))
{
reader
.read_to_end(&mut contents)
.expect("could not read zip file");
found = true;
} else {
//skip other files
std::io::copy(reader, &mut std::io::sink())?;
}
Ok(true)
},
)
.expect("ok");
} else {
contents = fs::read(file_path).expect("Should have been able to read the file");
}
swap_rom(contents)
}
pub struct Device {
pub ui: ui::Ui,
byte_swap: usize,

View File

@ -1,7 +1,7 @@
#![feature(round_ties_even)]
#![feature(string_remove_matches)]
use std::fs;
use std::io::Read;
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
mod device;
mod ui;
use clap::Parser;
@ -55,158 +55,79 @@ struct Args {
clear_input_bindings: bool,
}
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
let test = u32::from_be_bytes(contents[0..4].try_into().unwrap());
if test == 0x80371240 {
// z64
contents
} else if test == 0x37804012 {
// v64
let mut data: Vec<u8> = vec![0; contents.len()];
for i in (0..contents.len()).step_by(2) {
let temp = u16::from_ne_bytes(contents[i..i + 2].try_into().unwrap());
data[i..i + 2].copy_from_slice(&temp.to_be_bytes());
}
return data;
} else if test == 0x40123780 {
// n64
let mut data: Vec<u8> = vec![0; contents.len()];
for i in (0..contents.len()).step_by(4) {
let temp = u32::from_ne_bytes(contents[i..i + 4].try_into().unwrap());
data[i..i + 4].copy_from_slice(&temp.to_be_bytes());
}
return data;
} else {
panic!("unknown rom format")
}
}
fn main() {
let cache_dir = dirs::cache_dir().unwrap().join("gopher64");
fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
let mut contents = vec![];
if file_path.extension().unwrap().to_ascii_lowercase() == "zip" {
let zip_file = fs::File::open(file_path).unwrap();
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let extension = file
.enclosed_name()
.unwrap()
.extension()
.unwrap()
.to_ascii_lowercase();
if extension == "z64" || extension == "n64" || extension == "v64" {
file.read_to_end(&mut contents)
.expect("could not read zip file");
break;
let _ = std::fs::create_dir_all(cache_dir.clone());
let _ = std::fs::remove_file(cache_dir.clone().join("game_running"));
let args = Args::parse();
let args_as_strings: Vec<String> = std::env::args().collect();
let args_count = args_as_strings.len();
if args_count > 1 {
let mut device = device::Device::new();
if args.clear_input_bindings {
ui::input::clear_bindings(&mut device.ui);
return;
}
if args.port.is_some() {
let port = args.port.unwrap();
if !(1..=4).contains(&port) {
println!("Port must be betwen 1 and 4");
return;
}
}
} else if file_path.extension().unwrap().to_ascii_lowercase() == "7z" {
let mut archive =
sevenz_rust::SevenZReader::open(file_path, sevenz_rust::Password::empty()).unwrap();
if args.list_controllers {
ui::input::list_controllers(&device.ui);
return;
}
if args.assign_controller.is_some() {
if args.port.is_none() {
println!("Must specify port number");
return;
}
ui::input::assign_controller(
&mut device.ui,
args.assign_controller.unwrap(),
args.port.unwrap(),
);
return;
}
if args.bind_input_profile.is_some() {
if args.port.is_none() {
println!("Must specify port number");
return;
}
ui::input::bind_input_profile(
&mut device.ui,
args.bind_input_profile.unwrap(),
args.port.unwrap(),
);
return;
}
if args.configure_input_profile.is_some() {
ui::input::configure_input_profile(
&mut device.ui,
args.configure_input_profile.unwrap(),
);
return;
}
let mut found = false;
archive
.for_each_entries(
&mut |entry: &sevenz_rust::SevenZArchiveEntry, reader: &mut dyn std::io::Read| {
let name = entry.name().to_ascii_lowercase();
if !found
&& (name.ends_with("z64") || name.ends_with("n64") || name.ends_with("v64"))
{
reader
.read_to_end(&mut contents)
.expect("could not read zip file");
found = true;
} else {
//skip other files
std::io::copy(reader, &mut std::io::sink())?;
}
Ok(true)
},
)
.expect("ok");
if args.game.is_some() {
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
device::run_game(file_path, &mut device, args.fullscreen);
}
} else {
contents = fs::read(file_path).expect("Should have been able to read the file");
}
swap_rom(contents)
}
fn run_game(file_path: &std::path::Path, device: &mut device::Device, fullscreen: bool) {
let rom_contents = get_rom_contents(file_path);
device::cart_rom::init(device, rom_contents); // cart needs to come before rdram
// rdram pointer is shared with parallel-rdp
let (rdram_ptr, rdram_size) = device::rdram::init(device);
ui::audio::init(&mut device.ui, 33600);
ui::video::init(&mut device.ui, rdram_ptr, rdram_size, fullscreen);
ui::input::init(&mut device.ui);
device::mi::init(device);
device::pif::init(device);
device::memory::init(device);
device::rsp_interface::init(device);
device::rdp::init(device);
device::vi::init(device);
device::cpu::init(device);
ui::storage::init(&mut device.ui);
ui::storage::load_saves(&mut device.ui);
device::cart_rom::load_rom_save(device);
device::cpu::run(device);
}
fn main() {
let args = Args::parse();
let mut device = device::Device::new();
if args.clear_input_bindings {
ui::input::clear_bindings(&mut device.ui);
return;
}
if args.port.is_some() {
let port = args.port.unwrap();
if !(1..=4).contains(&port) {
println!("Port must be betwen 1 and 4");
return;
}
}
if args.list_controllers {
ui::input::list_controllers(&device.ui);
return;
}
if args.assign_controller.is_some() {
if args.port.is_none() {
println!("Must specify port number");
return;
}
ui::input::assign_controller(
&mut device.ui,
args.assign_controller.unwrap(),
args.port.unwrap(),
);
return;
}
if args.bind_input_profile.is_some() {
if args.port.is_none() {
println!("Must specify port number");
return;
}
ui::input::bind_input_profile(
&mut device.ui,
args.bind_input_profile.unwrap(),
args.port.unwrap(),
);
return;
}
if args.configure_input_profile.is_some() {
ui::input::configure_input_profile(&mut device.ui, args.configure_input_profile.unwrap());
return;
}
if args.game.is_some() {
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
run_game(file_path, &mut device, args.fullscreen);
let options = eframe::NativeOptions {
initial_window_size: Some(eframe::egui::vec2(640.0, 480.0)),
..Default::default()
};
eframe::run_native(
"gopher64",
options,
Box::new(|_cc| Box::new(ui::gui::GopherEguiApp::new())),
)
.unwrap();
}
}

View File

@ -1,5 +1,6 @@
pub mod audio;
pub mod config;
pub mod gui;
pub mod input;
pub mod storage;
pub mod video;

220
src/ui/gui.rs Normal file
View File

@ -0,0 +1,220 @@
use crate::device;
use crate::ui;
use eframe::egui;
pub struct GopherEguiApp {
configure_profile: bool,
profile_name: String,
controllers: Vec<String>,
selected_controller: [i32; 4],
selected_profile: [String; 4],
input_profiles: Vec<String>,
}
fn get_input_profiles(game_ui: &ui::Ui) -> Vec<String> {
let mut profiles = vec![];
for key in game_ui.config.input.input_profiles.keys() {
profiles.push((*key).clone())
}
profiles
}
fn get_controllers(game_ui: &ui::Ui) -> Vec<String> {
let mut controllers: Vec<String> = vec![];
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
for i in 0..num_joysticks {
controllers.push(joystick_subsystem.name_for_index(i).unwrap());
}
controllers
}
impl GopherEguiApp {
pub fn new() -> GopherEguiApp {
let game_ui = ui::Ui::new();
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
let mut guids: Vec<String> = vec![];
for i in 0..num_joysticks {
guids.push(joystick_subsystem.device_guid(i).unwrap().to_string());
}
let mut selected_controller = [-1, -1, -1, -1];
for (pos, item) in game_ui
.config
.input
.controller_assignment
.iter()
.enumerate()
{
if item.is_some() {
for (guid_pos, guid) in guids.iter().enumerate() {
if item.as_deref().unwrap() == *guid {
selected_controller[pos] = guid_pos as i32;
break;
}
}
}
}
GopherEguiApp {
configure_profile: false,
profile_name: "".to_string(),
selected_profile: game_ui.config.input.input_profile_binding.clone(),
selected_controller,
controllers: get_controllers(&game_ui),
input_profiles: get_input_profiles(&game_ui),
}
}
}
fn save_config(game_ui: &mut ui::Ui, selected_controller: [i32; 4], selected_profile: [String; 4]) {
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
for (pos, item) in selected_controller.iter().enumerate() {
if *item != -1 {
game_ui.config.input.controller_assignment[pos] = Some(
joystick_subsystem
.device_guid(*item as u32)
.unwrap()
.to_string(),
);
} else {
game_ui.config.input.controller_assignment[pos] = None
}
}
game_ui.config.input.input_profile_binding = selected_profile;
}
impl Drop for GopherEguiApp {
fn drop(&mut self) {
let mut game_ui = ui::Ui::new();
save_config(
&mut game_ui,
self.selected_controller,
self.selected_profile.clone(),
);
}
}
impl eframe::App for GopherEguiApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
if self.configure_profile {
egui::Window::new("Configure Input Profile")
// .open(&mut self.configure_profile)
.show(ctx, |ui| {
ui.horizontal(|ui| {
let name_label = ui.label("Profile Name: ");
ui.text_edit_singleline(&mut self.profile_name)
.labelled_by(name_label.id);
});
ui.horizontal(|ui| {
if ui.button("Configure Profile").clicked() {
let profile_name = self.profile_name.clone();
execute(async {
let mut game_ui = ui::Ui::new();
ui::input::configure_input_profile(&mut game_ui, profile_name);
});
self.configure_profile = false;
if !self.profile_name.is_empty() && self.profile_name != "default" {
self.input_profiles.push(self.profile_name.clone())
}
};
if ui.button("Close").clicked() {
self.configure_profile = false
};
});
});
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.set_enabled(!self.configure_profile);
if ui.button("Open ROM").clicked() {
// Spawn dialog on main thread
let task = rfd::AsyncFileDialog::new().pick_file();
let selected_controller = self.selected_controller;
let selected_profile = self.selected_profile.clone();
execute(async move {
let file = task.await;
if let Some(file) = file {
let running_file = dirs::cache_dir()
.unwrap()
.join("gopher64")
.join("game_running");
if running_file.exists() {
return;
}
let _ = std::fs::File::create(running_file.clone());
let mut device = device::Device::new();
save_config(&mut device.ui, selected_controller, selected_profile);
device::run_game(std::path::Path::new(file.path()), &mut device, false);
let _ = std::fs::remove_file(running_file.clone());
}
});
}
if ui.button("Configure Input Profile").clicked() {
let running_file = dirs::cache_dir()
.unwrap()
.join("gopher64")
.join("game_running");
if running_file.exists() {
return;
}
self.configure_profile = true;
}
ui.add_space(32.0);
ui.label("Controller Config:");
egui::Grid::new("some_unique_id").show(ui, |ui| {
ui.label("Port");
ui.label("Profile");
ui.label("Controller");
ui.end_row();
for i in 0..4 {
ui.label(format!("{}", i + 1));
egui::ComboBox::from_id_source(format!("profile-combo-{}", i))
.selected_text(self.selected_profile[i].clone())
.show_ui(ui, |ui| {
for j in 0..self.input_profiles.len() {
ui.selectable_value(
&mut self.selected_profile[i],
self.input_profiles[j].clone(),
self.input_profiles[j].clone(),
);
}
});
let controller_text = if self.selected_controller[i] == -1 {
"None".to_string()
} else {
self.controllers[self.selected_controller[i] as usize].clone()
};
egui::ComboBox::from_id_source(format!("controller-combo-{}", i))
.selected_text(controller_text)
.show_ui(ui, |ui| {
ui.selectable_value(
&mut self.selected_controller[i],
-1,
"None".to_string(),
);
for j in 0..self.controllers.len() {
ui.selectable_value(
&mut self.selected_controller[i],
j as i32,
self.controllers[j].clone(),
);
}
});
ui.end_row();
}
});
});
}
}
fn execute<F: std::future::Future<Output = ()> + Send + 'static>(f: F) {
std::thread::spawn(move || futures::executor::block_on(f));
}

View File

@ -298,6 +298,10 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String) {
println!("Profile name cannot be default");
return;
}
if profile.is_empty() {
println!("Profile name cannot be empty");
return;
}
let joystick_subsystem = ui.sdl_context.as_ref().unwrap().joystick().unwrap();
let mut joysticks = vec![];
for i in 0..joystick_subsystem.num_joysticks().unwrap() {