-
-
Save bbayles/bc8973b3659d2035b6d82aecca53e2c7 to your computer and use it in GitHub Desktop.
Decode a Burning Rangers password
This file contains 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
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456" | |
XOR_CONSTANT = 0x9FD43B75 | |
def to_binary(text): | |
numbers = (ALPHABET.index(c) for c in text) | |
bits = "".join(f"{n:05b}" for n in numbers) | |
n = int(bits[-32:], 2) ^ XOR_CONSTANT | |
return format(n, "032b") | |
ALL_SURVIVOR_NAMES = [ | |
# Mission 1 | |
[ | |
"Curt Eisler", | |
"Richard Wells", | |
"Oliver Landon", | |
"Yasushi Nirenberg", | |
"Prince Lemmon", | |
"Paul Freeman", | |
"Danny Hackman", | |
"Frank Williams", | |
"Ahmad Khan", | |
"Arnold Leyden", | |
"Satoshi Okano", | |
"James Green", | |
"Hideaki Moriya", | |
"Takao Miyoshi", | |
"Linda Wirgman", | |
"Gena Austin", | |
"Rose Bergman", | |
"Mary Foster", | |
"Lucille Crawford", | |
"Lily Johnson", | |
"Sophie Rosay", | |
"Mira Redford", | |
"Marie Moore", | |
"Erika Pinter", | |
"Christine Fowles", | |
"Kathy Margret", | |
"Simone Darrieu", | |
"Nancy Keaton", | |
"Anne Connery", | |
"Janet Day", | |
"Emma Kensit", | |
"Hiroko Losey", | |
"Florenz Ranke", | |
"Jennifer Fox", | |
], | |
# Mission 2 | |
[ | |
"Marc Tyler", | |
"Will Jones", | |
"Mark Brando", | |
"Bob Fisher", | |
"Shinichi Higashi", | |
"Yasuhiro Watanabe", | |
"Yumy Bouwer", | |
"Janet Campbell", | |
"Scarlet Fonda", | |
"Nico Pernas", | |
"Julie Sanda", | |
"Michael Eastwood", | |
"Manabu Parton", | |
"David Weber", | |
"Martin Morton", | |
"Owen Eastwood", | |
"Bill Klein", | |
"Toru Watanuki", | |
"Yasuhiko Nagamichi", | |
"Elizabeth Klein", | |
"Bella Eastwood", | |
"Kanako Parton", | |
"Diane Tracy", | |
"Hanne Ringwald", | |
"Betty Forrest", | |
"Naomi Parton", | |
"Meg Tracy", | |
"Mira Ringwald", | |
"Ellen Kelly", | |
"Sonia Morton", | |
"Marcia Weber", | |
"Fumie Kumatani", | |
"Elliot Edwards", | |
"Claris Sinclair", | |
"Leonard Kennedy", | |
], | |
# Mission 3 | |
[ | |
"Ridley Thomson", | |
"Henry Pierce", | |
"Tyron Fage", | |
"John Sweet", | |
"Brown Epstein", | |
"Eric Howell", | |
"Elvis Douglas", | |
"Sam Murray", | |
"Tomonori Dobashi", | |
"Akio Setsumasa", | |
"Masaru Setsumaru", | |
"Naofumi Hataya", | |
"Gerardo Vanini", | |
"George Beck", | |
"Casper Wyld", | |
"David Jackson", | |
"Tim Leary", | |
"Charles Smyth", | |
"Jason Mark", | |
"Luke Scala", | |
"Joe Negrin", | |
"Takuya Matsumoto", | |
"Naoto Ohshima", | |
"Yuji Naka", | |
"Loretta Compton", | |
"Susan Gray", | |
"Grace Turner", | |
"Alicia Howard", | |
"Elena Eldon", | |
"Jill Woods", | |
"Kayo Shimizu", | |
"Matilda Kasdan", | |
"Mimi Follows", | |
"Bette Temin", | |
"Ami Shibata", | |
], | |
] | |
ALL_RESCUE_COUNTS = [ | |
# Mission 1: from 0x060484fc | |
[ | |
[2, 3, 1, 3, 3], | |
[2, 1, 2, 2, 2], | |
[4, 3, 2, 2, 2], | |
[3, 5, 6, 6, 6], | |
[1, 1, 1, 1, 1], | |
], | |
# Mission 2: from 0x06048516 | |
[ | |
[0, 1, 1, 1, 0], | |
[2, 2, 1, 2, 3], | |
[5, 5, 3, 4, 6], | |
[2, 2, 2, 2, 2], | |
[0, 0, 0, 0, 0], | |
], | |
# Mission 3: from 0x06048530 | |
[ | |
[0, 0, 0, 0, 0], | |
[5, 5, 5, 5, 5], | |
[1, 2, 2, 1, 1], | |
[0, 0, 0, 0, 0], | |
[0, 0, 0, 0, 0], | |
[0, 0, 0, 0, 0], | |
], | |
] | |
ALL_RESCUE_COUNT_ADJUSTMENTS = [ | |
# Mission 1: from 0x06048515 | |
0 - 2, | |
# Mission 2: from 0x0604852f | |
6 - 1, | |
# Mission 3: from 0x06048549 | |
0 - 1, | |
] | |
ALL_SURVIVOR_SEQUENCES = [ | |
# Mission 1: from 0x06037dbc | |
[ | |
0x00, | |
0x01, | |
0x02, | |
0x03, | |
0x04, | |
0x05, | |
0x06, | |
0x07, | |
0x08, | |
0x09, | |
0x0B, | |
0x0E, | |
0x0F, | |
0x10, | |
0x11, | |
0x12, | |
0x13, | |
0x14, | |
0x15, | |
0x16, | |
0x17, | |
0x18, | |
0x19, | |
0x1A, | |
0x1B, | |
0x1C, | |
0x1D, | |
0x1E, | |
0x1F, | |
0x20, | |
0x21, | |
], | |
# Mission 2: from 0x06037ddb | |
[ | |
0x00, | |
0x01, | |
0x02, | |
0x03, | |
0x06, | |
0x07, | |
0x08, | |
0x09, | |
0x0A, | |
0x0B, | |
0x0C, | |
0x0D, | |
0x0E, | |
0x0F, | |
0x10, | |
0x13, | |
0x14, | |
0x15, | |
0x16, | |
0x17, | |
0x18, | |
0x19, | |
0x1A, | |
0x1B, | |
0x1C, | |
0x1D, | |
0x1E, | |
0x22, | |
], | |
# Mission 3: from 0x06037df7 | |
[ | |
0x00, | |
0x01, | |
0x02, | |
0x03, | |
0x04, | |
0x05, | |
0x06, | |
0x07, | |
0x0C, | |
0x0D, | |
0x0E, | |
0x0F, | |
0x10, | |
0x11, | |
0x12, | |
0x13, | |
0x14, | |
0x18, | |
0x19, | |
0x1A, | |
0x1B, | |
0x1C, | |
0x1D, | |
0x1F, | |
0x20, | |
0x21, | |
], | |
] | |
# From 0x06037e11 | |
MISSION_3_EXTRA_SEQUENCE = [0x08, 0x09, 0x0A, 0x0B, 0x1E] | |
def interpret_password_flags(password_text): | |
# Parse the text password | |
password_bits = to_binary(password_text) | |
password_parts = [ | |
int(password_bits[24:32], 2), # 8 bits | |
int(password_bits[23:24], 2), # 1 bit flag (mission complete) | |
int(password_bits[22:23], 2), # 1 bit flag (NiGHTS data) | |
int(password_bits[21:22], 2), # 1 bit flag (Christmas NiGHTS data) | |
int(password_bits[20:21], 2), # 1 bit flag (unused) | |
int(password_bits[18:20], 2), # 2 bits flag (mission) | |
int(password_bits[15:18], 2), # 3 bits (set 1) | |
int(password_bits[12:15], 2), # 3 bits (set 2) | |
int(password_bits[9:12], 2), # 3 bits (set 3) | |
int(password_bits[6:9], 2), # 3 bits (set 4) | |
int(password_bits[3:6], 2), # 3 bits (set 5) | |
int(password_bits[0:3], 2), # 3 bits (unused) | |
] | |
# There is a standard list of survivors for each mission | |
mission_number = password_parts[5] | |
survivor_sequence = ALL_SURVIVOR_SEQUENCES[mission_number][:] | |
table_len = len(survivor_sequence) | |
# The flags add people to the standard list. | |
# Flag 1: Controls whether some types of special people get added to the list. | |
# Flag 2: Controls whether Elliot gets added. | |
# Flag 3: Controls whether Claris gets added. | |
all_selected = [] | |
special_survivor_count = 0 | |
if password_parts[1]: | |
if mission_number == 0x02: | |
table_len += 5 | |
survivor_sequence += MISSION_3_EXTRA_SEQUENCE | |
if password_parts[2] and (mission_number == 0x01): | |
all_selected.append(0x20) | |
table_len += 1 | |
special_survivor_count += 1 | |
if password_parts[3] and (mission_number == 0x01): | |
all_selected.append(0x21) | |
table_len += 1 | |
special_survivor_count += 1 | |
# Start with all the people in list | |
adjusted_table_len = table_len - special_survivor_count | |
for i in range(adjusted_table_len): | |
all_selected.append(survivor_sequence[i]) | |
# Shuffle the list. The last 8 bits of the password are used to determine whether | |
# to swap values as we iterate through it. | |
x = table_len << 0x10 | |
random_byte = password_parts[0] | |
for i in range(table_len - 1): | |
random_byte = ((random_byte * 5) + 1) & 0xFF | |
y = (x * (random_byte * 0x100 & 0xFFFF)) >> 0x20 | |
all_selected[i], all_selected[y] = all_selected[y], all_selected[i] | |
# Add in Sonic Team members based on the five set values | |
sonic_team = [] | |
if mission_number == 0: | |
if password_parts[6] == 0b001: | |
sonic_team.append(0x0A) # Okano | |
if password_parts[6] == 0b011: | |
sonic_team.append(0x0C) # Moriya | |
if password_parts[9] == 0b010: | |
sonic_team.append(0x0D) # Miyoshi | |
elif mission_number == 1: | |
if password_parts[7] == 0b100: | |
sonic_team.append(0x04) # Higashi | |
if password_parts[8] == 0b001: | |
sonic_team.append(0x05) # Watanabe | |
if password_parts[8] == 0b010: | |
sonic_team.append(0x11) # Nagamichi | |
if password_parts[9] == 0b001: | |
sonic_team.append(0x12) # Watanuki | |
if password_parts[9] == 0b011: | |
sonic_team.append(0x1F) # Kumatani | |
elif mission_number == 2: | |
if password_parts[7] == 0b011: | |
sonic_team.append(0x15) # Matsumoto | |
if password_parts[7] == 0b100: | |
sonic_team.append(0x16) # Ohshima | |
if password_parts[8] == 0b001: | |
sonic_team.append(0x17) # Naka | |
if password_parts[8] == 0b010: | |
sonic_team.append(0x22) # Shibata | |
# Compute how many survivors to make available based on the five set values | |
mission_rescue_counts = ALL_RESCUE_COUNTS[mission_number] | |
total_count = 0 | |
for i, rescues_by_set in enumerate(mission_rescue_counts, 6): | |
total_count += rescues_by_set[password_parts[i]] | |
# Add the baseline number of survivors and subtract the mandatory rescues | |
total_count += ALL_RESCUE_COUNT_ADJUSTMENTS[mission_number] | |
all_selected = all_selected[: total_count - len(sonic_team)] + sonic_team | |
mission_names = ALL_SURVIVOR_NAMES[mission_number] | |
return [mission_names[x] for x in all_selected] | |
if __name__ == "__main__": | |
from argparse import ArgumentParser | |
parser = ArgumentParser(description="Decode a Burning Rangers password") | |
parser.add_argument("password", type=str) | |
args = parser.parse_args() | |
for name in interpret_password_flags(args.password): | |
print(name) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment