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:
parent
887e561101
commit
94cfc61678
@ -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"] }
|
||||
|
||||
|
||||
30
README.md
30
README.md
@ -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
BIN
data/Roboto-Regular.ttf
Normal file
Binary file not shown.
@ -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) {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
88
src/main.rs
88
src/main.rs
@ -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);
|
||||
|
||||
66
src/ui.rs
66
src/ui.rs
@ -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
41
src/ui/config.rs
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
515
src/ui/input.rs
515
src/ui/input.rs
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user