flash cart menu support (#283)

* flash cart menu support

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
This commit is contained in:
Logan McNaughton 2025-02-26 10:04:44 +01:00 committed by GitHub
parent 8fbc63a677
commit 82b4adb4ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 237 additions and 116 deletions

View File

@ -72,7 +72,7 @@ pub fn run_game(rom_contents: Vec<u8>, device: &mut Device) {
ui::input::close(&mut device.ui);
ui::video::close(&device.ui);
ui::audio::close(&mut device.ui);
ui::storage::write_saves(&device.ui, &device.netplay);
ui::storage::write_saves(device);
}
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
@ -298,19 +298,18 @@ impl Device {
},
cart: cart::Cart {
rom: Vec::new(),
is_viewer_buffer: [0; 0xFFFF],
is_viewer_buffer: vec![0; 0xFFFF],
pal: false,
latch: 0,
cic_seed: 0,
cic_type: cart::CicType::CicNus6102,
rdram_size_offset: 0,
rtc: cart::AfRtc { control: 0x0200 },
sc64: cart::sc64::Sc64 {
regs: [0; cart::sc64::SC64_REGS_COUNT as usize],
regs_locked: true,
cfg: [0; cart::sc64::SC64_CFG_COUNT as usize],
sector: 0,
buffer: [0; 8192],
buffer: vec![0; 8192],
writeback_sector: vec![0; 256],
},
flashram: cart::sram::Flashram {
status: 0,

View File

@ -45,13 +45,10 @@ fn byte2bcd(mut n: u32) -> u8 {
pub struct Cart {
#[serde(skip)]
pub rom: Vec<u8>,
#[serde(with = "serde_big_array::BigArray")]
pub is_viewer_buffer: [u8; 0xFFFF],
pub is_viewer_buffer: Vec<u8>,
pub pal: bool,
pub latch: u32,
pub cic_type: CicType,
pub cic_seed: u8,
pub rdram_size_offset: usize,
pub rtc: AfRtc,
pub sc64: sc64::Sc64,
pub flashram: sram::Flashram,
@ -125,7 +122,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
}
fn format_eeprom(device: &mut device::Device) {
pub fn format_eeprom(device: &mut device::Device) {
if device.ui.saves.eeprom.data.len() < EEPROM_MAX_SIZE {
device.ui.saves.eeprom.data.resize(EEPROM_MAX_SIZE, 0xFF)
}

View File

@ -4,16 +4,12 @@ use sha2::{Digest, Sha256};
const CART_MASK: usize = 0xFFFFFFF;
fn read_cart_word(device: &mut device::Device, address: usize) -> u32 {
let mut data: [u8; 4] = device.cart.rom[address..address + 4].try_into().unwrap();
let mut data: [u8; 4] = [0; 4];
for i in 0..4 {
if device
.ui
.saves
.romsave
.data
.contains_key(&(address as u32 + i))
{
data[i as usize] = device.ui.saves.romsave.data[&(address as u32 + i)];
if let Some(value) = device.ui.saves.romsave.data.get(&(address as u32 + i)) {
data[i as usize] = *value;
} else {
data[i as usize] = *device.cart.rom.get(address + i as usize).unwrap_or(&0);
}
}
u32::from_be_bytes(data)
@ -46,7 +42,9 @@ pub fn read_mem(
}
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0
&& device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] == 0
{
let masked_address = address as usize & CART_MASK;
let mut data = read_cart_word(device, masked_address);
device::memory::masked_write_32(&mut data, value, mask);
@ -79,20 +77,19 @@ pub fn dma_read(
mut dram_addr: u32,
length: u32,
) -> u64 {
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0
&& device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] == 0
{
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= CART_MASK as u32;
for i in 0..length {
if cart_addr + i < device.cart.rom.len() as u32 {
device.ui.saves.romsave.data.insert(
cart_addr + i,
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
);
device.ui.saves.romsave.written = true;
}
device.ui.saves.romsave.data.insert(
cart_addr + i,
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
);
}
device.ui.saves.romsave.written = true;
}
device::pi::calculate_cycles(device, 1, length)
@ -109,24 +106,23 @@ pub fn dma_write(
cart_addr &= CART_MASK as u32;
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && j < device.cart.rom.len() as u32 {
while i < dram_addr + length {
if device.ui.saves.romsave.data.contains_key(&j) {
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.romsave.data[&j];
} else {
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.rom[j as usize];
device.rdram.mem[i as usize ^ device.byte_swap] =
*device.cart.rom.get(j as usize).unwrap_or(&0);
}
i += 1;
j += 1;
}
while i < dram_addr + length {
// DMAs that extend past the end of the ROM return 0's for the portion that extends past the ROM length
device.rdram.mem[i as usize ^ device.byte_swap] = 0;
i += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] = 1;
device.cart.rom = rom_file;
device.cart.pal = is_system_pal(device.cart.rom[0x3E]);
set_cic(device);
@ -153,40 +149,27 @@ fn set_cic(device: &mut device::Device) {
let hash = calculate_hash(&device.cart.rom[0x40..0x1000]);
match hash.as_str() {
"B99F06C4802C2377E31E388435955EF3E99C618A6D55D24699D828EB1075F1EB" => {
device.cart.cic_type = device::cart::CicType::CicNus6101;
device.cart.cic_seed = 0x3F;
device.cart.rdram_size_offset = 0x318;
device.cart.cic_seed = 0x3F; // CicNus6101
}
"61E88238552C356C23D19409FE5570EE6910419586BC6FC740F638F761ADC46E" => {
device.cart.cic_type = device::cart::CicType::CicNus6102;
device.cart.cic_seed = 0x3F;
device.cart.rdram_size_offset = 0x318;
device.cart.cic_seed = 0x3F; // CicNus6102
}
"BF3620D30817007091EBE9BDDD1B88C23B8A0052170B3309CDE5B6B4238E45E7" => {
device.cart.cic_type = device::cart::CicType::CicNus6103;
device.cart.cic_seed = 0x78;
device.cart.rdram_size_offset = 0x318;
device.cart.cic_seed = 0x78; // CicNus6103
}
"04B7BC6717A9F0EB724CF927E74AD3876C381CBB280D841736FC5E55580B756B" => {
device.cart.cic_type = device::cart::CicType::CicNus6105;
device.cart.cic_seed = 0x91;
device.cart.rdram_size_offset = 0x3F0;
device.cart.cic_seed = 0x91; // CicNus6105
}
"36ADC40148AF56F0D78CD505EB6A90117D1FD6F11C6309E52ED36BC4C6BA340E" => {
device.cart.cic_type = device::cart::CicType::CicNus6106;
device.cart.cic_seed = 0x85;
device.cart.rdram_size_offset = 0x318;
device.cart.cic_seed = 0x85; // CicNus6106
}
"53C0088FB777870D0AF32F0251E964030E2E8B72E830C26042FD191169508C05" => {
device.cart.cic_type = device::cart::CicType::CicNus5167;
device.cart.cic_seed = 0xdd;
device.cart.rdram_size_offset = 0x318;
device.cart.cic_seed = 0xdd; // CicNus5167
device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] = 1;
device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] = 0;
}
_ => {
device.cart.cic_type = device::cart::CicType::CicNus6102;
device.cart.cic_seed = 0x3F;
device.cart.rdram_size_offset = 0x318;
device.cart.cic_seed = 0x3F; // CicNus6102
println!("unknown IPL3 {}", hash)
}
}

View File

@ -1,4 +1,5 @@
use crate::device;
use crate::ui;
pub const SDCARD_SIZE: usize = 0x4000000;
@ -11,19 +12,22 @@ const SC64_KEY_REG: u32 = 4;
//const SC64_AUX_REG: u32 = 6;
pub const SC64_REGS_COUNT: u32 = 7;
pub const SC64_BOOTLOADER_SWITCH: u32 = 0;
pub const SC64_ROM_WRITE_ENABLE: u32 = 1;
pub const SC64_SAVE_TYPE: u32 = 6;
pub const SC64_CFG_COUNT: u32 = 15;
const SC64_BUFFER_MASK: usize = 0x1FFF;
const SC64_EEPROM_MASK: usize = 0xFFF;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Sc64 {
#[serde(with = "serde_big_array::BigArray")]
pub buffer: [u8; 8192],
pub buffer: Vec<u8>,
pub regs: [u32; SC64_REGS_COUNT as usize],
pub regs_locked: bool,
pub cfg: [u32; SC64_CFG_COUNT as usize],
pub sector: u32,
pub writeback_sector: Vec<u32>,
}
fn format_sdcard(device: &mut device::Device) {
@ -74,6 +78,11 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
SC64_SCR_REG => {
if !device.cart.sc64.regs_locked {
match char::from_u32(value & mask).unwrap() {
'V' => {
// get version
device.cart.sc64.regs[SC64_DATA0_REG as usize] = (2 << 16) | 20;
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 2;
}
'c' => {
// get config
device.cart.sc64.regs[SC64_DATA1_REG as usize] = device.cart.sc64.cfg
@ -81,6 +90,35 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
}
'C' => {
// set config
if device.cart.sc64.regs[SC64_DATA0_REG as usize] == SC64_SAVE_TYPE {
// if save type is being written, we are probably booting a game using the flash cart menu
// we shouldn't write saves to disk in this case (they are written to the SD card)
device.ui.saves.write_to_disk = false;
device.ui.save_type =
match device.cart.sc64.regs[SC64_DATA1_REG as usize] {
0 => {
vec![]
}
1 => {
vec![ui::storage::SaveTypes::Eeprom4k]
}
2 => {
vec![ui::storage::SaveTypes::Eeprom16k]
}
3 => {
vec![ui::storage::SaveTypes::Sram]
}
4 => {
vec![ui::storage::SaveTypes::Flash]
}
_ => {
panic!(
"unknown sc64 save type: {}",
device.cart.sc64.regs[SC64_DATA1_REG as usize]
)
}
}
}
std::mem::swap(
&mut device.cart.sc64.cfg
[device.cart.sc64.regs[SC64_DATA0_REG as usize] as usize],
@ -165,8 +203,24 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
}
device.ui.saves.sdcard.written = true;
}
'U' => {} // USB_WRITE_STATUS, ignored
'M' => {} // USB_WRITE, ignored
'U' | 'u' => {} // USB status, ignored
'M' | 'm' => {} // USB read/write, ignored
'w' => {
// SD card writeback pending
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
}
'W' => {
let writeback_sectors_address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64;
for i in 0..256 {
device.cart.sc64.writeback_sector[i] = device::memory::data_read(
device,
writeback_sectors_address + (i * 4) as u64,
device::memory::AccessSize::Word,
false,
);
}
}
_ => {
panic!(
"unknown sc64 command: {}",
@ -190,24 +244,48 @@ pub fn read_mem(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
let masked_address = address as usize & SC64_BUFFER_MASK;
u32::from_be_bytes(
device.cart.sc64.buffer[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
if address & 0x2000 != 0 {
device::cart::format_eeprom(device);
let masked_address = address as usize & SC64_EEPROM_MASK;
u32::from_be_bytes(
device.ui.saves.eeprom.data[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
} else {
let masked_address = address as usize & SC64_BUFFER_MASK;
u32::from_be_bytes(
device.cart.sc64.buffer[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
}
}
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let masked_address = address as usize & SC64_BUFFER_MASK;
let mut data = u32::from_be_bytes(
if address & 0x2000 != 0 {
device::cart::format_eeprom(device);
let masked_address = address as usize & SC64_EEPROM_MASK;
let mut data = u32::from_be_bytes(
device.ui.saves.eeprom.data[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.ui.saves.eeprom.data[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
device.ui.saves.eeprom.written = true
} else {
let masked_address = address as usize & SC64_BUFFER_MASK;
let mut data = u32::from_be_bytes(
device.cart.sc64.buffer[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.cart.sc64.buffer[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.cart.sc64.buffer[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
.copy_from_slice(&data.to_be_bytes());
}
}
pub fn dma_read(
@ -217,12 +295,20 @@ pub fn dma_read(
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= SC64_BUFFER_MASK as u32;
let buffer = if cart_addr & 0x2000 != 0 {
device::cart::format_eeprom(device);
cart_addr &= SC64_EEPROM_MASK as u32;
device.ui.saves.eeprom.written = true;
&mut device.ui.saves.eeprom.data
} else {
cart_addr &= SC64_BUFFER_MASK as u32;
&mut device.cart.sc64.buffer
};
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && i < device.rdram.size {
device.cart.sc64.buffer[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
buffer[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
i += 1;
j += 1;
}
@ -237,12 +323,19 @@ pub fn dma_write(
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= SC64_BUFFER_MASK as u32;
let buffer = if cart_addr & 0x2000 != 0 {
device::cart::format_eeprom(device);
cart_addr &= SC64_EEPROM_MASK as u32;
&device.ui.saves.eeprom.data
} else {
cart_addr &= SC64_BUFFER_MASK as u32;
&device.cart.sc64.buffer
};
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && i < device.rdram.size {
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.sc64.buffer[j as usize];
device.rdram.mem[i as usize ^ device.byte_swap] = buffer[j as usize];
i += 1;
j += 1;
}

View File

@ -153,13 +153,13 @@ pub fn init(device: &mut device::Device) {
} else if (MM_DOM2_ADDR2 >> 16..=(MM_DOM2_ADDR2 + 0x1FFFF) >> 16).contains(&i) {
device.memory.memory_map_read[i] = device::cart::sram::read_mem;
device.memory.memory_map_write[i] = device::cart::sram::write_mem;
} else if i >= MM_CART_ROM >> 16 && i <= (MM_CART_ROM + device.cart.rom.len() - 1) >> 16 {
device.memory.fast_read[i] = device::cart::rom::read_mem_fast;
device.memory.memory_map_read[i] = device::cart::rom::read_mem;
device.memory.memory_map_write[i] = device::cart::rom::write_mem;
} else if i >= MM_IS_VIEWER >> 16 && i <= (MM_IS_VIEWER + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::is_viewer::read_mem;
device.memory.memory_map_write[i] = device::is_viewer::write_mem;
} else if (MM_CART_ROM >> 16..=(MM_CART_ROM + 0x0FBFFFFF) >> 16).contains(&i) {
device.memory.fast_read[i] = device::cart::rom::read_mem_fast;
device.memory.memory_map_read[i] = device::cart::rom::read_mem;
device.memory.memory_map_write[i] = device::cart::rom::write_mem;
} else if i >= MM_PIF_MEM >> 16 && i <= (MM_PIF_MEM + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::pif::read_mem;
device.memory.memory_map_write[i] = device::pif::write_mem;

View File

@ -106,13 +106,12 @@ pub fn init(device: &mut device::Device) {
unsafe { Vec::from_raw_parts(ptr, device.rdram.size as usize, device.rdram.size as usize) };
// hack, skip RDRAM initialization
device.rdram.mem[device.cart.rdram_size_offset..device.cart.rdram_size_offset + 4]
.copy_from_slice(&device.rdram.size.to_ne_bytes());
device.rdram.mem[0x318..0x318 + 4].copy_from_slice(&device.rdram.size.to_ne_bytes());
// hack, skip RDRAM initialization
device.rdram.mem[0x3f0..0x3f0 + 4].copy_from_slice(&device.rdram.size.to_ne_bytes());
device.ri.regs[device::ri::RI_MODE_REG as usize] = 0x0e;
device.ri.regs[device::ri::RI_CONFIG_REG as usize] = 0x40;
device.ri.regs[device::ri::RI_SELECT_REG as usize] = 0x14;
device.ri.regs[device::ri::RI_REFRESH_REG as usize] = 0x00063634;
}
pub fn rdram_calculate_cycles(length: u64) -> u64 {

View File

@ -22,17 +22,24 @@ pub fn read_regs(
_access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, 20);
if ((address & 0xFFFF) >> 2) as u32 == RI_SELECT_REG && !device.ri.ram_init {
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
device.ri.ram_init = true;
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
RI_SELECT_REG => {
if !device.ri.ram_init {
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
device.ri.ram_init = true;
}
0x14 // hack, skip RDRAM initialization
}
RI_REFRESH_REG => 0x00063634, // hack, skip RDRAM initialization
_ => device.ri.regs[reg as usize],
}
device.ri.regs[((address & 0xFFFF) >> 2) as usize]
}
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
device::memory::masked_write_32(
&mut device.ri.regs[((address & 0xFFFF) >> 2) as usize],
value,
mask,
);
let reg = (address & 0xFFFF) >> 2;
if reg as u32 == RI_SELECT_REG {
device.ri.ram_init = false;
}
device::memory::masked_write_32(&mut device.ri.regs[reg as usize], value, mask);
}

View File

@ -122,6 +122,7 @@ impl Ui {
savestate_file_path: std::path::PathBuf::new(),
},
saves: storage::Saves {
write_to_disk: true,
eeprom: storage::Save {
data: Vec::new(),
written: false,

View File

@ -14,15 +14,20 @@ pub struct EventAudio {
pub fn init(ui: &mut ui::Ui, frequency: u64) {
ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO);
let audio_spec = sdl3_sys::audio::SDL_AudioSpec {
let game_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
freq: frequency as i32,
channels: 2,
};
let device_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
freq: 48000,
channels: 2,
};
ui.audio_device = unsafe {
sdl3_sys::audio::SDL_OpenAudioDevice(
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
&audio_spec,
&device_audio_spec,
)
};
if ui.audio_device == 0 {
@ -36,7 +41,7 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) {
panic!("Could not get audio device format");
}
ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&audio_spec, &dst) };
ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&game_audio_spec, &dst) };
if ui.audio_stream.is_null() {
return;
}

View File

@ -1,4 +1,4 @@
use crate::{netplay, ui};
use crate::{device, netplay, ui};
use std::io::Read;
use std::io::Write;
@ -45,6 +45,7 @@ pub struct Saves {
pub mempak: Save,
pub sdcard: Save,
pub romsave: RomSave,
pub write_to_disk: bool,
}
fn get_save_type(rom: &[u8], game_id: &str) -> Vec<SaveTypes> {
@ -317,25 +318,61 @@ fn write_rom_save(ui: &ui::Ui) {
std::fs::write(ui.paths.romsave_file_path.clone(), data).unwrap();
}
pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
if netplay.is_none() || netplay.as_ref().unwrap().player_number == 0 {
if ui.saves.eeprom.written {
write_save(ui, SaveTypes::Eeprom16k)
fn writeback_sdcard(device: &mut device::Device) {
let length;
let save_data: &[u8];
if device.ui.saves.eeprom.written {
if device
.ui
.save_type
.contains(&ui::storage::SaveTypes::Eeprom4k)
{
length = 1;
} else {
length = 4;
}
if ui.saves.sram.written {
write_save(ui, SaveTypes::Sram)
save_data = device.ui.saves.eeprom.data.as_ref();
} else if device.ui.saves.sram.written {
length = device.ui.saves.sram.data.len() / 512;
save_data = device.ui.saves.sram.data.as_ref();
} else if device.ui.saves.flash.written {
length = device.ui.saves.flash.data.len() / 512;
save_data = device.ui.saves.flash.data.as_ref();
} else {
return;
}
for i in 0..length {
let offset = device.cart.sc64.writeback_sector[i] as usize * 512;
device.ui.saves.sdcard.data[offset..offset + 512]
.copy_from_slice(&save_data[i * 512..(i + 1) * 512]);
}
device.ui.saves.sdcard.written = true;
}
pub fn write_saves(device: &mut device::Device) {
if device.netplay.is_none() || device.netplay.as_ref().unwrap().player_number == 0 {
if device.ui.saves.write_to_disk {
if device.ui.saves.eeprom.written {
write_save(&device.ui, SaveTypes::Eeprom16k)
}
if device.ui.saves.sram.written {
write_save(&device.ui, SaveTypes::Sram)
}
if device.ui.saves.flash.written {
write_save(&device.ui, SaveTypes::Flash)
}
if device.ui.saves.romsave.written {
write_save(&device.ui, SaveTypes::Romsave)
}
} else {
writeback_sdcard(device)
}
if ui.saves.flash.written {
write_save(ui, SaveTypes::Flash)
if device.ui.saves.mempak.written {
write_save(&device.ui, SaveTypes::Mempak)
}
if ui.saves.mempak.written {
write_save(ui, SaveTypes::Mempak)
}
if ui.saves.sdcard.written {
write_save(ui, SaveTypes::Sdcard)
}
if ui.saves.romsave.written {
write_save(ui, SaveTypes::Romsave)
if device.ui.saves.sdcard.written {
write_save(&device.ui, SaveTypes::Sdcard)
}
}
}