diff --git a/agb/src/asm_include.s b/agb/src/asm_include.s index 5508e4fe..1682792c 100644 --- a/agb/src/asm_include.s +++ b/agb/src/asm_include.s @@ -13,3 +13,19 @@ .size \functionName,.-\functionName .endfunc .endm + +.macro agb_thumb_func functionName:req +.section .iwram.\functionName, "ax", %progbits +.thumb +.align 2 +.global \functionName +.type \functionName, %function +.func \functionName +\functionName: +.endm + +.macro agb_thumb_end functionName:req +.pool +.size \functionName,.-\functionName +.endfunc +.endm diff --git a/agb/src/save/asm_routines.s b/agb/src/save/asm_routines.s index 41e8df74..b04d407b 100644 --- a/agb/src/save/asm_routines.s +++ b/agb/src/save/asm_routines.s @@ -1,89 +1,49 @@ +.include "src/asm_include.s" + @ @ char WramReadByte(const char* offset); @ @ A routine that reads a byte from a given memory offset. @ - .thumb - .global WramReadByte - .thumb_func - .align 2 -WramReadByte: - ldr r1, =WramReadByteInner - bx r1 - - .section .data - - .thumb - .thumb_func - .align 2 -WramReadByteInner: +agb_thumb_func agb_rs__WramReadByte ldrb r0, [r0] mov pc, lr - - .section .text +agb_thumb_end agb_rs__WramReadByte @ @ bool WramVerifyBuf(const char* buf1, const char* buf2, int count); @ @ A routine that compares two memory offsets. @ - .thumb - .global WramVerifyBuf - .thumb_func - .align 2 -WramVerifyBuf: +agb_thumb_func agb_rs__WramVerifyBuf push {r4-r5, lr} movs r5, r0 @ set up r5 to be r0, so we can use it immediately for the return result movs r0, #0 @ set up r0 so the default return result is false - ldr r4, =WramVerifyBufInner - bx r4 @ jump to the part in WRAM - .section .data - - .thumb - .thumb_func - .align 2 -WramVerifyBufInner: @ At this point, buf1 is actually in r5, so r0 can be used as a status return - ldrb r3, [r5,r2] +1: ldrb r3, [r5,r2] ldrb r4, [r1,r2] cmp r3, r4 bne 0f sub r2, #1 - bpl WramVerifyBufInner + bpl 1b @ Returns from the function successfully movs r0, #1 0: @ Jumps to here return the function unsuccessfully, because r0 contains 0 at this point pop {r4-r5, pc} +agb_thumb_end agb_rs__WramVerifyBuf - .section .text @ @ void WramXferBuf(const char* source, char* dest, int count); @ @ A routine that copies one buffer into another. @ - .thumb - .global WramXferBuf - .thumb_func - .align 2 -WramXferBuf: - ldr r3, =WramXferBufInner - bx r3 - - .pool - .section .data - - .thumb - .thumb_func - .align 2 -WramXferBufInner: - sub r2, #1 +agb_thumb_func agb_rs__WramXferBuf +0: sub r2, #1 ldrb r3, [r0,r2] strb r3, [r1,r2] - bne WramXferBufInner + bne 0b mov pc, lr - - .pool - .section .text +agb_thumb_end agb_rs__WramXferBuf diff --git a/agb/src/save/asm_utils.rs b/agb/src/save/asm_utils.rs index 030fe0df..930995ad 100644 --- a/agb/src/save/asm_utils.rs +++ b/agb/src/save/asm_utils.rs @@ -3,9 +3,9 @@ //! performed via code in WRAM and cannot be accessed by DMA. extern "C" { - fn WramXferBuf(src: *const u8, dst: *mut u8, count: usize); - fn WramReadByte(src: *const u8) -> u8; - fn WramVerifyBuf(buf1: *const u8, buf2: *const u8, count: usize) -> bool; + fn agb_rs__WramXferBuf(src: *const u8, dst: *mut u8, count: usize); + fn agb_rs__WramReadByte(src: *const u8) -> u8; + fn agb_rs__WramVerifyBuf(buf1: *const u8, buf2: *const u8, count: usize) -> bool; } /// Copies data from a given memory address into a buffer. @@ -18,7 +18,7 @@ extern "C" { #[inline(always)] pub unsafe fn read_raw_buf(dst: &mut [u8], src: usize) { if dst.len() != 0 { - WramXferBuf(src as _, dst.as_mut_ptr(), dst.len()); + agb_rs__WramXferBuf(src as _, dst.as_mut_ptr(), dst.len()); } } @@ -31,7 +31,7 @@ pub unsafe fn read_raw_buf(dst: &mut [u8], src: usize) { #[inline(always)] pub unsafe fn write_raw_buf(dst: usize, src: &[u8]) { if src.len() != 0 { - WramXferBuf(src.as_ptr(), dst as _, src.len()); + agb_rs__WramXferBuf(src.as_ptr(), dst as _, src.len()); } } @@ -45,7 +45,7 @@ pub unsafe fn write_raw_buf(dst: usize, src: &[u8]) { #[inline(always)] pub unsafe fn verify_raw_buf(buf1: &[u8], buf2: usize) -> bool { if buf1.len() != 0 { - WramVerifyBuf(buf1.as_ptr(), buf2 as _, buf1.len() - 1) + agb_rs__WramVerifyBuf(buf1.as_ptr(), buf2 as _, buf1.len() - 1) } else { true } @@ -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. #[inline(always)] pub unsafe fn read_raw_byte(src: usize) -> u8 { - WramReadByte(src as _) + agb_rs__WramReadByte(src as _) } diff --git a/agb/src/save/mod.rs b/agb/src/save/mod.rs index eef39947..af9d7927 100644 --- a/agb/src/save/mod.rs +++ b/agb/src/save/mod.rs @@ -370,7 +370,7 @@ impl SaveManager { /// given save type. /// /// Only one `init_*` function may be called in the lifetime of the program. - pub fn init_sram() { + pub fn init_sram(&mut self) { marker::emit_sram_marker(); set_save_implementation(&sram::BatteryBackedAccess); } @@ -379,14 +379,15 @@ impl SaveManager { /// /// You must have initialized the save manager beforehand to use a specific /// type of media before calling this method. - pub fn access() -> Result { + pub fn access(&mut self) -> Result { 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 { + pub fn access_with_timer(&mut self, timer: Timer) -> Result { SaveData::new(Some(timer)) } } \ No newline at end of file diff --git a/examples/hyperspace-roll/src/main.rs b/examples/hyperspace-roll/src/main.rs index 9acf3c04..9ec4cc1a 100644 --- a/examples/hyperspace-roll/src/main.rs +++ b/examples/hyperspace-roll/src/main.rs @@ -96,10 +96,10 @@ struct Agb<'a> { } fn main(mut gba: agb::Gba) -> ! { - save::init_save(); + save::init_save(&mut gba).expect("Could not initialize save game"); if save::load_high_score() > 1000 { - save::save_high_score(0); + save::save_high_score(&mut gba, 0).expect("Could not reset high score"); } let gfx = gba.display.object.get(); @@ -207,7 +207,8 @@ fn main(mut gba: agb::Gba) -> ! { agb.obj.commit(); agb.sfx.customise(); if save::load_high_score() < current_level { - save::save_high_score(current_level); + save::save_high_score(&mut gba, current_level) + .expect("Could not save high score"); } break; } diff --git a/examples/hyperspace-roll/src/save.rs b/examples/hyperspace-roll/src/save.rs index e5df03e8..0db839de 100644 --- a/examples/hyperspace-roll/src/save.rs +++ b/examples/hyperspace-roll/src/save.rs @@ -1,44 +1,42 @@ -use agb::interrupt::free; -use bare_metal::Mutex; -use core::cell::RefCell; +use agb::Gba; +use agb::save::Error; +use agb::sync::Static; -const RAM_ADDRESS: *mut u8 = 0x0E00_0000 as *mut u8; -const HIGH_SCORE_ADDRESS_START: *mut u8 = RAM_ADDRESS.wrapping_offset(1); +static HIGHSCORE: Static = Static::new(0); -static HIGHSCORE: Mutex> = Mutex::new(RefCell::new(0)); +pub fn init_save(gba: &mut Gba) -> Result<(), Error> { + gba.save.init_sram(); -pub fn init_save() { - if (unsafe { RAM_ADDRESS.read_volatile() } == !0) { - save_high_score(0); - unsafe { RAM_ADDRESS.write_volatile(0) }; - } + let mut access = gba.save.access()?; - let mut a = [0; 4]; - for (idx, a) in a.iter_mut().enumerate() { - *a = unsafe { HIGH_SCORE_ADDRESS_START.add(idx).read_volatile() }; - } + let mut buffer = [0; 1]; + access.read(0, &mut buffer)?; - let high_score = u32::from_le_bytes(a); + if buffer[0] != 0 { + access.prepare_write(0..1)?.write(0, &[0])?; + core::mem::drop(access); + save_high_score(gba, 0)?; + } else { + let mut buffer = [0; 4]; + access.read(1, &mut buffer)?; + let high_score = u32::from_le_bytes(buffer); - free(|cs| { if high_score > 100 { - HIGHSCORE.borrow(cs).replace(0); + HIGHSCORE.write(0) } else { - HIGHSCORE.borrow(cs).replace(high_score); + HIGHSCORE.write(high_score) } - }); + } + + Ok(()) } pub fn load_high_score() -> u32 { - free(|cs| *HIGHSCORE.borrow(cs).borrow()) + HIGHSCORE.read() } -pub fn save_high_score(score: u32) { - let a = score.to_le_bytes(); - - for (idx, &a) in a.iter().enumerate() { - unsafe { HIGH_SCORE_ADDRESS_START.add(idx).write_volatile(a) }; - } - - free(|cs| HIGHSCORE.borrow(cs).replace(score)); +pub fn save_high_score(gba: &mut Gba, score: u32) -> Result<(), Error> { + gba.save.access()?.prepare_write(1..5)?.write(1, &score.to_le_bytes())?; + HIGHSCORE.write(score); + Ok(()) }