Skip to content

Instantly share code, notes, and snippets.

@bbayles
Created April 20, 2023 23:38
Show Gist options
  • Save bbayles/bc8973b3659d2035b6d82aecca53e2c7 to your computer and use it in GitHub Desktop.
Save bbayles/bc8973b3659d2035b6d82aecca53e2c7 to your computer and use it in GitHub Desktop.
Decode a Burning Rangers password
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