[4.5.9] Adding a password system

kevin81

Well-known member
NESmaker tutorial: adding a password system

When creating larger games, you may want to give the player the opportunity to come back to where they left off after a play session. A common way to facilitate this is a password system. I've seen a couple of questions on this topic across the various community sites, like Facebook, Discord and the NESmakers forum. That's why I decided to write up a tutorial on how to add a password system to your NESmaker game.

This tutorial assumes that you are somewhat familiar with NESmaker and Assembly code already. If you've just started using NESmaker, I'd suggest you go to the LEARN page and follow the tutorials (the 12 days of NESmas and the intermediate/advanced module tutorials specifically) first. Also note that there are countless ways to implement a password system, this is just one way to go about it. You may pick and choose from this tutorial whatever you need for your game and implement your own ideas into them.

NOTE: You can find all asm files needed for this tutorial attached to this post; make sure you download these first. I'd suggest you unzip the files inside your NESmaker folder, in the GameEngineData/Routines subfolder. Also, please note that while I have tried to document codes as much as possible, whenever something is not clear, please let me know so I can help you with that.


Before we begin

Before actually making a password screen, we should think about what it should look like and why we need it. How long is a password going to be? What should the password do? Which digits, letters or other characters does the password system use? How will the player enter the password - using an on-screen keyboard (like Metroid), using up/down character by character input (like Dirty Harry), or something more graphical (like Mega Man)?

For this tutorial, I've decided to go with four-digit numeric passwords which will take you to a certain level of your game, going with the Dirty Harry type input scheme. We will add three passwords, which will each take you to a different screen on the overworld. Entering a wrong password will send you to the start of the game. This tutorial is based off the Maze base module, but I think it's not going to be too much of a problem to implement this screen in the other base modules as well.

For this tutorial, the final product may look something like this:


Add assets for your password screen

Create the tileset you want to use on your screen, add some music or sound effects if you like, and add a game object for the cursor on your password screen. If you are not yet familiar with these things, of if you don't know how to do this, I'd suggest you go to the LEARN page and follow the tutorials first.


Add Password Screen type

Go to Project Settings (the cog icon in the top icon menu). Open the Project Labels tab and find the item in the Labels list that says GameStates. Choose an unused game state from the Values list (in this tutorial we use GameState-3) and rename it to Password Screen.

fwsuj5M.png



Add Password screen to overworld

Go to your overworld. For this we need one empty 8x8 screen which we can assign our password screen to. If you have no 8x8 screens yet, click on Map in the top menu, and then click Advanced Map Properties. Here you can replace two rows of 16x16 screens with a single row of 8x8 screens. In this tutorial, I used the bottom row.

WgFgMpY.png


Once you've done that, double-click an empty 8x8 screen. Here, in the dropdown under the Tile Layout button, select "Password Screen". Now you can create the screen by selecting the correct tileset(s), drawing tiles onto the screen, adding music, et cetera. Again, go follow the tutorials first if you're new to this.

u6twhlC.png


Finally, to be able to access the password screen, either set it as the first screen of your game, or modify the Warp Out location of the title screen to warp to the password screen.


Add cursor to the screen

Graphics and music are in place? Great! Let's add the cursor to the password screen now. We want to position the cursor on the right pixel on the screen, so instead of adding it as a monster in the overworld UI, we'll do it through ASM code.

Open the file PostScreenLoad.asm from the tutorial files with your prefered code editor of choice. NESmaker has its own editor, but you can use any text editor, like Notepad, Visual Studio Code, Notepad++ or Sublime, just to name a few. This file contains the following code:

Code:
    LDA gameState
    CMP #$03
    BNE +
        CreateObject #88, #118, #8, #0
    +

This code checks if the current game state is 3 (i.e. the Password screen), and if it is, it will create object number 8 (which is the cursor in our tutorial project) at pixel position (88,118) on screen, beginning in state 0. You may have to tweak these numbers a bit, for example if your object occupies a different slot in the game objects list, or should be on a different position on screen.

Now, go back to NESmaker. Go to Project Settings, and then click the Script Settings tab. Scroll down to find the "Post screen load" script. In our case, the assigned script was blank*, so we can replace that with a script of our own. To assign a script, click on "Post screen load", then find the script on the right side under Visible .asm files and doubleclick it. If you can't find it in the list on the right, you can also click the Change button above and enter the path to the new script manually in there.
*If your project already has a Post screen load script assigned other than blank, you'll need to add the code above to that existing script file.

tgDRS2N.png


Build and test run your game, and you'll see that the cursor is in place on the password screen. But it doesn't do anything yet. For that, we'll need to add a few input scripts to the project. But first, we must set up some additional things.


Add password variables to the zero page

We need to keep track of the digits that the player entered on the password page, as well as where the cursor currently is. For this, we need to add a few variables to the game.

Go to Project Settings and click on the Zero Page RAM tab. here, add the following variables.
- Name: zp_password, Bytes: 4 (these are the bytes where the entered password is stored)
- Name: zp_cursor_location, Bytes: 1 (this is which digit is currently selected by the cursor)

POZgmXx.png



Add the password table data to the game

Now we can keep track of the password, but how would the game know if a password is correct or not? For this, we need to actually add the passwords into the game. We'll be using lookup tables for this.

Go back to your Project Settings > Script Settings and find the script that is assigned to Tables. In our case, a script called ExtraTables_MazeBase.asm is assigned, so we'll need to modify that script.

Open the ExtraTables.asm script from the tutorial files; this is the modified ExtraTables_MazeBase.asm script from the original module. At the end of the file, you'll find the following additions.

Code:
    ;; The correct passwords: 1234, 1337 and 0081
    tbl_password0: .db #$01, #$02, #$03, #$04  ; password 0 = 1234
    tbl_password1: .db #$01, #$03, #$03, #$07  ; password 1 = 1337
    tbl_password2: .db #$00, #$00, #$08, #$01  ; password 2 = 0081

    ;; These are the high bytes of the adresses where the passwords are stored
    tbl_password_addr_hi:
        .db >#tbl_password0, >#tbl_password1, >#tbl_password2
        
    ;; These are the low bytes of the adresses where the passwords are stored
    tbl_password_addr_lo:
        .db <#tbl_password0, <#tbl_password1, <#tbl_password2

    ;; This table binds screens that the player warps to, to passwords that are defined above
    tbl_password_warp_screen:
        .db #$10  ; password 0 warps to screen #$10 (or X:0 Y:1 in the Overworld UI)
        .db #$20  ; password 1 warps to screen #$20
        .db #$30  ; password 2 warps to screen #$30

Assign this modified script to the Tables script define in the Script Settings.

(to be continued in the next post, please stay tuned...)
 

Attachments

  • PasswordSystem.zip
    4.5 KB · Views: 20

kevin81

Well-known member
Add input scripts

Now we're ready to add some inputs for that cursor. We will need the following things to happen:
  • When the player presses up or down, the current digit should change.
  • When the player presses left or right, the cursor should move to the next digit.
  • When the player presses A, the password is evaluated and the corresponding level gets loaded in.
All these scripts are present in the tutorial files. For example, the input_password_screen_up.asm script, which looks like this:

Code:
    ;; Push x-register onto stack.
    TXA
    PHA

    ;; Load the current digit into the x-register.
    LDX zp_cursor_location

    ;; Increase the current value of the password number.
    INC zp_password,x

    ;; Check if it's over the maximum value; if so, reset
    ;; it back to zero (ie. loop it around).
    LDA zp_password,x
    CMP #$0A
    BNE +
        LDA #$00
        STA zp_password,x
    +

    ;; Update the corresponding tile on screen.
    JSR sub_UpdatePasswordTile

    ;; Pull x-register from stack
    PLA
    TAX
  
    ;; Return
    RTS

This script takes the currently selected digit and adds one to it, looping back to zero after nine. As you can see, there's a reference in the script to a subroutine called sub_UpdatePasswordTile. This subroutine does not yet exist though; we'll add that to the game after the input scripts.

The input script for pressing down looks pretty much the same, except that it subtracts one from the current digit instead of adding (DEC instead of INC). Open both up and down scripts, compare them and see if you can find out what they do differently.

The script for pressing left is at input_password_screen_left.asm and looks like this:

Code:
    ;; Decrease the password cursor.
    DEC zp_cursor_location

    ;; If it's below zero, set it to the last spot.
    BPL +
        LDA #$03
        STA zp_cursor_location
    +

    ;; Update cursor location on screen.
    JSR sub_UpdatePasswordCursor

    ;; Return
    RTS

What this script does, is subtract one the current location of the cursor, and if it's before the first digit of the password, loop it around to the last digit. Then it updates the location of the cursor object on screen. The script for a right press, index_password_screen_right.asm, does pretty much the same, but in the right direction instead of the left.

Finally, pressing the A button submits the password, checks if it's valid, and warps to the corresponding screen. If the password is not valid, it warps to the first level screen. The script, input_password_screen_a.asm, is a bit bigger. Here's what the script looks like, and what each section does.

Code:
    ;; First, it stores the X and Y registers by pushing them onto the stack.
    TXA
    PHA
    TYA
    PHA

    ;; We check the passwords in a loop. First we check password 0, then password 1, and so on, until we've either found the entered password or checked every password available.
    LDX #$00
-checkPasswordsLoop:

    ;; This loads the memory address where the first digit of "password x" is stored, and stores it into a two-byte pointer variable.
    LDA tbl_password_addr_lo,x
    STA pointer
    LDA tbl_password_addr_hi,x
    STA pointer+1

    ;; Here we start checking the entered password against "password x" digit by digit.
    LDY #$00
    -checkCurrentPasswordLoop:

        ;; Check "digit y" of "password x" against "digit y" of the entered password.
        LDA (pointer),y
        CMP zp_password,y

        ;; If the digits don't match, we can skip the rest and check the next password.
        BNE +checkNextPassword

        ;; If the current digit is correct, we check the next digit, until all digits are checked.
        INY
        CPY #$04
        BNE -checkCurrentPasswordLoop

    ;; If we're here, the entered password matches with "password x" from the table.
    ;; We now load the warp screen that belongs to this password, from the lookup table.
    LDA tbl_password_warp_screen,x
    JMP +warpToPasswordScreen
  
+checkNextPassword:

    ;; If we're here, the entered password does not match the currently checked password.
    ;; So we should check the next password in the table. Let's see if there are any
    ;; passwords left in the table, and if so, check the next one.
    INX
    CPX #$03
    BNE -checkPasswordsLoop

    ;; If we're here, the entered password does not match any passwords in the table.
    ;; Therefore we load the location of the first game screen, at location (0,0) in the overworld UI.
    LDA #$00

+warpToPasswordScreen:

    ;; At this point, the accumulator holds the value of the screen we should warp to.
    ;; So let's initiate warping to that screen right now:
    STA warpToScreen
    WarpToScreen #$00, warpToScreen, #$01

    ;; Finally, we need to restore the original Y- and X-register values by pulling them
    ;; off the stack, and return to where this subroutine was called from.
    PLA
    TAY
    PLA
    TAX
    RTS

Okay, that was a lot of scripting going on! Let's add all those input scripts to the project. First, expand the Scripts menu on the left and then click the Input Scripts icon. Find the folder where the tutorial scripts are stored and double-click the input scripts to add them to the project. Now we can assign them to input buttons.

ATuhSCi.png


Go to the Input Editor. Choose Password Screen as the game state in the top left corner, and set the Cursor object as the Target of the input script. Now add the input scripts for each button press: input_password_screen_up.asm when pressing up, input_password_screen_down.asm for pressing down, et cetera.

CiwyMfp.png


Save the project and test build and run it and... it will not compile. We're still missing those subroutines we've mentioned before. Let's add these in first.


Add subroutines

The script with the two missing subroutines can be found in tutorial file ExtraSubroutines.asm. This script needs to be added to the project as well. Go back to the Script Settings tab in your Project Settings. Scroll down to the Subroutines folder, click on it and then click the Add button. Now you can add the new script file to your subroutines. I've used the following settings:

Name: Extra Subroutines
Define: SCR_EXTRA_SUBROUTINES
Script: Routines\PasswordSystem\ExtraSubroutines.asm (this is the relative path to the script file)

FzMOSyu.png


The first subroutine updates the selected digit's tile on the screen. You will have to modify it to make it work for your game though. I'll go through the script first, then I'll go through the changes you'll need to make and how to make them.

Code:
sub_UpdatePasswordTile:

    ;; Push the y-register onto the stack
    TYA
    PHA

    ;; Add the high byte of the PPU address to update to scrollUpdateRAM
    LDY maxScrollOffsetCounter
    LDA #$26
    STA scrollUpdateRam,y
    INY

    ;; Add the low byte of the PPU address to update to scrollUpdateRAM
    LDA zp_cursor_location
    ASL
    CLC
    ADC #$0B  
    STA scrollUpdateRam,y
    INY

    ;; Add the tile index number of the updated character to scrollUpdateRAM
    LDA zp_password,x
    CLC
    ADC #$C0
    STA scrollUpdateRam,y
    INY

    ;; Update the max scroll offset counter
    STY maxScrollOffsetCounter
  
    ;; Update the tile on screen
    LDA updateScreenData
    ORA #%0000100
    STA updateScreenData
    JSR doWaitFrame

    ;; Optional: play a sound effect
    PlaySound #sfx_pwchange

    ;; Restore y-register from stack
    PLA
    TAY

    ;; Return from subroutine
    RTS

The main thing you'll need to change, is the values to write to the scrollUpdateRam. These are the PPU address and the tile id to update. To find these, open your game in Mesen and go to the password screen. Then, open up the PPU Viewer (Ctrl+P). Here you can find the PPU address of the password digits (in the Nametable Viewer tab) and the tile id's for the numbers (in the CHR Viewer tab).

9LQM2IS.png

AAvS00F.png


As you can see, in the tutorial screen, the PPU address of the first tile of the password is $260B (so #$26 is the high byte, and #$0B is the low byte). The second, third and fourth tiles are at addresses $260D, $260F and $2611 respectively, so +2 for each tile. The graphics for the 0-9 tiles use tile index C0 through C9. Looking back at the subroutine, this is how these values are represented:

Code:
    LDA #$26                    ; Load a value of #$26 into the accumulator
    STA scrollUpdateRam,y       ; Store it in the scrollUpdateRam as the PPU address high byte
    INY

    ;; Set the low byte of the PPU address to #$0B/0D/0F/11
    LDA zp_cursor_location      ; Load the current position of the cursor (0, 1, 2 or 3)
    ASL                         ; Shift this value to the left, multiplying it by two (0, 2, 4 or 6)
    CLC
    ADC #$0B                    ; Add #$0B to this value, so it's #$0B/0D/0F/11 now
    STA scrollUpdateRam,y       ; Store it in the scrollUpdateRam as the PPU address low byte
    INY

    ;; Add the tile index number of the updated character to scrollUpdateRAM
    LDA zp_password,x           ; Load the password's current digit (0-9)
    CLC
    ADC #$C0                    ; Add #$C0 to get the tile id (#$C0-C9)
    STA scrollUpdateRam,y       ; Store it in the scrollUpdateRam as the Tile ID
    INY

(to be continued in the next and final post, please hang in there!)
 
Last edited:

kevin81

Well-known member
Add subroutines (continued)

The other subroutine updates the x-position of the cursor on screen. The script looks and works like this:

Code:
sub_UpdatePasswordCursor:
    LDA zp_cursor_location      ; Get the cursor's position, which is 0, 1, 2 or 3
    ASL
    ASL                         ; Shift its value left four times, essentially
    ASL                         ; multiplying it by 16. The new value of the
    ASL                         ; of the accumulator is now 0, 16, 32 of 48
    CLC
    ADC #88                     ; Add 88 (the x-position offset on screen)

    STA Object_x_hi,x           ; Set this value as the new x-position of the cursor

    PlaySound #sfx_pwmove       ; Optionally play a sound effect - edit or remove as you'd like

    RTS                         ; Return from subroutine

If in your game the cursor is at a different position on screen, you'll need to modify #88 to the correct value. If your password is spaced out differently, you may need to add or remove an ASL line to multiply by 8 or 32 pixels, instead of 16. You'll probably be able to work this out through trial and error.

Finally, we must add these subroutines to the Load Subroutines script. You're still on the Script Settings tab, so that's great. In the Game folder (quite high up in the list) you'll find the Load Subroutines script define. Assign tutorial file LoadSubroutines.asm to that script define and you're all set. Alternatively, if you're using an already modified Load Subroutines script, you can open that script in your text/code editor and add the following line:

Code:
.include SCR_EXTRA_SUBROUTINES

If you compile and run the game now, you should have a fully functional base password system in your game. You can experiment with the tutorial scripts if you like. Try adding passwords, use different characters instead of digits, make the password longer or shorter, make a correct password do other things than warping... go crazy with it! And if you need anything clarified further, please feel free to reply and let me know.
 

dale_coop

Moderator
Staff member
Very nice tutorial @kevin81 !
Thank you for sharing.
And it's very interesting to see the way you did it (me I was using sprites for the password letters/numbers, but yeah using bg tiles might a be more elegant solution!)
 

kevin81

Well-known member
Very nice tutorial @kevin81 !
Thank you for sharing.
And it's very interesting to see the way you did it (me I was using sprites for the password letters/numbers, but yeah using bg tiles might a be more elegant solution!)

Thanks Dale! I guess that's a typical dev thing :D There are literally dozens of ways to do a single thing, and most of them are not wrong. For up to eight-digit passwords, sprites will work just as well, and they're probably easier to update since NESmaker's NMI routine handles that every frame by default.
 

force73

Member
Cool tutorial!
The only thing is, what to do with the player sprite as it will be visible on screen and get moved around by LEFT and RIGHT instead of the cursor itself. To thid the sprites on screen is not possible because it also hides the cursor. Any idea?
 

kevin81

Well-known member
Cool tutorial!
The only thing is, what to do with the player sprite as it will be visible on screen and get moved around by LEFT and RIGHT instead of the cursor itself. To thid the sprites on screen is not possible because it also hides the cursor. Any idea?

That's odd. It seems that the password screen uses the Main Game state in your case (especially since not only the player shows up, but also the cursor does not move).

Just to make sure: did you set the password screen to be its own game state (other than Main game) ?
passwordGameState.jpg
 

force73

Member
Hey Kevin,
thanks for your fast reply. I selected the correct state. :-(
When I think about it more closely, I notice that the player is always visible in every game state. Any idea where to look into?

The gamestate self is working, as the control bindings work. It's only the player_object that is visible.
 
Last edited:

TalkingCat

Member
Cool tutorial!
The only thing is, what to do with the player sprite as it will be visible on screen and get moved around by LEFT and RIGHT instead of the cursor itself. To thid the sprites on screen is not possible because it also hides the cursor. Any idea?
To hide a player's sprite, make all their palettes black for this screen.Снимок экрана 2023-06-19 210917.png

To move the cursor instead of the player, select the cursor object from the Target drop-down menu.

Снимок экрана 2023-06-19 212228.png
 
Last edited:

force73

Member
OMG @TalkingCat how stupid I were with the controlls. Give it a try soon. //Update a day later: works.

I worked around the "issue" and fixed it with another way:

  1. removed the CreateObject for the cursor
  2. placed the player right on the spot where the cursor would be (as the player moves like the cursor, this is fine for this step)
  3. created a new ActionStep for the player_object (where its sprite looks like a cursor) ;-)
  4. wrote a line (where the CreateObject from above normaly be) to change the player_object ActionStep
  5. moved the player_object some pixel (if needed) to start on the correct X+Y (because the Password screen is a 8x8 tiles one and not a standard 16x16
All this is just for the optic, the function was already there, just the cursor was not moving and player not hidden. Fixed.
 
Last edited:

force73

Member
I've got another question about the password system.

Is it locked to 3 passwords or can I implement 27 (in my case) different passwords. Not sure what to modify. I added them in the .db-part (counting up to 26) but it failes. There is this hi and lo check and it also checks up to password_3, maybe that's the part. I simply added a fourth one but the outcome is just an "out of range"
 

kevin81

Well-known member
It is possible to add as many passwords as ROM space allows you to; 27 passwords should not be too much of a problem. You'll have to update four things to add a password:

1) The password itself (in ExtraTables.asm);
2) The hi and lo pointers to the password (also in ExtraTables.asm);
3) The location to which the new password warps (at the end of ExtraTables.asm);
4) The number of passwords to loop through (in input_password_screen_a.asm).

In the following example, I've added 6502 as a fourth password. Changes are marked with extra semicolons/comments.

ExtraTables.asm:
Code:
     ;; The correct passwords: 1234, 1337 and 0081
    tbl_password0: .db #$01, #$02, #$03, #$04  ; password 0 = 1234
    tbl_password1: .db #$01, #$03, #$03, #$07  ; password 1 = 1337
    tbl_password2: .db #$00, #$00, #$08, #$01  ; password 2 = 0081

;;;;;;;;;;;;;;;;
;; ADDED PASSWORD (6502)
    tbl_password3: .db #$06, #$05, #$00, #$02
;;;;;;;;;;;;;;;;

    ;; These are the high bytes of the adresses where the passwords are stored
    tbl_password_addr_hi:

;;;;;;;;;;;;;;;;
;; ADDED HIGH BYTE OF NEW PASSWORD
        .db >#tbl_password0, >#tbl_password1, >#tbl_password2, >#tbl_password3
;;;;;;;;;;;;;;;;
        
    ;; These are the low bytes of the adresses where the passwords are stored
    tbl_password_addr_lo:

;;;;;;;;;;;;;;;;
;; ADDED LOW BYTE OF NEW PASSWORD
        .db <#tbl_password0, <#tbl_password1, <#tbl_password2, <#tbl_password3
;;;;;;;;;;;;;;;;

    ;; This table binds screens that the player warps to, to passwords that are defined above
    tbl_password_warp_screen:
        .db #$10  ; password 0 warps to screen #$10 (or X:0 Y:1 in the Overworld UI)
        .db #$20  ; password 1 warps to screen #$20
        .db #$30  ; password 2 warps to screen #$30

;;;;;;;;;;;;;;;;
;; ADDED WARP LOCATION FOR NEW PASSWORD
        .db #$40  ; password 3 warps to screen #$40
;;;;;;;;;;;;;;;;

input_password_screen_a.asm:
Code:
    ;; Push the x- and y-registers onto the stack.
    TXA
    PHA
    TYA
    PHA

    ;; Set up the loop to check each password.
    LDX #$00
-checkPasswordsLoop:

    ;; Load the password address into (pointer).
    LDA tbl_password_addr_lo,x
    STA pointer
    LDA tbl_password_addr_hi,x
    STA pointer+1

    ;; Set up the loop to check each character of the password.
    LDY #$00
    -checkCurrentPasswordLoop:

        ;; Check current character against entered password.
        LDA (pointer),y
        CMP zp_password,y

        ;; If they don't match, check the next password.
        BNE +checkNextPassword

        ;; Check the next character (if any are left).
        INY
        CPY #$04
        BNE -checkCurrentPasswordLoop

    ;; If we're here, the current password is a match.
    ;; Load the screen this password should take you to
    LDA tbl_password_warp_screen,x
    JMP +warpToPasswordScreen

+checkNextPassword:
    ;; Check the next password (if any are left).
    INX

;;;;;;;;;;;;;;;;
;; UPDATED THE NUMBER OF PASSWORDS TO CHECK
    CPX #$04
;;;;;;;;;;;;;;;;

    BNE -checkPasswordsLoop

    ;; If we're here, the entered password is incorrect.
    ;; Load the first game screen, at location (0,0).
    LDA #$00

+warpToPasswordScreen:
    ;; Warp to the screen that corresponds with the password
    ;; (which is loaded into the accumulator)
    STA warpToScreen
    WarpToScreen #$00, warpToScreen, #$01

    ;; Pull the x- and y-registers from the stack
    PLA
    TAY
    PLA
    TAX

    ;; Return
    RTS
 

Luxnolucas

New member
hello, thank you for sharing this tutorial. its near to work for me but i have two issues. I have a cursor and the cursor works well, left to right is good and up and down make good letter, because i choiced for letters for the password. The first issue is : when i choose a good letter, the password don't work and the warp to screen go to my 0,0 screen. And the second issue is maybe because i have a pause input... Now when i push start to make pause in my main game the pause screen appear but when i "re"-push start to go back to main game, the screen go/warp to my first screen map, the 0,0. if anyone can help me, thanks

Edit: I hadn't followed the tutorial correctly, but my passwords are working fine now. For my pause problem Dale to settle the problem as usual... Just a few small questions: I have some "glitches" when switching from one letter to another, never on the same letter. Is it possible to fix that? Also, when entering the wrong password and retrying, the letters of the previous incorrect attempt reappear. Is it possible to reset everything when the password is incorrect? Thank you.
 
Last edited:
Top Bottom