controller config (#19)

* controller config

* more

* list controllers

* add config file

* save at end

* fix path

* more

* clean

* readme

* more

* assign controller

* clear option

* more

* add bind profile

* pretty

* stub

* comments

* add comment

* move

* more

* use i

* use none

* add types

* more

* more

* more

* more

* more

* more controller stuff

* some joystick

* cleaner

* func

* func

* more

* clean

* more func

* more

* hat

* change defauly

* only p1

* bind using guid

* more

* windows

* add font

* more

* clean

* warnins

* more

* more

* more

* more

* more

* joystick

* clean

* more

* more
This commit is contained in:
Logan McNaughton 2023-10-23 21:44:14 +03:00 committed by GitHub
parent 887e561101
commit 94cfc61678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 818 additions and 100 deletions

View File

@ -11,8 +11,10 @@ zip = "0.6"
governor = "0.6"
sevenz-rust = "0.5"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = "0.10"
rusttype = "0.9"
sdl2-sys = { git = "https://github.com/Rust-SDL2/rust-sdl2.git" }
clap = { version = "4.4", features = ["derive"] }

View File

@ -1,5 +1,29 @@
# gopher64
```
Usage: gopher64 [OPTIONS] [GAME]
Arguments:
[GAME]
Options:
-f, --fullscreen
-c, --configure-input-profile <PROFILE_NAME>
Create a new input profile (keyboard/gamepad mappings).
-b, --bind-input-profile <PROFILE_NAME>
Must also specify --port. Used to bind a previously created profile to a port
-l, --list-controllers
Lists connected controllers which can be used in --assign-controller
-a, --assign-controller <CONTROLLER_NUMBER>
Must also specify --port. Used to assign a controller listed in --list-controllers to a port
-p, --port <PORT>
Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller
-h, --help
Print help
-V, --version
Print version
```
## goals
1. Performance. I want to be able to use this emulator on my laptop.
@ -16,7 +40,11 @@
## controls
Right now, only keyboard is supported. Keys are mapped according to mupen64plus defaults: https://mupen64plus.org/wiki/index.php/KeyboardSetup#2._Default_Key_Mappings_for_SDL-Input_Plugin
Keys are mapped according to mupen64plus defaults: https://mupen64plus.org/wiki/index.php/KeyboardSetup#2._Default_Key_Mappings_for_SDL-Input_Plugin. Xbox-style controllers also have a default mapping applied.
You can create you own mappings by running `./gopher64 --configure-input-profile my_profile`. You then bind that profile to a port: `./gopher64 --bind-input-profile my_profile --port 1`
In order to use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller <controller_number> --port 1`
## contributing

BIN
data/Roboto-Regular.ttf Normal file

Binary file not shown.

View File

@ -96,8 +96,8 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
pub fn format_eeprom(device: &mut device::Device) {
if device.ui.saves.eeprom.len() < EEPROM_MAX_SIZE {
device.ui.saves.eeprom.resize(EEPROM_MAX_SIZE, 0xFF)
if device.ui.saves.eeprom.0.len() < EEPROM_MAX_SIZE {
device.ui.saves.eeprom.0.resize(EEPROM_MAX_SIZE, 0xFF)
}
}
@ -107,7 +107,7 @@ pub fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usiz
format_eeprom(device);
device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE]
.copy_from_slice(&device.ui.saves.eeprom[address..address + EEPROM_BLOCK_SIZE]);
.copy_from_slice(&device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]);
}
pub fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
@ -115,20 +115,12 @@ pub fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usi
format_eeprom(device);
device.ui.saves.eeprom[address..address + EEPROM_BLOCK_SIZE]
device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]
.copy_from_slice(&device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE]);
device.pif.ram[status as usize] = 0x00;
let mut save_type = ui::storage::SaveTypes::Eeprom4k;
if device
.ui
.save_type
.contains(&ui::storage::SaveTypes::Eeprom16k)
{
save_type = ui::storage::SaveTypes::Eeprom16k
}
ui::storage::write_save(&mut device.ui, save_type);
device.ui.saves.eeprom.1 = true
}
pub fn time2data(device: &mut device::Device, offset: usize) {

View File

@ -1,5 +1,4 @@
use crate::device;
use crate::ui;
use sha2::{Digest, Sha256};
pub enum CicType {
@ -84,14 +83,14 @@ pub fn dma_read(
device.cart.rom[(cart_addr + i) as usize] =
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
device.ui.saves.romsave.insert(
device.ui.saves.romsave.0.insert(
(cart_addr + i).to_string(),
serde_json::to_value(device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap])
.unwrap(),
);
}
ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Romsave);
device.ui.saves.romsave.1 = true;
return device::pi::calculate_cycles(device, 1, length);
}
@ -131,10 +130,10 @@ pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
}
pub fn load_rom_save(device: &mut device::Device) {
if device.ui.saves.romsave.is_empty() {
if device.ui.saves.romsave.0.is_empty() {
return;
}
for (key, value) in device.ui.saves.romsave.iter() {
for (key, value) in device.ui.saves.romsave.0.iter() {
device.cart.rom[key.parse::<usize>().unwrap()] =
serde_json::from_value(value.clone()).unwrap()
}

View File

@ -1,12 +1,11 @@
use crate::device;
use crate::ui;
pub const MEMPAK_SIZE: usize = 0x8000;
pub const MPK_PAGE_SIZE: usize = 256;
pub fn format_mempak(device: &mut device::Device) {
if device.ui.saves.mempak.len() < MEMPAK_SIZE * 4 {
device.ui.saves.mempak.resize(MEMPAK_SIZE * 4, 0);
if device.ui.saves.mempak.0.len() < MEMPAK_SIZE * 4 {
device.ui.saves.mempak.0.resize(MEMPAK_SIZE * 4, 0);
let page_0: [u8; MPK_PAGE_SIZE] = [
/* Label area */
@ -39,28 +38,28 @@ pub fn format_mempak(device: &mut device::Device) {
let offset = i * MEMPAK_SIZE;
/* Fill Page 0 with pre-initialized content */
device.ui.saves.mempak[offset..offset + MPK_PAGE_SIZE].copy_from_slice(&page_0);
device.ui.saves.mempak.0[offset..offset + MPK_PAGE_SIZE].copy_from_slice(&page_0);
/* Fill INODE page 1 and update it's checkum */
let start_page = 5;
for i in 1 * MPK_PAGE_SIZE..2 * start_page {
device.ui.saves.mempak[offset + i] = 0;
device.ui.saves.mempak.0[offset + i] = 0;
}
for i in (1 * MPK_PAGE_SIZE + 2 * start_page..2 * MPK_PAGE_SIZE).step_by(2) {
device.ui.saves.mempak[offset + i + 0] = 0x00;
device.ui.saves.mempak[offset + i + 1] = 0x03;
device.ui.saves.mempak.0[offset + i + 0] = 0x00;
device.ui.saves.mempak.0[offset + i + 1] = 0x03;
}
device.ui.saves.mempak[offset + (1 * MPK_PAGE_SIZE + 1)] = 0x71;
device.ui.saves.mempak.0[offset + (1 * MPK_PAGE_SIZE + 1)] = 0x71;
/* Page 2 is identical to page 1 */
let page1 = offset + 1 * MPK_PAGE_SIZE;
let page2 = offset + 2 * MPK_PAGE_SIZE;
let page1data = device.ui.saves.mempak[page1..page1 + MPK_PAGE_SIZE].to_vec();
device.ui.saves.mempak[page2..page2 + MPK_PAGE_SIZE].copy_from_slice(&page1data);
let page1data = device.ui.saves.mempak.0[page1..page1 + MPK_PAGE_SIZE].to_vec();
device.ui.saves.mempak.0[page2..page2 + MPK_PAGE_SIZE].copy_from_slice(&page1data);
/* Remaining pages DIR+DATA (3...) are initialized with 0x00 */
for i in 3 * MPK_PAGE_SIZE..MEMPAK_SIZE - 3 * MPK_PAGE_SIZE {
device.ui.saves.mempak[offset + i] = 0;
device.ui.saves.mempak.0[offset + i] = 0;
}
}
}
@ -72,7 +71,7 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi
let offset = (channel * MEMPAK_SIZE) + address as usize;
device.pif.ram[data..data + size]
.copy_from_slice(&device.ui.saves.mempak[offset..offset + size])
.copy_from_slice(&device.ui.saves.mempak.0[offset..offset + size])
} else {
for i in 0..size {
device.pif.ram[data + i] = 0;
@ -85,9 +84,9 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
format_mempak(device);
let offset = (channel * MEMPAK_SIZE) + address as usize;
device.ui.saves.mempak[offset..offset + size]
device.ui.saves.mempak.0[offset..offset + size]
.copy_from_slice(&device.pif.ram[data..data + size]);
ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Mempak);
device.ui.saves.mempak.1 = true
}
}

View File

@ -28,14 +28,14 @@ pub struct Flashram {
}
pub fn format_sram(device: &mut device::Device) {
if device.ui.saves.sram.len() < SRAM_SIZE {
device.ui.saves.sram.resize(SRAM_SIZE, 0xFF)
if device.ui.saves.sram.0.len() < SRAM_SIZE {
device.ui.saves.sram.0.resize(SRAM_SIZE, 0xFF)
}
}
pub fn format_flash(device: &mut device::Device) {
if device.ui.saves.flash.len() < FLASHRAM_SIZE {
device.ui.saves.flash.resize(FLASHRAM_SIZE, 0xFF)
if device.ui.saves.flash.0.len() < FLASHRAM_SIZE {
device.ui.saves.flash.0.resize(FLASHRAM_SIZE, 0xFF)
}
}
@ -45,7 +45,7 @@ pub fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
format_sram(device);
return u32::from_be_bytes(
device.ui.saves.sram[masked_address..masked_address + 4]
device.ui.saves.sram.0[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
@ -86,14 +86,14 @@ pub fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mas
format_sram(device);
let mut data = u32::from_be_bytes(
device.ui.saves.sram[masked_address..masked_address + 4]
device.ui.saves.sram.0[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.ui.saves.sram[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
device.ui.saves.sram.0[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Sram);
device.ui.saves.sram.1 = true
}
pub fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
@ -142,12 +142,12 @@ pub fn dma_read_sram(
format_sram(device);
while i < dram_addr + length {
device.ui.saves.sram[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
device.ui.saves.sram.0[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
i += 1;
j += 1;
}
ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Sram);
device.ui.saves.sram.1 = true
}
pub fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
@ -192,7 +192,7 @@ pub fn dma_write_sram(
format_sram(device);
while i < dram_addr + length {
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.sram[j as usize];
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.sram.0[j as usize];
i += 1;
j += 1;
}
@ -233,7 +233,7 @@ pub fn dma_write_flash(
/* do actual DMA */
for i in 0..length {
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap] =
device.ui.saves.flash[(cart_addr + i) as usize];
device.ui.saves.flash.0[(cart_addr + i) as usize];
}
} else {
/* other accesses are not implemented */
@ -272,14 +272,14 @@ pub fn flashram_command(device: &mut device::Device, command: u32) {
if device.flashram.mode == FlashramMode::SectorErase {
let offset: usize = (device.flashram.erase_page & 0xff80) as usize * 128;
for i in 0..128 * 128 {
device.ui.saves.flash[offset as usize + i] = 0xFF;
device.ui.saves.flash.0[offset as usize + i] = 0xFF;
}
ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Flash);
device.ui.saves.flash.1 = true
} else if device.flashram.mode == FlashramMode::ChipErase {
for i in 0..FLASHRAM_SIZE {
device.ui.saves.flash[i] = 0xFF;
device.ui.saves.flash.0[i] = 0xFF;
}
ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Flash);
device.ui.saves.flash.1 = true
} else {
panic!("Unexpected flash erase command")
}
@ -297,9 +297,10 @@ pub fn flashram_command(device: &mut device::Device, command: u32) {
/* program selected page */
let offset: usize = (command & 0xffff) as usize * 128;
for i in 0..128 {
device.ui.saves.flash[(offset + i) as usize] = device.flashram.page_buf[i as usize];
device.ui.saves.flash.0[(offset + i) as usize] =
device.flashram.page_buf[i as usize];
}
ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Flash);
device.ui.saves.flash.1 = true;
/* clear program busy flag, set program success flag, transition to status mode */
device.flashram.status &= !0x01;

View File

@ -13,6 +13,46 @@ struct Args {
game: Option<String>,
#[arg(short, long)]
fullscreen: bool,
#[arg(
short,
long,
value_name = "PROFILE_NAME",
help = "Create a new input profile (keyboard/gamepad mappings)."
)]
configure_input_profile: Option<String>,
#[arg(
short,
long,
value_name = "PROFILE_NAME",
help = "Must also specify --port. Used to bind a previously created profile to a port"
)]
bind_input_profile: Option<String>,
#[arg(
short,
long,
help = "Lists connected controllers which can be used in --assign-controller"
)]
list_controllers: bool,
#[arg(
short,
long,
value_name = "CONTROLLER_NUMBER",
help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port"
)]
assign_controller: Option<u32>,
#[arg(
short,
long,
value_name = "PORT",
help = "Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller"
)]
port: Option<usize>,
#[arg(
short = 'z',
long,
help = "Clear all input profile bindings and controller assignments"
)]
clear_input_bindings: bool,
}
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
@ -93,12 +133,55 @@ fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
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 port < 1 || port > 4 {
println!("Port must be betwen 1 and 4");
return;
}
}
if args.list_controllers {
ui::input::list_controllers(&mut 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 file_path = std::path::Path::new(args.game.as_ref().unwrap());
let rom_contents = get_rom_contents(file_path);
let mut device = device::Device::new();
device::cart_rom::init(&mut device, rom_contents); // cart needs to come before rdram
// rdram pointer is shared with parallel-rdp
@ -106,6 +189,7 @@ fn main() {
ui::audio::init(&mut device.ui, 33600);
ui::video::init(&mut device.ui, rdram_ptr, rdram_size, args.fullscreen);
ui::input::init(&mut device.ui);
device::mi::init(&mut device);
device::pif::init(&mut device);

View File

@ -1,9 +1,13 @@
pub mod audio;
pub mod config;
pub mod input;
pub mod storage;
pub mod video;
pub struct Ui {
pub controllers: [input::Controllers; 4],
pub config_file_path: std::path::PathBuf,
pub config: config::Config,
pub save_type: Vec<storage::SaveTypes>,
pub game_id: String,
pub game_hash: String,
@ -12,16 +16,66 @@ pub struct Ui {
pub sdl_context: Option<sdl2::Sdl>,
pub video_subsystem: Option<sdl2::VideoSubsystem>,
pub audio_subsystem: Option<sdl2::AudioSubsystem>,
pub joystick_subsystem: Option<sdl2::JoystickSubsystem>,
pub controller_subsystem: Option<sdl2::GameControllerSubsystem>,
pub window: Option<sdl2::video::Window>,
pub audio_device: Option<sdl2::audio::AudioQueue<i16>>,
}
impl Drop for Ui {
fn drop(&mut self) {
storage::write_saves(self);
write_config(self);
}
}
fn write_config(ui: &mut Ui) {
let f = std::fs::File::create(ui.config_file_path.clone()).unwrap();
serde_json::to_writer_pretty(f, &ui.config).unwrap();
}
impl Ui {
pub fn new() -> Ui {
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
let audio_subsystem = sdl_context.audio().unwrap();
let joystick_subsystem = sdl_context.joystick().unwrap();
let controller_subsystem = sdl_context.game_controller().unwrap();
let config_file_path = dirs::config_dir()
.unwrap()
.join("gopher64")
.join("config.json");
let config_file = std::fs::read(config_file_path.clone());
let mut config_map = config::Config::new();
if config_file.is_ok() {
let result = serde_json::from_slice(config_file.unwrap().as_ref());
if result.is_ok() {
config_map = result.unwrap();
}
}
Ui {
controllers: [
input::Controllers {
game_controller: None,
joystick: None,
},
input::Controllers {
game_controller: None,
joystick: None,
},
input::Controllers {
game_controller: None,
joystick: None,
},
input::Controllers {
game_controller: None,
joystick: None,
},
],
config_file_path: config_file_path,
config: config_map,
save_type: vec![],
game_id: String::new(),
game_hash: String::new(),
@ -33,15 +87,17 @@ impl Ui {
romsave_file_path: std::path::PathBuf::new(),
},
saves: storage::Saves {
eeprom: Vec::new(),
sram: Vec::new(),
flash: Vec::new(),
mempak: Vec::new(),
romsave: serde_json::Map::new(),
eeprom: (Vec::new(), false),
sram: (Vec::new(), false),
flash: (Vec::new(), false),
mempak: (Vec::new(), false),
romsave: (serde_json::Map::new(), false),
},
sdl_context: Some(sdl_context),
video_subsystem: Some(video_subsystem),
audio_subsystem: Some(audio_subsystem),
joystick_subsystem: Some(joystick_subsystem),
controller_subsystem: Some(controller_subsystem),
window: None,
audio_device: None,
}

41
src/ui/config.rs Normal file
View File

@ -0,0 +1,41 @@
use crate::ui;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct InputProfile {
pub keys: [(bool, i32); 18],
pub controller_buttons: [(bool, i32); 14],
pub controller_axis: [(bool, i32, i16); 18],
pub joystick_buttons: [(bool, u32); 14],
pub joystick_hat: [(bool, u32, i8); 14],
pub joystick_axis: [(bool, u32, i16); 18],
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Input {
pub input_profiles: std::collections::HashMap<String, InputProfile>,
pub input_profile_binding: [String; 4],
pub controller_assignment: [Option<String>; 4],
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Config {
pub input: Input,
}
impl Config {
pub fn new() -> Config {
let mut input_profiles = std::collections::HashMap::new();
input_profiles.insert("default".to_string(), ui::input::get_default_profile());
Config {
input: Input {
input_profile_binding: [
"default".to_string(),
"default".to_string(),
"default".to_string(),
"default".to_string(),
],
controller_assignment: [None, None, None, None],
input_profiles: input_profiles,
},
}
}
}

View File

@ -1,7 +1,36 @@
use std::ops::Neg;
use crate::ui;
pub const R_DPAD: usize = 0;
pub const L_DPAD: usize = 1;
pub const D_DPAD: usize = 2;
pub const U_DPAD: usize = 3;
pub const START_BUTTON: usize = 4;
pub const Z_TRIG: usize = 5;
pub const B_BUTTON: usize = 6;
pub const A_BUTTON: usize = 7;
pub const R_CBUTTON: usize = 8;
pub const L_CBUTTON: usize = 9;
pub const D_CBUTTON: usize = 10;
pub const U_CBUTTON: usize = 11;
pub const R_TRIG: usize = 12;
pub const L_TRIG: usize = 13;
pub const X_AXIS: usize = 16;
pub const Y_AXIS: usize = 24;
pub const AXIS_LEFT: usize = 14;
pub const AXIS_RIGHT: usize = 15;
pub const AXIS_UP: usize = 16;
pub const AXIS_DOWN: usize = 17;
pub const MAX_AXIS_VALUE: f64 = 85.0;
pub struct Controllers {
pub game_controller: Option<sdl2::controller::GameController>,
pub joystick: Option<sdl2::joystick::Joystick>,
}
pub fn bound_axis(x: &mut f64, y: &mut f64) {
let radius = 95.0; // this is roughly the maxium diagonal distance of the controller
@ -15,42 +44,478 @@ pub fn bound_axis(x: &mut f64, y: &mut f64) {
*y *= scale_factor;
}
}
pub fn get(ui: &mut ui::Ui, _channel: usize) -> u32 {
pub fn set_axis_from_joystick(
profile: &ui::config::InputProfile,
joystick: &sdl2::joystick::Joystick,
) -> (f64, f64) {
let mut x = 0.0;
let mut y = 0.0;
if profile.joystick_axis[AXIS_LEFT].0 {
let axis_position = joystick.axis(profile.joystick_axis[AXIS_LEFT].1).unwrap();
if axis_position as isize * profile.joystick_axis[AXIS_LEFT].2 as isize > 0 {
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
}
}
if profile.joystick_axis[AXIS_RIGHT].0 {
let axis_position = joystick.axis(profile.joystick_axis[AXIS_RIGHT].1).unwrap();
if axis_position as isize * profile.joystick_axis[AXIS_RIGHT].2 as isize > 0 {
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
}
}
if profile.joystick_axis[AXIS_DOWN].0 {
let axis_position = joystick.axis(profile.joystick_axis[AXIS_DOWN].1).unwrap();
if axis_position as isize * profile.joystick_axis[AXIS_DOWN].2 as isize > 0 {
y = (axis_position as f64 * MAX_AXIS_VALUE as f64 / i16::MAX as f64).neg();
}
}
if profile.joystick_axis[AXIS_UP].0 {
let axis_position = joystick.axis(profile.joystick_axis[AXIS_UP].1).unwrap();
if axis_position as isize * profile.joystick_axis[AXIS_UP].2 as isize > 0 {
y = (axis_position as f64 * MAX_AXIS_VALUE as f64 / i16::MAX as f64).neg();
}
}
return (x, y);
}
pub fn set_axis_from_controller(
profile: &ui::config::InputProfile,
controller: &sdl2::controller::GameController,
) -> (f64, f64) {
unsafe {
let mut x = 0.0;
let mut y = 0.0;
if profile.controller_axis[AXIS_LEFT].0 {
let axis_position =
controller.axis(std::mem::transmute(profile.controller_axis[AXIS_LEFT].1));
if axis_position as isize * profile.controller_axis[AXIS_LEFT].2 as isize > 0 {
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
}
}
if profile.controller_axis[AXIS_RIGHT].0 {
let axis_position =
controller.axis(std::mem::transmute(profile.controller_axis[AXIS_RIGHT].1));
if axis_position as isize * profile.controller_axis[AXIS_RIGHT].2 as isize > 0 {
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
}
}
if profile.controller_axis[AXIS_DOWN].0 {
let axis_position =
controller.axis(std::mem::transmute(profile.controller_axis[AXIS_DOWN].1));
if axis_position as isize * profile.controller_axis[AXIS_DOWN].2 as isize > 0 {
y = (axis_position as f64 * MAX_AXIS_VALUE as f64 / i16::MAX as f64).neg();
}
}
if profile.controller_axis[AXIS_UP].0 {
let axis_position =
controller.axis(std::mem::transmute(profile.controller_axis[AXIS_UP].1));
if axis_position as isize * profile.controller_axis[AXIS_UP].2 as isize > 0 {
y = (axis_position as f64 * MAX_AXIS_VALUE as f64 / i16::MAX as f64).neg();
}
}
return (x, y);
}
}
pub fn set_axis_from_keys(
profile: &ui::config::InputProfile,
keyboard_state: sdl2::keyboard::KeyboardState,
) -> (f64, f64) {
unsafe {
let mut x = 0.0;
let mut y = 0.0;
if profile.keys[AXIS_LEFT].0 {
if keyboard_state.is_scancode_pressed(std::mem::transmute(profile.keys[AXIS_LEFT].1)) {
x = -MAX_AXIS_VALUE
}
}
if profile.keys[AXIS_RIGHT].0 {
if keyboard_state.is_scancode_pressed(std::mem::transmute(profile.keys[AXIS_RIGHT].1)) {
x = MAX_AXIS_VALUE
}
}
if profile.keys[AXIS_DOWN].0 {
if keyboard_state.is_scancode_pressed(std::mem::transmute(profile.keys[AXIS_DOWN].1)) {
y = -MAX_AXIS_VALUE
}
}
if profile.keys[AXIS_UP].0 {
if keyboard_state.is_scancode_pressed(std::mem::transmute(profile.keys[AXIS_UP].1)) {
y = MAX_AXIS_VALUE
}
}
return (x, y);
}
}
pub fn set_buttons_from_joystick(
profile: &ui::config::InputProfile,
i: usize,
joystick: &sdl2::joystick::Joystick,
keys: &mut u32,
) {
let profile_joystick_button = profile.joystick_buttons[i];
if profile_joystick_button.0 {
*keys |= (joystick.button(profile_joystick_button.1).unwrap() as u32) << i;
}
let profile_joystick_hat = profile.joystick_hat[i];
if profile_joystick_hat.0 {
if joystick.hat(profile_joystick_hat.1).unwrap()
== unsafe { std::mem::transmute(profile_joystick_hat.2) }
{
*keys |= 1 << i;
}
}
let profile_joystick_axis = profile.joystick_axis[i];
if profile_joystick_axis.0 {
let axis_position = unsafe {
joystick
.axis(std::mem::transmute(profile_joystick_axis.1))
.unwrap()
};
if axis_position as isize * profile_joystick_axis.2 as isize > 0 {
if axis_position.saturating_abs() > i16::MAX / 2 {
*keys |= 1 << i;
}
}
}
}
pub fn set_buttons_from_controller(
profile: &ui::config::InputProfile,
i: usize,
controller: &sdl2::controller::GameController,
keys: &mut u32,
) {
let profile_controller_button = profile.controller_buttons[i];
if profile_controller_button.0 {
unsafe {
*keys |=
(controller.button(std::mem::transmute(profile_controller_button.1)) as u32) << i;
}
}
let profile_controller_axis = profile.controller_axis[i];
if profile_controller_axis.0 {
let axis_position =
unsafe { controller.axis(std::mem::transmute(profile_controller_axis.1)) };
if axis_position as isize * profile_controller_axis.2 as isize > 0 {
if axis_position.saturating_abs() > i16::MAX / 2 {
*keys |= 1 << i;
}
}
}
}
pub fn get(ui: &mut ui::Ui, channel: usize) -> u32 {
let context = ui.sdl_context.as_mut().unwrap();
let events = context.event_pump().unwrap();
let keyboard_state = events.keyboard_state();
let profile_name = ui.config.input.input_profile_binding[channel].clone();
let profile = ui.config.input.input_profiles.get(&profile_name).unwrap();
let mut keys = 0;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::D) as u32) << 0;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::A) as u32) << 1;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::S) as u32) << 2;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::W) as u32) << 3;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Return) as u32) << 4;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Z) as u32) << 5;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::LCtrl) as u32) << 6;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::LShift) as u32) << 7;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::L) as u32) << 8;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::J) as u32) << 9;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::K) as u32) << 10;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::I) as u32) << 11;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::C) as u32) << 12;
keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::X) as u32) << 13;
let controller = &ui.controllers[channel].game_controller;
let joystick = &ui.controllers[channel].joystick;
for i in 0..14 {
if profile_name != "default" || (channel == 0 && profile_name == "default") {
let profile_key = profile.keys[i];
if profile_key.0 {
unsafe {
keys |= (keyboard_state.is_scancode_pressed(std::mem::transmute(profile_key.1))
as u32)
<< i;
}
}
}
if joystick.is_some() {
set_buttons_from_joystick(profile, i, joystick.as_ref().unwrap(), &mut keys);
} else if controller.is_some() {
set_buttons_from_controller(profile, i, controller.as_ref().unwrap(), &mut keys);
}
}
let mut x: f64 = 0.0;
let mut y: f64 = 0.0;
if keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Left) {
x = -MAX_AXIS_VALUE
} else if keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Right) {
x = MAX_AXIS_VALUE
if profile_name != "default" || (channel == 0 && profile_name == "default") {
(x, y) = set_axis_from_keys(profile, keyboard_state);
}
if keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Down) {
y = -MAX_AXIS_VALUE
} else if keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Up) {
y = MAX_AXIS_VALUE
if joystick.is_some() {
(x, y) = set_axis_from_joystick(profile, joystick.as_ref().unwrap())
} else if controller.is_some() {
(x, y) = set_axis_from_controller(profile, controller.as_ref().unwrap())
}
bound_axis(&mut x, &mut y);
keys |= (x.round() as i8 as u32) << 16;
keys |= (y.round() as i8 as u32) << 24;
keys |= (x.round() as i8 as u8 as u32) << X_AXIS;
keys |= (y.round() as i8 as u8 as u32) << Y_AXIS;
return keys;
}
pub fn list_controllers(ui: &mut ui::Ui) {
let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
if num_joysticks == 0 {
println!("No controllers connected")
}
for i in 0..num_joysticks {
println!("{}: {}", i, joystick_subsystem.name_for_index(i).unwrap())
}
}
pub fn assign_controller(ui: &mut ui::Ui, controller: u32, port: usize) {
let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
if controller < num_joysticks {
ui.config.input.controller_assignment[port - 1] = Some(
joystick_subsystem
.device_guid(controller)
.unwrap()
.to_string(),
);
} else {
println!("Invalid controller number")
}
}
pub fn bind_input_profile(ui: &mut ui::Ui, profile: String, port: usize) {
if ui.config.input.input_profiles.contains_key(&profile) {
ui.config.input.input_profile_binding[port - 1] = profile;
} else {
println!("Invalid profile name")
}
}
pub fn clear_bindings(ui: &mut ui::Ui) {
for i in 0..4 {
ui.config.input.controller_assignment[i] = None;
ui.config.input.input_profile_binding[i] = "default".to_string();
}
}
pub fn configure_input_profile(ui: &mut ui::Ui, profile: String) {
if profile == "default" {
println!("Profile name cannot be default");
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() {
joysticks.push(ui.sdl_context.as_ref().unwrap().joystick().unwrap().open(i));
}
let mut builder =
ui.video_subsystem
.as_ref()
.unwrap()
.window("configure input profile", 640, 480);
builder.position_centered();
let window = builder.build().unwrap();
let mut canvas = window.into_canvas().build().unwrap();
let font =
rusttype::Font::try_from_bytes(include_bytes!("../../data/Roboto-Regular.ttf")).unwrap();
let key_labels = [
("A", A_BUTTON),
("B", B_BUTTON),
("Start", START_BUTTON),
("D Up", U_DPAD),
("D Down", D_DPAD),
("D Left", L_DPAD),
("D Right", R_DPAD),
("C Up", U_CBUTTON),
("C Down", D_CBUTTON),
("C Left", L_CBUTTON),
("C Right", R_CBUTTON),
("L", L_TRIG),
("R", R_TRIG),
("Z", Z_TRIG),
("Control Stick Up", AXIS_UP),
("Control Stick Down", AXIS_DOWN),
("Control Stick Left", AXIS_LEFT),
("Control Stick Right", AXIS_RIGHT),
];
let mut new_keys = [(false, 0); 18];
let mut new_joystick_buttons = [(false, 0u32); 14];
let mut new_joystick_hat = [(false, 0u32, 0); 14];
let mut new_joystick_axis = [(false, 0u32, 0); 18];
let mut last_axis_result = (false, 0, 0);
let mut event_pump = ui.sdl_context.as_ref().unwrap().event_pump().unwrap();
for (key, value) in key_labels.iter() {
for _event in event_pump.poll_iter() {} // clear events
ui::video::draw_text(
format!("Select binding for: {key}").as_str(),
&mut canvas,
&font,
);
let mut key_set = false;
while !key_set {
std::thread::sleep(std::time::Duration::from_millis(100));
for event in event_pump.poll_iter() {
match event {
sdl2::event::Event::Window {
win_event: sdl2::event::WindowEvent::Close,
..
} => return,
sdl2::event::Event::KeyDown {
scancode: Some(scancode),
..
} => {
new_keys[*value] = (true, scancode as i32);
key_set = true
}
sdl2::event::Event::JoyButtonDown { button_idx, .. } => {
new_joystick_buttons[*value] = (true, button_idx as u32);
key_set = true
}
sdl2::event::Event::JoyHatMotion { hat_idx, state, .. } => {
new_joystick_hat[*value] =
unsafe { (true, hat_idx as u32, std::mem::transmute(state)) };
key_set = true
}
sdl2::event::Event::JoyAxisMotion {
axis_idx,
value: axis_value,
..
} => {
if axis_value.saturating_abs() > 24576 {
let result = (
true,
axis_idx as u32,
axis_value / axis_value.saturating_abs(),
);
if result != last_axis_result {
new_joystick_axis[*value] = result;
last_axis_result = result;
key_set = true
}
}
}
_ => {}
}
}
}
}
let new_profile = ui::config::InputProfile {
keys: new_keys,
controller_buttons: Default::default(),
controller_axis: Default::default(),
joystick_buttons: new_joystick_buttons,
joystick_hat: new_joystick_hat,
joystick_axis: new_joystick_axis,
};
ui.config.input.input_profiles.insert(profile, new_profile);
}
pub fn get_default_profile() -> ui::config::InputProfile {
let mut default_controller_buttons = [(false, 0); 14];
let mut default_controller_axis = [(false, 0, 0); 18];
let mut default_keys = [(false, 0); 18];
default_keys[R_DPAD] = (true, sdl2::keyboard::Scancode::D as i32);
default_keys[L_DPAD] = (true, sdl2::keyboard::Scancode::A as i32);
default_keys[D_DPAD] = (true, sdl2::keyboard::Scancode::S as i32);
default_keys[U_DPAD] = (true, sdl2::keyboard::Scancode::W as i32);
default_keys[START_BUTTON] = (true, sdl2::keyboard::Scancode::Return as i32);
default_keys[Z_TRIG] = (true, sdl2::keyboard::Scancode::Z as i32);
default_keys[B_BUTTON] = (true, sdl2::keyboard::Scancode::LCtrl as i32);
default_keys[A_BUTTON] = (true, sdl2::keyboard::Scancode::LShift as i32);
default_keys[R_CBUTTON] = (true, sdl2::keyboard::Scancode::L as i32);
default_keys[L_CBUTTON] = (true, sdl2::keyboard::Scancode::J as i32);
default_keys[D_CBUTTON] = (true, sdl2::keyboard::Scancode::K as i32);
default_keys[U_CBUTTON] = (true, sdl2::keyboard::Scancode::I as i32);
default_keys[R_TRIG] = (true, sdl2::keyboard::Scancode::C as i32);
default_keys[L_TRIG] = (true, sdl2::keyboard::Scancode::X as i32);
default_keys[AXIS_LEFT] = (true, sdl2::keyboard::Scancode::Left as i32);
default_keys[AXIS_RIGHT] = (true, sdl2::keyboard::Scancode::Right as i32);
default_keys[AXIS_UP] = (true, sdl2::keyboard::Scancode::Up as i32);
default_keys[AXIS_DOWN] = (true, sdl2::keyboard::Scancode::Down as i32);
default_controller_buttons[R_DPAD] = (true, sdl2::controller::Button::DPadRight as i32);
default_controller_buttons[L_DPAD] = (true, sdl2::controller::Button::DPadLeft as i32);
default_controller_buttons[D_DPAD] = (true, sdl2::controller::Button::DPadDown as i32);
default_controller_buttons[U_DPAD] = (true, sdl2::controller::Button::DPadUp as i32);
default_controller_buttons[START_BUTTON] = (true, sdl2::controller::Button::Start as i32);
default_controller_axis[Z_TRIG] = (true, sdl2::controller::Axis::TriggerLeft as i32, 1);
default_controller_buttons[B_BUTTON] = (true, sdl2::controller::Button::X as i32);
default_controller_buttons[A_BUTTON] = (true, sdl2::controller::Button::A as i32);
default_controller_axis[R_CBUTTON] = (true, sdl2::controller::Axis::RightX as i32, 1);
default_controller_axis[L_CBUTTON] = (true, sdl2::controller::Axis::RightX as i32, -1);
default_controller_axis[D_CBUTTON] = (true, sdl2::controller::Axis::RightY as i32, 1);
default_controller_axis[U_CBUTTON] = (true, sdl2::controller::Axis::RightY as i32, -1);
default_controller_buttons[R_TRIG] = (true, sdl2::controller::Button::RightShoulder as i32);
default_controller_buttons[L_TRIG] = (true, sdl2::controller::Button::LeftShoulder as i32);
default_controller_axis[AXIS_LEFT] = (true, sdl2::controller::Axis::LeftX as i32, -1);
default_controller_axis[AXIS_RIGHT] = (true, sdl2::controller::Axis::LeftX as i32, 1);
default_controller_axis[AXIS_UP] = (true, sdl2::controller::Axis::LeftY as i32, -1);
default_controller_axis[AXIS_DOWN] = (true, sdl2::controller::Axis::LeftY as i32, 1);
ui::config::InputProfile {
keys: default_keys,
controller_buttons: default_controller_buttons,
controller_axis: default_controller_axis,
joystick_buttons: Default::default(),
joystick_hat: Default::default(),
joystick_axis: Default::default(),
}
}
pub fn init(ui: &mut ui::Ui) {
let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap();
let mut taken = [false; 4];
for i in 0..4 {
let controller_assignment = &ui.config.input.controller_assignment[i];
if controller_assignment.is_some() {
let mut joystick_index = u32::MAX;
let guid = controller_assignment.clone().unwrap();
for i in 0..joystick_subsystem.num_joysticks().unwrap() {
if joystick_subsystem.device_guid(i).unwrap().to_string() == guid
&& !taken[i as usize]
{
joystick_index = i;
taken[i as usize] = true;
break;
}
}
if joystick_index < u32::MAX {
if ui.config.input.input_profile_binding[i] == "default" {
let controller_result = ui
.sdl_context
.as_ref()
.unwrap()
.game_controller()
.unwrap()
.open(joystick_index);
if controller_result.is_ok() {
ui.controllers[i].game_controller = Some(controller_result.unwrap());
}
}
if ui.controllers[i].game_controller.is_none() {
let joystick_result = ui
.sdl_context
.as_ref()
.unwrap()
.joystick()
.unwrap()
.open(joystick_index);
if joystick_result.is_err() {
println!("could not connect joystick")
} else {
ui.controllers[i].joystick = Some(joystick_result.unwrap());
}
}
} else {
println!("Could not bind assigned controller");
}
}
}
}

View File

@ -18,12 +18,14 @@ pub struct Paths {
pub romsave_file_path: std::path::PathBuf,
}
// the bool indicates whether the save has been written to
// if that is the case, it will be flushed to the disk when the program closes
pub struct Saves {
pub eeprom: Vec<u8>,
pub sram: Vec<u8>,
pub flash: Vec<u8>,
pub mempak: Vec<u8>,
pub romsave: serde_json::Map<String, serde_json::Value>,
pub eeprom: (Vec<u8>, bool),
pub sram: (Vec<u8>, bool),
pub flash: (Vec<u8>, bool),
pub mempak: (Vec<u8>, bool),
pub romsave: (serde_json::Map<String, serde_json::Value>, bool),
}
pub fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
@ -88,9 +90,7 @@ pub fn init(ui: &mut ui::Ui) {
let id = ui.game_id.as_str();
ui.save_type = get_save_type(id);
let mut base_path = dirs::data_dir().unwrap();
base_path.push("gopher64");
base_path.push("saves");
let base_path = dirs::data_dir().unwrap().join("gopher64").join("saves");
let result = std::fs::create_dir_all(base_path.clone());
if result.is_err() {
@ -126,23 +126,23 @@ pub fn init(ui: &mut ui::Ui) {
pub fn load_saves(ui: &mut ui::Ui) {
let eep = std::fs::read(&mut ui.paths.eep_file_path);
if eep.is_ok() {
ui.saves.eeprom = eep.unwrap();
ui.saves.eeprom.0 = eep.unwrap();
}
let sra = std::fs::read(&mut ui.paths.sra_file_path);
if sra.is_ok() {
ui.saves.sram = sra.unwrap();
ui.saves.sram.0 = sra.unwrap();
}
let fla = std::fs::read(&mut ui.paths.fla_file_path);
if fla.is_ok() {
ui.saves.flash = fla.unwrap();
ui.saves.flash.0 = fla.unwrap();
}
let mempak = std::fs::read(&mut ui.paths.pak_file_path);
if mempak.is_ok() {
ui.saves.mempak = mempak.unwrap();
ui.saves.mempak.0 = mempak.unwrap();
}
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
if romsave.is_ok() {
ui.saves.romsave = serde_json::from_slice(romsave.unwrap().as_ref()).unwrap();
ui.saves.romsave.0 = serde_json::from_slice(romsave.unwrap().as_ref()).unwrap();
}
}
@ -151,25 +151,43 @@ pub fn write_rom_save(ui: &mut ui::Ui) {
serde_json::to_writer(f, &ui.saves.romsave).unwrap();
}
pub fn write_saves(ui: &mut ui::Ui) {
if ui.saves.eeprom.1 {
write_save(ui, SaveTypes::Eeprom16k)
}
if ui.saves.sram.1 {
write_save(ui, SaveTypes::Sram)
}
if ui.saves.flash.1 {
write_save(ui, SaveTypes::Flash)
}
if ui.saves.mempak.1 {
write_save(ui, SaveTypes::Mempak)
}
if ui.saves.romsave.1 {
write_save(ui, SaveTypes::Romsave)
}
}
pub fn write_save(ui: &mut ui::Ui, save_type: SaveTypes) {
let path: &std::path::Path;
let data: &Vec<u8>;
match save_type {
SaveTypes::Eeprom4k | SaveTypes::Eeprom16k => {
path = ui.paths.eep_file_path.as_ref();
data = ui.saves.eeprom.as_ref();
data = ui.saves.eeprom.0.as_ref();
}
SaveTypes::Sram => {
path = ui.paths.sra_file_path.as_ref();
data = ui.saves.sram.as_ref();
data = ui.saves.sram.0.as_ref();
}
SaveTypes::Flash => {
path = ui.paths.fla_file_path.as_ref();
data = ui.saves.flash.as_ref();
data = ui.saves.flash.0.as_ref();
}
SaveTypes::Mempak => {
path = ui.paths.pak_file_path.as_ref();
data = ui.saves.mempak.as_ref();
data = ui.saves.mempak.0.as_ref();
}
SaveTypes::Romsave => {
write_rom_save(ui);

View File

@ -50,3 +50,36 @@ pub fn rdp_full_sync() {
full_sync();
}
}
pub fn draw_text(
text: &str,
canvas: &mut sdl2::render::Canvas<sdl2::video::Window>,
font: &rusttype::Font,
) {
let text_size = 32;
let scale = rusttype::Scale::uniform(text_size as f32);
let v_metrics = font.v_metrics(scale);
let offset = rusttype::point(10.0, 10.0 + v_metrics.ascent);
// Clear the canvas
canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 0, 0));
canvas.clear();
for glyph in font.layout(text, scale, offset) {
if let Some(bb) = glyph.pixel_bounding_box() {
glyph.draw(|x, y, v| {
let x = x as i32 + bb.min.x;
let y = y as i32 + bb.min.y + (240 - text_size);
if v > 0.5 {
canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255));
canvas
.draw_point(sdl2::rect::Point::new(x, y))
.expect("Error drawing pixel");
}
});
}
}
// Present the canvas
canvas.present();
}