Created
October 29, 2023 22:12
-
-
Save leo60228/2b5d69398d424c63a2b0af205b94b43c to your computer and use it in GitHub Desktop.
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
#!/usr/bin/python3 | |
import os, platform, time, shutil, binascii, sys, fs, codecs | |
VERSION = "v1.1" | |
def prgood(content): | |
print(f"[\033[0;32m✓\033[0m] {content}") | |
def prbad(content): | |
print(f"[\033[0;91mX\033[0m] {content}") | |
def prinfo(content): | |
print(f"[*] {content}") | |
def exitOnEnter(errCode = 0): | |
input("[*] Press Enter to exit...") | |
exit(errCode) | |
osver = platform.system() | |
def clearScreen(): | |
if osver == "Windows": | |
os.system("cls") | |
else: | |
os.system("clear") | |
cwd = fs.open_fs(f"fat://{sys.argv[1]}") | |
# Section: insureRoot | |
if not cwd.exists("Nintendo 3DS/"): | |
prbad("Error 01: Couldn't find Nintendo 3DS folder! Make sure you are running in the SD Card root!") | |
prbad("If that doesn't work, eject the SD card, and put it back in your console. Turn it on and off again, then restart the script.") | |
prinfo(f"Current dir: {cwd}") | |
exitOnEnter() | |
# Section: sdWritable | |
def writeProtectCheck(): | |
prinfo("Checking if SD card is writeable...") | |
writeable = os.access(cwd, os.W_OK) | |
try: # Bodge for windows | |
with open("test.txt", "w") as f: | |
f.write("test") | |
f.close() | |
os.remove("test.txt") | |
except: | |
writeable = False | |
if not writeable: | |
prbad("Error 02: Your sd is write protected! Please ensure the switch on the side of your SD card is facing upwards.") | |
prinfo("Visual aid: https://nintendohomebrew.com/assets/img/nhmemes/sdlock.png") | |
exitOnEnter() | |
else: | |
prgood("SD card is writeable!") | |
# Section: SD card free space | |
# ensure 16MB free space | |
#freeSpace = shutil.disk_usage(cwd).free | |
#if freeSpace < 16777216: | |
# prbad(f"Error 06: You need at least 16MB free space on your SD card, you have {(freeSpace / 1000000):.2f} bytes!") | |
# prinfo("Please free up some space and try again.") | |
# exitOnEnter() | |
clearScreen() | |
print(f"MSET9 {VERSION} SETUP by zoogie and Aven") | |
print("What is your console model and version?") | |
print("Old 3DS has two shoulder buttons (L and R)") | |
print("New 3DS has four shoulder buttons (L, R, ZL, ZR)") | |
print("\n-- Please type in a number then hit return --\n") | |
print("↓ Input one of these Numbers!") | |
print("1. Old 3DS, 11.8.0 to 11.17.0") | |
print("2. New 3DS, 11.8.0 to 11.17.0") | |
print("3. Old 3DS, 11.4.0 to 11.7.0") | |
print("4. New 3DS, 11.4.0 to 11.7.0") | |
encodedId1s = { | |
1: "FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A0871A0050899CE0408730064006D00630000900A0862003900", | |
2: "FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A0871A005085DCE0408730064006D00630000900A0862003900", | |
3: "FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A08499E050899CC0408730064006D00630000900A0862003900", | |
4: "FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A08459E050881CC0408730064006D00630000900A0862003900" | |
} | |
hackedId1Encoded, consoleModel, consoleFirmware = "", "", "" | |
while 1: | |
try: | |
sysModelVerSelect = input(">>> ") | |
if sysModelVerSelect.startswith("11"): | |
prbad("Don't type the firmware version, just the selection number!") | |
sysModelVerSelect = 42 | |
sysModelVerSelect = int(sysModelVerSelect) | |
except KeyboardInterrupt: | |
print() | |
prgood("Goodbye!") | |
exitOnEnter() | |
except: | |
sysModelVerSelect = 42 | |
if sysModelVerSelect == 1: | |
hackedId1Encoded = encodedId1s[1] | |
consoleModel = "OLD3DS" | |
consoleFirmware = "11.8-11.17" | |
break | |
if sysModelVerSelect == 2: | |
hackedId1Encoded = encodedId1s[2] | |
consoleModel = "NEW3DS" | |
consoleFirmware = "11.8-11.17" | |
break | |
if sysModelVerSelect == 3: | |
hackedId1Encoded = encodedId1s[3] | |
consoleModel = "OLD3DS" | |
consoleFirmware = "11.4-11.7" | |
break | |
if sysModelVerSelect == 4: | |
hackedId1Encoded = encodedId1s[4] | |
consoleModel = "NEW3DS" | |
consoleFirmware = "11.4-11.7" | |
break | |
else: | |
prbad("Invalid input, try again.") | |
trigger = "002F003A.txt" # all 3ds ":/" in hex | |
hackedId1 = bytes.fromhex(hackedId1Encoded).decode("utf-16le") # ID1 - arm injected payload in readable format | |
id1 = "" | |
id0 = "" | |
realId1Path = "" | |
extdataRoot = "" | |
realId1BackupTag = "_user-id1" | |
id0Count = 0 | |
id1Count = 0 | |
id0List = [] | |
homeMenuExtdata = [0x8F, 0x98, 0x82, 0xA1, 0xA9, 0xB1] # us,eu,jp,ch,kr,tw | |
miiMakerExtdata = [0x217, 0x227, 0x207, 0x267, 0x277, 0x287] # us,eu,jp,ch,kr,tw | |
# make a table so we can print regions based on what hex code from the above is found | |
regionTable = { | |
0x8F: "USA Region", | |
0x98: "EUR Region", | |
0x82: "JPN Region", | |
0xA1: "CHN Region", | |
0xA9: "KOR Region", | |
0xB1: "TWN Region" | |
} | |
homeDataPath, miiDataPath, homeHex, miiHex = "", "", 0x0, 0x0 | |
def sanity(): | |
global haxState, realId1Path, id0, id1, homeDataPath, miiDataPath, homeHex, miiHex | |
menuExtdataGood = False | |
miiExtdataGood = False | |
print() | |
prinfo("Performing sanity checks...") | |
#writeProtectCheck() | |
prinfo("Ensuring extracted files exist...") | |
fileSanity = 0 | |
fileSanity += softcheck("boot9strap/boot9strap.firm", 0, 0x08129C1F, 1) | |
fileSanity += softcheck("boot.firm", retval = 1) | |
fileSanity += softcheck("boot.3dsx", retval = 1) | |
fileSanity += softcheck("b9", retval = 1) | |
fileSanity += softcheck("SafeB9S.bin", retval = 1) | |
if fileSanity > 0: | |
prbad("Error 08: One or more files are missing or malformed!") | |
prinfo("Please extract the MSET9 zip file again, being sure to Overwrite any files.") | |
exitOnEnter() | |
prgood("All files look good!") | |
prinfo("Checking databases...") | |
checkTitledb = softcheck(realId1Path + "/dbs/title.db", 0x31E400, 0, 1) | |
checkImportdb = softcheck(realId1Path + "/dbs/import.db", 0x31E400, 0, 1) | |
if checkTitledb or checkImportdb: | |
prbad("Error 10: Database(s) malformed or missing!") | |
if not ( | |
cwd.exists(realId1Path + "/dbs/import.db") | |
or cwd.exists(realId1Path + "/dbs/title.db") | |
): | |
if not cwd.exists(realId1Path + "/dbs"): | |
cwd.makedir(realId1Path + "/dbs") | |
if checkTitledb: | |
cwd.open(realId1Path + "/dbs/title.db", "x").close() | |
if checkImportdb: | |
cwd.open(realId1Path + "/dbs/import.db", "x").close() | |
prinfo("Created empty databases.") | |
prinfo("please reset the database files in settings -> data management -> nintendo 3ds -> software first before coming back!") | |
prinfo("Visual guide: https://3ds.hacks.guide/images/screenshots/database-reset.jpg") | |
exitOnEnter() | |
else: | |
prgood("Databases look good!") | |
if cwd.exists(realId1Path + "/extdata/" + trigger): | |
prinfo("Removing stale trigger...") | |
cwd.remove(realId1Path + "/extdata/" + trigger) | |
extdataRoot = realId1Path + "/extdata/00000000" | |
prinfo("Checking for home menu extdata...") | |
for i in homeMenuExtdata: | |
extdataRegionCheck = extdataRoot + f"/{i:08X}" | |
if cwd.exists(extdataRegionCheck): | |
prgood(f"Detected {regionTable[i]} home data!") | |
homeHex = i | |
homeDataPath = extdataRegionCheck | |
menuExtdataGood = True | |
break | |
if not menuExtdataGood: | |
prbad("Error 04: No Home Menu Data!") | |
prinfo("This shouldn't really happen, Put the sd card back in your console.") | |
prinfo("Turn it on and off again, then restart the script.") | |
prinfo("For assistance, come visit us: https://discord.gg/nintendohomebrew") | |
exitOnEnter() | |
prinfo("Checking for mii maker extdata...") | |
for i in miiMakerExtdata: | |
extdataRegionCheck = extdataRoot + f"/{i:08X}" | |
if cwd.exists(extdataRegionCheck): | |
prgood("Found mii maker data!") | |
miiHex = i | |
miiDataPath = extdataRegionCheck | |
miiExtdataGood = True | |
break | |
if not miiExtdataGood: | |
prbad("Error 05: No Mii Maker Data!") | |
prinfo("Please go to https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 for instructions.") | |
exitOnEnter() | |
def injection(): | |
global realId1Path, id1 | |
if not cwd.exists(id0 + "/" + hackedId1): | |
prinfo("Creating hacked Id1...") | |
hackedId1Path = id0 + "/" + hackedId1 | |
cwd.makedir(hackedId1Path) | |
cwd.makedir(hackedId1Path + "/extdata") | |
cwd.makedir(hackedId1Path + "/extdata/00000000") | |
else: | |
prinfo("Reusing existing hacked Id1...") | |
hackedId1Path = id0 + "/" + hackedId1 | |
if not cwd.exists(hackedId1Path + "/dbs"): | |
prinfo("Copying databases to hacked Id1...") | |
cwd.copydir(realId1Path + "/dbs", hackedId1Path + "/dbs", create=True) | |
prinfo("Copying extdata to hacked Id1...") | |
if not cwd.exists(hackedId1Path + f"/extdata/00000000/{homeHex:08X}"): | |
cwd.copydir(homeDataPath, hackedId1Path + f"/extdata/00000000/{homeHex:08X}", create=True) | |
if not cwd.exists(hackedId1Path + f"/extdata/00000000/{miiHex:08X}"): | |
cwd.copydir(miiDataPath, hackedId1Path + f"/extdata/00000000/{miiHex:08X}", create=True) | |
prinfo("Injecting trigger file...") | |
triggerFilePath = id0 + "/" + hackedId1 + "/extdata/" + trigger | |
if not cwd.exists(triggerFilePath): | |
with cwd.open(triggerFilePath, "w") as f: | |
f.write("plz be haxxed mister arm9, thx") | |
f.close() | |
if cwd.exists(realId1Path) and realId1BackupTag not in realId1Path: | |
prinfo("Backing up real Id1...") | |
cwd.movedir(realId1Path, realId1Path + realId1BackupTag, create=True) | |
id1 += realId1BackupTag | |
realId1Path = f"{id0}/{id1}" | |
else: | |
prinfo("Skipping backup because a backup already exists!") | |
prgood("MSET9 successfully injected!") | |
def remove(): | |
global realId1Path, id0, id1 | |
prinfo("Removing MSET9...") | |
if cwd.exists(realId1Path) and realId1BackupTag in realId1Path: | |
prinfo("Renaming original Id1...") | |
cwd.movedir(realId1Path, id0 + "/" + id1[:32], create=True) | |
else: | |
prgood("Nothing to remove!") | |
return | |
# print(id1_path, id1_root+"/"+id1[:32]) | |
for id1Index in range(1,5): # Attempt to remove *all* hacked id1s | |
maybeHackedId = bytes.fromhex(encodedId1s[id1Index]).decode("utf-16le") | |
if cwd.exists(id0 + "/" + maybeHackedId): | |
prinfo("Deleting hacked Id1...") | |
cwd.removetree(id0 + "/" + maybeHackedId) | |
id1 = id1[:32] | |
realId1Path = id0 + "/" + id1 | |
prgood("Successfully removed MSET9!") | |
def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): | |
shortname = keyfile.rsplit("/")[-1] | |
if not cwd.exists(keyfile): | |
prbad(f"{shortname} does not exist on SD card!") | |
return retval | |
elif expectedSize: | |
fileSize = cwd.getsize(keyfile) | |
if expectedSize != fileSize: | |
prbad(f"{shortname} is size {fileSize:,} bytes, not expected {expectedSize:,} bytes") | |
return retval | |
elif crc32: | |
with cwd.open(keyfile, "rb") as f: | |
checksum = binascii.crc32(f.read()) | |
if crc32 != checksum: | |
prbad(f"{shortname} was not recognized as the correct file") | |
f.close() | |
return retval | |
f.close() | |
prgood(f"{shortname} looks good!") | |
return 0 | |
def reapplyWorkingDir(): | |
try: | |
os.chdir(cwd) | |
return True | |
except Exception: | |
prbad("Error 09: Couldn't reapply working directory, is sdcard reinserted?") | |
return False | |
# Section: sdwalk | |
for root, dirs, files in cwd.walk("Nintendo 3DS/", search='breadth'): | |
for info in dirs: | |
name = info.name | |
# If the name doesn't contain sdmc (Ignores MSET9 exploit folder) | |
if "sdmc" not in name and len(name[:32]) == 32: | |
try: | |
# Check to see if the file name encodes as an int (is hex only) | |
hexVerify = int(name[:32], 16) | |
except: | |
continue | |
if type(hexVerify) is int: | |
# Check if the folder (which is either id1 or id0) has the extdata folder | |
# if it does, it's an id1 folder | |
if cwd.exists(fs.path.join(root, name) + "/extdata"): | |
id1Count += 1 | |
id1 = name | |
id0 = root | |
realId1Path = fs.path.join(root, name) | |
# Otherwise, add it to the id0 list because we need to make sure we only have one id0 | |
else: | |
if len(name) == 32: | |
id0Count += 1 | |
id0List.append(fs.path.join(root, name)) | |
for info in dirs: # Run the check for existing install after figuring out the structure | |
name = info.name | |
# CHeck if we have an MSET9 Hacked id1 folder | |
if "sdmc" in name and len(name) == 32: | |
# If the MSET9 folder doesn't match the proper haxid1 for the selected console version | |
if hackedId1 != name: | |
prbad("Error 03: don't change console version in the middle of MSET9!") | |
prbad("Please restart the setup.") | |
remove() | |
exitOnEnter() | |
prinfo("Detected ID0(s):") | |
for i in id0List: | |
prinfo(i) | |
print() | |
if id0Count != 1: | |
prbad(f"Error 07: You don't have 1 ID0 in your Nintendo 3DS folder, you have {id0Count}!") | |
prinfo("Consult: https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 for help!") | |
exitOnEnter() | |
if id1Count != 1: | |
prbad(f"Error 12: You don't have 1 ID1 in your Nintendo 3DS folder, you have {id1Count}!") | |
prinfo("Consult: https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 for help!") | |
exitOnEnter() | |
clearScreen() | |
print(f"MSET9 {VERSION} SETUP by zoogie and Aven") | |
print(f"Using {consoleModel} {consoleFirmware}") | |
print("\n-- Please type in a number then hit return --\n") | |
print("↓ Input one of these Numbers!") | |
print("1. Perform sanity checks") | |
print("2. Inject MSET9 payload") | |
print("3. Remove MSET9") | |
print("4. Exit") | |
while 1: | |
try: | |
sysModelVerSelect = int(input(">>> ")) | |
except KeyboardInterrupt: | |
sysModelVerSelect = 4 # exit on Ctrl+C | |
print() | |
except: | |
sysModelVerSelect = 42 | |
if sysModelVerSelect == 1: | |
sanity() | |
prgood("Everything appears to be functional!\n") | |
exitOnEnter() | |
elif sysModelVerSelect == 2: | |
sanity() | |
injection() | |
exitOnEnter() | |
elif sysModelVerSelect == 3: | |
remove() | |
exitOnEnter() | |
elif sysModelVerSelect == 4 or "exit": | |
prgood("Goodbye!") | |
break | |
else: | |
prinfo("Invalid input, try again.") | |
time.sleep(2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment