This repository has been archived on 2026-08-27. You can view files and clone it, but cannot push or open issues or pull requests.
gopher64-netplay/src/device/cart.rs
2026-08-06 23:00:08 +00:00

212 lines
6.9 KiB
Rust

use chrono::Datelike;
use chrono::Timelike;
use crate::{device, ui};
pub mod rom;
pub mod sc64;
pub mod sram;
const JCMD_STATUS: u8 = 0x00;
const JCMD_EEPROM_READ: u8 = 0x04;
const JCMD_EEPROM_WRITE: u8 = 0x05;
const JCMD_AF_RTC_STATUS: u8 = 0x06;
const JCMD_AF_RTC_READ: u8 = 0x07;
const JCMD_AF_RTC_WRITE: u8 = 0x08;
const JCMD_RESET: u8 = 0xff;
const JDT_AF_RTC: u16 = 0x1000; /* RTC */
const JDT_EEPROM_4K: u16 = 0x8000; /* 4k EEPROM */
const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */
const EEPROM_BLOCK_SIZE: usize = 8;
pub const EEPROM_MAX_SIZE: usize = 0x800;
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct AfRtc {
pub control: u16,
}
fn byte2bcd(mut n: u32) -> u8 {
n %= 100;
(((n / 10) << 4) | (n % 10)) as u8
}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Cart {
#[serde(skip)]
pub rom: Vec<u8>,
pub is_viewer_buffer: Vec<u8>,
pub pal: bool,
pub latch: u32,
pub cic_seed: u8,
pub rtc: AfRtc,
pub sc64: sc64::Sc64,
pub flashram: sram::Flashram,
pub rtc_timestamp: i64,
}
pub fn process(device: &mut device::Device, channel: usize) {
let cmd = device.pif.ram[device.pif.channels[channel].tx_buf.unwrap()];
match cmd {
JCMD_RESET | JCMD_STATUS => {
let eeprom_type = if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Eeprom16k)
{
JDT_EEPROM_16K
} else if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Eeprom4k)
{
JDT_EEPROM_4K
} else {
device.pif.ram[device.pif.channels[channel].rx.unwrap()] |= 0x80;
return;
};
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = eeprom_type as u8;
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap() + 1] =
(eeprom_type >> 8) as u8;
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap() + 2] = 0;
}
JCMD_EEPROM_READ => {
eeprom_read_block(
device,
device.pif.channels[channel].tx_buf.unwrap() + 1,
device.pif.channels[channel].rx_buf.unwrap(),
);
}
JCMD_EEPROM_WRITE => eeprom_write_block(
device,
device.pif.channels[channel].tx_buf.unwrap() + 1,
device.pif.channels[channel].tx_buf.unwrap() + 2,
device.pif.channels[channel].rx_buf.unwrap(),
),
JCMD_AF_RTC_STATUS => {
/* set type and status */
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = JDT_AF_RTC as u8;
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap() + 1] =
(JDT_AF_RTC >> 8) as u8;
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap() + 2] = 0x00;
}
JCMD_AF_RTC_READ => {
af_rtc_read_block(
device,
device.pif.channels[channel].tx_buf.unwrap() + 1,
device.pif.channels[channel].rx_buf.unwrap(),
device.pif.channels[channel].rx_buf.unwrap() + 8,
);
}
JCMD_AF_RTC_WRITE => af_rtc_write_block(
device,
device.pif.channels[channel].tx_buf.unwrap() + 1,
device.pif.channels[channel].tx_buf.unwrap() + 2,
device.pif.channels[channel].rx_buf.unwrap(),
),
_ => {
panic!("unknown cart command")
}
}
}
pub fn format_eeprom(device: &mut device::Device) {
if device.ui.storage.saves.eeprom.data.len() < EEPROM_MAX_SIZE {
device
.ui
.storage
.saves
.eeprom
.data
.resize(EEPROM_MAX_SIZE, 0xFF)
}
}
fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
let address = device.pif.ram[block] as usize * EEPROM_BLOCK_SIZE;
device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE].copy_from_slice(
&device.ui.storage.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE],
);
}
fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
let address = device.pif.ram[block] as usize * EEPROM_BLOCK_SIZE;
device.ui.storage.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE]
.copy_from_slice(&device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE]);
device.pif.ram[status] = 0x00;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
}
fn time2data(device: &mut device::Device, offset: usize) {
let timestamp = device.cart.rtc_timestamp + device.vi.elapsed_time as i64;
let now = chrono::DateTime::from_timestamp(timestamp, 0)
.unwrap()
.with_timezone(&chrono::Local);
device.pif.ram[offset] = byte2bcd(now.second());
device.pif.ram[offset + 1] = byte2bcd(now.minute());
device.pif.ram[offset + 2] = 0x80 + byte2bcd(now.hour());
device.pif.ram[offset + 3] = byte2bcd(now.day());
device.pif.ram[offset + 4] = byte2bcd(now.weekday().num_days_from_sunday());
device.pif.ram[offset + 5] = byte2bcd(now.month());
device.pif.ram[offset + 6] = byte2bcd(now.year() as u32 - 1900);
device.pif.ram[offset + 7] = byte2bcd((now.year() as u32 - 1900) / 100);
}
fn af_rtc_read_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
match device.pif.ram[block] {
0 => {
device.pif.ram[offset] = device.cart.rtc.control as u8;
device.pif.ram[offset + 1] = (device.cart.rtc.control >> 8) as u8;
device.pif.ram[status] = 0x00;
}
1 => {
panic!("AF-RTC reading block 1 is not implemented !");
}
2 => {
time2data(device, offset);
device.pif.ram[status] = 0x00;
}
_ => {
panic!("AF-RTC read invalid block");
}
}
}
fn af_rtc_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
match device.pif.ram[block] {
0 => {
device.cart.rtc.control =
((device.pif.ram[offset + 1] as u16) << 8) | device.pif.ram[offset] as u16;
device.pif.ram[status] = 0x00;
}
1 => {
/* block 1 read-only when control[0] is set */
if (device.cart.rtc.control & 0x01) != 0 {
return;
}
panic!("AF-RTC writing block 1 is not implemented !");
}
2 => {
/* block 2 read-only when control[1] is set */
if (device.cart.rtc.control & 0x02) != 0 {
return;
}
/* TODO: implement block 2 writes */
panic!("AF-RTC writing block 2 is not implemented !");
}
_ => {
panic!("AF-RTC write invalid block");
}
}
}