Lets talk about saving [4.1+]

FrankenGraphics

New member
Hey Dale, did you figure this stuff out?

I just tried it today and got it working (thanks for the tutorial and examples, kasumi!). Both scores are now persistent outside mainGame (only that i haven't hooked up the new/high score compare so it stores nonsensical tile references on the lower row, presently).
game.png

There's not much to managing banks in this case - If you put code in mainASM.asm after JMP MainGameLoop (but before vectors; they must always come last in the fixed bank), yo know that code will be put in your fixed bank and so you're safe.

So that is where you put flash.asm (.include Routines\Basic\Flash.asm for example) and "SaveSubroutine".

I made it a little bit simpler for myself - i commented out the verification block, and made the bank select ($8000) immediate.

Code:
lda #$18 ;bank $18, now save bank
sta TargetBank
lda #0 
sta TargetAddress
lda #$80 ;this selects the switchable bank window. We'll erase the first 4kB:s of this bank.  
sta TargetAddress_h
CallEraseSector

I also rolled all my data-to-save in a loop since i'm really tight on space in the fixed bank at this point.

Another approach that would give you ample space to elaborate and specify what and how you want stuff saved would be to keep all the save routines in the same bank as the save file. In that case, you must make a bankswitch prior to JSR:ing SaveSubroutine, and you need to do this from a any point in the mainloop/fixed bank.

Might be other caveats with that method; haven't tried to do it since i got everything i wanted in.
 

dale_coop

Moderator
Staff member
Oh wow, FrankenGraphics, thanks for your feedback, on that.
I didn't find time to do some tests again >_< (busy life)
Your informations will be very useful.

Btw, how is going your game? Can't wait to see the first gameplay... ;)
 

Kasumi

New member
You can have the save routines in the same bank as the save file, so long as they are not in the same 4KB sector. This is because erasing the entire sector is required to write anything new (which would erase the code for the save routines as well). You also can't run the flash write parts of the save routine from ROM (because the reads from ROM required to execute the code would affect how the flash writing works, from my understanding).

I'm glad you got it working!
 

FrankenGraphics

New member
Yeah, i figure if i get to the point where i need to break out of the fixed bank, the most straightforward thing to do would be to assign $B000 as the block to erase and rewrite, and use the first 12kB:s of that bank for save code and misc. expansion code/tables. In ca65, i'd define the block as a .segment from the linker config and then securely forget about it. I'm not too used to the manual memory management of asm6, but there are probably more elegant methods here as well to make sure you don't overflow that i'm not aware of... but 12kB:s is a lot of space to feel safe within.

kasumi said:
because the reads from ROM required to execute the code would affect how the flash writing works, from my understanding.
My concept is completely foggy so i just assume that any flashing operations must execute from RAM wholesale to be on the safe side. All the preparations leading up to the flashing, on the other hand, may be executed from ROM (as you evidently know. just writing it out for clarity).

dale said:
Btw, how is going your game?
Thanks, i'm seeing the light at the end of the tunnel. Most features is in by now. :) The biggest missing part is an interface with my custom peripheral that displays current score on old nixie tubes (or that's the idea). My circuit does the binary coded decimal shift registering for me so i don't need to burden the cpu with that at least. It's just that it expects a hexadecimal integer fed serially over the 2nd controller port; and not the decimal string system used for myScore. So i need to reintegrate the score into 3 hexadecimal-coded bytes. Or maybe 2, depending on how i tune/rationalize scores (not yet sure if i will have 10-potential score increases, for example).
 

dale_coop

Moderator
Staff member
FrankenGraphics said:
Thanks, i'm seeing the light at the end of the tunnel. Most features is in by now. :) The biggest missing part is an interface with my custom peripheral that displays current score on old nixie tubes (or that's the idea). My circuit does the binary coded decimal shift registering for me so i don't need to burden the cpu with that at least. It's just that it expects a hexadecimal integer fed serially over the 2nd controller port; and not the decimal string system used for myScore. So i need to reintegrate the score into 3 hexadecimal-coded bytes. Or maybe 2, depending on how i tune/rationalize scores (not yet sure if i will have 10-potential score increases, for example).

That's a very good news. The installation with the dedicated cabinet (and the score nixie tubes?) will be awesome. Can't wait to see that (if I'd live near, I would go to see your work irl...) on photos ;)
 

dale_coop

Moderator
Staff member
I am completely lost... it's very hard to understand this bank concept, this fixed bank thing, reset, vectors... even with the technical informations provided by kasumi and FrankenGraphics.
I am good at code logic but when it's about hardware & system architecture... I am completely lost.

Hummmm...
Include the flash. asm script... I got it.
Fill the bank18.asm with some code I don't know what for... yeah, I think I quite understand that too.
But paste some read and save in the mainASM script in the fixed bank after reset... I can't figure where and what does what.

Would anyone have complete scripts to share for example & educational purpose (mainASM.asm, Bank18, flash.asm, etc...)?

m(_ _)m

Thanks in advance for your help, it would be very appreciated <3
 

FrankenGraphics

New member
Bank: with mapper 30, you have two bank windows, 16kB:s each. Think of it as having two browser windows up on your laptop. In the analogy, One always displays your "home page" (the main loop and some other utility stuff), and you can never change it. Having some essential stuff always visible is handy. The other browser has a bunch of tabs. each tab contains 16kB:s of the total of 512kB the cartridge can store, but you can only view one tab at a time. So if you want to call a subroutine or look up some data in one of those tabs, that tab needs to be clicked on before umm clicking on any of its contents, in our browser analogy.

The swappable banks use addresses $8000 to $BFFF in parallel to each other.
The fixed bank use address $C000 to $FFFF.

Vectors must always be stored at $FFFA-$FFFF.

Vectors: The 6502 cpu family have three special addresses which it jumps to in case of the following special events: reset, nmi, irq. reset is when someone pushes the reset button. NMI can be different things on different systems, but on the nes it is used by the PPU to signal the vblank period under which it is suitable to update graphics ram, controller reads, and music updates. IRQ reserved for hardware expansions on certain cartridges, like scanline counters and timers. These are the 6502 interrupts. Whenever one of those conditions are met, the cpu will jump to the label stored in each corresponding vector.

The vectors are hardwired to the last few adresses in the total address range the cpu can view, which happens to be the fixed bank in our case (and most often the case, but not always), which means the last 6 bytes of the "main bank" or fixed bank must contain these vector adresses in order to know what to do on startup/reset or vblank.


The only thing that should go in bank #$18, if anything at all (this is completely optional), is some label definitions to allow you to access your save data with aliases, for example
mySavedFile,x

instead of $8000,x

just as an example.

this is because name definitions are local to each bank in asm6. You can't for example call a macro from a bank if that bank doesn't have that macro defined. That's why all macros should be defined in the fixed bank; preferrably in macros.asm.

As for where to put stuff in mainASM.asm...

Here:
Code:
;4 The Reset
	.include ROOT\System\Reset.asm

	;; initialize 
	.include ROOT\InitializationScripts\InitLoads.asm
	
	;mod 2019-04-30
	ldy #$18 ;prep bank #$18 to get switched in. 
	jsr bankswitchY ;to swap in what will be the save bank.
	
	;here, write some routine to read from the data you just have stored. this will be loaded into memory on each reset/startup. Just as a working example, this is my highscore loader
	ldx #7
-
lda $B000,x
sta lastScore,x
lda $B008,x
bmi + ;filters out a nonsensical read on the first-ever boot. Needed because of how i did stuff elseplace. 
sta highScore,x
+
dex
cpx #$ff  
bne -

and here:
Code:
	JSR HandleScroll
++

	
	
	;;;;;;;;;;;;;;;;;

	
	;;;;;;;;;;;;;;;
	
	
	JMP MainGameLoop
	
	RTS
	
	;here, include flash.asm then any sort of routine to call it.

btw that rts is unnecessary.


Some lessersteps i advise you to optionally do at your discretion to minimize the risk of unnecessary errors:
-comment out the whole "SoftwareIdentify" routine in flash.asm to save some precious space. You don't have much headroom left in the fixed bank even without custom modifications. This minimizes the risk of the fixed bank becoming too big (the first sign that you have a too big fixed bank which wont fit is that you get an error message that says that the "vectors are out of bounds"). This function is not essential to the main feature. It's just good to prohibit self-flashing on cartridges unsuitable for it.

-very optionally, cut and copy the macro definitions from flash.asm to macros.asm. it's not terribly important (not sure it even matters) but it makes sure that asm6 know the macro definitions right at the beginning of the 1st pass of the fixed bank, and it is sort of tidier to keep all macros in one single file for accessible reference.

Hopefully that helps. Let me know if anything could be better explained.
 

dale_coop

Moderator
Staff member
Wow, thanks FrankenGraphics. It's very kind.
Your explanation is very clear, I understand better now (the analogy with browser helped ;)).
And with your example (associated with Kasumi ones, that I already tried to implement in my demo game), I succeeded to make it work.

Really, thank you, I appreciate <3 I know your time is precious and I feel a little shame you wasted some for helping me.
 

dale_coop

Moderator
Staff member
Ah ah and Sorry/Thank you in advance... (I might bother you again... and again... in the future, as well :p)
 
Finally getting around to tinkering with this.
I'm getting this error though from the macro defines:

Code:
Routines\Metroidvania\\ModuleScripts\flash.asm(25): Need a name.
Routines\Metroidvania\\ModuleScripts\flash.asm(45): Need a name.
Routines\Metroidvania\\ModuleScripts\flash.asm(52): Need a name.
Routines\Metroidvania\\ModuleScripts\flash.asm(57): Need a name.
demo.txt written.

also, say I wanted to save/load the screenTriggers variable. (which is 32 bytes)
I assume I would need to do that differently than a 1 byte variable?
 

Kasumi

New member
Are you using the flash.asm from the mapper 30 ROM post for asm6? https://www.dropbox.com/s/3451vmtho4qs8ov/Mapper30%20Save.zip?dl=0
The one in the wiki page is for an assembler other than asm6. I adapted it in that zip.

Edit: A 32 byte variable is just thirty-two one byte variables. Given this:
Code:
.enum $0000;RAM

variable1 .dsb 1
variable2 .dsb 1

.ende
variable1 refers to address $00, and variable2 refers to $01. lda variable1 is equivalent to lda $00, and lda variable2 is equivalent to lda $01.
lda variable1+1 is equivalent to lda $00+1 is equivalent to lda $01 is equivalent to lda variable2.
So given this:
Code:
variable1 .dsb 4
lda variable1 still refers to address $00. There is no variable2, but $01 is now "reserved" by this same name. lda variable1+1 is one way to access the second byte, lda variable1+2 is one way to access the third byte and so on. Another way is lda variable1,x or lda variable1,y, where X or Y is the byte you want to access. The 6502 has no concept of any of this. In all cases listed above, it will see either lda $00, or lda $01. (or lda $00,x or lda $00,y for the "another way" cases.)

You'd use that code that writes each 1 byte reserved by the name variable (32 total writes). The process isn't different for each write, just the source and destination.

Edit: This post talks about how to write more than one byte: http://nesmakers.com/viewtopic.php?f=60&t=2417&start=10#p15095

But I've also clarified what a 32 byte variable is. (32 single bytes, increasing contiguously in address, accessed by one name through addition.)
 
Thanks again Kasumi!

Yea I was using the wrong file. That got it fixed.

I am working on slowly figuring this out one part at a time.

I almost have loading working (I have dummy data in the save bank for now).
 
Well, with the first test run, it appears to work!

It doesn't save the player's exact screen position for some reason, but I got it to save the players health, lives, score, and current screen!

It should also be saving game progress (bosses defeated and screens triggered) but that will require a bit more testing)

Thank you everyone in this thread. This has been the feature I have been scared to try and implement forever because it seemed too difficult. I finally got it going!
 

Atarath

Member
I just spent the last four hours reading these forum posts, making changes, testing, and rereading these posts. Finally, I managed to save and then load some variables (not all for some reason). I appreciate those that know more than me posting here, because I certainly couldn't have done it without you. I would have to restructure the direction of my game if I couldn't have a save feature.
 

TokyoScarab

New member
I recently got saving working but I'm having some problems reading the values I'm saving. I usually like to use a small input script that writes things to ram that I can look at in FCEUX's hex viewer. Basically using as sort of a mini console. Each time I try to read these values, I get crashes. I then took the same code and put it in the main code loop and it runs just fine. So I'm really at a loss of what is going on. I assume the bank switching is causing some sort of issue being called from an input script, but I'm really not sure. Any help would be great! :D


LDA prevBank
PHA
LDA currentBank
STA prevBank
LDY #$18
JSR bankswitchY
LDA $8000
STA $030E
LDA $8001
STA $030F
LDY prevBank
JSR bankswitchY
PLA
STA prevBank
 
Top Bottom