212 lines
6.9 KiB
Rust
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");
|
|
}
|
|
}
|
|
}
|