use crate::save::utils::Timeout;
use crate::sync::{Lock, RawLockGuard};
use crate::timer::Timer;
use core::num::NonZeroUsize;
use core::ops::Range;
mod asm_utils;
mod eeprom;
mod flash;
mod sram;
mod utils;
#[doc(inline)]
pub use agb_save::{SerializationError, Slot};
pub type SaveError = agb_save::SaveError<StorageError>;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
#[non_exhaustive]
pub enum MediaType {
Sram32K,
Eeprom8K,
Eeprom512B,
Flash64K,
Flash128K,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum StorageError {
NoMedia,
WriteError,
OperationTimedOut,
OutOfBounds,
MediaInUse,
IncompatibleCommand,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct MediaInfo {
pub media_type: MediaType,
pub sector_shift: usize,
pub sector_count: usize,
pub uses_prepare_write: bool,
}
impl MediaInfo {
#[must_use]
pub fn sector_size(&self) -> usize {
1 << self.sector_shift
}
#[must_use]
#[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize {
self.sector_count << self.sector_shift
}
}
trait RawSaveAccess: Sync {
fn info(&self) -> Result<&'static MediaInfo, StorageError>;
fn read(
&self,
offset: usize,
buffer: &mut [u8],
timeout: &mut Timeout,
) -> Result<(), StorageError>;
fn verify(
&self,
offset: usize,
buffer: &[u8],
timeout: &mut Timeout,
) -> Result<bool, StorageError>;
fn prepare_write(
&self,
sector: usize,
count: usize,
timeout: &mut Timeout,
) -> Result<(), StorageError>;
fn write(
&self,
offset: usize,
buffer: &[u8],
timeout: &mut Timeout,
) -> Result<(), StorageError>;
}
static CURRENT_SAVE_ACCESS: Lock<Option<&'static dyn RawSaveAccess>> = Lock::new(None);
struct SaveConfig {
num_slots: usize,
magic: [u8; 32],
}
static SAVE_CONFIG: Lock<Option<SaveConfig>> = Lock::new(None);
fn set_save_implementation(
access_impl: &'static dyn RawSaveAccess,
num_slots: usize,
magic: [u8; 32],
) {
let mut access = CURRENT_SAVE_ACCESS.lock();
assert!(
access.is_none(),
"Cannot initialize the save media engine more than once."
);
*access = Some(access_impl);
let mut config = SAVE_CONFIG.lock();
*config = Some(SaveConfig { num_slots, magic });
}
fn get_save_implementation() -> Option<&'static dyn RawSaveAccess> {
*CURRENT_SAVE_ACCESS.lock()
}
pub(crate) struct SaveData {
_lock: RawLockGuard<'static>,
access: &'static dyn RawSaveAccess,
info: &'static MediaInfo,
timeout: utils::Timeout,
}
impl SaveData {
fn new(timer: Option<Timer>) -> Result<SaveData, StorageError> {
match get_save_implementation() {
Some(access) => Ok(SaveData {
_lock: utils::lock_media_access()?,
access,
info: access.info()?,
timeout: utils::Timeout::new(timer),
}),
None => Err(StorageError::NoMedia),
}
}
fn check_bounds(&self, range: Range<usize>) -> Result<(), StorageError> {
let len = self.info.len();
if range.start >= len || range.end > len {
Err(StorageError::OutOfBounds)
} else {
Ok(())
}
}
fn check_bounds_len(&self, offset: usize, len: usize) -> Result<(), StorageError> {
self.check_bounds(offset..(offset + len))
}
}
mod marker {
#[repr(align(4))]
struct Align<T>(T);
static EEPROM: Align<[u8; 12]> = Align(*b"EEPROM_Vnnn\0");
static SRAM: Align<[u8; 12]> = Align(*b"SRAM_Vnnn\0\0\0");
static FLASH512K: Align<[u8; 16]> = Align(*b"FLASH512_Vnnn\0\0\0");
static FLASH1M: Align<[u8; 16]> = Align(*b"FLASH1M_Vnnn\0\0\0\0");
#[inline(always)]
pub fn emit_eeprom_marker() {
core::hint::black_box(&EEPROM);
}
#[inline(always)]
pub fn emit_sram_marker() {
core::hint::black_box(&SRAM);
}
#[inline(always)]
pub fn emit_flash_512k_marker() {
core::hint::black_box(&FLASH512K);
}
#[inline(always)]
pub fn emit_flash_1m_marker() {
core::hint::black_box(&FLASH1M);
}
}
#[non_exhaustive]
pub struct SaveManager {}
impl SaveManager {
pub(crate) const fn new() -> Self {
SaveManager {}
}
pub fn init_sram<Metadata>(
&mut self,
num_slots: usize,
magic: [u8; 32],
) -> Result<SaveSlotManager<Metadata>, SaveError>
where
Metadata: serde::Serialize + serde::de::DeserializeOwned + Clone,
{
marker::emit_sram_marker();
set_save_implementation(&sram::BatteryBackedAccess, num_slots, magic);
let save_data = SaveData::new(None).map_err(SaveError::Storage)?;
Ok(SaveSlotManager {
inner: agb_save::SaveSlotManager::new(save_data, num_slots, magic)?,
})
}
pub fn init_flash_64k<Metadata>(
&mut self,
num_slots: usize,
magic: [u8; 32],
timer: Option<Timer>,
) -> Result<SaveSlotManager<Metadata>, SaveError>
where
Metadata: serde::Serialize + serde::de::DeserializeOwned + Clone,
{
marker::emit_flash_512k_marker();
set_save_implementation(&flash::FlashAccess, num_slots, magic);
let save_data = SaveData::new(timer).map_err(SaveError::Storage)?;
Ok(SaveSlotManager {
inner: agb_save::SaveSlotManager::new(save_data, num_slots, magic)?,
})
}
pub fn init_flash_128k<Metadata>(
&mut self,
num_slots: usize,
magic: [u8; 32],
timer: Option<Timer>,
) -> Result<SaveSlotManager<Metadata>, SaveError>
where
Metadata: serde::Serialize + serde::de::DeserializeOwned + Clone,
{
marker::emit_flash_1m_marker();
set_save_implementation(&flash::FlashAccess, num_slots, magic);
let save_data = SaveData::new(timer).map_err(SaveError::Storage)?;
Ok(SaveSlotManager {
inner: agb_save::SaveSlotManager::new(save_data, num_slots, magic)?,
})
}
pub fn init_eeprom_512b<Metadata>(
&mut self,
num_slots: usize,
magic: [u8; 32],
timer: Option<Timer>,
) -> Result<SaveSlotManager<Metadata>, SaveError>
where
Metadata: serde::Serialize + serde::de::DeserializeOwned + Clone,
{
marker::emit_eeprom_marker();
set_save_implementation(&eeprom::Eeprom512B, num_slots, magic);
let save_data = SaveData::new(timer).map_err(SaveError::Storage)?;
Ok(SaveSlotManager {
inner: agb_save::SaveSlotManager::new(save_data, num_slots, magic)?,
})
}
pub fn init_eeprom_8k<Metadata>(
&mut self,
num_slots: usize,
magic: [u8; 32],
timer: Option<Timer>,
) -> Result<SaveSlotManager<Metadata>, SaveError>
where
Metadata: serde::Serialize + serde::de::DeserializeOwned + Clone,
{
marker::emit_eeprom_marker();
set_save_implementation(&eeprom::Eeprom8K, num_slots, magic);
let save_data = SaveData::new(timer).map_err(SaveError::Storage)?;
Ok(SaveSlotManager {
inner: agb_save::SaveSlotManager::new(save_data, num_slots, magic)?,
})
}
pub fn reopen<Metadata>(
&mut self,
timer: Option<Timer>,
) -> Result<SaveSlotManager<Metadata>, SaveError>
where
Metadata: serde::Serialize + serde::de::DeserializeOwned + Clone,
{
let config = SAVE_CONFIG.lock();
let config = config
.as_ref()
.ok_or(SaveError::Storage(StorageError::NoMedia))?;
let save_data = SaveData::new(timer).map_err(SaveError::Storage)?;
Ok(SaveSlotManager {
inner: agb_save::SaveSlotManager::new(save_data, config.num_slots, config.magic)?,
})
}
}
pub struct SaveSlotManager<Metadata = ()> {
inner: agb_save::SaveSlotManager<SaveData, Metadata>,
}
impl<Metadata> SaveSlotManager<Metadata>
where
Metadata: serde::Serialize + serde::de::DeserializeOwned + Clone,
{
#[must_use]
pub fn num_slots(&self) -> usize {
self.inner.num_slots()
}
#[must_use]
pub fn slot(&self, slot: usize) -> Slot<'_, Metadata> {
self.inner.slot(slot)
}
#[must_use]
pub fn metadata(&self, slot: usize) -> Option<&Metadata> {
self.inner.metadata(slot)
}
pub fn read<T>(&mut self, slot: usize) -> Result<T, SaveError>
where
T: serde::de::DeserializeOwned,
{
self.inner.read(slot)
}
pub fn write<T>(&mut self, slot: usize, data: &T, metadata: &Metadata) -> Result<(), SaveError>
where
T: serde::Serialize,
{
self.inner.write(slot, data, metadata)
}
pub fn erase(&mut self, slot: usize) -> Result<(), SaveError> {
self.inner.erase(slot)
}
pub fn slots(&self) -> impl Iterator<Item = Slot<'_, Metadata>> {
self.inner.slots()
}
}
impl agb_save::StorageMedium for SaveData {
type Error = StorageError;
fn info(&self) -> agb_save::StorageInfo {
let sector_size = self.info.sector_size();
agb_save::StorageInfo {
size: self.info.len(),
erase_size: if self.info.uses_prepare_write {
NonZeroUsize::new(sector_size)
} else {
None
},
write_size: NonZeroUsize::new(if self.info.uses_prepare_write {
1
} else {
sector_size
})
.unwrap(),
}
}
fn read(&mut self, offset: usize, buf: &mut [u8]) -> Result<(), Self::Error> {
self.check_bounds_len(offset, buf.len())?;
self.access.read(offset, buf, &mut self.timeout)
}
fn erase(&mut self, offset: usize, len: usize) -> Result<(), Self::Error> {
if self.info.uses_prepare_write {
let shift = self.info.sector_shift;
self.access
.prepare_write(offset >> shift, len >> shift, &mut self.timeout)
} else {
Ok(())
}
}
fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), Self::Error> {
self.check_bounds_len(offset, data.len())?;
self.access.write(offset, data, &mut self.timeout)
}
fn verify(&mut self, offset: usize, expected: &[u8]) -> Result<bool, Self::Error> {
self.check_bounds_len(offset, expected.len())?;
self.access.verify(offset, expected, &mut self.timeout)
}
}