
Welcome to the page 2 of this article. Whilst I have kept Part 1 as short and simple to understand as possible, here we once again are doing a dive deep into the code to uncover hidden gems of 68000 assembly optimisations as well as a bug or two.
Please note that all code examples below as well as ROM and RAM offsets will be referring to the 1994 US version of Ecco 2. Keep this in mind when trying things out yourself!
Rom data is as follows:
| Version: | GM MK-1553 -00 |
| CRC32: | ccb21f98 |
| MD5: | 3b28a4ddddb6ff6ea3bfb3c8b117522a |
Code examples are going to be in C with occasional 68000 assembly where appropriate. All constant and variable names are of my own since disassembled code would obviously not have that kind of information. Unless otherwise noted, the listings are going to match the disassembled program as close as possible to illustrate the control flow. Full source code is available in the appendix for the article.
Before we go in describing exactly how a level password is generated I would like to pause for a moment to describe where exactly the data for that is coming from as well as point out a few oddities about it. Please feel free to skip to the next section if you’re not interested.
Passwords are essentially a snapshot of game’s current status and as it was shown previously the game packs data from a few global status variables into a single one to be stored. Those variables are as follows:
| Name | Size | RAM location | Function |
|---|---|---|---|
| gAsteriteGlobesLeft gAsteriteGlobesRight |
4 bytes | 0xFFD434 0xFFD438 |
Asterite’s Globes |
| gTimeElapsed | 2 bytes | 0xFFD4AC | Elapsed time |
| gSkillPoints | 1 byte | 0xFFA7BC | Hard Flag and Difficulty Points |
| gEasyFlags | 1 byte | 0xFFA7BD | Easy Flags |
| gCheatEn | 1 byte | 0xFFA7BE | Cheat Mode Flag |
| gStageID | 1 byte | 0xFFA7CF | Current Stage ID |
Everything is straightforward here, save for the unusual way Asterite’s internal state is represented: note how there are two 32-bit variables, one for each helix. On top of that the game also treats them as bit fields, each bit corresponding to a globe present or missing. This is most likely a leftover from the previous game where Asterite could have “holes” in him with globes missing and by directly manipulating those variables in memory it’s possible to make him asymmetrical like that in Tides of Time as well when his stage is loaded as shown on the picture to the left.
Curiously, this is never actually used in the game and the function responsible for populating the globes (ROM offset 0xB0BD8) will construct one helix and copy that into the second variable. Similarly, subroutine that’s called to count the current number of globes (0xB0BA4) shown here reconstucted in C will indicate an error if there is a mismatch:
int countAsteriteGlobePairs(void) {
int retval = -1;
uint32_t MASK = 0x80000000;
int16_t globeCount = 0;
if (gAsteriteGlobesLeft == gAsteriteGlobesRight) {
for (uint16_t i = 32; i > 0; i--) {
if ((gAsteriteGlobesLeft & MASK) != 0) {
globeCount++;
}
MASK = MASK >> 1;
}
retval = globeCount;
}
return retval;
}To summarise: when password is generated the game counts the globes one by one and stores the value inside the password and when one is entered the extracted count is used to populate him back. This is not very efficient but definitely not time critical. More details on this are available in this topic on our Message Board.
The game timer is run off the 16-bit global frames counter gFramesCount stored at RAM address of 0xFFA7C4 which is incremented every frame and allowed to roll over. There are few taps from that to tell the engine, for example, whether current frame is odd or even. The one we are interested in is located in ROM at 0x86462 and looks as follows:
move.w (gFramesCount).w,D0w ; Load global frames counter into D0
andi.w #0x3ff,D0w ; D0 &= 0x3FF
bne.b .noIncrementTime ; Branch and skip next line if not zero
addq.w #0x1,(gTimeElapsed).w ; Increment Elapsed Time
.noIncrementTime
; Continue execution
Or if translated into C:
if ((gFramesCount & 0x3ff) == 0) {
gTimeElapsed = gTimeElapsed + 1;
}You can see how it’s a simple AND against 0x3FF to increment the Time Elapsed every 1024 frames rendered, which on an NTSC system gives us a theoretical minimum resolution of 17 seconds.

Curiously, not all levels use the subroutine this code is from, 3D stages being the most notable example. The frames counter will still run, however the elapsed time will not budge. The counter is also stopped when the game is paused and during message screens.
There is another catch with how this data is stored within the password. If you remember the block diagram from the previous chapter, you may recall there are only 15 bits available to store that value:

Novotrade are doing things in an interesting way here again: instead of truncating the most significant bit the game discards the least significant one when generating a new password. This allows for values up to 65535, however only even numbers will be available, further limiting timer’s resolution to 34 seconds!
Variables that I like to call Easy Flags and Skill Points store information related to the current difficulty settings:

The way those work has already been described in the previous article, so here I am only going to note that most important here is the Easy Flag. The game actively tracks its state on every frame and will switch to Hard Mode as soon as that has changed to zero. You can try manipulating this memory location yourself and note how Ecco’s life and air bar change scale slightly the moment it has been altered. Levels would still need to be reloaded for the change to take full effect.
As mentioned previously, only 4 bits are reserved to store the deaths counter in the password and there is an explicit check to cap that value at 15. Since that memory location is also shared with Dynamic Difficulty points which can go up to 31, this means a Normal saved game would be affected by this as well and will load with no more than 15 points. That could be another oversight indicating dynamic difficulty was added much later during the development.
We’ve established all the data that’s required to be packed into the password. Let us now see how all of it is actually done in the code. The function that is responsible for this is located at the ROM offset of 0xD1ABE.
The first thing the game does is fetch the global variables and extract required data from them. During this stage it’s also checked whether global Cheat Mode flag is set in which case both local flags of Force Easy and Force Hard will be raised to indicate this.
// Calculate Asterite’s Globe pairs
uint8_t globesCount = countAsteriteGlobePairs();
// Fetch deaths/points, cap at 15
uint8_t skillPoints = gSkillPoints & 0x7F;
if (skillPoints > 15) skillPoints = 15;
// Fetch FE and FH flags
uint8_t forceEasy = gEasyFlags >> 7;
if (gCheatEn != 0) forceEasy = 1;
uint8_t forceHard = gSkillPoints >> 7;
if (gCheatEn != 0) forceHard = 1;
Packing everything into a single 32-bit variable according to the block diagram above is then a trivial process: just a bunch of bit-shifting and masking to do this.
uint32_t combinedPassword = (
((gTimeElapsed & 0xFFFE ) << 0x10) |
(forceHard << 0x10) |
(forceEasy << 0x0F) |
(skillPoints << 0x0F) |
((gStageID & 0x3F) << 0x05) |
(globesCount & 0x1F)
);
Note the 0xFFFE mask used for the elapsed time to truncate this data to 15 bits as mentioned as well as Force Hard flag using the same offset to hijack the least significant bit we have just vacated.

What follows next is the key difference between how Ecco 1 and 2 handle their passwords. The packed data is first fed through a subroutine (ROM 0xD1A1A) that obfuscates it by flipping various bits around. The algorithm itself resembles bit mirroring but does not quite function like that. The process is done in reverse when decoding a password.
uint32_t scramblePassword(uint32_t rawPassword) {
uint32_t hmask = 0x80000000;
uint32_t lmask = 0x40000000;
uint32_t key = 0x00000001;
uint32_t result = 0;
for (int i = 16; i > 0; i--) {
if ((rawPassword & hmask) != 0) {
result |= key;
}
if ((rawPassword & lmask) != 0) {
result |= hmask;
}
hmask = hmask >> 2;
lmask = lmask >> 2;
key = key << 2;
}
return result;
}The now obfuscated data is then additionally salted with a preset key using a simple XOR operation.
uint32_t encodedPassword = scramblePassword(combinedPassword);
encodedPassword ^= 0x15B63C7A;In Ecco 1 that was the only step to obfuscate the password, and every version of the game would additionally use a different secret where a password from a Mega Drive version would not work in Mega CD and so. Unlike those, Tides of Time passwords will work across all the board because they all share the same key.
Finally, we arrive at the function’s core where the obfuscated and salted password data must now be turned into letters to be presented to the player. Consider below an example password for Tides of Time that starts you at the beginning of the game in Normal mode, only here we are also showing the internal representation of those letters:

There are two key things to note here:
You can see how presenting the password as letters is merely a convenience feature for the player — this overall approach is exactly the same in both games. Tides of Time uses a different letter position for the checksum and does an extra step to obfuscate it, however. We’ll get there in a moment.
Now that you know that letters actually represent numbers, you may already have a good idea what is going to happen next. At this point I am just going to let the reconstructed C code speak for itself:
char curChar; // Current character being constructed
uint8_t checksum = 0; // Password checksum
// Destination string for the password
char dstString[8] = { 0 };
for (int i = 0; i < 8; i++) {
if (i == 3) i++; // Skip CRC character
// Extract character from the password
curChar = encodedPassword % 26;
dstString[i] = curChar;
// Add current character code to password checksum
checksum += curChar;
// Prepare password for next iteration
encodedPassword /= 26;
}
// Turn checksum into a character code
checksum = checksum % 26;
// Obfuscate checksum and insert it into the password
dstString[3] = encodeLUT[checksum];
// Terminate the string
dstString[8] = '\0';
As you may have already guessed, all the game is doing here is progressively divide the password data by 26 seven times. At every iteration, the remainder is set aside to be used as a letter’s character code, whilst the quotient is sent back for another loop. Note how the fourth position in the target array is deliberately skipped as that will be used to store the checksum that is also being computed here.
Once the loop is done, the checksum is divided by 26 to turn it into a character code. Unlike in the first Ecco game, however, a substitution table stored at ROM offset of 0x6E08 is used to obfuscate it:
uint8_t encodeLUT[] = {
0x16, 0x08, 0x01, 0x0F, 0x02, 0x07, 0x14, 0x09,
0x0D, 0x19, 0x17, 0x0A, 0x00, 0x0B, 0x04, 0x0C,
0x06, 0x0E, 0x05, 0x10, 0x13, 0x11, 0x03, 0x15,
0x12, 0x18
};This is yet another measure that makes passwords harder to brute force. In the example above, the checksum for that password would be 80 = (22+6+18+12+16+6+0), giving us a remainder of 2, which in Ecco 1 would become a letter C. In Tides of Time, however, the table lookup will result in 1, corresponding to B.
The finished password is then put through another loop that simply adds 65 to every element – the ASCII code for a capital A, which automatically turns the array into a valid string to be passed to the text display engine and shown in a level’s password screen:
for (int i = 0; i < 8; i++) {
curChar = dstString[i] + 'A';
dstString[i] = curChar;
}To sum this up, an Ecco 2 level password is generated as follows:
Before we begin decoding passwords we need to make a short detour. I have briefly mentioned in Chapter 1 how at some point Ecco 1 has incorporated a special EASY---- password to enable an easier gameplay experience and how Tides of Time has inherited the very same check. Looking at the way this is actually coded in the game suggests that programmers likely asked whether It’s going to be the only exception to check for and once they knew that’s the case they simply did the following inside the subroutine that drives the password entry screen:
int retval = -1; // Return value
if (
inputPassword[0] == 'E' &&
inputPassword[1] == 'A' &&
inputPassword[2] == 'S' &&
inputPassword[3] == 'Y'
) {
// Set Force Easy mode, return Home Bay stage ID
gEasyFlags |= 0x80; // Set Force Easy Mode
retval = 14; // Stage ID for Home Bay
} else {
// Check the password the normal way
retval = passwordChecker(inputPassword);
}
return retval;
As you can see, this is a literal hard-coded check to see if first four letters of the password spell EASY before it's even sent to the decoder. In case that is true the password screen just sets the Forced Easy flag and returns stage ID for Home Bay, effectively starting a new Easy game. This was likely used during development as a quick way to access Easy mode until a proper selection screen was added in the release, making this section of the code redundant but clearly forgotten about.
With this interesting historical artifact out of the way it’s now time to go about…
Turning a packed variable into a bunch of letters is easy: just divide it by 26 seven times, setting the remainder aside as character codes. It’s getting that data back from a password supplied by the player that I personally had a hard time wrapping my head around when I only started this. Just how does one reverse a modulo operation when we only have a remainder? Surely the number of possible combinations would quickly get out of hand!
Once again Novotrade have a trick up their sleeve here. They know exactly how many times source data was divided by 26 and they are also aware the final iteration has reduced it to the point there was nothing left. As you will shortly see, this is enough to reverse what was done and rebuild the data.
As you remember, a finalised password was turned into a string by adding 65 to every character code. The password entry screen automatically does the opposite, subtracting that number from all the symbols, before passing the data to the decoder. We are going to reuse our sample password from the beginning, which should give us 0x4B2BEDA when decoded:

The very first thing the game does when a password has been submitted by the player is verify its checksum.
uint8_t charCode;
uint16_t computedChecksum = 0; // CRC as computed for given password
uint16_t givenChecksum = 0; // CRC supplied in the password
// Source password we’re processing
char srcString[] = {22,6,18,1,12,16,6,0};
/* Build the password checksum before everything else */
for (int i = 0; i < 8; i++) {
if (i == 3) i++; // Skip 4th character
charCode = srcString[i];
computedChecksum += charCode;
}
// Turn computed checksum into a character code
computedChecksum = computedChecksum % 26;
// Look up the value for the given checksum
charCode = srcString[3];
givenChecksum = decodeLUT[charCode];
Since the checksum character was obfuscated earlier using a substitution table, another lookup is performed to restore it using a second table located at ROM offset of 0x6E22. Invalid passwords are discarded at this stage.
uint8_t decodeLUT[] = {
0x0C, 0x02, 0x04, 0x16, 0x0E, 0x12, 0x10, 0x05,
0x01, 0x07, 0x0B, 0x0D, 0x0F, 0x08, 0x11, 0x03,
0x13, 0x15, 0x18, 0x14, 0x06, 0x17, 0x00, 0x0A,
0x19, 0x09
};
And now it’s time for our reverse modulo operation! It is not immediately obvious what is going on when reading this procedure in 68000 disassembly which made reverse engineering that section a bit more tricky, however once past that, it turns out the algorithm is actually quite simple. Once again I think it best to let you see the reconstructed C code first, then I am going add my remarks:
uint32_t multiplier = 1;
uint32_t decodedPass = 0;
if (computedChecksum == givenChecksum) {
// Rebuild scrambled password from character codes
for (int i = 0; i < 8; i++) {
if (i == 3) i++; // Skip CRC character
charCode = srcString[i];
decodedPass += (charCode * multiplier);
multiplier *= 26;
}
}I will admit, I was expecting something much more complicated when I started this. We have the same number of iterations and we are even going through the source password in the same order we’ve encoded it! On every iteration the extracted character index is essentially multiplied by 26i and resulting product summed together.
In the actual program you can note how the multiplier is initialised to 1 before the loop starts and in the end of every iteration that variable is then multiplied by 26 to the same effect. Now, here is how this multiplication by 26 actually looks in assembly if we focus on the multipiler only that is currently stored in register D4:
moveq #0x1,D4 ; D4 = 1, the multiplier
moveq #0x0,D5 ; D5 = 0, loop counter
.myLoop
; D4 = D4 * 26
add.l D4,D4 ; D4 += D4
move.l D4,D0 ; D0 = D4
lsl.l #0x2,D4 ; D4 = D4 << 2 [D4 *= 4]
add.l D4,D0 ; D0 += D4
add.l D4,D4 ; D4 += D4
add.l D0,D4 ; D4 += D0
; Increment loop counter and check
addq.b #0x1,D5b ; D5++
cmpi.b #0x7,D5b ; D5 == 7?
ble.b .myLoop ; Loop if less or equal
Not a single MUL instruction in sight! To me this suggests the password related functions were most likely written in C and then compiled since they are not time critical in the slightest and I have had experience with compilers optimising arithmetical operations with constants in a very similar way. Another possible optimisation a human could have done here is ditch multiplication altogether and replace it with a lookup table, given how it’s literally 26i every time, and save even more CPU cycles at the cost of some extra 28 bytes of ROM space.
With the most difficult part done everything else is straightforward. The now reconstructed password is XORed with the same secret key of 0x15B63C7A and unscrambled via bit flipping.
uint32_t decodedPass = unscramblePassword(decodedPass ^ 0x15B63C7A);Source code for the descramble function is very similar to one used to scramble it in the first place, therefore it has been placed in the Appendix to save space. At any rate, flipping bits around is boring: let us now get to unpacking the password data!
We have now successfully converted the supplied password back into a packed data set, descrambled it and finally it is time to restore that data back into the game. If you would like to follow the code along, the sample password above after it has been un-salted and descrambled is 0x61D8:

To recap, we have several GLOBAL variables to populate as follows:
// Asterite's globes. Used as bitfield
uint32_t gAsteriteGlobesLeft, gAsteriteGlobesRight;
uint16_t gGameTimer; // Frames rendered / 1024
uint8_t gSkillPoints; // Deaths or skill points plus Force Hard flag
uint8_t gEasyFlags; // Stores Force Easy and Easy Mode flags
uint8_t gStageID; // Current Stage ID
uint8_t gCheatEn; // Cheats Enabled flag
The subroutine responsible for checking the password also creates a few LOCAL variables the data being unpacked is stored into. This will be very important shortly!
uint8_t globePairs, stageID, skillPoints, forcedEasy, forcedHard;Once again it’s the good old song of doing some bit-shifts and masking:
// Extract data from password into temp variables
globePairs = decodedPass & 0x1F;
stageID = (decodedPass >> 5) & 0x3F;
skillPoints = (decodedPass >> 11) & 0x0F;
forcedEasy = (decodedPass >> 15) & 1;
forcedHard = (decodedPass >> 16) & 1;
There is no separate variable to temporarily store the elapsed time and location containing the decoded password is used for this instead (which makes even more sense when viewed in assembly). Next, the stage ID is verified to be below 49. As you recall, cinematics and even the Epilogue are technically considered game levels just as well and this way we are prevented from accessing them directly.
if (stageID < 49) {
// Continue processing the password
}
// Invalid stage ID, return error
return -1;
Having passed the Stage ID validation, we now arrive to what is probably the most interesting section of this subroutine. You may remember from Page 1 of this article how the game checks if cheat mode has been used and a sound effect is played back to indicate this. After studying the code responsible for this, I have good reasons to believe developers were also intending to force such passwords into a permanent Hard Mode! However there does appear to be a logical error in their code that prevents this from happening.
Cheat mode is indicated by having Force Easy and Force Hard flags in the password both set to 1 which is a trivial check to do and make the final decision:
// Check if Cheat Mode was enabled
if ((forcedHard == 0) || (forcedEasy == 0)) {
gCheatEn = 0; // One of the flags is zero, not cheater
} else {
// Cheater password detected
gCheatEn = 1; // Set GLOBAL cheat flag
forcedEasy = 0; // Reset LOCAL force easy flag to 0
gSkillPoints = 0; // Reset GLOBAL skill points variable
gEasyFlags = 0; // Reset GLOBAL easy flags
forcedHard = 0; // Reset LOCAL force hard flag
_playSFX(25); // Emit the Wop of Shame
}
Once the code knows the password is from a game that had the Cheater flag set, this global flag is reinstated, both LOCAL flags for Force Easy and Force Hard are set to 0. The GLOBAL variables for storing them as well as player’s deaths and points are also reset.
Had nothing else been done after this point, it would cause the game to start in what would technically be Normal mode on Hard difficulty, as all related flags have now been reset to zero, including the Easy Flag. The player would also have ZERO difficulty points, the idea clearly being to prevent dynamic difficulty functions from touching them. Once again, most likely the final goal being to clue the player cheating is bad for you.
Let us note that and see what the game does next. What follows is yet another sanity check that I like to call Asterite Globes Audit. It’s a special function at ROM offset of 0xD1BE6 that verifies supplied Stage ID and number of Asterite’s globes against the actual number of pairs he is supposed to have for it using a table located at ROM offset of (0x6E3C).
bool asteriteGlobeAudit(uint8_t stageID, uint8_t globePairs) {
// List of stage numbers to reject right away
switch (stageID) {
case 8: // Selection Scr
case 26: // Vortex arrived
case 47: // The Pod
case 48: // Tmachine
return false;
break;
}
uint8_t expectedGlobes = gAsteriteTable[stageID];
return (expectedGlobes == globePairs);
}The table has the same number of entries as there are stages in the game and is also used to populate The Asterite when a stage is loaded and you can check the Appendix for its actual content. Interestingly, the audit function also has several hard-coded stage numbers to invalidate automatically which to me looks like an absolute afterthought in an effort to additionally harden the password system.
Rather amusingly, the globe audit check is done AFTER testing the Cheater flag, meaning you would still hear the sound effect, had everything else been decoded correctly.
if (asteriteGlobeAudit(stageID, globePairs)) {
// Continue processing the password
}
// Invalid password has been supplied. Clean up.
clearAsteriteGlobes(); // Initialise Asterite
gGameTimer = 0; // Reset Elapsed Time
gSkillPoints = 0x10; // Reset Skill Points
gEasyFlags = 0; // Reset Easy Flags
Once we have passed the Globes Audit, all that is left to do now is restore the remaining global variables to their corresponding state. Given what you just saw moments ago, you might already be suspicious and certainly for a good reason!
if (forcedEasy != 0) {
// Set Forced Easy Mode
gEasyFlags = gEasyFlags | 0x80;
}
if (forcedHard == 0) {
// Just restore deaths/points counter
gSkillPoints = skillPoints;
if (skillPoints > 3) {
// Dynamic difficulty: set Easy Flag if more than 3 points
gEasyFlags = gEasyFlags | 0x01;
}
} else {
// Set Forced Hard flag along with deaths/points
gSkillPoints = skillPoints | 0x1;
}
// Restore Elapsed Time from the password, note the mask
gGameTimer = (decodedPass >> 16) & 0xFFFE;
setAsteriteGlobes(globePairs);
return 0;
And here is where the logical error that I mentioned above lies. Note how once a password has been confirmed to have Cheater flag set the code clears the GLOBAL gSkillPoints variable, yet just some moments later its contents is then restored from the LOCAL variable of skillPoints that was left untouched. This will cause the following once the game is resumed:
As I have mentioned in Part 1, there are multiple signs that dynamic difficulty was added into the game fairly late and what we have just witnessed is another demonstration of that. To me it does look like the developers had intended to lock “cheater” passwords into a permanent Hard mode, however via a combination of mixing up global and local variables and Dynamic Difficulty related functions not checking whether your points are below 3, this ended up not functioning as intended.
The topic of Cheat Mode actually brings us very neatly to the Secret Password section which is the final item left to dissect. As a reminder, the Epilogue is treated by the game just as a regular level with its own password screen and Stage ID of 63. The function responsible for showing the level title and its password is located at ROM offset 0xD1E42 and every time it is called, there is an explicit check whether Stage ID is set to 63 that triggers a separate branch of code:
// Pointer to string in ROM
char *levelTitle = NULL;
if (stageID == 63) {
if (gCheatEn == 0) {
// Not a cheater
levelTitle = "THE SECRET\nPASSWORD IS:";
}
else {
levelTitle = "TRY PLAYING\nTHE GAME\nWITHOUT THE\nCHEAT MODE";
}
}
else {
// Obtain level title the normal way
}This is really it and the message has been there all along! As the password screen is being set up further into the function there will be another check for the Cheater Flag and Stage ID of 63 where the variable storing the currently generated password is cleared to stop it from being displayed. Other than that, the Secret Password would be absolutely no different from a regular level password! It’s not entirely clear how Ecco 3 would be using this information, given how there isn’t a lot to work with there, but perhaps one day even that mystery will be solved.
The password system in Ecco 2 is an evolutionary progress over what has been used in the first part of the game. There are stricter sanity checks as well as measures to prevent them from being brute forced. Whether that helped stopping the hackers from working things out is debatable, however this has certainly helped make the passwords much more stable and prevent them from soft locking the game. On the other hand the additional functions to obfuscate that password data appear to have not been thoroughly tested and occasionally the flags inside can get corrupted, causing the game to assume the player has cheated. And even though the developers have fallen short of their goal of trying to force genuine cheaters into a permanent hard mode, at the very least never does this break the game or cause undefined behaviour.
I did my best to document the Ecco 2 password system down to the smallest detail and hope you now have a much better understanding of how it actually works. In case you still have questions or would like to discuss the material, you are welcome to do so on our Message Board and I hope to see you there.
Stay tuned for Part Three conclusion of the Under the Hood series where we are going to give the password system used in the first Ecco game the same amount of scrutiny!
Please note that listings provided in this article are not meant to give you a fully functional password generator. This has already been done by Johnny and for this you can head to our website and study the source code of the application he wrote in Javascript to implement this.
void setAsteriteGlobes(uint8_t numPairs) {
uint32_t MASK = 1;
gAsteriteGlobesLeft = 0;
for (int i = numPairs; i > 0; i--) {
gAsteriteGlobesLeft |= MASK;
MASK = MASK << 1;
}
gAsteriteGlobesRight = gAsteriteGlobesLeft;
return;
}uint32_t unscramblePassword(uint32_t rawPassword) {
uint32_t hmask = 0x80000000;
uint32_t lmask = 0x40000000;
uint32_t key = 0x00000001;
uint32_t result = 0;
for (int i = 16; i > 0; i--) {
if ((rawPassword & key) != 0) {
result |= hmask;
}
if ((rawPassword & hmask) != 0) {
result |= lmask;
}
hmask = hmask >> 2;
lmask = lmask >> 2;
key = key << 2;
}
return result;
}
uint16_t gAsteriteTable[] = {
0x01, 0x0e, 0x03, 0x01, 0x17, 0x17, 0x17,
0x17, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x18, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x18, 0x18, 0x18, 0x18,
0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x18, 0x0d, 0x18, 0x18
};