Sunday, March 27. 2005Faxanadu Mantras - Part II
As I had promised in my last update here's a complete discussion of the mantra checking code of the NES game Faxanadu. If you don't know the 6502 assembler instructions yet now might be the best time to read about them. Here are some prerequisites you need to know before you can fully understand the code you will find in the rest of this post: The mantra is a password that can be 0 to 32 characters long. There are 64 valid characters (A-Za-z0-9,?) that can be used in mantras. They are represented in memory by their numeric value. The numeric values are between 0 for the character 'A' and 0x3F for the symbol '?'. When a user enters a mantra this numeric data is stored in a 0x20 bytes long field starting at memory offset 0x600. In the following code the data in this array is transformed and stored in another array starting at 0x4CD. Let's recall what I had already posted last time, the code where the mantra is checked for validity. CODE: $914f-> ad 66 06: LDA $0666 ; $0666 = Length of Mantra
<br />$9152-> f0 21: BEQ $9175 ; Branch if no mantra was entered
<br />$9154-> 20 50 98: JSR $9850 ; Check checksum of mantra
<br />$9157-> b0 06: BCS $915f ; Branch if wrong checksum
<br />$9159-> 20 e9 95: JSR $95e9 ; Check data of mantra
<br />$915c-> b0 01: BCS $915f ; Branch if wrong data
<br />$915e-> 60: RTS ; If this point is reached
<br /> ; a valid mantra was entered
Let's start with the subroutine that begins at offset 0x9850. The first few bytes of the mantra the user entered are read and transformed. Afterwards two checks are performed which ensure the validity of the mantra.
At the beginning of that function some indexes are initialized. CODE: $9850-> a2 00: LDX #$00
<br />$9852-> 8e c9 04: STX $04c9 ; $4C9 is an index into the
<br /> ; array starting at $4CD.
<br />$9855-> 8e ca 04: STX $04ca ; $4CA counts how many bits
<br /> ; of the current byte in the
<br /> ; array at $4CD were already
<br /> ; processed.
<br />$9858-> 8e cc 04: STX $04cc ; $4CC contains the sum of all
<br /> ; bytes in the array at $4CD.
<br /> ; It will be used to perform
<br /> ; a checksum test.
Afterwards another function (offset 0x98A7; I call this function GetLSBBits) is called three times. This function takes two parameters: The register A contains the next character from the mantra and the register Y contains the number of bits that should be read from that byte. Note that the Y least-significant bits of the character are read, the 8-Y most significant bits are disregarded. In all three instances 6 bits are supposed to be read from the first three characters. This makes perfect sense if you recall that the highest numeric value for a character in the mantra was 0x3F or 111111 in binary form. CODE: $985b-> a0 06: LDY #$06
<br />$985d-> bd 00 06: LDA $0600,X
<br />$9860-> 20 a7 98: JSR $98a7 ; Load the 6 LSB bits from
<br /> ; the 1st mantra character.
<br />$9863-> e8: INX
<br />$9864-> a0 06: LDY #$06
<br />$9866-> bd 00 06: LDA $0600,X
<br />$9869-> 20 a7 98: JSR $98a7 ; Load the 6 LSB bits from
<br /> ; the 2nd mantra character.
<br />$986c-> e8: INX
<br />$986d-> a0 06: LDY #$06
<br />$986f-> bd 00 06: LDA $0600,X
<br />$9872-> 20 a7 98: JSR $98a7 ; Load the 6 LSB bits from
<br /> ; the 3rd mantra character.
<br />$9875-> e8: INX
At this point you might already suspect the format of the mantra. 64 input characters and extracting the six least significant bits of an 8 bit byte. That sounds familiar. The mantra is in fact basically a Base64 encoded string of the player's inventory and stats with some checksums mixed in. It's a slightly modified Base64 code though because later a number of bits that's not 6 will be read depending on the storage size of certain attributes or items. The next part of this function is the first check for validity. You might remember that memory address $666 holds the length of the password the user entered. The 5 most significant bits of that value must equal the value of the 2nd byte in the array that was generated from the mantra. How exactly the value of this byte is created is mentioned later. CODE: $9876-> ad ce 04: LDA $04ce ; Load the 2nd byte from the array
<br /> ; starting at $4CD.
<br />$9879-> 4a: LSR A ; Shift by three => Only consider
<br />$987a-> 4a: LSR A ; the 5 MSB bits.
<br />$987b-> 4a: LSR A
<br />$987c-> cd 66 06: CMP $0666 ; Length of the mantra the user entered
<br />$987f-> d0 24: BNE $98a5 ; Jump to BadMantra
The last part of the function converts all remaining bytes from the mantra array to the new array. While doing this it also adds up all bytes of the $4CD array to perform another check on the validity of the mantra. CODE: $9881-> a0 06: LDY #$06
<br />$9883-> bd 00 06: LDA $0600,X
<br />$9886-> 20 a7 98: JSR $98a7 ; Load the 6 LSB bits of the
<br /> ; remaining bytes.
<br />$9889-> e8: INX
<br />$988a-> ec 66 06: CPX $0666 ; Do so until the end of the
<br />$988d-> d0 f2: BNE $9881 ; mantra is reached.
<br />$988f-> ad ca 04: LDA $04ca
<br />$9892-> f0 0a: BEQ $989e
<br />$9894-> a0 01: LDY #$01 ; If not 8 bits were written
<br />$9896-> a9 00: LDA #$00 ; to the current byte keep
<br />$9898-> 20 a7 98: JSR $98a7 ; padding the remaining space
<br />$989b-> 4c 8f 98: JMP $988f ; with bits of value 0.
<br />$989e-> ad cc 04: LDA $04cc ; Here's another validity check.
<br />$98a1-> d0 02: BNE $98a5 ; The sum of all bytes must be 0.
<br />$98a3-> 18: CLC ; Clear the carry flag
<br /> ; if the mantra is valid.
<br />$98a4-> 60: RTS
<br />$98a5-> 38: SEC ; Set the carry flag if
<br /> ; the mantra is invalid.
<br />$98a6-> 60: RTS
Now that we know how the bytes in the $4CD array are used let's see how that array was created in the first place. As I said it's a transformation of the mantra bytes from the array at $600. This transformation takes place in the GetLSBBits function at offset $98A7. CODE: $98a7-> 85 ec: STA $ec
<br />$98a9-> 8a: TXA
<br />$98aa-> 48: PHA
<br />$98ab-> 84 ed: STY $ed
<br />$98ad-> a9 08: LDA #$08
<br />$98af-> 38: SEC
<br />$98b0-> e5 ed: SBC $ed
<br />$98b2-> f0 08: BEQ $98bc ; If less than 8 bits should
<br />$98b4-> 85 ed: STA $ed ; be read it's necessary to
<br />$98b6-> 06 ec: ASL $ec ; rotate the most significant
<br />$98b8-> c6 ed: DEC $ed ; bits away so that the LSB
<br />$98ba-> d0 fa: BNE $98b6 ; bits can be read.
<br />$98bc-> ae c9 04: LDX $04c9 ; Load the index into the $4CD array.
<br />$98bf-> 26 ec: ROL $ec ; Rotate the next bit out of the char
<br />$98c1-> 3e cd 04: ROL $04cd,X ; and into the byte in the array.
<br />$98c4-> ee ca 04: INC $04ca ; Update the bit position.
<br />$98c7-> ae ca 04: LDX $04ca
<br />$98ca-> e0 08: CPX #$08 ; If the bit position is
<br />$98cc-> d0 15: BNE $98e3 ; not yet 8 just keep going.
<br />$98ce-> ae c9 04: LDX $04c9 ; Otherwise load the value
<br />$98d1-> bd cd 04: LDA $04cd,X ; of the current byte.
<br />$98d4-> 18: CLC
<br />$98d5-> 6d cc 04: ADC $04cc ; And add it to the checksum.
<br />$98d8-> 8d cc 04: STA $04cc
<br />$98db-> ee c9 04: INC $04c9 ; Move to the next byte.
<br />$98de-> a2 00: LDX #$00
<br />$98e0-> 8e ca 04: STX $04ca ; And reset the bit position.
<br />$98e3-> 88: DEY ; Decrease the number of bits to read.
<br />$98e4-> d0 d6: BNE $98bc
<br />$98e6-> 68: PLA
<br />$98e7-> aa: TAX
<br />$98e8-> 60: RTS
I've actually had major problems to understand that function. The reason for this was that the 6502 rotate instructions (ROL in this case) do not work like their x86 equivalents and it took me a while to realize that (Update: I forgot about the existence of the x86 instructions RCR and RCL which behave like the 6502 instructions). If you rotate a value by 1 on x86 CPUs the LSB of that value gets the value of the former MSB. On 6502 it gets the value of the carry flag. There were two reasons why I was confused before I understood this behaviour: The first one was that the values of the $4CD array are not reset after the user enters a wrong password. The 6502 ROL behaviour makes that perfectly fine though. All the old values will have been rotated out of all bytes after the mantra is processed. The 2nd point of confusion was how bytes of value 0 could suddenly turn into bytes of value 1 after a rotation. An impossibility on x86 CPUs but it works on 6502 due to the carry flag which is rotated in. Let's recall the two validity checks. The first one is that the 5 most significant bits from the byte at offset $4ED must equal the size of the mantra the user entered. To get through this check it's possible to generate the mantra and just put the length of the mantra into the highest 5 bits of the 2nd byte. The other check, the sum of all bytes in the array must be 0, is just as simple if you know that the first byte of the array ($4CD) is not used for anything at all. It was probably used by the programmers of Faxanadu to correct the checksum and that's what I used it for too. Later we will see that it would also be possible to append a correcting byte to the mantra because even the longest valid mantra (without appended random characters) doesn't nearly need 32 characters. That was (nearly) all that's necessary to check the validity of a password. Let's move on to the actual game data that resides in the mantra. This data is extracted from the mantra in the subroutine at $95E9. It all starts with the code below. I've called the subroutine at $98E9 GetNextBits. It works nearly like GetLSBBits but reads bits from an array in sequential order without disregarding the MSB bits. The number of bits to read is passed in Y, the current byte position and the current bit position are kept in the same registers as in GetLSBBits. I'm not going to discuss GetNextBits because it's nearly identical to GetLSBBits. The code snippet shows that the GetNextBits function is called 5 times. The Y bit value it returns is stored elsewhere in memory. I've found out what exactly is read to what memory position using trial-and-error. In most cases it was not necessary to create passwords to try what happens for a certain value. When the data is read from the mantra it's directly stored in the part of the memory that's also used during the game, so changing from a Long Sword to a Giant Blade or something can be done by editing a single byte in memory. CODE: $95e9-> a2 00: LDX #$00
<br />$95eb-> 8e c9 04: STX $04c9
<br />$95ee-> 8e ca 04: STX $04ca
<br />$95f1-> a0 0d: LDY #$0d ; Skip the first 13 bits
<br />$95f3-> 20 e9 98: JSR $98e9 ; Those were for error checking
<br />$95f6-> a0 03: LDY #$03 ; 3 bits for the town where
<br />$95f8-> 20 e9 98: JSR $98e9 ; the player starts.
<br />$95fb-> 8d 39 04: STA $0439
<br />$95fe-> a0 04: LDY #$04 ; 4 bits for the title
<br />$9600-> 20 e9 98: JSR $98e9 ; of the player.
<br />$9603-> 8d 37 04: STA $0437
<br />$9606-> a0 08: LDY #$08 ; 8 bits for the 8 unselectable
<br />$9608-> 20 e9 98: JSR $98e9 ; items your player carries.
<br />$960b-> 8d 2c 04: STA $042c
<br />$960e-> a0 08: LDY #$08 ; 8 bits for the quests
<br />$9610-> 20 e9 98: JSR $98e9 ; the player solved.
<br />$9613-> 8d 2d 04: STA $042d
After these four values were read the modus operandi changes a bit because in the next few lines the current equipment of the player is read. To read those values a new function (at $96B0) is introduced. This function - which I won't discuss extensively - reads the next bit (using GetNextBits) and if the value is 1 (Item equipped) reads another Y bits (using GetNextBits again) to find out which item is worn. CODE: $9616-> a0 02: LDY #$02 ; Two bits for the
<br />$9618-> 20 b0 96: JSR $96b0 ; equipped weapon.
<br />$961b-> 8d bd 03: STA $03bd
<br />$961e-> a0 02: LDY #$02 ; Two bits for the
<br />$9620-> 20 b0 96: JSR $96b0 ; aquipped armor.
<br />$9623-> 8d be 03: STA $03be
<br />$9626-> a0 02: LDY #$02 ; Two bits for the
<br />$9628-> 20 b0 96: JSR $96b0 ; equipped shield.
<br />$962b-> 8d bf 03: STA $03bf
<br />$962e-> a0 03: LDY #$03 ; Three bits for the
<br />$9630-> 20 b0 96: JSR $96b0 ; equipped magic.
<br />$9633-> 8d c0 03: STA $03c0
<br />$9636-> a0 05: LDY #$05 ; Five bits for the
<br />$9638-> 20 b0 96: JSR $96b0 ; equipped item.
<br />$963b-> 8d c1 03: STA $03c1
The last thing that's left to read is the inventory of the player. This is once again done differently. At first a number of bits (in Y) are read that gives away the number of items in a specific part of the inventory. Then another number of bits (X) are read Y times to fill the inventory with the item identified by the next X bits. Every part of the inventory has a maximum amount of items it can support. If this maximum number is exceeded the mantra is once again flagged as invalid. CODE: <br />...
<br />$9646-> a0 03: LDY #$03 ; 3 bits for the number of weapons
<br />$9648-> a2 02: LDX #$02 ; Each weapon has 2 bits
<br />$964a-> 20 c2 96: JSR $96c2
<br />$964d-> c9 05: CMP #$05 ; Invalid mantra if there are
<br />$964f-> b0 5d: BCS $96ae ; 5 or more weapons
<br />...
<br />$965c-> a0 03: LDY #$03 ; 3 bits for the number of armors
<br />$965e-> a2 02: LDX #$02 ; Each armor has 2 bits
<br />$9660-> 20 c2 96: JSR $96c2
<br />$9663-> c9 05: CMP #$05 ; Invalid mantra if there are
<br />$9665-> b0 47: BCS $96ae ; 5 or more armors
<br />...
<br />$9672-> a0 03: LDY #$03 ; 3 bits for the number of shields
<br />$9674-> a2 02: LDX #$02 ; Each shield has 2 bits
<br />$9676-> 20 c2 96: JSR $96c2
<br />$9679-> c9 05: CMP #$05 ; Invalid mantra if there are
<br />$967b-> b0 31: BCS $96ae ; 5 or more shields
<br />...
<br />$9688-> a0 03: LDY #$03 ; 3 bits for the number of magic
<br />$968a-> a2 03: LDX #$03 ; Each magic has 3 bits
<br />$968c-> 20 c2 96: JSR $96c2
<br />$968f-> c9 05: CMP #$05 ; Invalid mantra if there are
<br />$9691-> b0 1b: BCS $96ae ; 5 or more shields
<br />...
<br />$969e-> a0 04: LDY #$04 ; 4 bits for the number of items
<br />$96a0-> a2 05: LDX #$05 ; Each item has 5 bits
<br />$96a2-> 20 c2 96: JSR $96c2
<br />$96a5-> c9 09: CMP #$09 ; Invalid mantra if there are
<br />$96a7-> b0 05: BCS $96ae ; 9 or more items
<br />
That's it. The rest of the function only sets or clears the carry flag depending on whether the mantra was recognized as invalid or not. If you know about Faxanadu you might wonder how that can be everything. Neither your experience nor your gold is encoded in the mantra. These two values are not stored separately, apparently they depend on your rank. Depending on which rank you have you'll get a certain amount of gold and experience when you restart the game. There is actually a Faxanadu mantra generator available online (written in JavaScript) and I've written my own one in C++ (not very usable, you have to recompile the file every time you want to generate a new mantra). If you compare the passwords generated by these two generators you might notice that there are often differences between the generated passwords. This can easily be explained. Trailing 'A' characters are in most cases unnecessary because 'A' is the character with the numeric value 0 which is coincidentally the value that's used to pad the mantra if the number of bits it's not evenly divisible by 8. The online generator removes unnecessary trailing zeros, my generator does not. This also has an effect on the 2nd on the 3rd character of the mantra because that's where the size of the mantra is stored. The checksum in byte 0 (mantra characters 1 and 2) is not affected. I haven't yet mentioned the meaning of certain bit values in the mantra (like Hand Dagger = 0; Long Sword = 1). This information can be found in the source file of my mantra generator. By the way, vsj????,Xwbg3BuApxA5ikQl4 is a decent mantra that can be used to start in the first city with full equipment. Time to teach those first few monsters a lesson with your Dragon Slayer and Tilte. Trackbacks
Trackback specific URI for this entry
No Trackbacks
|
Calendar
QuicksearchArchivesContact
Links
Errorserendipity error: could not include serendipity_plugin_topexits:9e394f6ce1233c944505729bbd323460 - exiting.
Blog AdministrationPowered byCategories |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||



