Would you recommend this FAQ? Yes No You must register to leave a comment.

Submit Recommendation

+-----------------------------------------------------------------------------+ | Reverse Engineering of Adventures of Lolo 3's Password System v. 0.98 | | by kingshriek (Dean Fehlau) | | email: kingshriek at yahoo dot com | +-----------------------------------------------------------------------------+ The document is organized into sections and subsections in the following way. 1. Introduction 2. Notation 3. Password Generation 3.1. Memory Locations 3.2. Reading the Data 3.3. Compressing the Data 3.4. Calculating the Checksum 3.5. The Random Number Generator 3.6. Encrypting the Data 3.7. Unpacking the Data 3.8. Transcribing the Password 4. Password Verification 4.1. Checking the Data 4.2. Packing the Data 4.3. Decrypthing the Data 4.4. Checksum Comparison Test 4.5. Decompressing the Data 4.6. A Little Fixing Up 5. Example of Password Generation 5.1. Initial Data 5.2. Example - Compressing the Data 5.3. Example - Checksum and Random Value 5.4. Example - Encrypting the Data 5.5. Example - Unpacking the Data 5.6. Example - Transcribing the Password 6. Closing Notes A. Appendix A - Update History B. Appendix B - Password List and Miscellaneous Facts C. Appendix C - C code for password generator/verifier In the document are also tables and snippets of C-like code. They are labeled Table x.y and Code x.y respectively, where x is the section number and y means it's the yth table or code in the section. +-----------------------------------------------------------------------------+ | 1. Introduction | +-----------------------------------------------------------------------------+ This document contains a reverse engineering of the password system used in Adventures of Lolo 3. It also works in the game's Japanese counterpart, Adventures of Lolo 2 (J). The password system used in the game is far more complicated than the previous Lolo titles, which used 4-letter passwords that followed a very noticeable pattern. The passwords in Lolo 3 contain 16 characters (alphanumeric and some special ones), and have built into them a checksum and a randomizer. It should be noted that the game contains some special, built-in passwords (see Appendix B) that are completely outside of the password system. They all fail the game's and verification check and only serve to do interesting things on the password screen such as playing different background music or displaying different characters/monsters from the game. If you are more interested in the passwords themselves than the process of generating and verifying them, feel free to head down to Appendix B, which has a list of passwords to all the levels of the game. The special, built-in passwords are listed here as well. For the readers that are interested in the password system, it is assumed that you are familiar with binary and hexadecimal notation and are comfortable converting both to and from decimal notation. It is also assumed that you are familiar with basic logic operations (AND, OR, XOR, left shift, right shift). Knowing some C and being familiar with its syntax will help you better understand this document, but it shouldn't be necessary. It will also be helpful if you have some familiarity with 6502 assembly and how the NES CPU RAM is organized. I have also included in this document source code for a simple console program that generates and degenerates the passwords of Adventures of Lolo 3. +-----------------------------------------------------------------------------+ | 2. Notation | +-----------------------------------------------------------------------------+ Before the password system can be discussed, the reader needs to be familiar with the notation used in the document. I tried to use C syntax as much as possible. The code snippets in the document are pretty much C, aside from some shorthand addressing I put in (I hope it's easy to understand). Data representation ('.' is a placeholder for a digit): $.. or $.... memory address ($xx is a zero-page address) 0b........ value in binary 0x.... value in hexadecimal Bit order (in a byte, from highest to lowest order): highest lowest +-------+-------+-------+-------+-------+-------+-------+-------+ | bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 | +---------------------------------------------------------------+ Operators (in order of precedence): ! Logical NOT <---- done first + , - Addition, Subtraction << , >> Left Shift, Right Shift & Bit-wise AND ^ Bit-wise XOR | Bit-wise OR <---- done last The characters for the password use some non-standard characters. This document will use other characters in their place. Non-standard characters: + (cross) @ (heart) $ (diamond) & (triangle) The symbols '$' and '&' here are not to be confused with the address symbol '$' and the operator '&'. The meaning of these at each place where they are used should be obvious to the reader. In all code sections, the variable 'A' refers to the accumulator register in the CPU. +-----------------------------------------------------------------------------+ | 3. Password Generation | +-----------------------------------------------------------------------------+ This section will describe how the game generates a password when the player presses the start button on the map screen. The generation algorithm, which begins at $9AFD in PRG-ROM, uses 5 subroutines. 1. Read data in $0151-$0159, $014F, $52, compress, and store in $DF-$E6 2. Calculate checksum and store in $E7 3. Generate a random value based on the values at $2C and $2D and store in $E8 4. Encrypt the data in $DF-$E8 5. Unpack the data in $DF-$E8 into $E9-$F8, where the password is stored As for a less detailed and easier to remember description, 1. compress 2. checksum 3. randomize 4. encrypt 5. unpack The Lolo 3 password saves the following information: 1. The character that you are using (Lolo or Lala) 2. The area of the game which you are at 3. The floors that you have cleared on each level Before we explore password generation algorithm, we need to know where and how the game stores the information listed above in memory. +-----------------------+ | 3.1. Memory Locations | +-----------------------+ The addresses for the information stored in the password are as follows: $52 - Character Character data is stored in bits 0 and 4. Bit 0 determines whether Lolo or Lala is selected. Bit 4 determines if Lolo and Lala are together or if the selected character is alone. The other bits are set to 0b0 under normal gameplay circumstances. The game ignores them anyway. In summary, 0x00 - Lolo, 0x01 - Lala if both Lolo and Lala are together 0x10 - Lolo, 0x11 - Lala if alone $014F - Location Location data is stored in bits 0-4. Bits 5-7 are set to 0b0 under normal gameplay circumstances. The game ignores these bits. There are 19 valid locations - Levels 1 through 17 and the two Grandpa's Tree training levels. Thus, in normal gameplay $014F has a value in the range 0x00-0x12. Here's a list of valid location values for $014F: 0x00 - Grandpa's Tree 1 0x01 - Grandpa's Tree 2 0x02 - Level 1 0x03 - Level 2 0x04 - Level 3 . . . 0x12 - for Level 17 Each of these locations resides in of the game's 3 map areas - the Overworld, the Underwater World, and the Underworld. The Overworld is a 2-screen map, whereas the Underwater World and Underworld are 1-screen maps. Level 3 is split between the two screens of the Overworld. Here's a list of areas and levels contained within: Overworld (left screen) - Levels 1-3 Overworld (right screen) - Levels 3-8 Underwater World - Levels 9-13 Underworld - Levels 14-17 0x13, while not a valid location value the game will generate, corresponds to the right screen portion of Level 3 (0x04 corresponds to the left screen portion), but is only tied to the coordinates there. If you create a password with this value (as you have to, the game won't generate one), the game will try to place you in the Underworld with these coordinates. Since the Underworld is only a 1 screen map, you will be placed to the right and off of the map! 0x14-0x1F will remove you from the map completely! $0151-$0159 - Level Completion Data Each byte corresponds to a set of 2 adjacent (in number) levels. Bits 4-7 correspond to the earlier of the two levels and bits 0-3 correspond to the later of the two levels. $159 only contains one level, Level 17, which is placed in bits 4-7. The lower 4 bits are not used. In summary, $0151 - Levels 1 & 2 $0152 - Levels 3 & 4 $0153 - Levels 5 & 6 . . $0158 - Levels 15 & 16 $0159 - Level 17 For floors, the current floor you are on at each level is stored in the set of 4 bits that correspond to the level. Thus, someone at Level 1, Floor 4 and Level 2, Floor 3, would have 0x43 as the value for $151. Special rooms where you get/use items or fight bosses use the value 0x0. If the level doesn't have a special room or boss (Levels 8,14,15,16), then you won't be able to enter the level. Thus, 0x00 at $153 would place you at the bosses of Levels 5 & 6. Level completion is denoted by 0xF. In the course of normal gameplay, completion status is not given for Level 13 (as the boss defeats you instead). Therefore, right after finishing Level 13, bits 4-7 of $157 are 0x0. Also, since Level 17 is the final level, it is impossible in normal gameplay for the higher 4 bits of $159 to have the value 0xF. If these levels are set to 0xF, they won't be destroyed (as there aren't graphics for this in the game) and they will be impossible to enter. In summary, 0x0 - Special or boss room (except for Levels 8,14,15,16) 0x1 - Floor 1 0x2 - Floor 2 0x3 - Floor 3 . . . 0xA - Floor 10 0xF - Level completed (except for Levels 13 and 17) $0150 contains the information for what floors are cleared in the Grandpa's Tree training levels. It is done so in the same way as in $0151-$0159. The password does not save this information, hence why it's not listed up above. +-----------------------+ | 3.2. Reading the Data | +-----------------------+ In order to read the data needed for the password, the game makes use of a 80 byte table at values at $9A29-$9A79, which is provided below (values are in hex). Table 3.1 - Table used for reading data +----------------------------------------------------------------------+ | $9A29-$9A79 | +----------------------------------------------------------------------+ | $9A20: xx xx xx xx xx xx xx xx xx 01 51 06 03 01 51 02 | | $9A30: 03 01 52 07 04 01 52 02 03 01 53 06 03 01 53 02 | | $9A40: 03 01 54 06 03 01 54 02 03 01 55 06 03 01 55 02 | | $9A50: 03 01 56 06 03 01 56 02 03 01 57 07 04 01 57 02 | | $9A60: 03 01 58 06 03 01 58 02 03 01 59 07 04 01 4F 04 | | $9A70: 05 00 52 04 01 00 52 00 01 FF xx xx xx xx xx xx | +----------------------------------------------------------------------+ | $9A29-$9A2C: 01 51 06 03 Level 1, $0151H, 5 floors | | $9A2D-$9A30: 01 51 02 03 Level 2, $0151L, 5 floors | | $9A31-$9A34: 01 52 07 04 Level 3, $0152H, 10 floors | | $9A35-$9A38: 01 52 02 03 Level 4, $0152L, 5 floors | | $9A39-$9A3C: 01 53 06 03 Level 5, $0153H, 5 floors | | $9A3D-$9A40: 01 53 02 03 Level 6, $0153L, 5 floors | | $9A41-$9A44: 01 54 06 03 Level 7, $0154H, 5 floors | | $9A45-$9A48: 01 54 02 03 Level 8, $0154L, 5 floors | | $9A49-$9A4C: 01 55 06 03 Level 9, $0155H, 5 floors | | $9A4D-$9A50: 01 55 02 03 Level 10, $0155L, 5 floors | | $9A51-$9A54: 01 56 06 03 Level 11, $0156H, 5 floors | | $9A55-$9A58: 01 56 02 03 Level 12, $0156L, 5 floors | | $9A59-$9A5C: 01 57 07 04 Level 13, $0157H, 10 floors | | $9A5D-$9A60: 01 57 02 03 Level 14, $0157L, 5 floors | | $9A61-$9A64: 01 58 06 03 Level 15, $0158H, 5 floors | | $9A65-$9A68: 01 58 02 03 Level 16, $0158L, 5 floors | | $9A69-$9A6C: 01 59 07 04 Level 17, $0159H, 10 floors | | $9A6D-$9A70: 01 4F 04 05 Location data, $014F (bits 0-4) | | $9A71-$9A74: 00 52 04 01 Character data, $52, (bit 4) | | $9A75-$9A78: 00 52 00 01 Character data, $52, (bit 0) | | | | In the level addresses, | | H - bits 4-7 L - bits 0-3 | | | +----------------------------------------------------------------------+ The bytes in the table are in sequences of four, i.e. (01 51 06 03), (01 51 02 03), (01 52 07 04), etc. where each sequence corresponds to an information entry that the password stores. The first 2 bytes are the address of the memory from which information for password generation is received (the information that's stored in the password). These 2 bytes are ordered with the higher order byte first. Thus, the first 2 bytes in the table give the address $0151. Recall that $0151-$0159 are level addresses and that only bits 4-7 of $0159 are used (for Level 17). Also, recall that bits 0-4 of $014F correspond to location data and that bits 0 and 4 of $0052 correspond to character data. The 3rd byte gives the number of the highest order bit (in the bit order) used in the data stored at the address given by the first 2 bytes. The 4th byte gives how many bits are used to store the information. So, for level data, levels with 5 floors need 3 bits and levels with 10 floors need 4 bits. The location data needs 5 bits and each set of character data needs 1 bit. +---------------------------+ | 3.3. Compressing the Data | +---------------------------+ Right now, the information that's needed for the password is somewhat sparse, meaning that not all the bits in each byte have useful information. For example, character data only needs 2 bits, but it's currently being stored with 2 sets of 4 bits (a byte). It would be nice to compress this data so that only the useful bits are stored, and that's exactly what the game does. The game does this by sequentially loading the required information into an 8-byte shift register, which is located at $DF-$E6. The data is loaded into bit 7 of $E6 and shifted left until the shift register holds all of it. In the process of compression, the game refers to the table starting at $9A29 (Table 3.1). Given by the table, address $0151 is loaded first, followed by $0152-$0159, and then finally $015F and $52. The game then stores the requested data, the byte given by each address in the table, into the accumulator (an 8-bit register on the CPU, denoted by A in the code examples). Here, the data only occupies the lower order bits and needs to be shifted all the way to the left so that it is in position to be loaded bit by bit into the shift register. The 3rd byte in each 4-byte sequence in the table gives the position of the highest order bit of the data to be loaded. Thus, the number of times the data needs to be shifted in the accumulator is given by subtracting this bit position from 7 (since the accumulator is an 8-bit register). Thus, the number of times needed to shift the data is determined by the 3rd byte in each 4-byte sequence in the table (hence, why data stored on the lower 4 bits of a byte have a value that is 4 higher than the data stored on the higher 4 bits). The number of times to shift the data through the shift register is given by the 4th byte in the 4-byte sequence, which stores how many bits the information takes up. Hence, this subroutines compresses the data by storing only useful information in the shift register. The above is summarized by the following code. Code 3.1 - compression subroutine $E6 = 0x00; for(x=0x9A29; x<=0x9A79; x+=4) { // run through data in table y1 = $x & 0xFF00; // upper byte of address y2 = $(x+1) & 0x00FF; // lower byte of address A = $(y1+y2); // addresses for password information ns = $(x+2); ns = 7 - ns ; // number of times to left shift the accumulator nr = $(x+3); // number of times to left shift through $DF-$E6 A = (A << ns) & 0xFF; // shift data into loading position for(i=0; i<nr; i++) { carry[0xE7] = (A & 0x80) >> 7; // carry from A into $E6 A = (A << 1) & 0xFF; for(y=0xE6; y>=0xE0; y--) { // shift data through $DF-$E6 carry[y] = ($y & 0x80) >> 7; // carry from $y into $(y-1) $y = ($y << 1) & 0xFF; $y += carry[y+1]; } $DF = ($DF << 1) & 0xFF; $DF += carry[$E0]; } } +-------------------------------+ | 3.4. Calculating the Checksum | +-------------------------------+ Once the compression is finished, a checksum calculation subroutine is entered. The lowest byte of the sum of the bytes $D9-$E6 is stored in $E7. That is, Code 3.2 - checksum subroutine $E7 = ($D7 + $E0 + $E1 + $E2 + $E3 + $E4 + $E5 + $E6) & 0xFF; +----------------------------------+ | 3.5. The Random Number Generator | +----------------------------------+ $E8 is the next value that needs to be computed. $E8 is given by a pseudorandom number generator (RNG). The RNG is in the form of a three-tap linear feedback shift register (LFSR). If you are creating your own password, you can skip this step, as the value it affects can be anything without affecting the validity of the password. This is just how the game actually generates passwords and it's only included for completeness. The game uses $2D and $2C in RAM for the shift register, where $2D is of higher order than $2C. Both addresses are initialized to an unknown value and are updated 11 times when the player requests a password (pressing Start on the map screen). After these 11 iterations, the value of $2C is stored in $E8. $2C and $2D are only initialized at power-on, i.e. resetting the game doesn't change their values. The taps are at bits 15, 1, and 0. In binary, the RNG satisfies the reccurence relation: b[n] = !(b[n+16] ^ b[n+2] ^ b[n+1]) Below is a diagram of the RNG: Figure 3.1 - Random Number Generator $2D $2C +-------------------------+-------------------------+ <---- 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 <-----+ +-------------------------+-------------------------+ | | | | | | +-----+ | | | | | | | v v | +----------------------------------->(+)----->(+)----->(o) (+) XOR (o) NOT It would be nice to know a few things about the RNG, such as if it generates all the values between $0000 and $FFFF for $2C-2D. Also, since the RNG is completely deterministic, it will have to repeat itself eventually. How many iterations are needed for this to happen (the RNG's period)? Since there are an odd number of taps, if the NOT were removed, the RNG would preserve either an even number or an odd number of set bits (equal to 0b1) amongst $2C-$2D and the new bit that is added to the shift register. What the NOT does is switch between an even number and an odd number of set bits in these 17 bit locations after each iteration. We will need this fact later in the determination of the period. From the taps, the feedback polynomial is x^16 + x^2 + x + 1. On the integer ring Z(mod 2), this factors into x + 1 and x^15 + x^14 + x^13 + ... + x^4 + x^3 + x^2 + 1. Thus, the greatest common divisor (GCD) of the feedback polynomial with another polynomial mod 2 will either be 1, x + 1, or x^15 + x^14 + x^13 + ... + x^4 + x^3 + x^2 + 1. We will investigate the period by looking at each of these cases. For the first case, x will be used, since GCD(x , x^16 + x^2 + x + 1) = 1. Continuously squaring mod (x^16 + x^2 + x + 1) gives: x x^256 = x^4 + x + 1 x^2 x^512 = x^8 + x^2 + 1 x^4 x^1024 = x^4 + x^2 + x x^8 x^2048 = x^8 + x^4 + x^2 x^16 = x^2 + x + 1 x^4096 = x^8 + x^4 + x^2 + x + 1 x^32 = x^4 + x^2 + 1 x^8192 = x^8 + x^4 + x x^64 = x^8 + x^4 + 1 x^16384 = x^8 + x + 1 x^128 = x^8 + x^2 + x x^32768 = x So, after 32768 iterations, we are back at the starting point. Thus, if the GCD of the starting polynomial and the feedback polynomial is 1, the period is 32767. By plugging in 1 into all the polynomials, it is seen that every polynomial in this period has an odd number of terms. So x is a generator of these odd polynomials. For a GCD of x + 1, we square in the same fashion and also get a period of 32767. Here, all the polynomials in a period have an even number of terms, and x + 1 is a generator. For a GCD of x^15 + x^14 + x^13 + ... + x^4 + x^3 + x^2 + 1, we find that squaring it doesn't change its value. Hence, the period is 1, and seeding the RNG with the value corresponding to this polynomial will produce a constant sequence. Only one polynomial mod (x^16 + x^2 + x + 1) has this GCD, so this is the identity coresponding to an even number of terms. The only polynomial left mod (x^16 + x^2 + x + 1) is the identity for an odd number of terms, which is obviously 1 itself. Therefore, since the NOT switches between even and odd numbered terms after each iteration, the RNG will have a period of 32767 x 2 = 65534, unless it is seeded with two specific values that generate constant sequences. These two values are 0x5555 and 0xAAAA, each consisting of alternating digits in their binary representations. Taking this into account, in a single period, $2C has a nearly uniform distribution. Value Probability ----------------------------------------- 0x55 255 / 65534 0xAA 255 / 65534 all other values 256 / 65534 65534 and 11 are relatively prime. Thus, $E8 has the same distribution. The RNG algorithm can be expressed by the following code: Code 3.3 - random number generator r = $2D << 8 | $2C; for(j=0; j<11; j++) { r = (r << 1 | !((r >> 15 ^ r >> 1 ^ r) & 0x0001)) & 0xFFFF; } $2D = r & 0xFF00; $2C = r & 0x00FF; $E8 = $2C; Below is a short table of values ($E8) that the RNG generates when successive passwords are brought up. The table is seeded with $2C = 0xFF and $2D = 0xFF. Table 3.2 - RNG Pattern Table +-----------------------------------------------------------+ | RNG Pattern Table (passwords 1-50) | +----+------+----+------+----+------+----+------+----+------+ | # | $E8 | # | $E8 | # | $E8 | # | $E8 | # | $E8 | +----+------+----+------+----+------+----+------+----+------+ | 1 | 0x6D | 11 | 0x8E | 21 | 0x0A | 31 | 0x7B | 41 | 0xB6 | | 2 | 0x86 | 12 | 0xA2 | 22 | 0x72 | 32 | 0x26 | 42 | 0x95 | | 3 | 0xC2 | 13 | 0x4E | 23 | 0xEB | 33 | 0x68 | 43 | 0x94 | | 4 | 0x21 | 14 | 0x91 | 24 | 0xF6 | 34 | 0xDD | 44 | 0xB9 | | 5 | 0xE8 | 15 | 0x39 | 25 | 0x9B | 35 | 0x77 | 45 | 0x58 | | 6 | 0xF6 | 16 | 0x85 | 26 | 0x42 | 36 | 0x8B | 46 | 0x67 | | 7 | 0x16 | 17 | 0xD9 | 27 | 0x4A | 37 | 0x34 | 47 | 0x46 | | 8 | 0xF2 | 18 | 0xCC | 28 | 0xB1 | 38 | 0x48 | 48 | 0x51 | | 9 | 0xC0 | 19 | 0x4C | 29 | 0x8E | 39 | 0xDC | 49 | 0x5C | | 10 | 0xAC | 20 | 0x47 | 30 | 0xE2 | 40 | 0xEC | 50 | 0x27 | +----+------+----+------+----+------+----+------+----+------+ +--------------------------+ | 3.6. Encrypting the Data | +--------------------------+ Next, $DF-$E7 are encrypted by a combination of summing adjacent values, right rotating, and XOR. The encryption is initialized by the random value $E8 generated in the previous step. The encryption algorithm can be more clearly seen in the figure below. Figure 3.2 - Encryption ---+-----+-----+-----+-----+---------------+ | $E4 | $E5 | $E6 | $E7 | $E8 |*************> ---+-----+-----+-----+-----+---------------+ | | | | | | | | v v v v v v v v +---------------+ | $E8 |********************** +---------------+ * | | | | | | | | * v v v v v v v v * +-------------------+ * | 7 6 5 4 3 2 1 0 | * * | 8| +-----* * (+) XOR | 7|-----|-----* * | 6|-----|-----* * *** Bus | 5|-----|-----* v | Adder 4|-----|-----******>(+) | 3|-----|-----* * | 2|-----|-----* * | 1|-----|-----* * | 0|-----+ * * | 7 6 5 4 3 2 1 0 | * +-------------------+ * ^ ^ ^ ^ ^ ^ ^ ^ * | | | | | | | | * ---+-----+-----+-----+-----+---------------+ * | $E3 | $E4 | $E5 | $E6 | $E7 |************> * ---+-----+-----+-----+-----+---------------+ * ^ ^ ^ ^ ^ ^ ^ ^ * | | | | | | | | * ***************** * * * ******************************* In the diagram above, $DF-$E8 is represented by two parallel, offset shift registers that shift a byte at a time, where each address is mapped with the corresponding address on the other shift register. You can picture this by rolling the diagram into a cylinder, matching the $E7s, $E6s, $E5s, etc. In this way, the encryption algorithm could be thought of as a helical shift register passing through an adder inside that feeds back to the preceding byte of the addition (a first order feedback). Here is the code that does the encryption. Code 3.4 - encryption subroutine for(y=0xE7; y>=0xDF; x--) { $y= ($y + $(y+1)) & 0xFF; carry = ($y & 0x01) << 7; // carry from $y into $y $y >>= 1; $y += carry; $y ^= $(y+1); } +-------------------------+ | 3.7. Unpacking the Data | +-------------------------+ Next, the data in $DF-$E8 is unpacked onto $E9-$F8. Observe that $DF-$E8 contain 80 bits of information. The password is 16 characters long, so each byte in $E9-$F8 needs to contain 5 bits from $DF-$E8. Thus, 5 bit chunks of data are sequentially taken off of $DF-$E8 and loaded into each byte of $E9-$F8. The data is loaded by setting up $DF-$E8 as a shift register so that the lowest order bits of $DF-$E8 are loaded onto $E9, the next 5 onto $EA, the next 5 onto $EB, and so on. This is done by the following code. Code 3.5 - unpacking subroutine for(z=0xE9; z<=0xF8; x++) { // run through $E9-$F8 A = 0x00; // clear accumulator for(i=0; i<5; i++) { // load 5 bits onto a byte in $DF-$E8 carry0 = $E8 & 0x01; // carry from $E8 into A carry[0xDF] = ($DF & 0x01) << 7; // carry from $DF into $E0 $DF >>= 1; for(y=0xE0; y<=0xE7; y++) { carry[y] = ($y & 0x01) << 7; // carry from $y into $(y+1) $y >>= 1; $y += carry[y-1]; } A = (A << 1) & 0xFF; A += carry0; } $z = A; } +-------------------------------+ | 3.8. Transcibing the Password | +-------------------------------+ Now, all we need to know is the correspondence between the bytes in $E9-$F8 and the characters used in the password. This document uses @, $, & for heart, diamond, and triangle respectively for readability purposes. Here is a table that shows the correspondence: Table 3.3 - Password Transcription Table +------------------------------------------------------+ | Password Transcription Table | +----------+----------+----------+---------------------+ | 0x00 - 2 | 0x08 - B | 0x10 - L | 0x18 - V | | 0x01 - 3 | 0x09 - C | 0x11 - M | 0x19 - W | | 0x02 - 4 | 0x0A - D | 0x12 - N | 0x1A - Y | | 0x03 - 5 | 0x0B - F | 0x13 - P | 0x1B - Z | | 0x04 - 6 | 0x0C - G | 0x14 - Q | 0x1C - + (cross) | | 0x05 - 7 | 0x0D - H | 0x15 - R | 0x1D - @ (heart) | | 0x06 - 8 | 0x0E - J | 0x16 - S | 0x1E - $ (diamond) | | 0x07 - 9 | 0x0F - K | 0x17 - T | 0x1F - & (triangle) | +----------+----------+----------+---------------------+ The table above does not encompass all the characters on the password entry screen. Except for a few very special passwords (see Appendix B), characters not used in any password are: 0 , 1 , # , (smiley) , ? , ! , * , X US version 0 , 1 , A , E , I , O , U , X Japan version which all have the value 0xFF. The final password is the 16 characters given by the correspondence in the table above in the same order as the bytes in $E9-$F8. +-----------------------------------------------------------------------------+ | 4. Password Verification | +-----------------------------------------------------------------------------+ This section will describe how the game verfies a password a user has inputted and how it extracts the data stored in it. The verification algorithm begins at $9BD3 in PRG-ROM. The password is verified and read with 6 subroutines. 1. Check password ($E9-$F8) for forbidden characters (0xFF) 2. Read data in $E9-$F8, and pack into $DF-$E8 3. Decrypt the data in $DF-$E8 4. Calculate the checksum and proceed to step 4 only if it equals $E7 5. Decompress the data in $DF-$E6 and write to $0151-$0159 ,$014F, $52 6. Fix up the data in $0151-$0159, $014F, $52 (see Section 4.5 for details) As for a less detailed, easier to remember description, 1. check 2. pack 3. decrypt 4. compare 5. decompress 6. fix The section begins assuming the bits for the password are loaded into $E9-$F8. +------------------------+ | 4.1. Checking the Data | +------------------------+ First, the password, stored in $E9-$F8, is checked for any forbidden characters (see Section 3.8). Recall that a forbidden character is represented by 0xFF. If any are found, the game returns to the password screen for the player to input another one. If no forbidden characters are encoutered, the game proceeds to the next subroutine. Code 4.1 - checking subroutine for(z=0xE9; z<=0xF8; z++) { // run through $E9-$F8 if($z < $80) { (proceed checking characters) } else { (break out of loop and return to password screen) } } +-----------------------+ | 4.2. Packing the Data | +-----------------------+ Next, the bits in $E9-$F8 are read and packed into $DF-$E8 by reversing the process outlined in Section 3.7. Hence, starting with $F8 and going backwards through $E9, the lowest 5 bits on each byte get loaded sequentially onto the lowest order bit of the shift register $DF-$E8. Code 4.2 - packing subroutine for(z=0xF8; z>=0xE9; z--) { // run through $F8-$E9 A = $z; for(i=0; i<5; i++) { // load lowest 5 bits onto $DF-$E8 carry0 = $A & 0x01; // carry from A into $E8 A >>= 1; carry[0xE8] = ($E8 & 0x80) >> 7; // carry from $E8 into $E7 $E8 = ($E8 << 1) & 0xFF for(y=0xE7; y>=0xDF; y--) { carry[y] = ($y & 0x80) >> 7; // carry from $y into $(y-1) $y = ($y << 1) & 0xFF; $y += carry[y+1]; } } } +--------------------------+ | 4.3. Decrypting the Data | +--------------------------+ Next, $DF-$E8 are decrypted by reversing the process outlined in Section 3.6, that is, replacing the adder with a subtracter, replacing the right rotate with a left rotate, and carrying out the steps outlined in the encryption algorithm backwards. Code 4.3 - decryption subroutine for(y=0xDF; y<=0xE7; x++) { $y ^= $(y+1); carry = ($y & 0x80) >> 7; // carry from $y into $y $y = ($y << 1) & 0xFF; $y += carry; $y= ($y - $(y+1)) & 0xFF; } +------------------------------+ | 4.4. Checksum Compaison Test | +------------------------------+ The checksum is then calculated and stored in the accumulator. The game compares this value to what is stored at $E7. If the values match,the password as validated and proceeds to next step. If they don't match, the password is invalid, and the game will ask prompt the player to enter a new one. Code 4.4 - compare subroutine A = ($DF + $E0 + $E1 + $E2 + $E3 + $E4 + $E5 + $E6) & 0xFF; if (A == $E7) { // compare to checksum (proceed to decompression subroutine) } else { (return to password screen) } +-----------------------------+ | 4.5. Decompressing the Data | +-----------------------------+ After a successful checksum, the data the password represents is decompressed and read from $DF-$E6. It is stored in the addresses given by the first two bytes of the $9A29 table (Table 3.1), that is $5A, $014D, and $0159-$0151. Before it stores the data in these addresses, the values in these addresses are first cleared, as well as the value in the address $0150. This value describes what floors have been cleared in the Grandpa's Tree training levels (which the password doesn't store - see Section 3.1 - Memory Locations). It does this by reversing the process outlined in Section 3.3. Code 4.5 - decompression subroutine $014F = 0; // clear out data in $014F $52 = 0; // clear out data in $52 for(w=0x159; w<=0x150; w--) { // clear out data in $0150-$0159 $w = 0; } for(x=0x9A75; x>=0x9A29; y-=4) { // run through data in table A = 0x00; // clear accumulator ns = $(x+2); ns = 7 - ns; // number of times to right shift the accumulator nr = $(x+3); // number of times to right shift the data $DF-$E6 for(i=0; i<nr; i++) { carry[0xDF] = ($DF & 0x01) << 7; // carry from $DF into $E0 0xDF >>= 1; for(y=0xE0; y<=0xE6; y++) { carry[y] = ($y & 0x01) << 7; // carry from $y into $(y+1) $y >>= 1; $y += carry[y-1]; } A >>= 1; A += $E6; } A >>= ns; y1 = $x & 0xFF00; // upper byte of address y2 = $(x+1) & 0x00FF; // lower byte of address A = A | $(y1+y2); $(y1+y2) = A; // load data from A into the desired address } +-------------------------+ | 4.6. A Little Fixing Up | +-------------------------+ You may be thinking the verification/extraction process is now done, but it's not quite yet. Remember how the information stored for the levels with 5 floors is done so with 3 bits? This is troublesome because the value 0xF is used to denote that the level has been beaten, and it requires 4 bits. So, levels with 5 floors at this point have the value 0x7. Hence, some extra code is needed to fix these to the desired 0xF. This way this is done is shown below. Code 4.6 - fix 0x7 to 0xF subroutine for(x=0x9A29; x<=0x9A69; y+=4) { y1 = $x & 0xFF00; // upper byte of address y2 = $(x+1) & 0x00FF; // lower byte of address if($(x+2) == 6) { // if level has 5 floors and is stored on higher 4 bits if(($(y1+y2) & 0xF0) == 0x70) { // change 0x7 to 0xF $(y1+y2) |= 0x80; } } if($(x+2) == 2) { // if level has 5 floors and is stored on lower 4 bits if(($(y1+y2) & 0x0F) = 0x07) { // change 0x7 to 0xF $(y1+y2) |= 0x08; } } } Finally, the required data has been extracted from the password. The game then uses this data to place you in the right location, to give you the right character, and to remember which floors you have beaten. +-----------------------------------------------------------------------------+ | 5. Example of password generation | +-----------------------------------------------------------------------------+ I thought it would be a good idea to go step by step through the password generation algorthim with an example. What will be produced is a working password from a set of initial data. +-------------------+ | 5.1. Initial Data | +-------------------+ The password we will generate will have the following attributes. $0151 = 0xFF Levels 1-2 completed $0152 = 0xF1 Level 3 completed, at floor 1 on Level 4 $0153 = 0x23 At floor 2 on Level 5, floor 3 on Level 6 $0154 = 0x01 At boss on Level 7, Level 8 not started $0155-$158 = 0x11 Levels 9-16 not started $159 = 0x10 Level 17 not started $014F = 0x01 Location is Grandpa's Tree 2 $52 = 0x01 Character is Lala with Lolo The data above is in the same order as the table at $9A29 (Table 3.1) in memory that the game uses to generate passwords, so no rearranging needs to be done. First, we'll express these values in binary. Second, we'll mark off the bits to be used in the algorithm by referring to the $9A29 table. These bits are marked below by asterisks. $0151 $0152 $0153 $0154 $0155 $0156 11111111 11110001 00100011 00000001 00010001 00010001 *** *** **** *** *** *** *** *** *** *** *** *** $0157 $0158 $0159 $014F $52 00010001 00010001 00010000 00000001 00000001 **** *** *** *** **** ***** * * +-------------------------------------+ | 5.2. Example - Compressing the Data | +-------------------------------------+ Next, compress these bits (Code 3.1) onto $DF-$E6 by writing only the asterisked bits in the same order. Note that there are 61 asterisked bits, so append 0b000 to the left of the bit sequence (in $DF). $DF-$E6 00011111 11111001 01001100 00010010 01001001 00010010 01001000 10000101 We'll now express $DF-$E6 in hex for convenience. $DF-$E6 +-------------------------+ | 1F F9 4C 12 49 12 48 85 | +-------------------------+ +------------------------------------------+ | 5.3. Example - Checksum and Random Value | +------------------------------------------+ Next, calculate the checksum $E7 (Code 3.2) by adding $DF-$E6 to get $E7. $E7 = (0x1F + 0xF9 + 0x4C + 0x12 + 0x49 + 0x12 + 0x48 + 0x85) & 0xFF = 0x9E Now we must find the random value $E8. For the sake of convenience, we will just make up something instead of tediously stepping through iterations in the RNG. Let's use 0x5C. $E8 = 0x5C Thus, we have $DF-$E8 +-------------------------------+ | 1F F9 4C 12 49 12 48 85 9E 5C | +-------------------------------+ +------------------------------------+ | 5.4. Exmaple - Encrypting the Data | +------------------------------------+ Next, we go into the encryption subroutine (Code 3.4) Start with $E7, and see its value is 0x9E. Then, we add it to $E8 and take lowest order byte to get 0xFA. The lowest order bit in 0xFA is 0b0, so we right shift $E7 without a carry. (if it were 0b1, there would be a carry). Doing the shifting gives 0x7D (if there was a carry, add 0x80 afterwards). Next, we XOR this with the next higher address, $E8. XORing 0x7D with $E8 results in 0x21. $E7 gets updated with this value. Summarizing, $E7 = 0x9E $E8 = 0x5C $E7 = ($E7 + $E8) & 0xFF = 0xFA (no carry) $E7 = $E7 >> 1 = 0x7D $E7 = $E7 ^ $E8 = 0x21 For the rest of the iterations, we just decreases the addresses in question. So, for the next iteration, replace $E7 up above with $E6 and replace $E8 with $E7 (the new $E7). Continuing until $DF, we get: $E6 = 0x85 $E7 = 0x21 $E6 = ($E6 + $E7) & 0xFF = 0xA6 (no carry) $E6 = $E6 >> 1 = 0x53 $E6 = $E6 ^ $E7 = 0x72 $E5 = 0x48 $E6 = 0x72 $E5 = ($E5 + $E6) & 0xFF = 0xBA (no carry) $E5 = $E5 >> 1 = 0x5D $E5 = $E5 ^ $E6 = 0x2F $E4 = 0x12 $E5 = 0x2F $E4 = ($E4 + $E5) & 0xFF = 0x41 (carry) $E4 = $E4 >> 1 = 0x20 $E4 = $E4 + 0x80 = 0xA0 $E4 = $E4 ^ $E5 = 0x8F $E3 = 0x49 $E4 = 0x8F $E3 = ($E3 + $E4) & 0xFF = 0xD8 (no carry) $E3 = $E3 >> 1 = 0x6C $E3 = $E3 ^ $E4 = 0xE3 $E2 = 0x12 $E3 = 0xE3 $E2 = ($E2 + $E3) & 0xFF = 0xF5 (carry) $E2 = $E2 >> 1 = 0x7A $E2 = $E2 + 0x80 = 0xFA $E2 = $E2 ^ $E3 = 0xCF = 0x19 $E1 = 0x4C $E2 = 0x19 $E1 = ($E1 + $E2) & 0xFF = 0x65 (carry) $E1 = $E1 >> 1 = 0x32 $E1 = $E1 + 0x80 = 0xB2 $E1 = $E1 ^ $E2 = 0xAB $E0 = 0xF9 $E1 = 0xAB $E0 = ($E0 + $E1) & 0xFF = 0xA4 (no carry) $E0 = $E0 >> 1 = 0x52 $E0 = $E0 ^ $E1 = 0xF9 $DF = 0x1F $E0 = 0xF9 $DF = ($DF + $E0) & 0xFF = 0x18 (no carry) $DF = $DF >> 1 = 0x0C $DF = $DF ^ $E0 = 0xF5 So now we have: $DF-$E8 +-------------------------------+ | F5 F9 AB 19 E3 8F 2F 72 21 5C | +-------------------------------+ +-----------------------------------+ | 5.5. Example - Unpacking the Data | +-----------------------------------+ The unpacking subrouting (Code 3.5) is next. Since the bits in $DF-$E8 are right shifted into each of the $E9-$F8, we will first express $DF-$E8 in bits and reverse the sequence so we can read off $E9-$F8 in a left-to-right manner. $DF-$E3 11110101 11111001 10101011 00011001 11100011 $E4-$E8 10001111 00101111 01110010 00100001 01011100 Reversing gives: $E8-E4 00111010 10000100 01001110 11110100 11110001 * * * * * * * * $E3-$DF 11000111 10011000 11010101 10011111 10101111 * * * * * * * * Now, split this sequence into groups of 5 bits each and store into $E9-$F8. $E9-$F0 00111 01010 00010 00100 11101 11101 00111 10001 $F1-$F8 11000 11110 01100 01101 01011 00111 11101 01111 Expressing $E9-$F8 in hex gives: $E9-$EC $ED-$F0 $F1-$F4 $F5-$F8 +-------------+-------------+-------------+-------------+ | 07 0A 02 04 | 1D 1D 07 11 | 18 1E 0C 0D | 0B 07 1D 0F | +-------------+-------------+-------------+-------------+ +--------------------------------+ | 5.6. Transcribing the Password | +--------------------------------+ Now look at the Password Transcription Table, printed again below for your convenience, to form the password. +------------------------------------------------------+ | Password Transcription Table | +----------+----------+----------+---------------------+ | 0x00 - 2 | 0x08 - B | 0x10 - L | 0x18 - V | | 0x01 - 3 | 0x09 - C | 0x11 - M | 0x19 - W | | 0x02 - 4 | 0x0A - D | 0x12 - N | 0x1A - Y | | 0x03 - 5 | 0x0B - F | 0x13 - P | 0x1B - Z | | 0x04 - 6 | 0x0C - G | 0x14 - Q | 0x1C - + | | 0x05 - 7 | 0x0D - H | 0x15 - R | 0x1D - @ (heart) | | 0x06 - 8 | 0x0E - J | 0x16 - S | 0x1E - $ (diamond) | | 0x07 - 9 | 0x0F - K | 0x17 - T | 0x1F - & (triangle) | +----------+----------+----------+---------------------+ Doing so gives you the following password: 9D46 @@9M V$GH F9@K where @ is a heart and $ is a diamond. Try it out. It works! I'm leaving off a verification example because it's really just generation in reverse, only with a compare added after the checksum is calculated. +-----------------------------------------------------------------------------+ | 6. Closing Notes | +-----------------------------------------------------------------------------+ What is presented here is hopefully a complete and fully correct explanation of how Adventures of Lolo 3's passwords are generated and verified; however, it may be the case that I missed something or didn't explain something clearly. If I'm missing something, or if something in this document is incorrect or badly explained, feel free to email me at kingshriek at yahoo dot com. Corrections, additions, and constructive criticism will be greatly appreciated. Also, any worthy additions added to a future version of this document will be duly credited to the information provider. Thanks for reading. +-----------------------------------------------------------------------------+ | A. Appendix A - Update History | +-----------------------------------------------------------------------------+ v.0.98 (9/08/2005) - added information about the game's special, built-in passwords - corrected a counting error in the password trivia section of Appendix B - corrected various minor errors in the document v.0.97 (8/31/2005) - expanded Section 3.5 - The Random Number Generator with a more thorough discussion of its period - expanded Section 3.6 - Encrypting the Data, adding a diagram and giving a better explanation of the algorithm - added Appendix B - Password List and Miscellaneous Facts - fixed a bug in the C code's authentication routine v.0.96 (8/28/2005) - expanded Section 3.1 - Memory Locations - expanded Section 3.5 - The Random Number Generator and greatly simplified the algorithm - cleaned up C code a bit - corrected various minor errors in the document v.0.95 (8/27/2005) - added a random, authentic password generator in the C code - fixed a bug in the password generation routine in the C code - corrected various minor errors in the document initial (8/26/2005) - initial release +-----------------------------------------------------------------------------+ | B. Appendix B - Password List and Miscellaneous Facts | +-----------------------------------------------------------------------------+ Below is a list of passwords to every floor in the game, where for each one, every floor before it has been completed. All passwords use 0x00 in $E8, thus they all begin with the character '2'. The Level column is in the format of level-floor, with floor B being the boss fight (if there is one). For readability purposes, this document uses '+', '@', '$', and '&' for the non-standard characters. For your convenience, this convention will be posted multiple times on the right side of the table. +-----------------------------------------------+ | Passwords | +---------+-----------------------+-----------------------+ | Level | Lolo | Lala | +---------+-----------------------+-----------------------+ | 1-1 | 25S3 J5+K 8&7M B5YM | 25S$ N9R+ KWTH W8TK | + (cross) | 1-2 | 24JC T6M9 CV97 WSYQ | 24JM N7@C F+$P 8WQD | @ (heart) | 1-3 | 25J7 TQ@6 C236 @+D4 | 25JW P7MF FBY4 2L@7 | $ (diamond) | 1-4 | 24$K SGS8 RMR@ F5GD | 24$T PHYB &TQ7 5L4M | & (triangle) | 1-5 | 25$5 S+Z@ TMHY DPGF | 25$@ NRMD F6VN STR+ | +---------+-----------------------+-----------------------+ | 2-1 | 24MF FYS9 R@SH M6S& | 24MP JZYK $ZZS H4K4 | | 2-2 | 24LZ 3WJ$ P2WN YRQR | 23LK 8BTS WN7@ C55B | | 2-3 | 24MZ HZJD T3CZ Y$J4 | 23MK DDT8 &PRR CP$& | | 2-4 | 23LK 6BKP VJFK FP$$ | 23L& 2CK& ZJZD DPY& | | 2-5 | 23MK BDK5 $K&9 FF3W | 23M& GFKF +KF5 DZ3V | +---------+-----------------------+-----------------------+ | 3-1 | 22CN JPVK 8Z63 429M | 24C4 BN22 6T$T 7CK5 | | 3-2 | 22WQ KZ$J SM9W $KGD | 24W6 CY83 Q@@K MVMJ | | 3-3 | 227L JF&K @K52 DK6C | 2472 BD92 $PWS W4+R | | 3-4 | 22RS K5VJ 8T8M QCH4 | 24R8 C423 6Z+9 Z46L | + (cross) | 3-5 | 22HP JZYK $ZZS H3+9 | 24H5 BY42 +TJ4 NQ5W | @ (heart) | 3-6 | 22@R KP+J 8P93 YG6W | 24@7 CN63 6&@T R8@7 | $ (diamond) | 3-7 | 225M S6@7 CG5Q K@2L | 2453 L77G D2M2 +C@8 | & (triangle) | 3-8 | 22PT TGY6 @922 N98C | 24P9 MH4H $F$T 76@@ | | 3-9 | 22FN SSWR HC4B 2CR7 | 24F4 LT3V JRV$ TVCF | | 3-10 | 22ZQ T$&6 &5MQ WPDZ | 24Z6 M&9C +&H4 J&2C | +---------+-----------------------+-----------------------+ | 4-1 | 232W 53LZ 4DNB 4KGD | 252K 6BKP VJFK FTZK | | 4-2 | 2523 82+R 3J5+ HG$@ | 252& 4CTY Y4MV VCH4 | | 4-3 | 252M 236+ 5Y&F F9@K | 22L3 82+R 3J5+ GGQ& | | 4-4 | 22LC 626L 2NKJ Y@LR | 22LM 236+ 5Y&F F9RG | | 4-5 | 22LM 43+W 4$RW BT5C | 24L3 626L 2NKJ ZH$T | | 4-6 | 232C 72BN 28B& QF@$ | 232W 33B$ 58VY RPY& | +---------+-----------------------+-----------------------+ | 5-1 | 25LM 236+ 5Y&F FTZK | 22B3 S6@7 CG5Q JN4M | + (cross) | 5-2 | 25QL 2C9+ ZY&Z RPN+ | 22G2 8B&R WJ5G YRZS | @ (heart) | 5-3 | 25NM V9R+ KWTH LY3M | 22D3 82+R 3J5+ 6MN7 | $ (diamond) | 5-4 | 25SL LK8V NLTM 9R5L | 22J2 SJ$R MG56 LSF4 | & (triangle) | 5-5 | 25MM G56B 9PH4 FFT+ | 22C3 Y9HZ JH&& D9$J | | 5-B | 25TL VG82 QM&& $2KN | 22K2 $H$C T@JD CZ3V | +---------+-----------------------+-----------------------+ | 6-1 | 24F@ BNB5 6FY8 DFR@ | 23FC K5LH 8F42 F@LQ | | 6-2 | 25FM B462 6P&5 977L | 22Z3 J5+K 8&7T 26JY | | 6-3 | 24ZW C4B4 69VQ VRFN | 23ZH JPLG 892N WHJP | | 6-4 | 25ZR CN63 6&@M WMD5 | 2298 &7WB FQ+R SZ3W | | 6-5 | 249$ V6H5 BGFS 4BRK | 239D &RRF FGZP P4Q5 | | 6-B | 25PS C423 6Z+2 K+2V | 22F7 KP+J 8P97 SZ3W | +---------+-----------------------+-----------------------+ | 7-1 | 25TP V&PG N6L4 3BKF | 22K4 $RWC FV&2 7$V7 | + (cross) | 7-2 | 22J4 QS3L GRKV 326B | 22JN LT3V JRW@ B&2C | @ (heart) | 7-3 | 22K4 +R3G D6LN 7C&9 | 22KN VQ32 BQJQ GNYQ | $ (diamond) | 7-4 | 22JN NTW& KCP4 RK$K | 24J4 QS3L GRKS 4BRK | & (triangle) | 7-5 | 22KN YQW7 CB4K B98C | 24K4 +R3G D6LY 9WC5 | | 7-B | 25SP L@5L QR&G 6MN7 | 22J4 SSWR HC5F 7RQP | +---------+-----------------------+-----------------------+ | 8-1 | 25K4 $RWC FV&B 93G5 | 25K@ DNL7 6TQP T56V | | 8-2 | 2522 SJ$R MGFH SZCY | 252$ N9R+ KWZ3 TP4V | | 8-3 | 25B5 S+Z@ TMCT B9JD | 25B+ N&PG N6QF PNYR | | 8-4 | 2563 82+R 3JGY T&BF | 256& 4CTY Y4Z@ NYWQ | | 8-5 | 25G4 8LVR 3D3B H4Q5 | 25G@ YT3V JRZM MNDP | +---------+-----------------------+-----------------------+ | 9-1 | 24YJ 82QS 3N4B W77L | 24YS 53VV 4SP$ NJ+6 | + (cross) | 9-2 | 23YD 7LGN 23K8 8C73 | 23YY 3MG$ 53+2 PYMS | @ (heart) | 9-3 | 25Y4 8LVR 3F97 6@VS | 25Y+ 4WNY Y9C9 G8$6 | $ (diamond) | 9-4 | 2285 Q+5V ST&2 7$V7 | 228P L@SL QTF9 Q5&J | & (triangle) | 9-5 | 248F T+T& ZJ8C CK8B | 248P N@ZT RW3Q BPDY | | 9-B | 22Y8 722M 2VCH J$B4 | 22YS 332@ 5LYC ZC&9 | +---------+-----------------------+-----------------------+ | 10-1 | 24SD 9LQT 3&LN 9C92 | 24SN 4MVW 4ZH8 +G++ | | 10-2 | 24RD TSRT HG$V H4Q5 | 24RN NTW& KL4K D9$J | | 10-3 | 24TD KPQH 88CZ FTZK | 24TN DNV7 74+G LS53 | | 10-4 | 24QY WQH4 BW7R VWC4 | 23QJ $7RD FWMN CFKZ | | 10-5 | 24SY 3MG$ 556R SZ3W | 23SJ 82Q5 3PNN &G8V | | 10-B | 24QD &RRF FRPN MJJ5 | 24QN YQW7 CWC9 J883 | +---------+-----------------------+-----------------------+ | 11-1 | 25TN BN22 6$QK L493 | 22K5 JZYK $LJG DPY$ | + (cross) | 11-2 | 25KT CD53 SPJ8 BFHY | 22&7 KP+J 3FTP 56R$ | @ (heart) | 11-3 | 25&M B462 8YHY Z@VT | 2223 S6@7 F7R5 &26B | $ (diamond) | 11-4 | 252R MR7H K+2K $2KN | 22L9 TGY6 VR9R 2+BY | & (triangle) | 11-5 | 25LP L@5L R&VM 9R5L | 22B4 SSWR GVRZ WM42 | | 11-B | 259Q CY83 LS9W 5G$@ | 22T8 K5VJ 4&GR LJG4 | +---------+-----------------------+-----------------------+ | 12-1 | 246+ V+FZ RPY3 WMD5 | 236B &HNF @LYC ZC&9 | | 12-2 | 236B @HDJ 6DP$ NJ+6 | 236V WGD4 9D9W 5G$@ | | 12-3 | 236V ZGN9 84BZ 9RFP | 256J +7HK S9CT D9SH | | 12-4 | 2562 $H$C 7D&+ 4LYG | 256$ Y6R8 RK$L K+2V | | 12-5 | 256L VG82 V362 4LNK | 22Q2 $H$C ZHSP H8@7 | | 12-B | 246G $@PQ TP46 VR4N | 246Q Z+&+ 6STK 67SR | +---------+-----------------------+-----------------------+ | 13-1 | 23QV ZGN9 C3G5 QK+J | 25QK +JJP JQKG 8MD4 | + (cross) | 13-2 | 23+Y ZQR& V73Q BPDY | 25+G +@F5 6BS& J8J4 | @ (heart) | 13-3 | 23NW 53LR ZWM4 KD9L | 25NJ Q8H& V73Q BPDY | $ (diamond) | 13-4 | 23YZ P@TH 8$GD M$22 | 25YH QQCT P4QK L493 | & (triangle) | 13-5 | 23SV 5CP@ 2ZWM FTPG | 25SK 6BKT 5977 +VWG | | 13-6 | 23$Y 5MQ3 LJ4V &6HZ | 25$G 6VDK D3VM 7RZT | | 13-7 | 23MW &8M@ LY7C @BTJ | 25MJ 62G& Q9MN FFT+ | | 13-8 | 23WZ 5WSC FHS5 F9RG | 25WH 6LB3 &L4K D9$J | | 13-9 | 23RV PKN& G2LY 7WM6 | 25RK QGJ3 BH46 YRPR | | 13-10 | 23@Y PTRK $6C@ B&2C | 25@G Q$F@ 2ZWM FTPG | | 13-B | 23VZ FYS& G6MD KYCP | 25VH GPBT Z3Q5 P4QS | +---------+-----------------------+-----------------------+ | 14-1 | 246S W63M NDZH QZM@ | 2367 &S@G $LJG DPY$ | + (cross) | 14-2 | 236R ZTC8 RK$L K+2V | 2567 @S3C TF97 6@VS | @ (heart) | 14-3 | 256@ VTY& G2LY 7WM6 | 22QF &$RT Z7RP W3G5 | $ (diamond) | 14-4 | 22QZ Z&3W MDKD 7WM6 | 24QK +JFP 4PJS JD7M | & (triangle) | 14-5 | 24QP V&N+ GQTF 9RFP | 23Q5 $$YR KV3& L493 | +---------+-----------------------+-----------------------+ | 15-1 | 24GN VQ52 68MJ GDRT | 23G4 $RZC 9D9W 5G$@ | | 15-2 | 24JN 2LNS KG8J DF@$ | 23J4 8MYW HB&V 326B | | 15-3 | 24HN LTP+ 2TZ3 RPY& | 23H4 SSFP 4PJS JD7M | | 15-4 | 23K4 GNN9 C3GS QK+J | 23KN BP4C FCTP 36HZ | | 15-5 | 23G4 +R5H 8YHY Z@VT | 23GN VQ53 6DP$ NJ+6 | +---------+-----------------------+-----------------------+ | 16-1 | 22@F 7VWR PYMJ GDRT | 22@Z 3WWW MDKD 7WM6 | | 16-2 | 225G +NY6 CRF9 S59C | 225+ VPDD F@L4 5BTJ | | 16-3 | 22PB @D&7 7$VH 2+2W | 22PV WFKF 9S5B R9RG | | 16-4 | 22FK +4&+ 2PYM &L4B | 22F& V5KS 5F9R 4+L& | | 16-5 | 22ZD @SZR 5Z3Q DPN@ | 22ZY WTFZ 3PNN &G8V | +---------+-----------------------+-----------------------+ | 17-1 | 22TC H8@R Z@LN 7C&9 | 23TS M462 L42B 326B | + (cross) | 17-2 | 22SW 87JJ DCZM MNDP | 25S8 @2+R T&2Q 8M43 | @ (heart) | 17-3 | 22TW D9$$ GBPY M$22 | 25T8 R5+K N8W@ D&VJ | $ (diamond) | 17-4 | 24S3 56@7 @+VW 7HSQ | 25SS Y@VB @6@V W77L | & (triangle) | 17-5 | 24T3 K8HP YR&L 3&VK | 25TS N$VQ ZRPN MJJ5 | | 17-6 | 24SM 77@C &G8$ GDRT | 22JC &LDP JLJ+ GNNT | | 17-7 | 24TM C9HZ WRFT L4K4 | 22KC TPDP FMNN @G$@ | | 17-8 | 23S3 2Q53 6DP$ NJ+6 | 22JW WMD$ G2LY 7WM6 | | 17-9 | 23T3 GSPT 556R SZ3W | 22KW MND5 BM8R G$V6 | | 17-10 | 23SM 8R5H 8YHY Z@VT | 24JH @VMS PJS& J8J4 | | 17-B | 22SC 36H5 +QTZ 3Q5V | 23SS W36+ QPS5 C9HF | +---------+-----------------------+-----------------------+ Lolo 3 contains some hidden, special passwords that have different effects on the game's password screen. All of these passwords fail the game's verification check, so you will hear a buzz (signifying a password failure) after the 16th character is entered. To trigger their effects, you need to manually select 'END'. All the sound test passwords begin with a two-digit number, followed by a double zero, '00'. Each two-digit number (within a range) corresponds to a different background music or sound effect. Note: In place of the vowels A, E, I, O, and U, the US version of the game uses the characters # , (smiley) , ? , ! , * respectively. The Japanese version retains the vowels. +---------------------------------------------------------+ | Special Passwords | +---------------------------------+-----------------------+ | Effect | Password | +---------------------------------+-----------------------+ | Background Music Test | %%00 LOLO 3BGM TEST | US version | %% is a number between 00 and 23| %%00 LOLO 2BGM TEST | JP version +---------------------------------+-----------------------+ | Sound Effect Test | %%00 LOLO 3FGM TEST | US version | %% is a number between 00 and 48| %%00 LOLO 2FGM TEST | JP version +---------------------------------------------------------+ | Character Display | | | Lolo | LOLO LOOK PLEA SE@@ | | Lala | LALA LOOK PLEA SE@@ | | Snakey | @SNA KEY+ MONS TER@ | | Leaper | @LEA PER+ MONS TER@ | + (cross) | Skull | @SKA LWG+ MONS TER@ | @ (heart) | Rocky | @ROC KY++ MONS TER@ | $ (diamond) | Medusa | @MED USA+ MONS TER@ | & (triangle) | Gol | @GOL +GOL MONS TER@ | | Alma | @ALM ARK+ MONS TER@ | | Don Medusa | DONM EDUS AMON STER | | Moby | @CAP OXX+ MONS TER@ | +---------------------------------+-----------------------+ Also, here are some miscellaneous facts about the password system: The total number of passwords that can be entered from the password is screen is 40^16 = 42949672960000000000000000. Out of these, 32^16 = 1208925819614629174706176 entirely consist of valid characters (see Section 3.8 for details). The total number of valid passwords, i.e. ones that do not have a failed checksum (see Section 4.4), is 2^72 = 4722366482869645213696. Thus, if you are entering in only valid characters at random, you will have a 1/256 chance of inputting a correct password. The total number of authentic passwords, i.e. ones that you can actually receive from the game itself is 7512576. Thus, out of the valid passwords, 14673 / 9223372036854775808 (about 1.59 x 10^-13 percent) of them are ones that you can actually get through the game. If you are entering passwords (comprised of only valid characters again) at random, then you will have a 14673 / 2361183241434822606848 (about 6.21 x 10^-16 percent) chance of entering one you can get from the game. Below is the distribution given by the areas and locations of the game if authentic passwords are generated in a uniform, random manner. Area Probability ------------------------------------ Overworld (left) 88 / 4891 Overworld (right) 7805 / 14673 Underwater World 6530 / 14673 Underworld 74 / 14673 Location Probability Probability given Area --------------------------------------------------------------------- Grandpa's Tree 1 22 / 4891 1 / 4 Level 1 22 / 4891 1 / 4 Level 2 22 / 4891 1 / 4 Level 3 1366 / 14673 1 / 4 (left), 260 / 1561 (right) Level 4 1300 / 14673 260 / 1561 Level 5 1300 / 14673 260 / 1561 Level 6 1300 / 14673 260 / 1561 Level 7 1300 / 14673 260 / 1561 Level 8 5 / 14673 1 / 1561 Level 9 1306 / 14673 1 / 5 Level 10 1306 / 14673 1 / 5 Level 11 1306 / 14673 1 / 5 Level 12 1306 / 14673 1 / 5 Level 13 1306 / 14673 1 / 5 Level 14 26 / 14673 13 / 37 Level 15 21 / 14673 21 / 74 Level 16 16 / 14673 8 / 37 Level 17 11 / 14673 11 / 74 +-----------------------------------------------------------------------------+ | C. Appendix C - C code for password generator/verifier/authenticator | +-----------------------------------------------------------------------------+ Here's some C code that will compile into a console program that generates, verifies, and authenticates Lolo 3 passwords. The passwords can either be generated from values the user enters or generated randomly. The passwords that are randomly generated can either be completely random (valid, but might produce some weird effects in the game when entered) or they can be both random and authentic (can actually be generated by the game itself). The code also contains an authentication subroutine which determines if a password is authentic. I compiled the code with mingw32-gcc-3.4.2. ----------------------------- code begins here--------------------------------- #include <stdio.h> #include <stdlib.h> #include <time.h> int randomval,indexval; int data[11]; int E[10]; int F[16]; int rnd(int,int); int rndp(int,int,int); int test(int*,int*,int*); int initialize(int*,int*); int generate(); int degenerate(); int generate_authentic(); int authenticate(); int T[60] = { 0, 6, 3, 0, 2, 3, 1, 7, 4, 1, 2, 3, 2, 6, 3, 2, 2, 3, 3, 6, 3, 3, 2, 3, 4, 6, 3, 4, 2, 3, 5, 6, 3, 5, 2, 3, 6, 7, 4, 6, 2, 3, 7, 6, 3, 7, 2, 3, 8, 7, 4, 9, 4, 5, 10, 4, 1, 10, 0, 1 }; int main() { int i,k; int temp,check; int gord,sel,aorm; int level,floors,floor,location,character,auth; char password[19]; char chr[32] = "23456789BCDFGHJKLMNPQRSTVWYZ+@$&"; /* Intialize data */ initialize(data,F); srand((unsigned)time(NULL)); printf("\n"); printf("Generate or degenerate a password\n\n"); printf(" 0. Generate 1. Degenerate\n\n"); printf("Selection? "); scanf("%d",&gord); switch(gord) { case 0: printf("\n"); printf("Select the type of password generation:\n\n"); printf(" 0. Manual - Data needed is given by the user\n"); printf(" 1. Random - Generates a random but not necessarily "); printf("authentic password\n"); printf(" 2. Authentic Random - Generates a random, authentic "); printf("password\n\n"); printf("Selection? "); scanf("%d",&sel); switch(sel) { case 0 : /* Get data from user */ printf("\n"); printf("Enter which floor you are on for each level.\n\n"); printf("1-10 are floor numbers, 0 means a special or boss room,\n"); printf("15 means the level is completed.\n"); printf("If 0 is picked for a level that has no special room or\n"); printf("boss, entry to the level will be prohibited.\n\n"); for(i=0; i<9; i+=1) { level = 2*i+1; if(level == 3 || level == 13 || level == 17) { floors = 10; } else { floors = 5; } printf("Level %d (%d floors): ",level,floors); scanf("%d",&floor); if(i != 8) { level = 2*i+2; floors = 5; printf("Level %d (%d floors): ",level,floors); scanf("%d", &data[i]); } data[i] |= (floor << 4); } printf("\n"); printf("Choose a location:\n\n"); printf(" 0. Grandpa's Tree 1 1. Grandpa's Tree 2 2. Level 1\n"); printf(" 3. Level 2 4. Level 3 5. Level 4\n"); printf(" 6. Level 5 7. Level 6 8. Level 7\n"); printf(" 9. Level 8 10. Level 9 11. Level 10\n"); printf(" 12. Level 11 13. Level 12 14. Level 13\n"); printf(" 15. Level 14 16. Level 15 17. Level 16\n"); printf(" 18. Level 17\n\n"); printf("Selection? "); scanf("%d",&data[9]); printf("\n"); printf("Choose character:\n\n"); printf(" 0. Lolo (w/ Lala) 1. Lala (w/ Lolo)\n"); printf(" 2. Lolo (alone) 3. Lala (alone)\n\n"); printf("Selection? "); scanf("%d",&temp); data[10] = ((temp & 0x02) << 3) | (temp & 0x01); printf("\n"); printf("Select randomization method:\n\n"); printf(" 0. Auto randomize 1. Manual input \n\n"); printf("Selection? "); scanf("%d",&aorm); switch(aorm) { case '0': indexval = rand() % 65535; break; case 1: printf("\n"); printf("Enter random value (0-255): "); scanf("%d",&randomval); randomval = randomval % 256; indexval = 65536; break; default: break; } break; /* random generation */ case 1: for(i=0; i<11; i++) { data[i] = rand() % 256; } indexval = rand() % 65535; break; /* random, authentic generation */ case 2: generate_authentic(); indexval = rand() % 65535; break; default: break; break; } /* Generate password */ generate(); /* Display password */ printf("\n"); printf("Legend: + - Cross\n"); printf(" @ - Heart\n"); printf(" $ - Diamond\n"); printf(" & - Triangle\n\n"); printf("Password: "); for(k=0; k<16; k++) { printf("%c",chr[F[k]]); if(k % 4 == 3) { printf(" "); } } break; case 1: /* Get password from user */ for(i = 0; i<19; i++) { password[i] = (char)20; } printf("\n"); printf("Input the password in the following format "); printf("(xxxx xxxx xxxx xxxx).\n"); printf("Use uppercase letters.\n"); printf("Legend: + - Cross\n"); printf(" @ - Heart\n"); printf(" $ - Diamond\n"); printf(" & - Triangle\n\n"); printf("Password? "); scanf("%s%s%s%s",password,password+5,password+10,password+15); k = 0; for(i=0; i<19; i++) { switch(password[i]) { case '2' : F[k] = 0x00; break; case '3' : F[k] = 0x01; break; case '4' : F[k] = 0x02; break; case '5' : F[k] = 0x03; break; case '6' : F[k] = 0x04; break; case '7' : F[k] = 0x05; break; case '8' : F[k] = 0x06; break; case '9' : F[k] = 0x07; break; case 'B' : F[k] = 0x08; break; case 'C' : F[k] = 0x09; break; case 'D' : F[k] = 0x0A; break; case 'F' : F[k] = 0x0B; break; case 'G' : F[k] = 0x0C; break; case 'H' : F[k] = 0x0D; break; case 'J' : F[k] = 0x0E; break; case 'K' : F[k] = 0x0F; break; case 'L' : F[k] = 0x10; break; case 'M' : F[k] = 0x11; break; case 'N' : F[k] = 0x12; break; case 'P' : F[k] = 0x13; break; case 'Q' : F[k] = 0x14; break; case 'R' : F[k] = 0x15; break; case 'S' : F[k] = 0x16; break; case 'T' : F[k] = 0x17; break; case 'V' : F[k] = 0x18; break; case 'W' : F[k] = 0x19; break; case 'Y' : F[k] = 0x1A; break; case 'Z' : F[k] = 0x1B; break; case '+' : F[k] = 0x1C; break; case '@' : F[k] = 0x1D; break; case '$' : F[k] = 0x1E; break; case '&' : F[k] = 0x1F; break; default : k--; break; } k++; } if(k != 16) { printf("\nInvalid password\n"); return 0; } break; default: break; } /* Degenerate password */ check = degenerate(); /* Print out data stored in password */ printf("\n\n"); for(i = 0; i < 9; i++) { level = 2*i+1; floor = (data[i] & 0xF0) >> 4; if(floor == 0) { printf("Level %d - at boss or special room\n",level); } if(floor == 15) { printf("Level %d - completed\n",level); } if(floor != 0 && floor != 15) { printf("Level %d - at floor %d\n",level,floor); } if(i != 8) { level = 2*i+2; floor = data[i] & 0x0F; if(floor == 0) { printf("Level %d - at boss or special room\n",level); } if(floor == 15) { printf("Level %d - completed\n",level); } if(floor != 0 && floor != 15) { printf("Level %d - at floor %d\n",level,floor); } } } location = (data[9] & 0x1F); switch(location) { case 0x00: printf("Location - Grandpa's Tree 1"); break; case 0x01: printf("Location - Grandpa's Tree 2"); break; case 0x02: printf("Location - Level 1"); break; case 0x03: printf("Location - Level 2"); break; case 0x04: printf("Location - Level 3"); break; case 0x05: printf("Location - Level 4"); break; case 0x06: printf("Location - Level 5"); break; case 0x07: printf("Location - Level 6"); break; case 0x08: printf("Location - Level 7"); break; case 0x09: printf("Location - Level 8"); break; case 0x0A: printf("Location - Level 9"); break; case 0x0B: printf("Location - Level 10"); break; case 0x0C: printf("Location - Level 11"); break; case 0x0D: printf("Location - Level 12"); break; case 0x0E: printf("Location - Level 13"); break; case 0x0F: printf("Location - Level 14"); break; case 0x10: printf("Location - Level 15"); break; case 0x11: printf("Location - Level 16"); break; case 0x12: printf("Location - Level 17"); break; default : printf("Location - Not valid location"); break; } printf("\n"); character = (data[10] & 0x11); switch(character) { case 0x00: printf("Character - Lolo (with Lala)"); break; case 0x01: printf("Character - Lala (with Lolo)"); break; case 0x10: printf("Character - Lolo (alone)"); break; case 0x11: printf("Character - Lala (alone)"); break; default : break; } printf("\n"); /* authenticate password */ if(check == 0) { auth = authenticate(); if(auth == 0) { printf("\nAuthentic password\n"); } else { printf("\nValid but not authentic\n"); } } else { printf("\nError: checksum failure\n"); } return 0; } int generate() { int compress(); int checksum(); int rng(); int encrypt(); int unpack(); compress(); checksum(); rng(); encrypt(); unpack(); return 0; } int degenerate() { int fail; int pack(); int decrypt(); int compare(); int decompress(); int fix(); pack(); decrypt(); fail = compare(); decompress(); fix(); return fail; } /* Code 3.1 - compression subroutine */ int compress() { int A,ns,nr,i,j,k; int carryE[10]; E[7] = 0x00; for(i=0; i<60; i+=3) { /* run through data in table */ A = data[T[i]]; ns = T[i+1]; ns = 7 - ns; /* number of times to left shift the accumulator */ nr = T[i+2]; /* number of times to left shift through $DF-$E6 */ A = (A << ns) & 0xFF; /* shift data into loading position */ for(j=0; j<nr; j++) { carryE[8] = (A & 0x80) >> 7; /* carry from A into $E6 */ A = (A << 1) & 0xFF; for(k=7; k>=1; k--) { /* shift data through $DF-$E6 */ carryE[k] = (E[k] & 0x80) >> 7; /* carry from $y into $(y-1) */ E[k] = (E[k] << 1) & 0xFF; E[k] += carryE[k+1]; } E[0] = (E[0] << 1) & 0xFF; E[0] += carryE[1]; } } return 0; } /* Code 3.2 - checksum subroutine */ int checksum() { E[8] = (E[0] + E[1] + E[2] + E[3] + E[4] + E[5] + E[6] + E[7]) & 0xFF; return 0; } /* Code 3.3 - random number generator */ int rng() { int i,j,x; x = 0xFFFF; if(indexval < 65535) { for(i=0; i<indexval; i++) { for(j=0; j<11; j++) { x = (x << 1 | !((x >> 15 ^ x >> 1 ^ x) & 0x0001)) & 0xFFFF; } } E[9] = x & 0xFF; } else { E[9] = randomval; } return 0; } /* Code 3.4 - encryption subroutine */ int encrypt() { int i,carry; for(i=8; i>=0; i--) { E[i] = (E[i] + E[i+1]) & 0xFF; carry = (E[i] & 0x01) << 7; /* carry from E[i] into E[i] */ E[i] >>= 1; E[i] += carry; E[i] ^= E[i+1]; } return 0; } /* 3.5 - unpacking subroutine */ int unpack() { int A,i,j,k,carry; int carryE[10]; for(i=0; i<16; i++) { /* run through $E9-$F8 */ A = 0x00; /* clear accumulator */ for(j=0; j<5; j++) { /* load 5 bits onto a byte in $DF-$E8 */ carry = E[9] & 0x01; /* carry from $E8 into A */ carryE[0] = (E[0] & 0x01) << 7; /* carry from $DF into $E0 */ E[0] >>= 1; for(k=1; k<10; k++) { carryE[k] = (E[k] & 0x01) << 7; /* carry from $y into $(y+1) */ E[k] >>= 1; E[k] += carryE[k-1]; } A = (A << 1) & 0xFF; A += carry; } F[i] = A; } return 0; } /* Code 4.2 - packing subroutine */ int pack() { int A,carry,i,j,k; int carryE[10]; for(i=15; i>=0; i--) { /* run through $F8-$E9 */ A = F[i]; for(j=0; j<5; j++) { /* load lowest 5 bits onto $DF-$E8 */ carry = A & 0x01; /* carry A into $E8 */ A >>= 1; carryE[9] = (E[9] & 0x80) >> 7; /* carry from $E8 into $E7 */ E[9] = (E[9] << 1) & 0xFF; E[9] += carry; for(k=8; k>=0; k--) { carryE[k] = (E[k] & 0x80) >> 7; /* carry from $y into $(y-1) */ E[k] = (E[k] << 1) & 0xFF; E[k] += carryE[k+1]; } } } return 0; } /* Code 4.3 - decryption subroutine */ int decrypt() { int i,carry; for(i=0; i<=8; i++) { E[i] ^= E[i+1]; carry = (E[i] & 0x80) >> 7; /* carry from E[i] into E[i] */ E[i] = (E[i] << 1) & 0xFF; E[i] += carry; E[i] = (E[i] - E[i+1]) & 0xFF; } return 0; } /* Code 4.4 - compare subroutine */ int compare() { int checksum; checksum = (E[0] + E[1] + E[2] + E[3] + E[4] + E[5] + E[6] + E[7]) & 0xFF; if(checksum != E[8]) { /* compare to checksum */ return 1; } else { return 0; } } /* Code 4.5 - decompression subroutine */ int decompress() { int i,j,k,A,nr,ns; int carryE[10]; for(i=0; i<11; i++) { /* run through data in table */ data[i] = 0; /* clear out data */ } for(i=57; i>=0; i-=3) { /* run through data in table */ A = 0x00; /* clear accumulator */ ns = T[i+1]; ns = 7 - ns; /* number of times to right shift the accumulator */ nr = T[i+2]; /* number of times to right shift the data $DF-$E6 */ for(j=0; j<nr; j++) { carryE[0] = (E[0] & 0x01) << 7; /* carry from $DF into $E0 */ E[0] >>= 1; for(k=1; k<8; k++) { carryE[k] = (E[k] & 0x01) << 7; /* carry from $y into $(y+1) */ E[k] >>= 1; E[k] += carryE[k-1]; } A >>= 1; A += carryE[7]; } A >>= ns; A = (A | data[T[i]]); data[T[i]] = A; /* load data */ } return 0; } /* Code 4.6 - fix 0x7 to 0xF subroutine */ int fix() { int i; for(i=0; i<=51; i+=3) { if(T[i+1] == 6) { /* if level has 5 floors and is stored on higher 4 bits */ if((data[T[i]] & 0xF0) == 0x70) { data[T[i]] |= 0x80; } } if(T[i+1] == 2) { /* if level has 5 floors and is stored on lower 4 bits */ if((data[T[i]] & 0x0F) == 0x07) { data[T[i]] |= 0x08; } } } return 0; } int test(int data[11],int E[10],int F[16]) { int i; printf("\n"); printf("$0151-$0159: "); for(i=0; i<9; i++) { printf("%02X ",data[i]); } printf("\n"); printf("$014F: %02X",data[9]); printf("\n"); printf("$52: %02X",data[10]); printf("\n"); printf("$DF-$E8: "); for(i=0; i<10; i++) { printf("%02X ",E[i]); } printf("\n"); printf("$E9-$F8: "); for(i=0; i<16; i++) { printf("%02X ",F[i]); } printf("\n"); return 0; } int initialize(int data[11],int F[16]) { int i; for(i=0; i<11; i++) { data[i] = 0; } for(i=0; i<16; i++) { F[i] = 0; } return 0; } /* Generate authentic password */ int generate_authentic() { int level,temp; level = rnd(1,17); /* Levels 1 & 2 */ if(level == 1 || level == 2) { data[0] = (rndp(0,5,15) << 4) | rndp(0,5,15); if((data[0] & 0x0F) == 0x0F) { if((data[0] & 0xF0) == 0xF0) { data[1] = (rnd(0,10) << 4) | 0x01; } else { data[1] = (rnd(0,5) << 4) | 0x01; } } else { data[1] = 0x11; } data[2] = 0x11; data[3] = 0x11; data[4] = 0x11; data[5] = 0x11; data[6] = 0x11; data[7] = 0x11; data[8] = 0x10; data[9] = rndp(2,4,0); data[10] = rnd(0,1); } /* Level 3 */ if(level == 3) { temp = rnd(0,1); if(temp == 1) { data[0] = (rnd(0,5) << 4) | 0x0F; data[1] = (rnd(0,5) << 4) | 0x01; } else { data[0] = 0xFF; data[1] = (rnd(0,10) << 4) | 0x01; } data[2] = 0x11; data[3] = 0x11; data[4] = 0x11; data[5] = 0x11; data[6] = 0x11; data[7] = 0x11; data[8] = 0x10; data[9] = rndp(2,4,0); data[10] = rnd(0,1); } /* Levels 4-7 */ if(level >=4 && level <= 7) { data[0] = 0xFF; data[1] = 0xF0 | rndp(0,5,15); data[2] = (rndp(0,5,15) << 4) | rndp(0,5,15); data[3] = (rndp(0,5,15) << 4) | 0x01; data[4] = 0x11; data[5] = 0x11; data[6] = 0x11; data[7] = 0x11; data[8] = 0x10; data[9] = rndp(4,8,1); data[10] = rnd(0,1); } /* Level 8 */ if(level == 8) { data[0] = 0xFF; data[1] = 0xFF; data[2] = 0xFF; data[3] = 0xF0 | rnd(1,5); data[4] = 0x11; data[5] = 0x11; data[6] = 0x11; data[7] = 0x11; data[8] = 0x10; data[9] = rndp(4,9,1); data[10] = rnd(0,1); } /* Levels 9-12 */ if(level >= 9 && level <= 12) { data[0] = 0xFF; data[1] = 0xFF; data[2] = 0xFF; data[3] = 0xFF; data[4] = (rndp(0,5,15) << 4) | rndp(0,5,15); data[5] = (rndp(0,5,15) << 4) | rndp(0,5,15); data[6] = 0x11; data[7] = 0x11; data[8] = 0x10; data[9] = rnd(10,14); data[10] = rnd(0,1); } /* Level 13 */ if(level == 13) { data[0] = 0xFF; data[1] = 0xFF; data[2] = 0xFF; data[3] = 0xFF; data[4] = 0xFF; data[5] = 0xFF; data[6] = (rnd(0,10) << 4) | 0x01; data[7] = 0x11; data[8] = 0x10; data[9] = rnd(10,14); data[10] = rnd(0,1); } /* Level 14 */ if(level == 14) { data[0] = 0xFF; data[1] = 0xFF; data[2] = 0xFF; data[3] = 0xFF; data[4] = 0xFF; data[5] = 0xFF; data[6] = rnd(1,5); data[7] = 0x11; data[8] = 0x10; data[9] = 15; data[10] = 0x10 | rnd(0,1); } /* Level 15 */ if(level == 15) { data[0] = 0xFF; data[1] = 0xFF; data[2]= 0xFF; data[3] = 0xFF; data[4] = 0xFF; data[5] = 0xFF; data[6] = 0x0F; data[7] = (rnd(1,5) << 4) | 0x01; data[8] = 0x10; data[9] = rnd(15,16); data[10] = 0x10 | rnd(0,1); } /* Level 16 */ if(level == 16) { data[0] = 0xFF; data[1] = 0xFF; data[2] = 0xFF; data[3] = 0xFF; data[4] = 0xFF; data[5] = 0xFF; data[6] = 0x0F; data[7] = 0xF0 | rnd(1,5); data[8] = 0x10; data[9] = rnd(15,17); data[10] = 0x10 | rnd(0,1); } /* Level 17 */ if(level == 17) { data[0] = 0xFF; data[1] = 0xFF; data[2] = 0xFF; data[3] = 0xFF; data[4] = 0xFF; data[5] = 0xFF; data[6] = 0x0F; data[7] = 0xFF; data[8] = rnd(0,10) << 4; data[9] = rnd(15,18); data[10] = 0x10 | rnd(0,1); } return 0; } /* Check password for authenticity */ int authenticate() { int area,floor; /* determine area */ if(data[9] == 0 || (data[9] >= 2 && data[9] <= 4 && ((data[1] & 0xF0) != 0xF0))) { area = 0; if(data[10] != 0x00 && data[10] != 0x01) { return 36; } } else if(data[9] == 1 || (data[9] >= 4 && data[9] <= 9 && ((data[1] & 0xF0) == 0xF0))) { area = 1; if(data[10] != 0x00 && data[10] != 0x01) { return 37; } } else if(data[9] >= 10 && data[9] <= 14) { area = 2; if(data[10] != 0x00 && data[10] != 0x01) { return 38; } } else if(data[9] >= 15 && data[9] <= 18) { area = 3; if(data[10] != 0x10 && data[10] != 0x11) { return 39; } } else { return 35; } switch(area) { /* area 1 */ case 0: /* Level 1 */ floor = (data[0] & 0xF0) >> 4; if(!((floor >=0 && floor <= 5) || floor == 15)) { return 1; } /* Level 2 */ floor = data[0] & 0x0F; if(!((floor >= 0 && floor <= 5) || floor == 15)) { return 2; } /* Level 3 */ floor = (data[1] & 0xF0) >> 4; if((data[0] & 0x0F) == 0x0F) { if ((data[0] & 0xF0) == 0xF0) { if(!((floor >= 0 && floor <= 10) || floor == 15)) { return 3; } } else { if(!((floor >= 0 && floor <= 5) || floor == 15)) { return 4; } } } else { if(floor != 1) { return 5; } } /* Level 4 */ floor = data[1] & 0x0F; if(floor != 1) { return 6; } /* Levels 5-17 */ if(data[2] != 0x11 || data[3] != 0x11 || data[4] != 0x11 || data[5] != 0x11 || data[6] != 0x11 || data[7] != 0x11 || data[8] != 0x10) { return 7; } break; /* area 2 */ case 1: /* Levels 1-2 */ if(data[0] != 0xFF) { return 8; } /* Level 3 */ floor = (data[1] & 0xF0) >> 4; if(floor != 15) { return 9; } /* Level 4 */ floor = data[1] & 0x0F; if(!((floor >= 0 && floor <= 5) || floor == 15)) { return 10; } /* Level 5 */ floor = (data[2] & 0xF0) >> 4; if(!((floor >= 0 && floor <= 5) || floor == 15)) { return 11; } /* Level 6 */ floor = data[2] & 0x0F; if(!((floor >= 0 && floor <= 5) || floor == 15)) { return 12; } /* Level 7 */ floor = (data[3] & 0xF0) >> 4; if(!((floor >= 0 && floor <= 5) || floor == 15)) { return 13; } /* Level 8 */ floor = data[3] & 0x0F; if(data[1] == 0xFF && data[2] == 0xFF && (data[3] & 0xF0) == 0xF0) { if(!((floor >= 1 && floor <= 5) || floor == 15)) { return 14; } } else { if(floor != 1) { return 15; } } /* Levels 9-17 */ if(data[4] != 0x11 || data[5] != 0x11 || data[6] != 0x11 || data[7] != 0x11 || data[8] != 0x10) { return 16; } break; /* area 3 */ case 2: /* Levels 1-8 */ if(data[0] != 0xFF || data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF) { return 17; } /* Level 9 */ floor = (data[4] & 0xF0) >> 4; if(!((floor >= 0 && floor <= 5) || floor == 15)) { return 18; } /* Level 10 */ floor = data[4] & 0x0F; if(!((floor >= 0 && floor <= 5) || floor == 15)) { return 19; } /* Level 11 */ floor = (data[5] & 0xF0) >> 4; if(!((floor >= 0 && floor <= 5) || floor == 15)) { return 20; } /* Level 12 */ floor = data[5] & 0x0F; if(!((floor >= 0 && floor <= 5) || floor == 15)) { return 21; } /* Level 13 */ floor = (data[6] & 0xF0) >> 4; if(data[4] == 0xFF && data[5] == 0xFF) { if(!(floor >= 0 && floor <= 10)) { return 22; } } else { if(floor != 1) { return 23; } } /* Level 14 */ floor = data[6] & 0x0F; if(floor != 1) { return 24; } /* Levels 15-17 */ if(data[7] != 0x11 || data[8] != 0x10) { return 25; } break; /* area 4 */ case 3: /* Levels 1-12 */ if(data[0] != 0xFF || data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF || data[4] != 0xFF || data[5] != 0xFF) { return 26; } /* Level 13 */ floor = (data[6] & 0xF0) >> 4; if(floor != 0) { return 27; } /* Level 14 */ floor = data[6] & 0x0F; if(!((floor >= 1 && floor <= 5) || floor == 15)) { return 28; } /* Level 15 */ floor = (data[7] & 0xF0) >> 4; if(data[6] == 0x0F) { if(!((floor >= 1 && floor <= 5) || floor == 15)) { return 29; } } else { if(floor != 1) { return 30; } } /* Level 16 */ floor = data[7] & 0x0F; if(data[6] == 0x0F && (data[7] & 0xF0) == 0xF0) { if(!((floor >= 1 && floor <= 5) || floor == 15)) { return 31; } } else { if(floor != 1) { return 32; } } /* Level 17 */ floor = (data[8] & 0xF0) >> 4; if(data[6] == 0x0F && data[7] == 0xFF) { if(!((floor >= 1 && floor <= 10)) && floor != 0) { return 33; } } else { if(floor != 1) { return 34; } } break; default: break; } return 0; } /* Generate random integer between a and b */ int rnd(int a, int b) { return (rand() % (b - a + 1)) + a; } /* Generate random integer amongst the set {a to b, c} */ int rndp(int a, int b, int c) { int temp; temp = (rand() % (b - a + 2)) + a; if(temp == b+1) { temp = c; } return temp; } ------------------------------ code ends here --------------------------------- --eof--