Skip to content

Instantly share code, notes, and snippets.

@Miss-Inputs
Created February 27, 2018 12:34
Show Gist options
  • Save Miss-Inputs/9fddf201a0187f62bb3aed81bb5ea91e to your computer and use it in GitHub Desktop.
Save Miss-Inputs/9fddf201a0187f62bb3aed81bb5ea91e to your computer and use it in GitHub Desktop.
Read info from DS firmware dump
#!/usr/bin/env python3
import sys
import calendar
CONSOLE_TYPES = {
0xff: 'DS',
0x20: 'DS Lite',
0x57: 'DSi (or 3DS)',
0x43: 'iQue DS',
0x63: 'iQue DS Lite'
}
COLOURS = {
1: 'Gray',
2: 'Brown',
3: 'Red',
4: 'Light Pink',
5: 'Orange',
6: 'Yellow',
7: 'Light Green',
8: 'Green',
9: 'Dark Green',
10: 'Teal',
11: 'Blue',
12: 'Dark Blue',
13: 'Dark Purple',
14: 'Purple',
15: 'Pink',
}
WIFI_STATUSES = {
0: 'Normal',
1: 'AOSS',
16: 'WPA/WPA2',
255: 'Unconfigured'
}
WPA_TYPES = {
0: 'None/WEP',
4: 'WPA-TKIP',
5: 'WPA2-TKIP',
6: 'WPA-AES',
7: 'WPA2-AES',
}
LANGUAGES = {
0: 'Japanese',
1: 'English',
2: 'French',
3: 'German',
4: 'Italian',
5: 'Spanish',
6: 'Chinese',
7: 'Reserved',
}
def format_ip_address(b):
#Apparently I can't just use str.join for this thanks I hate it
return '{0}.{1}.{2}.{3}'.format(*b)
def is_null_ip_address(b):
return b[0] == 0 and b[1] == 0 and b[2] == 0 and b[3] == 0
def read_wifi_config(f, offset, slot_number):
f.seek(offset + 0xe7)
status = f.read(1)[0]
if status == 0xff:
return
print('Wifi %d status: %s' % (slot_number, WIFI_STATUSES.get(status, 'Unknown 0x{0:02X}'.format(status))))
f.seek(offset + 0x40)
ssid = f.read(32).decode('ascii', errors='backslashreplace').rstrip('\0')
print('Wifi %d SSID: %s' % (slot_number, ssid))
ssid_aoss = f.read(32).decode('ascii', errors='backslashreplace').rstrip('\0')
print('Wifi %d SSID for AOSS: %s' % (slot_number, ssid_aoss))
f.seek(offset + 0xe6)
wep_mode = f.read(1)[0]
#0: None, 1 = 5 hex, 2 = 13 hex, 3 = 16 hex, 5 = 5 ascii, 6 = 13 byte, 7 = 16 byte
print('Wifi %d WEP mode: %d' % (slot_number, wep_mode))
f.seek(offset + 0xea)
#DSi only
mtu = f.read(1)[0] | (f.read(1)[0] << 8)
print('Wifi %d MTU: %d' % (slot_number, mtu))
f.seek(offset + 0xc0)
ip_address = f.read(4)
if is_null_ip_address(ip_address):
print('Wifi %d IP address: DHCP' % slot_number)
else:
print('Wifi %d IP address: %s' % (slot_number, format_ip_address(ip_address)))
gateway = f.read(4)
print('Wifi %d gateway: %s' % (slot_number, format_ip_address(gateway)))
primary_dns = f.read(4)
print('Wifi %d primary: %s' % (slot_number, format_ip_address(primary_dns)))
secondary_dns = f.read(4)
print('Wifi %d secondary: %s' % (slot_number, format_ip_address(secondary_dns)))
subnet_mask = f.read(1)[0]
print('Wifi %d subnet mask: /%d' % (slot_number, subnet_mask))
if wep_mode > 0:
f.seek(offset + 0x80)
wep_keys = [None] * 4
for i in range(4):
wep_keys[i] = f.read(16)
if wep_mode == 5:
wep_keys[i] = wep_keys[i].decode('ascii', errors='backslashreplace')[:5]
if wep_mode == 6:
wep_keys[i] = wep_keys[i].decode('ascii', errors='backslashreplace')[:13]
if wep_mode == 7:
wep_keys[i] = wep_keys[i].decode('ascii', errors='backslashreplace')[:16]
#TODO Format properly when WEP mode is hex
print('Wifi %d WEP keys: %s' % (slot_number, list(wep_keys)))
def read_dsi_wifi_config(f, offset, slot_number):
f.seek(offset + 0xe7)
status = f.read(1)[0]
if status == 0xff:
return
print('Wifi %d status: %s' % (slot_number, WIFI_STATUSES.get(status, 'Unknown 0x{0:02X}'.format(status))))
ssid_length = f.read(1)[0]
f.seek(offset + 0x40)
ssid = f.read(ssid_length).decode('ascii', errors='backslashreplace')
print('Wifi %d SSID: %s' % (slot_number, ssid))
f.seek(offset + 0xe6)
wep_type = f.read(1)[0]
if wep_type != 0:
print('Wifi %d WEP type: %s' % (slot_number, wep_type))
f.seek(offset + 0x182)
proxy_enabled = f.read(1)[0] > 0
if proxy_enabled:
proxy_uses_auth = f.read(1)[0] > 0
proxy_name = f.read(100).decode('ascii', errors='backslashreplace').rstrip('\0')
print('Wifi %d proxy name: %s' % (slot_number, proxy_name))
proxy_port = f.read(1)[0] | (f.read(1)[0] << 8)
print('Wifi %d proxy port: %s' % (slot_number, proxy_port))
if proxy_uses_auth:
f.seek(offset)
proxy_auth_username = f.read(32).decode('ascii', errors='backslashreplace').rstrip('\0')
proxy_auth_password = f.read(32).decode('ascii', errors='backslashreplace').rstrip('\0')
print('Wifi %d proxy authentication: username %s password %s' % (slot_number, proxy_auth_username, proxy_auth_password))
f.seek(offset + 0xc0)
ip_address = f.read(4)
if is_null_ip_address(ip_address):
print('Wifi %d IP address: DHCP' % slot_number)
else:
print('Wifi %d IP address: %s' % (slot_number, format_ip_address(ip_address)))
gateway = f.read(4)
print('Wifi %d gateway: %s' % (slot_number, format_ip_address(gateway)))
primary_dns = f.read(4)
print('Wifi %d primary: %s' % (slot_number, format_ip_address(primary_dns)))
secondary_dns = f.read(4)
print('Wifi %d secondary: %s' % (slot_number, format_ip_address(secondary_dns)))
subnet_mask = f.read(1)[0]
print('Wifi %d subnet mask: /%d' % (slot_number, subnet_mask))
if status == 16:
f.seek(offset + 0x120)
wpa_key = f.read(64).decode('ascii', errors='backslashreplace').rstrip('\0')
#GBATEK says this is only 16 bytes, but that is clearly wrong as WPA keys can be longer than that
print('Wifi %d WPA/WPA2 key: %s' % (slot_number, wpa_key))
f.seek(offset + 0x181)
wpa_type = f.read(1)[0]
print('Wifi %d WPA type: %s' % (slot_number, WPA_TYPES.get(wpa_type, 'Unknown 0x{0:02X}'.format(wpa_type))))
with open(sys.argv[1], 'rb') as f:
f.seek(8)
firmware_id = f.read(4)
print('Firmware ID: %s' % list(firmware_id))
f.seek(0x18)
firmware_version = f.read(5)
#BCD minute hour day month year
#TODO Convert this
#TODO Can we get the fat DS firmware version from this?
print('Firmware build date: %s' % list(firmware_version))
console_type = f.read(1)[0]
print('Console type: %s' % CONSOLE_TYPES.get(console_type, 'Unknown 0x{0:02X}'.format(console_type)))
f.seek(32)
user_settings_offset = f.read(2)
offset = ((user_settings_offset[1] << 8) | user_settings_offset[0]) * 8
f.seek(offset) #I guess?
#It's always 5, unless something goes wrong
user_settings_version = f.read(2)
print('User settings version: %s' % list(user_settings_version))
favourite_colour = f.read(1)[0]
print('Favourite colour: %s' % COLOURS.get(favourite_colour, 'Unknown 0x{0:02X}'.format(favourite_colour)))
birthday_month = f.read(1)[0]
birthday_year = f.read(1)[0]
print('Birthday: {0} {1}'.format(calendar.month_name[birthday_month], birthday_year))
unused = f.read(1)[0]
print('Secret unused user setting: %s' % unused)
name = f.read(20).decode('utf-16le', errors='backslashreplace')
name_length = f.read(1)[0] | (f.read(1)[0] << 8)
print('Name: %s' % name[:name_length])
message = f.read(52).decode('utf-16le', errors='backslashreplace')
message_length = f.read(1)[0] | (f.read(1)[0] << 8)
print('Message: %s' % message[:message_length])
alarm_hour = f.read(1)[0]
alarm_minute = f.read(1)[0]
print('Alarm time: {0}:{1}'.format(alarm_hour, alarm_minute))
#TODO Touch calibration and whatnot
f.seek(offset + 0x64)
flags = f.read(2)
language = flags[0] & 7
print('Language: %s' % LANGUAGES.get(language))
gba_screen = flags[0] & 8
print('GBA screen: %s' % 'Upper' if gba_screen == 0 else 'Lower')
backlight_level = (flags[0] & 48) >> 4 #0 if not DS Lite
print('Backlight level: %d' % backlight_level)
#TODO other flags (cbf tbh m8)
f.seek(0x2f)
wifi_version = f.read(1)[0]
print('Wifi version: %d' % wifi_version)
read_wifi_config(f, offset - 0x400, 1)
read_wifi_config(f, offset - 0x300, 2)
read_wifi_config(f, offset - 0x200, 3)
if console_type == 0x57:
read_dsi_wifi_config(f, offset - 0xa00, 4)
read_dsi_wifi_config(f, offset - 0x800, 5)
read_dsi_wifi_config(f, offset - 0x600, 6)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment