How to work with SRAM

Discussion in 'Tutorials' started by Selbi, Aug 3, 2010.

Thread Status:
Not open for further replies.
  1. Selbi

    Selbi The Euphonic Mess Member

    Joined:
    Jul 20, 2008
    Messages:
    2,429
    Location:
    Northern Germany
    MarkeyJester asked me in the Basic Questions & Answers Thread if I could put what I wrote there into the turorial section, so I figured, why not extending it as well? (I wrote a lot more than I expected though.)


    So here we go:


    ======================================================================


    Working with SRAM (Save RAM, a method to save data which will stay for a longer time, mainly used for saving games) isn't as hard to use as many people believe. The problem most people had is to know how SRAM actually works and how to enable it. Here's what you need to do including an example:


    The first thing you wanna do is to make SRAM actually working, so go the top of the disassembly, and replace this:



    SRAMSupport: dc.l $20202020 ; change to $5241E020 to create SRAM
    dc.l $20202020 ; SRAM start


    dc.l $20202020 ; SRAM end



    With this:


    SRAMSupport: dc.l $5241F820 ; change to $5241F820 (NOT $5241E020) to create SRAM
    dc.l $200000 ; SRAM start


    dc.l $200200 ; SRAM end



    Those $20202020 (for SRAM start and SRAM end) are probably just a random number that doesn't do anything, so we need to set a real location for it. Let's use $200000. We can set the size of the maximum SRAM size by increasing the number of SRAM end, which is, obviously, SRAM start + your size in bytes. Right now we have $200 bytes of space ($200000 to $200200), you can increase that number though. But keep in mind not to set it to an extreme giant number (like $200000 to $300000). It will probably work, but it's just stupid to use much more than you actually need.
    Anyway, if you open up and close your emulator now (after building of course), you should see a file called s1built.srm, which means SRAM itself is enabled now.


    Next we gotta deal with writing to and loading from the SRAM. There are 2 basic commands to use SRAM + 1 optional one, one to enable it, one to set the location (actually not required, but much easier to use this way, because SRAM needs to be at odd locations) and one to disable it. As an example, let's add a feature that allows you to save your current level, so instead of starting at GHZ 1 each time you press start on the Title Screen, you always skip to your last level played.


    So go to the loc_37B6: routine and put this at the very beginning:



    move.b #1,($A130F1).l ; enable SRAM (required)
    lea ($200001).l,a1 ; base of SRAM + 1


    move.w ($FFFFFE10).w,d0 ; move zone and act number to d0 (we can't do it directly)


    movep.w d0,0(a1) ; save zone and act to SRAM


    move.b #0,($A130F1).l ; disable SRAM (required)



    You might already get an idea from this code. The movep command is required to make every byte only to be placed on odd values. Because of the way SRAM works, we can't put anything on even values. Anyway, save and build now.

    Play it and select any level on the level select, best would be with a complex level ID (like SBZ 2, which is 0501). After the level loaded, look in the s1built.srm with a hex editor. It should now contain the zone and act ID of that level. If that's so, let's now test that feature by switching the lines:



    move.w ($FFFFFE10).w,d0 ; move zone and act number to d0 (we can't do it directly)
    movep.w d0,0(a1) ; save zone and act to SRAM



    Into this:


    movep.w 0(a1),d0 ; load zone and act off SRAM into d0
    move.w d0,($FFFFFE10).w ; move the un-odded result into the level ID



    (Remember not to modify or even delete the s1built.srm file!) Build again. Now just press start on the title screen. You should be sent to the level you played there, instead of GHZ 1.

    However, I doubt that most people will edit the SRAM manually or switch the line all the time. So you can save the current act, but also load what act is currently in the SRAM file.


    Go to Title_ChkLevSel: and add this just after the label:



    move.b #1,($A130F1).l ; enable SRAM (required)
    lea ($200001).l,a1 ; base of SRAM


    movep.w 0(a1),d0 ; load zone and act off SRAM into d0


    move.w d0,($FFFFFE10).w ; move the un-odded result into the level ID


    move.b #0,($A130F1).l ; disable SRAM (required)



    This will set the current level ID off the SRAM file, if you press start on the title screen.
    The other part is in loc_37B6: again. Replace our current SRAM code with this:



    move.b #1,($A130F1).l ; enable SRAM (required)
    lea ($200001).l,a1 ; base of SRAM


    move.w ($FFFFFE10).w,d0 ; move zone and act number to d0


    movep.w d0,0(a1) ; save zone and act to SRAM


    move.b #0,($A130F1).l ; disable SRAM (required)



    Now you can play the game as usual, but when pressing start on the title screen, you are being lead to the last level you played. That doesn't even require closing and reopening the emulator! Just do a hard reset.

    That said, SRAM is an interesting thing to work with and of course not meant to save only one thing, like we did here. I might extend this guide one day, going deeper in saving multiple things, like rings, score counter and so on. However, with this base knowledge I don't think you'll have any troubles learning more stuff yourself. I'll just leave you with these advices:

    • The size is double as big as normal stuff, meaning 1 byte takes 2 bytes in SRAM, a word (2 bytes) takes 4 bytes in SRAM, a longword (4 bytes) takes 8 bytes in SRAM.
    • Look at this topic for more information or if you run into any troubles.


    Credits and Thanks:


    - Me (Selbi) for writing the guide


    - This topic: http://forums.sonicretro.org/index.php?showtopic=20608


    - Chilly Willy, for making the knowledge public (to be honest, I learned everything you see here from this topic)


    - theocas, for making that topic, otherwise this tutorial here wouldn't even exist


    - shobiz, for suggestion a lot of things and making it work for real hardware (see a few posts below)
     
    Last edited by a moderator: Aug 6, 2010
    realwushy and Nat The Porcupine like this.
  2. MarkeyJester

    MarkeyJester ♡ ! Member

    Joined:
    Jun 27, 2009
    Messages:
    2,867
    Thanks for popping this info up, now we have an easy accessable tutorial for future questions related on this subject.


    One thing though:


    0(a-) could be replaced with (a-), as they pretty much do the same thing, the only difference being is one is index'd while the other is not, and according to Tiido, indexing is slower, and never have I doubted Tiido on subjects like this =P
     
  3. Selbi

    Selbi The Euphonic Mess Member

    Joined:
    Jul 20, 2008
    Messages:
    2,429
    Location:
    Northern Germany
    I was aware of this, but I felt like this makes more sense to beginners than just writing (a0). So I'm keeping it in. =P
     
  4. theocas

    theocas #! Member

    Joined:
    Apr 10, 2010
    Messages:
    375
    Nice guide Selbi! I have implemented SRAM the 'hard way' but I think now that you explained it all every hack might have SRAM. Just like with the jumodash and spindash. It'll make the hacks of the people that took time and learned how it works and then implement it like idiots. Just my 2 cents, but it you explained it very nicely!
     
  5. MarkeyJester

    MarkeyJester ♡ ! Member

    Joined:
    Jun 27, 2009
    Messages:
    2,867
    Please don't be a hypocrite, you go around helping people all the time in the Q&A's topic, and even give code/help out through PM, there's nothing wrong with it though, nor is providing a tutorial "Which I asked him to do" (Had you read the very top), so don't give the whole almighty glory of how you "think" it isn't such a good idea, while your methods perform the same results anyway.
     
  6. Selbi

    Selbi The Euphonic Mess Member

    Joined:
    Jul 20, 2008
    Messages:
    2,429
    Location:
    Northern Germany
    Actually I doubt that this will be overused as the spindash or jumpdash, because this guide still requires a brain that can do more than just pressing Ctrl + C and V.
     
  7. amphobius

    amphobius spreader of the pink text Member

    Joined:
    Feb 24, 2008
    Messages:
    970
    Location:
    United Kingdom
    I'd be greatful if you could attempt a similar version for Sonic 2.
     
  8. shobiz

    shobiz Well-Known Member Member

    Joined:
    Aug 11, 2007
    Messages:
    198
    Location:
    Karachi, Pakistan
    Neat guide, but the examples you posted won't work on a flash cart because flash carts support odd-only SRAM. In the example you posted, you have



    lea ($200001).l,a1 ; base of SRAM + 1
    move.w ($FFFFFE10).w,0(a1) ; save zone and act



    The move.w will transfer the zone to $200001 and the act to $200002. Do you see the problem? $200002 can't be used because it's an even address whereas our SRAM supports only odd address. The movep instruction can be used to fix this:



    lea ($200001).l,a1 ; base of SRAM + 1
    move.w ($FFFFFE10).w,d0 ; we need to move it to a register first


    movep.w d0,0(a1) ; save zone and act



    Note how I had to move $FFFFFE10 to a register first. movep only supports two forms:



    movep.x dN,disp(aN)
    movep.x disp(aN),dN



    where x is either w or l (movep.b doesn't exist, move.b should be used instead), dN is any data register and aN is any address register. Similarly, to load data from SRAM, instead of doing



    lea ($200001).l,a1 ; base of SRAM
    move.w 0(a1),($FFFFFE10).w ; load zone and act off SRAM



    you should do



    lea ($200001).l,a1 ; base of SRAM
    movep.w 0(a1),d0 ; have to move it to a register first


    move.w d0,($FFFFFE10).w ; load zone and act off SRAM



    What movep does is it transfers bytes to/from alternate addresses. That is, when we save the zone and act using movep, the zone goes to $200001 and the act goes to $200003. Both addresses are odd so this works on a flash cart. Similarly, when we load the zone and act from SRAM, the zone is read from $200001 and the act is read from $200003, so it works fine.


    One additional step needs to be taken. The header should be changed from



    SRAMSupport: dc.l $5241E020 ; change to $5241E020 to create SRAM
    dc.l $200000 ; SRAM start


    dc.l $200200 ; SRAM end



    to



    SRAMSupport: dc.l $5241F820
    dc.l $200001 ; SRAM start


    dc.l $2001FF ; SRAM end



    $5241F820 indicates that the SRAM uses odd addresses only, and both the start and end addresses are changed to odd address accordingly.


    SRAM can be used for all sorts of neat stuff. For example, you can keep track of how many times a ROM has been played. You can also make the game crash when a savestate is loaded provided the emulator you're using doesn't store SRAM in the savestate (Kega Fusion and Regen don't, so it works on them, but Gens Movie and Gens/GS store SRAM in the savestate so it won't work on them). I'll leave figuring out how to do that as an exercise to the reader.
     
    Last edited by a moderator: Aug 5, 2010
  9. Selbi

    Selbi The Euphonic Mess Member

    Joined:
    Jul 20, 2008
    Messages:
    2,429
    Location:
    Northern Germany
    Alright, thanks shobiz. But while I'm editing my tutorial, could you answer me this, please: What EXACTLY is movep doing? And why do you know about $5241F820 for enabling SRAM? I mean, isn't this just a random value? (I know it's not, but I don't know how it is made up.)
     
  10. shobiz

    shobiz Well-Known Member Member

    Joined:
    Aug 11, 2007
    Messages:
    198
    Location:
    Karachi, Pakistan
    Basically, a normal move transfers stuff to/from consecutive memory addresses whereas a movep transfers stuff to/from every alternate memory address. For example, take the following two pieces of code:



    lea ($200001).l,a1 ; start of SRAM
    move.w ($FFFFFE10).w,d0 ; get zone and act


    move.w d0,0(a1)



    vs.



    lea ($200001).l,a1 ; start of SRAM
    move.w ($FFFFFE10).w,d0 ; get zone and act


    movep.w d0,0(a1)



    Suppose we're in SBZ 3, i.e. the value of $FFFFFE10 is $0501. With a normal move, the $05 is transferred to $200001 and the $01 is transferred to $200002. With a movep, the $05 is transferred to $200001 and the $01 is transferred to $200003. Another example:



    lea ($200001).l,a1 ; start of SRAM
    move.l #$12345678,d0 ; just a random value


    move.l d0,0(a1)



    vs.



    lea ($200001).l,a1 ; start of SRAM
    move.l #$12345678,d0 ; just a random value


    movep.l d0,0(a1)



    With a normal move, the SRAM will look like this:



    Address: $200001 $200002 $200003 $200004
    Value: $12 $34 $56 $78



    With a movep, the SRAM will look like this:



    Address: $200001 $200003 $200005 $200007
    Value: $12 $34 $56 $78



    See how it works? If a movep transfer starts off from an odd address it takes place to odd addresses only, which is exactly what we want for SRAM. The reverse process happens when you read from memory - bytes are read from every alternate memory address instead of consecutive ones. So if the SRAM has the layout described above and we do



    lea ($200001).l,a1 ; start of SRAM
    movep.l 0(a1),d0



    we get $12345678 in d0.

    Taken from the Genesis software manual:


    If you break down that longword into its individual bytes, they're 'RA',%1x1yz000,%00100000. x is 1 if it's backup RAM and 0 for non-backup RAM. yz are 10 for even-only SRAM and 11 for odd-only SRAM (I believe all flash carts are odd-only). The ASCII codes for 'RA' are $5241. Our RAM is backup RAM so x is 1, and it's odd-only so yz are 11. So the next two bytes become %11111000 and %00100000, or $F820 in hex. Combine the two and you get $5241F820.
     
  11. theocas

    theocas #! Member

    Joined:
    Apr 10, 2010
    Messages:
    375
    I think it's the same, except the RAM adresses the level variable is stored.
     
  12. amphobius

    amphobius spreader of the pink text Member

    Joined:
    Feb 24, 2008
    Messages:
    970
    Location:
    United Kingdom
    After getting things working myself, the guide is incomplete, and there's three reasons behind that.

    • A way to check if there's SRAM or not; if there is no SRAM, start a new save.
    • A way to clear the SRAM.
    • SRAM saves during the game demos. You'll almost definately need a way to avoid this scenario.
     
    Last edited by a moderator: Aug 5, 2010
  13. Selbi

    Selbi The Euphonic Mess Member

    Joined:
    Jul 20, 2008
    Messages:
    2,429
    Location:
    Northern Germany
    @shobiz: You are officially awesome. Thanks. =)


    @DalekSam: That shouldn't be a problem like at all.

    Just read the SRAM and look if you got only 0 as response. You could for example write a byte with $FF somewhere in the SRAM, say for example right at the beginning. Check if $200001 is zero and if so, create new SRAM file, otherwise just skip or something.

    Just do something like:


    lea ($200000).l,a1 ; get SRAM location
    move.w #$4F,d1 ; size of SRAM / 4 - 1


    moveq #0,d2 ; presetting the 0 in a data register is a lot more efficient


    Loop_ClrSRAM:


    move.l d2,(a1)+ ; clear 4 bytes of SRAM


    dbf d1,Loop_ClrSRAM ; loop



    Or if you want less calculating, you can set the entire size of the SRAM - 1 in d1 and instad of move.l d2,(a1)+ you do a move.b.

    This should do it:


    cmpi.b #$08,(Game_Mode).w ; is game mode = $08 (demo)?
    beq.s NoSRAM ; if yes, branch


    ; SRAM stuff


    NoSRAM:
     
    Last edited by a moderator: Aug 5, 2010
  14. shobiz

    shobiz Well-Known Member Member

    Joined:
    Aug 11, 2007
    Messages:
    198
    Location:
    Karachi, Pakistan
    Kega Fusion returns $FF for uninitialized SRAM instead of 0. It's better to store a byte which is neither $0 nor $FF (for example $87) and then check for the byte's existence. If it isn't there you can assume uninitialized SRAM.

    Problem with that is that you're using move.l, which will write to even addresses as well as odd ones. A general rule of thumb is that if you want your SRAM code to work on flash carts, you use movep for word and long-sized transfers. Only use move for bytes. An additional complication is that you can't use the (a1)+ addressing mode with movep. So you'd do something like:



    lea ($200001).l,a1 ; start of SRAM
    moveq #$3F,d1 ; assuming SRAM is from $200001 to $2001FF and is odd addresses only


    moveq #0,d0


    ClrSRAMLoop:


    movep.l d0,0(a1)


    addq.l #8,a1 ; since every other byte is skipped we add 8 instead of 4


    dbf d1,ClrSRAMLoop
     
  15. EMK-20218

    EMK-20218 The Fuss Maker Exiled

    Joined:
    Aug 8, 2008
    Messages:
    1,067
    Location:
    Jardim Capelinha, São Paulo
    Hm, I like this guide. Interesting for me with my hard levels. I'll probably use it.
     
  16. RetroX

    RetroX Active Member Member

    Joined:
    Jun 15, 2012
    Messages:
    46
    Location:
    United States
    The code works, but when I use level select and I pick a level (Let's say Final Zone), it brings me to GHZ2.
     
  17. Selbi

    Selbi The Euphonic Mess Member

    Joined:
    Jul 20, 2008
    Messages:
    2,429
    Location:
    Northern Germany
    Well, of course. I didn't make any checks if you ran the level using level select or just by pressing start. You'd need to do that yourself.
     
  18. PsychoSk8r

    PsychoSk8r HighKnights Member

    Joined:
    Aug 9, 2007
    Messages:
    271
    Location:
    Birmingham, UK
    Pinning this guide, because this is useful for teaching the basics of SRAM, which can lead to so much more if you use the basic ideas from this thread right!
     
Thread Status:
Not open for further replies.