mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-26 00:56:38 +11:00
Reintegrate SRAM save media reader.
This commit is contained in:
parent
d50413a3cc
commit
ec41db2fc9
4 changed files with 302 additions and 195 deletions
|
@ -224,6 +224,8 @@ pub struct Gba {
|
||||||
pub sound: sound::dmg::Sound,
|
pub sound: sound::dmg::Sound,
|
||||||
/// Manages access to the Game Boy Advance's direct sound mixer for playing raw wav files.
|
/// Manages access to the Game Boy Advance's direct sound mixer for playing raw wav files.
|
||||||
pub mixer: sound::mixer::MixerController,
|
pub mixer: sound::mixer::MixerController,
|
||||||
|
/// Manages access to the Game Boy Advance cartridge's save chip.
|
||||||
|
pub save: save::SaveManager,
|
||||||
/// Manages access to the Game Boy Advance's 4 timers.
|
/// Manages access to the Game Boy Advance's 4 timers.
|
||||||
pub timers: timer::TimerController,
|
pub timers: timer::TimerController,
|
||||||
}
|
}
|
||||||
|
@ -240,6 +242,7 @@ impl Gba {
|
||||||
display: display::Display::new(),
|
display: display::Display::new(),
|
||||||
sound: sound::dmg::Sound::new(),
|
sound: sound::dmg::Sound::new(),
|
||||||
mixer: sound::mixer::MixerController::new(),
|
mixer: sound::mixer::MixerController::new(),
|
||||||
|
save: save::SaveManager::new(),
|
||||||
timers: timer::TimerController::new(),
|
timers: timer::TimerController::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
//! performed via code in WRAM and cannot be accessed by DMA.
|
//! performed via code in WRAM and cannot be accessed by DMA.
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn WramXferBuf(src: *const u8, dst: *mut u8, count: usize);
|
fn WramXferBuf(src: *const u8, dst: *mut u8, count: usize);
|
||||||
fn WramReadByte(src: *const u8) -> u8;
|
fn WramReadByte(src: *const u8) -> u8;
|
||||||
fn WramVerifyBuf(buf1: *const u8, buf2: *const u8, count: usize) -> bool;
|
fn WramVerifyBuf(buf1: *const u8, buf2: *const u8, count: usize) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies data from a given memory address into a buffer.
|
/// Copies data from a given memory address into a buffer.
|
||||||
|
@ -17,9 +17,9 @@ extern "C" {
|
||||||
/// This uses raw addresses into the memory space. Use with care.
|
/// This uses raw addresses into the memory space. Use with care.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn read_raw_buf(dst: &mut [u8], src: usize) {
|
pub unsafe fn read_raw_buf(dst: &mut [u8], src: usize) {
|
||||||
if dst.len() != 0 {
|
if dst.len() != 0 {
|
||||||
WramXferBuf(src as _, dst.as_mut_ptr(), dst.len());
|
WramXferBuf(src as _, dst.as_mut_ptr(), dst.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies data from a buffer into a given memory address.
|
/// Copies data from a buffer into a given memory address.
|
||||||
|
@ -30,9 +30,9 @@ pub unsafe fn read_raw_buf(dst: &mut [u8], src: usize) {
|
||||||
/// This uses raw addresses into the memory space. Use with care.
|
/// This uses raw addresses into the memory space. Use with care.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn write_raw_buf(dst: usize, src: &[u8]) {
|
pub unsafe fn write_raw_buf(dst: usize, src: &[u8]) {
|
||||||
if src.len() != 0 {
|
if src.len() != 0 {
|
||||||
WramXferBuf(src.as_ptr(), dst as _, src.len());
|
WramXferBuf(src.as_ptr(), dst as _, src.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies that the data in a buffer matches that in a given memory address.
|
/// Verifies that the data in a buffer matches that in a given memory address.
|
||||||
|
@ -44,11 +44,11 @@ pub unsafe fn write_raw_buf(dst: usize, src: &[u8]) {
|
||||||
/// This uses raw addresses into the memory space. Use with care.
|
/// This uses raw addresses into the memory space. Use with care.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn verify_raw_buf(buf1: &[u8], buf2: usize) -> bool {
|
pub unsafe fn verify_raw_buf(buf1: &[u8], buf2: usize) -> bool {
|
||||||
if buf1.len() != 0 {
|
if buf1.len() != 0 {
|
||||||
WramVerifyBuf(buf1.as_ptr(), buf2 as _, buf1.len() - 1)
|
WramVerifyBuf(buf1.as_ptr(), buf2 as _, buf1.len() - 1)
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a byte from a given memory address.
|
/// Reads a byte from a given memory address.
|
||||||
|
@ -59,5 +59,5 @@ pub unsafe fn verify_raw_buf(buf1: &[u8], buf2: usize) -> bool {
|
||||||
/// This uses raw addresses into the memory space. Use with care.
|
/// This uses raw addresses into the memory space. Use with care.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn read_raw_byte(src: usize) -> u8 {
|
pub unsafe fn read_raw_byte(src: usize) -> u8 {
|
||||||
WramReadByte(src as _)
|
WramReadByte(src as _)
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,238 +107,286 @@ use crate::sync::{Mutex, RawMutexGuard};
|
||||||
use crate::timer::Timer;
|
use crate::timer::Timer;
|
||||||
|
|
||||||
mod asm_utils;
|
mod asm_utils;
|
||||||
//mod setup;
|
|
||||||
mod utils;
|
|
||||||
|
|
||||||
//pub use asm_utils::*;
|
|
||||||
//pub use setup::*;
|
|
||||||
|
|
||||||
//pub mod eeprom;
|
//pub mod eeprom;
|
||||||
//pub mod flash;
|
//pub mod flash;
|
||||||
//pub mod sram;
|
mod sram;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
/// A list of save media types.
|
/// A list of save media types.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum MediaType {
|
pub enum MediaType {
|
||||||
/// 32KiB Battery-Backed SRAM or FRAM
|
/// 32KiB Battery-Backed SRAM or FRAM
|
||||||
Sram32K,
|
Sram32K,
|
||||||
/// 8KiB EEPROM
|
/// 8KiB EEPROM
|
||||||
Eeprom8K,
|
Eeprom8K,
|
||||||
/// 512B EEPROM
|
/// 512B EEPROM
|
||||||
Eeprom512B,
|
Eeprom512B,
|
||||||
/// 64KiB flash chip
|
/// 64KiB flash chip
|
||||||
Flash64K,
|
Flash64K,
|
||||||
/// 128KiB flash chip
|
/// 128KiB flash chip
|
||||||
Flash128K,
|
Flash128K,
|
||||||
/// A user-defined save media type
|
|
||||||
Custom,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type used for errors encountered while reading or writing save media.
|
/// The type used for errors encountered while reading or writing save media.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There is no save media attached to this game cart.
|
/// There is no save media attached to this game cart.
|
||||||
NoMedia,
|
NoMedia,
|
||||||
/// Failed to write the data to save media.
|
/// Failed to write the data to save media.
|
||||||
WriteError,
|
WriteError,
|
||||||
/// An operation on save media timed out.
|
/// An operation on save media timed out.
|
||||||
OperationTimedOut,
|
OperationTimedOut,
|
||||||
/// An attempt was made to access save media at an invalid offset.
|
/// An attempt was made to access save media at an invalid offset.
|
||||||
OutOfBounds,
|
OutOfBounds,
|
||||||
/// The media is already in use.
|
/// The media is already in use.
|
||||||
///
|
///
|
||||||
/// This can generally only happen in an IRQ that happens during an ongoing
|
/// This can generally only happen in an IRQ that happens during an ongoing
|
||||||
/// save media operation.
|
/// save media operation.
|
||||||
MediaInUse,
|
MediaInUse,
|
||||||
/// This command cannot be used with the save media in use.
|
/// This command cannot be used with the save media in use.
|
||||||
IncompatibleCommand,
|
IncompatibleCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information about the save media used.
|
/// Information about the save media used.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct MediaInfo {
|
pub struct MediaInfo {
|
||||||
/// The type of save media installed.
|
/// The type of save media installed.
|
||||||
pub media_type: MediaType,
|
pub media_type: MediaType,
|
||||||
/// The power-of-two size of each sector. Zero represents a sector size of
|
/// The power-of-two size of each sector. Zero represents a sector size of
|
||||||
/// 0, implying sectors are not in use.
|
/// 0, implying sectors are not in use.
|
||||||
///
|
///
|
||||||
/// (For example, 512 byte sectors would return 9 here.)
|
/// (For example, 512 byte sectors would return 9 here.)
|
||||||
pub sector_shift: usize,
|
pub sector_shift: usize,
|
||||||
/// The size of the save media, in sectors.
|
/// The size of the save media, in sectors.
|
||||||
pub sector_count: usize,
|
pub sector_count: usize,
|
||||||
/// Whether the save media type requires media be prepared before writing.
|
/// Whether the save media type requires media be prepared before writing.
|
||||||
pub uses_prepare_write: bool,
|
pub uses_prepare_write: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait allowing low-level saving and writing to save media.
|
/// A trait allowing low-level saving and writing to save media.
|
||||||
trait RawSaveAccess: Sync {
|
trait RawSaveAccess: Sync {
|
||||||
fn info(&self) -> Result<&'static MediaInfo, Error>;
|
fn info(&self) -> Result<&'static MediaInfo, Error>;
|
||||||
fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error>;
|
fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error>;
|
||||||
fn verify(&self, offset: usize, buffer: &[u8]) -> Result<bool, Error>;
|
fn verify(&self, offset: usize, buffer: &[u8]) -> Result<bool, Error>;
|
||||||
fn prepare_write(&self, sector: usize, count: usize) -> Result<(), Error>;
|
fn prepare_write(&self, sector: usize, count: usize) -> Result<(), Error>;
|
||||||
fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error>;
|
fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CURRENT_SAVE_ACCESS: Mutex<Option<&'static dyn RawSaveAccess>> = Mutex::new(None);
|
static CURRENT_SAVE_ACCESS: Mutex<Option<&'static dyn RawSaveAccess>> = Mutex::new(None);
|
||||||
|
|
||||||
fn set_save_implementation(access_impl: &'static dyn RawSaveAccess) {
|
fn set_save_implementation(access_impl: &'static dyn RawSaveAccess) {
|
||||||
let mut access = CURRENT_SAVE_ACCESS.lock();
|
let mut access = CURRENT_SAVE_ACCESS.lock();
|
||||||
assert!(access.is_none(), "Cannot initialize the savegame engine more than once.");
|
assert!(access.is_none(), "Cannot initialize the savegame engine more than once.");
|
||||||
*access = Some(access_impl);
|
*access = Some(access_impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_save_implementation() -> Option<&'static dyn RawSaveAccess> {
|
fn get_save_implementation() -> Option<&'static dyn RawSaveAccess> {
|
||||||
*CURRENT_SAVE_ACCESS.lock()
|
*CURRENT_SAVE_ACCESS.lock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows reading and writing of save media.
|
/// Allows reading and writing of save media.
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct SaveData {
|
pub struct SaveData {
|
||||||
lock: RawMutexGuard<'static>,
|
lock: RawMutexGuard<'static>,
|
||||||
access: &'static dyn RawSaveAccess,
|
access: &'static dyn RawSaveAccess,
|
||||||
info: &'static MediaInfo,
|
info: &'static MediaInfo,
|
||||||
timeout: utils::Timeout,
|
timeout: utils::Timeout,
|
||||||
}
|
}
|
||||||
impl SaveData {
|
impl SaveData {
|
||||||
/// Creates a new save accessor around the current save implementaiton.
|
/// Creates a new save accessor around the current save implementaiton.
|
||||||
fn new(timer: Option<Timer>) -> Result<SaveData, Error> {
|
fn new(timer: Option<Timer>) -> Result<SaveData, Error> {
|
||||||
match get_save_implementation() {
|
match get_save_implementation() {
|
||||||
Some(access) => Ok(SaveData {
|
Some(access) => Ok(SaveData {
|
||||||
lock: utils::lock_media()?,
|
lock: utils::lock_media()?,
|
||||||
access,
|
access,
|
||||||
info: access.info()?,
|
info: access.info()?,
|
||||||
timeout: utils::Timeout::new(timer),
|
timeout: utils::Timeout::new(timer),
|
||||||
}),
|
}),
|
||||||
None => Err(Error::NoMedia),
|
None => Err(Error::NoMedia),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the media info underlying this accessor.
|
/// Returns the media info underlying this accessor.
|
||||||
pub fn media_info(&self) -> &'static MediaInfo {
|
pub fn media_info(&self) -> &'static MediaInfo {
|
||||||
self.info
|
self.info
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the save media type being used.
|
|
||||||
pub fn media_type(&self) -> MediaType {
|
|
||||||
self.info.media_type
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the sector size of the save media. It is generally optimal to
|
|
||||||
/// write data in blocks that are aligned to the sector size.
|
|
||||||
pub fn sector_size(&self) -> usize {
|
|
||||||
1 << self.info.sector_shift
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the total length of this save media.
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.info.sector_count << self.info.sector_shift
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_bounds(&self, range: Range<usize>) -> Result<(), Error> {
|
|
||||||
if range.start >= self.len() || range.end >= self.len() {
|
|
||||||
Err(Error::OutOfBounds)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
fn check_bounds_len(&self, offset: usize, len: usize) -> Result<(), Error> {
|
|
||||||
self.check_bounds(offset..(offset + len))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copies data from the save media to a buffer.
|
/// Returns the save media type being used.
|
||||||
///
|
pub fn media_type(&self) -> MediaType {
|
||||||
/// If an error is returned, the contents of the buffer are unpredictable.
|
self.info.media_type
|
||||||
pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
}
|
||||||
self.check_bounds_len(offset, buffer.len())?;
|
|
||||||
self.access.read(offset, buffer)
|
/// Returns the sector size of the save media. It is generally optimal to
|
||||||
}
|
/// write data in blocks that are aligned to the sector size.
|
||||||
|
pub fn sector_size(&self) -> usize {
|
||||||
/// Verifies that a given block of memory matches the save media.
|
1 << self.info.sector_shift
|
||||||
pub fn verify(&self, offset: usize, buffer: &[u8]) -> Result<bool, Error> {
|
}
|
||||||
self.check_bounds_len(offset, buffer.len())?;
|
|
||||||
self.access.verify(offset, buffer)
|
/// Returns the total length of this save media.
|
||||||
}
|
pub fn len(&self) -> usize {
|
||||||
|
self.info.sector_count << self.info.sector_shift
|
||||||
/// Returns a range that contains all sectors the input range overlaps.
|
}
|
||||||
///
|
|
||||||
/// This can be used to calculate which blocks would be erased by a call
|
fn check_bounds(&self, range: Range<usize>) -> Result<(), Error> {
|
||||||
/// to [`prepare_write`](`SaveAccess::prepare_write`)
|
if range.start >= self.len() || range.end >= self.len() {
|
||||||
pub fn align_range(&self, range: Range<usize>) -> Range<usize> {
|
Err(Error::OutOfBounds)
|
||||||
let shift = self.info.sector_shift;
|
} else {
|
||||||
let mask = (1 << shift) - 1;
|
Ok(())
|
||||||
(range.start & !mask)..((range.end + mask) & !mask)
|
}
|
||||||
}
|
}
|
||||||
|
fn check_bounds_len(&self, offset: usize, len: usize) -> Result<(), Error> {
|
||||||
/// Prepares a given span of offsets for writing.
|
self.check_bounds(offset..(offset + len))
|
||||||
///
|
}
|
||||||
/// This will erase any data in any sector overlapping the input range. To
|
|
||||||
/// calculate which offset ranges would be affected, use the
|
/// Copies data from the save media to a buffer.
|
||||||
/// [`align_range`](`SaveAccess::align_range`) function.
|
///
|
||||||
pub fn prepare_write(&mut self, range: Range<usize>) -> Result<SavePreparedBlock, Error> {
|
/// If an error is returned, the contents of the buffer are unpredictable.
|
||||||
self.check_bounds(range.clone())?;
|
pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
||||||
if self.info.uses_prepare_write {
|
self.check_bounds_len(offset, buffer.len())?;
|
||||||
let range = self.align_range(range.clone());
|
self.access.read(offset, buffer)
|
||||||
let shift = self.info.sector_shift;
|
}
|
||||||
self.access.prepare_write(range.start >> shift, range.len() >> shift)?;
|
|
||||||
|
/// Verifies that a given block of memory matches the save media.
|
||||||
|
pub fn verify(&self, offset: usize, buffer: &[u8]) -> Result<bool, Error> {
|
||||||
|
self.check_bounds_len(offset, buffer.len())?;
|
||||||
|
self.access.verify(offset, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a range that contains all sectors the input range overlaps.
|
||||||
|
///
|
||||||
|
/// This can be used to calculate which blocks would be erased by a call
|
||||||
|
/// to [`prepare_write`](`SaveAccess::prepare_write`)
|
||||||
|
pub fn align_range(&self, range: Range<usize>) -> Range<usize> {
|
||||||
|
let shift = self.info.sector_shift;
|
||||||
|
let mask = (1 << shift) - 1;
|
||||||
|
(range.start & !mask)..((range.end + mask) & !mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a given span of offsets for writing.
|
||||||
|
///
|
||||||
|
/// This will erase any data in any sector overlapping the input range. To
|
||||||
|
/// calculate which offset ranges would be affected, use the
|
||||||
|
/// [`align_range`](`SaveAccess::align_range`) function.
|
||||||
|
pub fn prepare_write(&mut self, range: Range<usize>) -> Result<SavePreparedBlock, Error> {
|
||||||
|
self.check_bounds(range.clone())?;
|
||||||
|
if self.info.uses_prepare_write {
|
||||||
|
let range = self.align_range(range.clone());
|
||||||
|
let shift = self.info.sector_shift;
|
||||||
|
self.access.prepare_write(range.start >> shift, range.len() >> shift)?;
|
||||||
|
}
|
||||||
|
Ok(SavePreparedBlock {
|
||||||
|
parent: self,
|
||||||
|
range
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Ok(SavePreparedBlock {
|
|
||||||
parent: self,
|
|
||||||
range
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A block of save memory that has been prepared for writing.
|
/// A block of save memory that has been prepared for writing.
|
||||||
pub struct SavePreparedBlock<'a> {
|
pub struct SavePreparedBlock<'a> {
|
||||||
parent: &'a mut SaveData,
|
parent: &'a mut SaveData,
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
}
|
}
|
||||||
impl<'a> SavePreparedBlock<'a> {
|
impl<'a> SavePreparedBlock<'a> {
|
||||||
/// Writes a given buffer into the save media.
|
/// Writes a given buffer into the save media.
|
||||||
///
|
///
|
||||||
/// Multiple overlapping writes to the same memory range without a separate
|
/// Multiple overlapping writes to the same memory range without a separate
|
||||||
/// call to `prepare_write` will leave the save data in an unpredictable
|
/// call to `prepare_write` will leave the save data in an unpredictable
|
||||||
/// state. If an error is returned, the contents of the save media is
|
/// state. If an error is returned, the contents of the save media is
|
||||||
/// unpredictable.
|
/// unpredictable.
|
||||||
pub fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
pub fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
||||||
if buffer.len() == 0 {
|
if buffer.len() == 0 {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if !self.range.contains(&offset) ||
|
} else if !self.range.contains(&offset) ||
|
||||||
!self.range.contains(&(offset + buffer.len() - 1)) {
|
!self.range.contains(&(offset + buffer.len() - 1)) {
|
||||||
Err(Error::OutOfBounds)
|
Err(Error::OutOfBounds)
|
||||||
} else {
|
} else {
|
||||||
self.parent.access.write(offset, buffer)
|
self.parent.access.write(offset, buffer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes and validates a given buffer into the save media.
|
/// Writes and validates a given buffer into the save media.
|
||||||
///
|
///
|
||||||
/// This function will verify that the write has completed successfully, and
|
/// This function will verify that the write has completed successfully, and
|
||||||
/// return an error if it has not done so.
|
/// return an error if it has not done so.
|
||||||
///
|
///
|
||||||
/// Multiple overlapping writes to the same memory range without a separate
|
/// Multiple overlapping writes to the same memory range without a separate
|
||||||
/// call to `prepare_write` will leave the save data in an unpredictable
|
/// call to `prepare_write` will leave the save data in an unpredictable
|
||||||
/// state. If an error is returned, the contents of the save media is
|
/// state. If an error is returned, the contents of the save media is
|
||||||
/// unpredictable.
|
/// unpredictable.
|
||||||
pub fn write_and_verify(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
pub fn write_and_verify(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
||||||
self.write(offset, buffer)?;
|
self.write(offset, buffer)?;
|
||||||
if !self.parent.verify(offset, buffer)? {
|
if !self.parent.verify(offset, buffer)? {
|
||||||
Err(Error::WriteError)
|
Err(Error::WriteError)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
crate::sync::memory_read_hint(&EEPROM);
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn emit_sram_marker() {
|
||||||
|
crate::sync::memory_read_hint(&SRAM);
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn emit_flash_512k_marker() {
|
||||||
|
crate::sync::memory_read_hint(&FLASH512K);
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn emit_flash_1m_marker() {
|
||||||
|
crate::sync::memory_read_hint(&FLASH1M);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows access to the cartridge's save data.
|
/// Allows access to the cartridge's save data.
|
||||||
pub struct SaveManager;
|
#[non_exhaustive]
|
||||||
|
pub struct SaveManager {}
|
||||||
impl SaveManager {
|
impl SaveManager {
|
||||||
pub fn access() -> Result<SaveData, Error> {
|
pub(crate) const fn new() -> Self {
|
||||||
SaveData::new(None)
|
SaveManager {}
|
||||||
}
|
}
|
||||||
pub fn access_with_timer(timer: Timer) -> Result<SaveData, Error> {
|
|
||||||
SaveData::new(Some(timer))
|
/// Declares that the ROM uses battery backed SRAM/FRAM.
|
||||||
}
|
///
|
||||||
|
/// Battery Backed SRAM is generally very fast, but limited in size compared
|
||||||
|
/// to flash chips.
|
||||||
|
///
|
||||||
|
/// This creates a marker in the ROM that allows emulators to understand what
|
||||||
|
/// save type the Game Pak uses, and configures the save manager to use the
|
||||||
|
/// given save type.
|
||||||
|
///
|
||||||
|
/// Only one `init_*` function may be called in the lifetime of the program.
|
||||||
|
pub fn init_sram() {
|
||||||
|
marker::emit_sram_marker();
|
||||||
|
set_save_implementation(&sram::BatteryBackedAccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new accessor to the save data.
|
||||||
|
///
|
||||||
|
/// You must have initialized the save manager beforehand to use a specific
|
||||||
|
/// type of media before calling this method.
|
||||||
|
pub fn access() -> Result<SaveData, Error> {
|
||||||
|
SaveData::new(None)
|
||||||
|
}
|
||||||
|
/// Creates a new accessor to the save data that uses the given timer for timeouts.
|
||||||
|
///
|
||||||
|
/// You must have initialized the save manager beforehand to use a specific
|
||||||
|
/// type of media before calling this method.
|
||||||
|
pub fn access_with_timer(timer: Timer) -> Result<SaveData, Error> {
|
||||||
|
SaveData::new(Some(timer))
|
||||||
|
}
|
||||||
}
|
}
|
56
agb/src/save/sram.rs
Normal file
56
agb/src/save/sram.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
//! Module for battery backed SRAM save media support.
|
||||||
|
//!
|
||||||
|
//! SRAM acts as ordinary memory mapped into the memory space, and as such
|
||||||
|
//! is accessed using normal memory read/write commands.
|
||||||
|
|
||||||
|
use crate::save::{Error, MediaInfo, MediaType, RawSaveAccess};
|
||||||
|
use crate::save::asm_utils::*;
|
||||||
|
|
||||||
|
const SRAM_SIZE: usize = 32 * 1024; // 32 KiB
|
||||||
|
|
||||||
|
/// Checks whether an offset is contained within the bounds of the SRAM.
|
||||||
|
fn check_bounds(offset: usize, len: usize) -> Result<(), Error> {
|
||||||
|
if offset.checked_add(len).is_none() || offset + len > SRAM_SIZE {
|
||||||
|
return Err(Error::OutOfBounds);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`RawSaveAccess`] used for battery backed SRAM.
|
||||||
|
pub struct BatteryBackedAccess;
|
||||||
|
impl RawSaveAccess for BatteryBackedAccess {
|
||||||
|
fn info(&self) -> Result<&'static MediaInfo, Error> {
|
||||||
|
Ok(&MediaInfo {
|
||||||
|
media_type: MediaType::Sram32K,
|
||||||
|
sector_shift: 0,
|
||||||
|
sector_count: SRAM_SIZE,
|
||||||
|
uses_prepare_write: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
|
||||||
|
check_bounds(offset, buffer.len())?;
|
||||||
|
unsafe {
|
||||||
|
read_raw_buf(buffer, 0x0E000000 + offset);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify(&self, offset: usize, buffer: &[u8]) -> Result<bool, Error> {
|
||||||
|
check_bounds(offset, buffer.len())?;
|
||||||
|
let val = unsafe { verify_raw_buf(buffer, 0x0E000000 + offset) };
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_write(&self, _: usize, _: usize) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
|
||||||
|
check_bounds(offset, buffer.len())?;
|
||||||
|
unsafe {
|
||||||
|
write_raw_buf(0x0E000000 + offset, buffer);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue