-
-
Save bbayles/f66d815e76ddd4988e9e4c6809bcfbaf to your computer and use it in GitHub Desktop.
The Crow: City of Angels for Saturn - generate all level passwords
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| LETTER_MAP = {"A": 2, "B": 3, "X": 0, "Y": 1} | |
| def rotate_right(value, positions, bit_width=32): | |
| # This is the function at 06031e3c (NTSC-U Saturn verison) | |
| mask = (1 << bit_width) - 1 | |
| value &= mask | |
| positions %= bit_width # Handle positions > bit_width | |
| return ((value >> positions) | (value << (bit_width - positions))) & mask | |
| def check_password_accumulator(acc): | |
| # Adapted from Ghidra's decompilation of the functions at 06031718 | |
| # and 06031eec (NTSC-U Saturn version) | |
| # Decrypt using fixed key | |
| acc = acc ^ 0xA5A5A | |
| # Extract low 4 bits | |
| uVar3 = acc & 0xF | |
| # Extract high 14 bits and XOR with low 4 bits, keep low 4 bits | |
| starting_stage = (uVar3 ^ (acc >> 0xE)) & 0xF | |
| # Extract bits 4-13: (acc & 0x3ff0) >> 4 | |
| # Extract bits 0-1, shift to bits 8-9: (acc & 3) << 8 | |
| # Low 4 bits shifted to bits 4-7: uVar3 << 4 | |
| # Low 4 bits in bits 0-3: uVar3 | |
| # XOR the extracted middle bits with the constructed value | |
| rotate_input = ((acc & 0x3FF0) >> 4) ^ ((acc & 3) << 8 | uVar3 << 4 | uVar3) | |
| # Rotate by the calculated amount | |
| uVar1 = rotate_right(rotate_input, starting_stage, 10) | |
| # Extract components from rotated result | |
| uVar3 = (uVar1 & 0xC) >> 2 # Bits 2-3 | |
| # Validation check: sum of components should equal original high bits | |
| if ( | |
| starting_stage == 0 | |
| or ((uVar1 >> 4) + uVar3 + (uVar1 & 3) + starting_stage) & 0x3F != acc >> 0xE | |
| ): | |
| return () | |
| # Store extracted components globally | |
| sVar2 = uVar1 >> 4 | |
| lives = uVar3 # Bits 2-3 of rotated value | |
| difficulty_level = uVar1 & 3 # Bits 0-1 of rotated value | |
| # Calculate some derived value | |
| if sVar2 == 0: | |
| starting_health = 2 | |
| else: | |
| starting_health = sVar2 * 2 + 1 | |
| return (starting_stage, lives, difficulty_level, starting_health) | |
| def check_password(password_letters): | |
| if len(password_letters) != 10: | |
| return () | |
| accumulator = 0 | |
| for i, letter in enumerate(password_letters): | |
| mapped_letter = LETTER_MAP.get(letter, -1) & 0xFFFFFFFF | |
| accumulator |= (mapped_letter << 2 * i) & 0xFFFFFFFF | |
| return check_password_accumulator(accumulator) | |
| if __name__ == "__main__": | |
| from itertools import product | |
| seen = set() | |
| for prod in product("ABXY", repeat=10): | |
| password_letters = "".join(prod) | |
| params = check_password(password_letters) | |
| if not params: | |
| continue | |
| starting_stage, lives, difficulty_level, starting_health = params | |
| if (lives == 3) and (starting_health == 0x7F) and (1 <= difficulty_level <= 3): | |
| if (starting_stage, difficulty_level) not in seen: | |
| seen.add((starting_stage, difficulty_level)) | |
| print(password_letters, *params, sep="\t") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment