From 01c4758c8a0c46fa850dea7864d759586174260f Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Sun, 16 Jun 2024 10:00:55 -0500 Subject: [PATCH 01/30] putting this on a branch for now --- MSET9_installer_script/mset9.py | 481 +++++++++++++++++++------------- 1 file changed, 288 insertions(+), 193 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 4507efa..0145167 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -4,20 +4,22 @@ VERSION = "v1.2c" def prgood(content): - print(f"[\033[0;32m✓\033[0m] {content}") + # print(f"[\033[0;32m✓\033[0m] {content}") + # so that people aren't confused by the [?]. stupid Windows. + print(f"[\033[0;32mOK\033[0m] {content}") def prbad(content): - print(f"[\033[0;91mX\033[0m] {content}") + print(f"[\033[0;91mXX\033[0m] {content}") def prinfo(content): - print(f"[*] {content}") + print(f"[--] {content}") def cleanup(remount=False): pass def exitOnEnter(errCode = 0, remount=False): cleanup(remount) - input("[*] Press Enter to exit...") + input("[--] Press Enter to exit...") exit(errCode) # wrapper for fs operations. can use pyfilesystem2 directly, @@ -394,6 +396,8 @@ def __init__(self, device): self.reload() def exists(self, path): return self.fs.exists(path) + def isdir(self, path): + return self.fs.getinfo(path).is_dir def mkdir(self, path): self.fs.makedir(path) def open(self, path, mode='r'): @@ -421,6 +425,8 @@ def rmtree(self, path): self.fs.removetree(path) def copytree(self, src, dst): self.fs.copydir(src, dst, create=True) + def listdir(self, path): + return self.fs.listdir(path) def walk(self, path, topdown=False): # topdown is ignored for dir_path, dirs, files in self.fs.walk(path): yield dir_path, list(map(lambda x: x.name, dirs)), list(map(lambda x: x.name, files)) @@ -493,6 +499,8 @@ def abs(self, path): return os.path.join(self.root, path) def exists(self, path): return os.path.exists(self.abs(path)) + def isdir(self, path): + return os.path.isdir(self.abs(path)) def mkdir(self, path): os.mkdir(self.abs(path)) def open(self, path, mode='r'): @@ -507,6 +515,8 @@ def rmtree(self, path): shutil.rmtree(self.abs(path)) def copytree(self, src, dst): shutil.copytree(self.abs(src), self.abs(dst)) + def listdir(self, path): + return os.listdir(path) def walk(self, path, topdown=False): return os.walk(self.abs(path), topdown=topdown) def is_writable(self): @@ -533,7 +543,28 @@ def print_root(self): prinfo(f"Current dir: {self.root}") - fs = OSFS(os.path.dirname(thisfile)) + scriptroot = os.path.dirname(thisfile) + if os.stat(scriptroot).st_dev == os.stat("/").st_dev: + prbad("Error 01: Script is not running on your SD card!") + prinfo(f"Current location: {scriptroot}") + exitOnEnter() + + if not os.path.ismount(scriptroot): + root = scriptroot + while not os.path.ismount(root) and root != os.path.dirname(root): + root = os.path.dirname(root) + + try: + for f in ["SafeB9S.bin", "b9", "boot.firm", "boot.3dsx", "boot9strap/"]: + shutil.move(os.path.join(scriptroot, f), os.path.join(root, f)) + except FileNotFoundError as e: + prbad("Error 08: One or more files are missing!") + prinfo("Please re-extract the MSET9 zip file to the root of your SD card, overwriting any existing files when prompted.") + exitOnEnter() + + scriptroot = root + + fs = OSFS(scriptroot) def clearScreen(): if osver == "Windows": @@ -561,9 +592,9 @@ def writeProtectCheck(): # Section: SD card free space # ensure 16MB free space -if not fs.ensurespace(16777216): +if not fs.ensurespace(16 * 1024 * 1024): #prbad(f"Error 06: You need at least 16MB free space on your SD card, you have {(freeSpace / 1000000):.2f} bytes!") - prbad(f"Error 06: You need at least 16MB free space on your SD card!") + prbad("Error 06: You need at least 16MB free space on your SD card!") prinfo("Please free up some space and try again.") exitOnEnter() @@ -572,74 +603,71 @@ def writeProtectCheck(): 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") + +consoleNames = { + 1: "Old 3DS/2DS, 11.8.0 to 11.17.0", + 2: "New 3DS/2DS, 11.8.0 to 11.17.0", + 3: "Old 3DS/2DS, 11.4.0 to 11.7.0", + 4: "New 3DS/2DS, 11.4.0 to 11.7.0" +} + print("Enter one of these four numbers!") -print("Enter 1 for: Old 3DS/2DS, 11.8.0 to 11.17.0") -print("Enter 2 for: New 3DS/2DS, 11.8.0 to 11.17.0") -print("Enter 3 for: Old 3DS/2DS, 11.4.0 to 11.7.0") -print("Enter 4 for: New 3DS/2DS, 11.4.0 to 11.7.0") +for i in consoleNames: + print(f"Enter {i} for: {consoleNames[i]}") -encodedId1s = { +# print("Enter 1 for: Old 3DS/2DS, 11.8.0 to 11.17.0") +# print("Enter 2 for: New 3DS/2DS, 11.8.0 to 11.17.0") +# print("Enter 3 for: Old 3DS/2DS, 11.4.0 to 11.7.0") +# print("Enter 4 for: New 3DS/2DS, 11.4.0 to 11.7.0") + +encodedID1s = { 1: "FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A0871A0050899CE0408730064006D00630000900A0862003900", 2: "FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A0871A005085DCE0408730064006D00630000900A0862003900", 3: "FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A08499E050899CC0408730064006D00630000900A0862003900", 4: "FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A08459E050881CC0408730064006D00630000900A0862003900" } -hackedId1Encoded, consoleModel, consoleFirmware = "", "", "" + +consoleIndex = 0 + 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) + continue + + consoleIndex = int(sysModelVerSelect) except KeyboardInterrupt: print() prgood("Goodbye!") exitOnEnter(remount=True) except: - sysModelVerSelect = 42 - if sysModelVerSelect == 1: - hackedId1Encoded = encodedId1s[1] - consoleModel = "OLD3DS" - consoleFirmware = "11.8-11.17" - break + consoleIndex = 255 - if sysModelVerSelect == 2: - hackedId1Encoded = encodedId1s[2] - consoleModel = "NEW3DS" - consoleFirmware = "11.8-11.17" - break + if consoleIndex > 4: + prbad("Invalid input, try again. Valid inputs: 1, 2, 3, 4") + continue - if sysModelVerSelect == 3: - hackedId1Encoded = encodedId1s[3] - consoleModel = "OLD3DS" - consoleFirmware = "11.4-11.7" - break + break - if sysModelVerSelect == 4: - hackedId1Encoded = encodedId1s[4] - consoleModel = "NEW3DS" - consoleFirmware = "11.4-11.7" - break +ID0, ID0Count, ID1, ID1Count = "", 0, "", 0 - else: - prbad("Invalid input, try again. Valid inputs: 1, 2, 3, 4") +#haxStates = ["Not set up", "Set up", "Injected"] +#haxState = 0 -trigger = "002F003A.txt" # all 3ds ":/" in hex -hackedId1 = bytes.fromhex(hackedId1Encoded).decode("utf-16le") # ID1 - arm injected payload in readable format -id1 = "" -id0 = "" -realId1Path = "" +realID1Path = "" +realID1BackupTag = "_user-id1" -extdataRoot = "" -realId1BackupTag = "_user-id1" -id0Count = 0 -id1Count = 0 -id0List = [] +hackedID1 = bytes.fromhex(encodedID1s[consoleIndex]).decode("utf-16le") # ID1 - arm injected payload in readable f +hackedID1Path = "" +extdataRoot = "" 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 +trigger = "002F003A.txt" # all 3ds ":/" in hexormat + # make a table so we can print regions based on what hex code from the above is found regionTable = { @@ -652,70 +680,106 @@ def writeProtectCheck(): } homeDataPath, miiDataPath, homeHex, miiHex = "", "", 0x0, 0x0 +# def sanity(): +# global fs, haxState, RealID1Path, id0, id1, homeDataPath, miiDataPath, homeHex, miiHex +# +# print() +# prinfo("Performing sanity checks...") +# +# writeProtectCheck() +# + + +def createHaxID1(): + global fs, ID0, hackedID1Path, realID1Path, realID1BackupTag + + if hackedID1Path and fs.exists(hackedID1Path): + prinfo("Hacked ID1 already exists!") + prinfo("Are you looking to do the sanity checks? Enter '2' instead.") + return + + print("\033[0;33m=== DISCLAIMER ===\033[0m") # 5;33m? The blinking is awesome but I also don't want to frighten users lol + print() + print("This process will temporarily reset all your 3DS data.") + print("All your applications and themes will disappear.") + print("This is perfectly normal, and if everything goes right. it will re-appear") + print("at the end of the process.") + print() + print("In any case, you are free to make a backup of your SD card's contents.") + print("(Especially the 'Nintendo 3DS' folder.)") + print() + + if osver == "Linux": # ... + print("(on Linux, things like to not go right - please ensure that your SD card is mounted with the 'utf8' option.)") + print() + + print("Press Enter to continue.") + print("Press CTRL+C / Command+C to cancel.") + try: + input() + except KeyboardInterrupt: + prinfo("Cancelled.") + exitOnEnter() + + hackedID1Path = ID0 + "/" + hackedID1 + + if realID1BackupTag not in realID1Path: + prinfo("Backing up original ID1...") + fs.rename(realID1Path, realID1Path + realID1BackupTag) + + prinfo("Creating hacked ID1...") + fs.mkdir(hackedID1Path) + #fs.mkdir(hackedID1Path + "/extdata") + fs.mkdir(hackedID1Path + "/dbs") + fs.open (hackedID1Path + "/dbs/title.db", "w").close() + fs.open (hackedID1Path + "/dbs/import.db", "w").close() + + prgood("Created hacked ID1.") + exitOnEnter() + + def sanity(): - global fs, haxState, realId1Path, id0, id1, homeDataPath, miiDataPath, homeHex, miiHex + global fs, hackedID1Path + 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 re-extract the MSET9 zip file, overwriting any existing files when prompted.") - exitOnEnter() - prgood("All files look good!") + if not hackedID1Path or not fs.exists(hackedID1Path): + prbad("Hacked ID1 does not exist!") + + prinfo("Creating hacked ID1 now.") + createHaxID1() prinfo("Checking databases...") - checkTitledb = softcheck(realId1Path + "/dbs/title.db", 0x31E400, 0, 1) - checkImportdb = softcheck(realId1Path + "/dbs/import.db", 0x31E400, 0, 1) + checkTitledb = softcheck(hackedID1Path + "/dbs/title.db", 0x31E400, 0, 1) + checkImportdb = softcheck(hackedID1Path + "/dbs/import.db", 0x31E400, 0, 1) if checkTitledb or checkImportdb: - prbad("Error 10: Database(s) malformed or missing!") - if not ( - fs.exists(realId1Path + "/dbs/import.db") - or fs.exists(realId1Path + "/dbs/title.db") - ): - if not fs.exists(realId1Path + "/dbs"): - fs.mkdir(realId1Path + "/dbs") - if checkTitledb: - fs.open(realId1Path + "/dbs/title.db", "x").close() - if checkImportdb: - fs.open(realId1Path + "/dbs/import.db", "x").close() - - prinfo("Created empty databases.") + prbad("Error 10: Databases not initialized!") + + # The files should exist. We created them when we created the hacked ID1 prinfo("Please initialize the title database by inserting the SD into your console, powering it on, then navigating to System Settings -> Data Management -> Nintendo 3DS -> Software -> Reset, then rerun this script.") prinfo("Visual guide: https://3ds.hacks.guide/images/screenshots/database-reset.jpg") exitOnEnter() else: prgood("Databases look good!") - if fs.exists(realId1Path + "/extdata/" + trigger): + if fs.exists(hackedID1Path + "/extdata/" + trigger): prinfo("Removing stale trigger...") - fs.remove(realId1Path + "/extdata/" + trigger) + fs.remove(hackedID1Path + "/extdata/" + trigger) - extdataRoot = realId1Path + "/extdata/00000000" + extdataRoot = hackedID1Path + "/extdata/00000000" prinfo("Checking for HOME Menu extdata...") for i in homeMenuExtdata: extdataRegionCheck = extdataRoot + f"/{i:08X}" if fs.exists(extdataRegionCheck): prgood(f"Detected {regionTable[i]} HOME Menu data!") - homeHex = i - homeDataPath = extdataRegionCheck menuExtdataGood = True break if not menuExtdataGood: prbad("Error 04: No HOME Menu data!") + # L+R+Down+B prinfo("Your SD is not formatted properly, or isn't being read by the console.") prinfo("Please go to https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 for instructions.") prinfo("If you need help, join Nintendo Homebrew on Discord: https://discord.gg/nintendohomebrew") @@ -726,8 +790,6 @@ def sanity(): extdataRegionCheck = extdataRoot + f"/{i:08X}" if fs.exists(extdataRegionCheck): prgood("Found Mii Maker data!") - miiHex = i - miiDataPath = extdataRegionCheck miiExtdataGood = True break @@ -736,66 +798,47 @@ def sanity(): prinfo("Please go to https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 for instructions.") exitOnEnter() + prgood("All files seem to be OK!") + def injection(): - global fs, realId1Path, id1 - - if not fs.exists(id0 + "/" + hackedId1): - prinfo("Creating hacked ID1...") - hackedId1Path = id0 + "/" + hackedId1 - fs.mkdir(hackedId1Path) - fs.mkdir(hackedId1Path + "/extdata") - fs.mkdir(hackedId1Path + "/extdata/00000000") - else: - prinfo("Reusing existing hacked ID1...") - hackedId1Path = id0 + "/" + hackedId1 + global fs, hackedID1Path, trigger - if not fs.exists(hackedId1Path + "/dbs"): - prinfo("Copying databases to hacked ID1...") - fs.copytree(realId1Path + "/dbs", hackedId1Path + "/dbs") + if not hackedID1Path or not fs.exists(hackedID1Path): + prbad("Hacked ID1 does not exist!") - prinfo("Copying extdata to hacked ID1...") - if not fs.exists(hackedId1Path + f"/extdata/00000000/{homeHex:08X}"): - fs.copytree(homeDataPath, hackedId1Path + f"/extdata/00000000/{homeHex:08X}") - if not fs.exists(hackedId1Path + f"/extdata/00000000/{miiHex:08X}"): - fs.copytree(miiDataPath, hackedId1Path + f"/extdata/00000000/{miiHex:08X}") + prinfo("Creating hacked ID1 now.") + createHaxID1() - prinfo("Injecting trigger file...") - triggerFilePath = id0 + "/" + hackedId1 + "/extdata/" + trigger - if not fs.exists(triggerFilePath): - with fs.open(triggerFilePath, "w") as f: - f.write("plz be haxxed mister arm9, thx") - f.close() - - if fs.exists(realId1Path) and realId1BackupTag not in realId1Path: - prinfo("Backing up real ID1...") - fs.rename(realId1Path, realId1Path + realId1BackupTag) - id1 += realId1BackupTag - realId1Path = f"{id0}/{id1}" - else: - prinfo("Skipping backup because a backup already exists!") + triggerFilePath = hackedID1Path + "/extdata/" + trigger + if fs.exists(triggerFilePath): + fs.remove(triggerFilePath) + prinfo("Removed trigger file.") + prinfo("Please restart from the beginning of Section III.") + return + prinfo("Injecting trigger file...") + with fs.open(triggerFilePath, 'w') as f: + #f.write("not so useless FAT-fs null deref") + f.write("pls be haxxed mister arm9, thx") + f.close() prgood("MSET9 successfully injected!") def remove(): - global fs, realId1Path, id0, id1 + global fs, ID0, ID1, hackedID1Path, realID1Path, realID1BackupTag + prinfo("Removing MSET9...") - if fs.exists(realId1Path) and realId1BackupTag in realId1Path: - prinfo("Renaming original Id1...") - fs.rename(realId1Path, id0 + "/" + id1[:32]) - 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 fs.exists(id0 + "/" + maybeHackedId): - prinfo("Deleting hacked ID1...") - fs.rmtree(id0 + "/" + maybeHackedId) - id1 = id1[:32] - realId1Path = id0 + "/" + id1 + if hackedID1Path and fs.exists(hackedID1Path): + prinfo("Deleting hacked ID1...") + fs.rmtree(hackedID1Path) + + if fs.exists(realID1Path) and realID1BackupTag in realID1Path: + prinfo("Renaming original ID1...") + fs.rename(realID1Path, ID0 + "/" + ID1[:32]) + ID1 = ID1[:32] + realID1Path = ID0 + "/" + ID1 + prgood("Successfully removed MSET9!") def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): @@ -820,95 +863,147 @@ def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): prgood(f"{filename} looks good!") return 0 +def is3DSID(name): + if not len(name) == 32: + return False + + try: + hex_test = int(name, 0x10) + except: + return False + + return True + + +# Section: Sanity checks A (global files required for exploit) +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 re-extract the MSET9 zip file, overwriting any existing files when prompted.") + exitOnEnter() + +# prgood("All files look good!") + # Section: sdwalk -for root, dirs, files in fs.walk("Nintendo 3DS/", topdown=True): +for dirname in fs.listdir("Nintendo 3DS/"): + fullpath = "Nintendo 3DS/" + dirname + + if not fs.isdir(fullpath): + prinfo(f"Found file in Nintendo 3DS folder? '{dirname}'") + continue + + if not is3DSID(dirname): + continue - for name in dirs: - # If the name doesn't contain sdmc (Ignores MSET9 exploit folder) - if "sdmc" not in name and len(name[:32]) == 32: + prinfo(f"Detected ID0: {dirname}") + ID0 = fullpath + ID0Count += 1 + +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() + +for dirname in fs.listdir(ID0): + fullpath = ID0 + "/" + dirname + + if not fs.isdir(fullpath): + prinfo(f"Found file in ID0 folder? '{dirname}'") + continue + + if is3DSID(dirname[:32]): + prinfo(f"Detected ID1: {dirname}") + ID1 = dirname + realID1Path = ID0 + "/" + ID1 + ID1Count += 1 + elif "sdmc" in dirname and len(dirname) == 32: + currentHaxID1enc = dirname.encode("utf-16le").hex().upper() + currentHaxID1index = 0 + + for haxID1index in encodedID1s: + if currentHaxID1enc == encodedID1s[haxID1index]: + currentHaxID1index = haxID1index + break + + if currentHaxID1index == 0 or (hackedID1Path and fs.exists(hackedID1Path)): # shouldn't happen + prbad("Unrecognized/duplicate hax ID1 in ID0 folder, removing!") + fs.rmtree(ID0 + "/" + dirname) + elif currentHaxID1index != consoleIndex: + prbad("Error 03: Don't change console model/version in the middle of MSET9!") + print(f"Earlier, you selected: '[{currentHaxID1index}.] {consoleNames[currentHaxID1index]}'") + print(f"Now, you selected: '[{consoleIndex}.] {consoleNames[consoleIndex]}'") + print() + print(f"Switch to '[{consoleIndex}.] {consoleNames[consoleIndex]}' ?") + print("Press Enter to continue.") + print("Press CTRL+C / Command+C to cancel.") 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 fs.exists(os.path.join(root, name) + "/extdata"): - id1Count += 1 - id1 = name - id0 = root - realId1Path = os.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(os.path.join(root, name)) - - for name in dirs: # Run the check for existing install after figuring out the structure - # 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 model/version in the middle of MSET9!") - prbad("Please restart the setup.") + input() + except KeyboardInterrupt: + prinfo("Cancelled.") + hackedID1Path = ID0 + "/" + dirname remove() exitOnEnter() + fs.rename(ID0 + "/" + dirname, ID0 + "/" + hackedID1) -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() + hackedID1Path = ID0 + "/" + hackedID1 -if id1Count != 1: - prbad(f"Error 12: You don't have 1 ID1 in your Nintendo 3DS folder, you have {id1Count}!") +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, Aven and DannyAAM") -print(f"Using {consoleModel} {consoleFirmware}") +print(f"Using {consoleNames[consoleIndex]}") 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") +print("1. Create MSET9 ID1") +print("2. Perform sanity checks") +print("3. Inject MSET9 trigger") +print("4. Remove MSET9") +print("0. Exit") while 1: try: - sysModelVerSelect = int(input(">>> ")) + optSelect = int(input(">>> ")) except KeyboardInterrupt: - sysModelVerSelect = 4 # exit on Ctrl+C + optSelect = 0 # exit on Ctrl+C print() except: - sysModelVerSelect = 42 + optSelect = 255 - fs.reload() + fs.reload() # (?) - if sysModelVerSelect == 1: + if optSelect == 1: + createHaxID1() + elif optSelect == 2: sanity() - prgood("Everything appears to be functional!\n") exitOnEnter() - elif sysModelVerSelect == 2: + elif optSelect == 3: sanity() injection() exitOnEnter() - elif sysModelVerSelect == 3: + elif optSelect == 4: remove() - remove_extra() + remove_extra() # (?) exitOnEnter(remount=True) - elif sysModelVerSelect == 4 or "exit": + elif sysModelVerSelect == 0 or "exit": break else: - prinfo("Invalid input, try again. Valid inputs: 1, 2, 3, 4") + prinfo("Invalid input, try again. Valid inputs: 1, 2, 3, 4, 0") cleanup(remount=True) prgood("Goodbye!") From 491a31bf79558946e1698c7d61d32054da5cb7e1 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Sun, 16 Jun 2024 20:04:24 -0500 Subject: [PATCH 02/30] make option 2 dynamic --- MSET9_installer_script/mset9.py | 281 +++++++++++++++++++------------- 1 file changed, 165 insertions(+), 116 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 0145167..7283996 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -572,6 +572,26 @@ def clearScreen(): else: os.system("clear") +# -1: Cancelled +def getInput(maximum, minimum=1): + while 1: + try: + opt = int(input(">>> ")) + except KeyboardInterrupt: + return -1 + except EOFError: + return -1 + except ValueError: + opt = 255 + + if opt > maximum or opt < minimum: + # prbad(f"Invalid input, try again. Valid inputs: {*range(minimum, maximum)}") :v + inputs = ', '.join(str(i) for i in range(minimum, maximum + 1)) + prbad(f"Invalid input, try again. Valid inputs: {inputs}") + continue + + return opt + # Section: insureRoot if not fs.exists("Nintendo 3DS/"): prbad("Error 01: Couldn't find Nintendo 3DS folder! Ensure that you are running this script from the root of the SD card.") @@ -629,33 +649,16 @@ def writeProtectCheck(): 4: "FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A08459E050881CC0408730064006D00630000900A0862003900" } -consoleIndex = 0 - -while 1: - try: - sysModelVerSelect = input(">>> ") - if sysModelVerSelect.startswith("11"): - prbad("Don't type the firmware version, just the selection number!") - continue - - consoleIndex = int(sysModelVerSelect) - except KeyboardInterrupt: - print() - prgood("Goodbye!") - exitOnEnter(remount=True) - except: - consoleIndex = 255 - - if consoleIndex > 4: - prbad("Invalid input, try again. Valid inputs: 1, 2, 3, 4") - continue - - break +consoleIndex = getInput(maximum=4) +if consoleIndex < 0: + print() + prgood("Goodbye!") + exitOnEnter(remount=True) ID0, ID0Count, ID1, ID1Count = "", 0, "", 0 -#haxStates = ["Not set up", "Set up", "Injected"] -#haxState = 0 +haxStates = ["\033[30;1m ID1 not created \033[0m", "\033[33;1m Not ready to inject \033[0m", "\033[32m Ready to inject \033[0m", "\033[32;1m Injected \033[0m"] +haxState = 0 realID1Path = "" realID1BackupTag = "_user-id1" @@ -666,7 +669,8 @@ def writeProtectCheck(): extdataRoot = "" 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 -trigger = "002F003A.txt" # all 3ds ":/" in hexormat +trigger = "002F003A.txt" # all 3ds ":/" in hex format +triggerFilePath = "" # make a table so we can print regions based on what hex code from the above is found @@ -695,7 +699,7 @@ def createHaxID1(): if hackedID1Path and fs.exists(hackedID1Path): prinfo("Hacked ID1 already exists!") - prinfo("Are you looking to do the sanity checks? Enter '2' instead.") + # prinfo("Are you looking to do the sanity checks? Enter '2' instead.") return print("\033[0;33m=== DISCLAIMER ===\033[0m") # 5;33m? The blinking is awesome but I also don't want to frighten users lol @@ -713,13 +717,14 @@ def createHaxID1(): print("(on Linux, things like to not go right - please ensure that your SD card is mounted with the 'utf8' option.)") print() - print("Press Enter to continue.") - print("Press CTRL+C / Command+C to cancel.") - try: - input() - except KeyboardInterrupt: + print("Input '1' again to confirm.") + print("Input '2' to cancel.") + time.sleep(3) + if getInput(maximum=2) != 1: + print() prinfo("Cancelled.") - exitOnEnter() + exitOnEnter(remount=True) + hackedID1Path = ID0 + "/" + hackedID1 @@ -737,35 +742,21 @@ def createHaxID1(): prgood("Created hacked ID1.") exitOnEnter() +titleDatabasesGood = False +menuExtdataGood = False +miiExtdataGood = False def sanity(): - global fs, hackedID1Path - - menuExtdataGood = False - miiExtdataGood = False - - if not hackedID1Path or not fs.exists(hackedID1Path): - prbad("Hacked ID1 does not exist!") - - prinfo("Creating hacked ID1 now.") - createHaxID1() + global fs, hackedID1Path, titleDatabasesGood, menuExtdataGood, miiExtdataGood prinfo("Checking databases...") checkTitledb = softcheck(hackedID1Path + "/dbs/title.db", 0x31E400, 0, 1) checkImportdb = softcheck(hackedID1Path + "/dbs/import.db", 0x31E400, 0, 1) - if checkTitledb or checkImportdb: - prbad("Error 10: Databases not initialized!") - - # The files should exist. We created them when we created the hacked ID1 - prinfo("Please initialize the title database by inserting the SD into your console, powering it on, then navigating to System Settings -> Data Management -> Nintendo 3DS -> Software -> Reset, then rerun this script.") - prinfo("Visual guide: https://3ds.hacks.guide/images/screenshots/database-reset.jpg") - exitOnEnter() - else: - prgood("Databases look good!") + titleDatabasesGood = not (checkTitledb or checkImportdb) - if fs.exists(hackedID1Path + "/extdata/" + trigger): - prinfo("Removing stale trigger...") - fs.remove(hackedID1Path + "/extdata/" + trigger) + # if fs.exists(hackedID1Path + "/extdata/" + trigger): + # prinfo("Removing stale trigger...") + # fs.remove(hackedID1Path + "/extdata/" + trigger) extdataRoot = hackedID1Path + "/extdata/00000000" @@ -773,48 +764,57 @@ def sanity(): for i in homeMenuExtdata: extdataRegionCheck = extdataRoot + f"/{i:08X}" if fs.exists(extdataRegionCheck): - prgood(f"Detected {regionTable[i]} HOME Menu data!") + # prgood(f"Detected {regionTable[i]} HOME Menu data!") menuExtdataGood = True break - if not menuExtdataGood: - prbad("Error 04: No HOME Menu data!") - # L+R+Down+B - prinfo("Your SD is not formatted properly, or isn't being read by the console.") - prinfo("Please go to https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 for instructions.") - prinfo("If you need help, join Nintendo Homebrew on Discord: https://discord.gg/nintendohomebrew") - exitOnEnter() - prinfo("Checking for Mii Maker extdata...") for i in miiMakerExtdata: extdataRegionCheck = extdataRoot + f"/{i:08X}" if fs.exists(extdataRegionCheck): - prgood("Found Mii Maker data!") + # prgood("Found Mii Maker data!") miiExtdataGood = True break - + + return menuExtdataGood and miiExtdataGood and titleDatabasesGood + +def sanityReport(): + if not menuExtdataGood: + prbad("HOME menu extdata: Missing!") + prinfo("Please power on your console with your SD inserted, then check again.") + prinfo("If this does not work, power it on while holding L+R+Down+B.") + else: + prgood("HOME menu extdata: OK!") + + print() + 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() + prbad("Mii Maker extdata: Missing!") + prinfo("Please power on your console with your SD inserted, then launch Mii Maker.") + else: + prgood("Mii Maker extdata: OK!") - prgood("All files seem to be OK!") + print() -def injection(): - global fs, hackedID1Path, trigger + if not titleDatabasesGood: + prbad("Title database: Not initialized!") + prinfo("Please power on your console with your SD inserted, open System Setttings,") + prinfo("navigate to Data Management -> Nintendo 3DS -> Software, then select Reset.") + else: + prgood("Title database: OK!") - if not hackedID1Path or not fs.exists(hackedID1Path): - prbad("Hacked ID1 does not exist!") + print() - prinfo("Creating hacked ID1 now.") - createHaxID1() + exitOnEnter() + +def injection(): + global fs, haxState, hackedID1Path, trigger triggerFilePath = hackedID1Path + "/extdata/" + trigger if fs.exists(triggerFilePath): fs.remove(triggerFilePath) prinfo("Removed trigger file.") - prinfo("Please restart from the beginning of Section III.") return prinfo("Injecting trigger file...") @@ -827,6 +827,19 @@ def injection(): def remove(): global fs, ID0, ID1, hackedID1Path, realID1Path, realID1BackupTag + if haxState == 3: + prinfo("MSET9 trigger is still injected!") + print() + prinfo("Enter '1' to only remove the MSET9 trigger.") + prinfo("Enter '2' to remove the MSET9 ID1 entirely.") + resp = getInput(2) + if resp < 0: + return + + elif resp == 1: + injection() + return + prinfo("Removing MSET9...") if hackedID1Path and fs.exists(hackedID1Path): @@ -844,22 +857,27 @@ def remove(): def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): global fs filename = keyfile.rsplit("/")[-1] + if not fs.exists(keyfile): prbad(f"{filename} does not exist on SD card!") return retval - if expectedSize: - fileSize = fs.getsize(keyfile) - if expectedSize != fileSize: - prbad(f"{filename} is size {fileSize:,} bytes, not expected {expectedSize:,} bytes") - return retval - elif crc32: + + fileSize = fs.getsize(keyfile) + if not fileSize: + prbad(f"{filename} is an empty file!") + return retval + elif expectedSize and fileSize != expectedSize: + prbad(f"{filename} is size {fileSize:,} bytes, not expected {expectedSize:,} bytes") + return retval + + if crc32: with fs.open(keyfile, "rb") as f: checksum = binascii.crc32(f.read()) + f.close() if crc32 != checksum: prbad(f"{filename} was not recognized as the correct file") - f.close() return retval - f.close() + prgood(f"{filename} looks good!") return 0 @@ -937,26 +955,46 @@ def is3DSID(name): if currentHaxID1index == 0 or (hackedID1Path and fs.exists(hackedID1Path)): # shouldn't happen prbad("Unrecognized/duplicate hax ID1 in ID0 folder, removing!") - fs.rmtree(ID0 + "/" + dirname) + fs.rmtree(fullpath) elif currentHaxID1index != consoleIndex: prbad("Error 03: Don't change console model/version in the middle of MSET9!") print(f"Earlier, you selected: '[{currentHaxID1index}.] {consoleNames[currentHaxID1index]}'") print(f"Now, you selected: '[{consoleIndex}.] {consoleNames[consoleIndex]}'") print() - print(f"Switch to '[{consoleIndex}.] {consoleNames[consoleIndex]}' ?") - print("Press Enter to continue.") - print("Press CTRL+C / Command+C to cancel.") - try: - input() - except KeyboardInterrupt: - prinfo("Cancelled.") - hackedID1Path = ID0 + "/" + dirname - remove() - exitOnEnter() + print("Please re-enter the number for your console model and version.") + + while True: + try: + choice = int(input(">>> ")) + except KeyboardInterrupt: + prinfo("Cancelled.") + hackedID1Path = fullpath + remove() + exitOnEnter() + except ValueError: + choice = 255 + + if choice != consoleIndex and choice != currentHaxID1index: + prinfo(f"Invalid input, try again. Valid inputs: {consoleIndex}, {currentHaxID1index}") + continue + + elif choice == currentHaxID1index: + consoleIndex = currentHaxID1index + hackedID1 = dirname + break - fs.rename(ID0 + "/" + dirname, ID0 + "/" + hackedID1) + elif choice == consoleIndex: + fs.rename(fullpath, ID0 + "/" + hackedID1) + break hackedID1Path = ID0 + "/" + hackedID1 + haxState = 1 # Created/Not ready. + + if fs.exists(hackedID1Path + "/extdata/" + trigger): + triggerFilePath = hackedID1Path + "/extdata/" + trigger + haxState = 3 # Injected. + elif sanity(): + haxState = 2 # Ready! if ID1Count != 1: prbad(f"Error 12: You don't have 1 ID1 in your Nintendo 3DS folder, you have {ID1Count}!") @@ -966,44 +1004,55 @@ def is3DSID(name): clearScreen() print(f"MSET9 {VERSION} SETUP by zoogie, Aven and DannyAAM") print(f"Using {consoleNames[consoleIndex]}") +print() +print(f"Current MSET9 state: {haxStates[haxState]}") print("\n-- Please type in a number then hit return --\n") print("↓ Input one of these numbers!") print("1. Create MSET9 ID1") -print("2. Perform sanity checks") -print("3. Inject MSET9 trigger") -print("4. Remove MSET9") + +if haxState > 0: + # Not ready (1) - Check for problems + # Ready (2) - Inject + # Injected (3) - Remove inject + option2label = { + 1: "Check for problems", + 2: "Inject MSET9 trigger", + 3: "Remove MSET9 trigger" + } + + print(f"2. {option2label[haxState]}") + print("3. Remove MSET9") + print("0. Exit") + while 1: - try: - optSelect = int(input(">>> ")) - except KeyboardInterrupt: - optSelect = 0 # exit on Ctrl+C - print() - except: - optSelect = 255 + optSelect = getInput(maximum=4, minimum=0) fs.reload() # (?) - if optSelect == 1: + if optSelect <= 0: + break + + elif optSelect == 1: createHaxID1() + elif optSelect == 2: - sanity() - exitOnEnter() - elif optSelect == 3: - sanity() + if haxState < 1: + prbad("Can't do that now! Please create the MSET9 ID1 first!") + continue + if haxState == 1: + sanityReport() + injection() exitOnEnter() - elif optSelect == 4: + + elif optSelect == 3: remove() remove_extra() # (?) exitOnEnter(remount=True) - elif sysModelVerSelect == 0 or "exit": - break - else: - prinfo("Invalid input, try again. Valid inputs: 1, 2, 3, 4, 0") cleanup(remount=True) prgood("Goodbye!") From 25d1a72b164887e78aa8252700aea9c4617f1629 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Sun, 16 Jun 2024 20:18:00 -0500 Subject: [PATCH 03/30] move MSET9 script itself to root --- MSET9_installer_script/mset9.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 7283996..5d4d543 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -544,7 +544,9 @@ def print_root(self): scriptroot = os.path.dirname(thisfile) - if os.stat(scriptroot).st_dev == os.stat("/").st_dev: + # I hate python ternary operator i hate python ternary operator + systemroot = os.environ["SYSTEMDRIVE"] if osver == "Windows" else "/" + if os.stat(scriptroot).st_dev == os.stat(systemroot).st_dev: prbad("Error 01: Script is not running on your SD card!") prinfo(f"Current location: {scriptroot}") exitOnEnter() @@ -555,12 +557,14 @@ def print_root(self): root = os.path.dirname(root) try: - for f in ["SafeB9S.bin", "b9", "boot.firm", "boot.3dsx", "boot9strap/"]: + for f in ["SafeB9S.bin", "b9", "boot.firm", "boot.3dsx", "boot9strap/", "mset9.py", "mset9.bat", "mset9.command"]: shutil.move(os.path.join(scriptroot, f), os.path.join(root, f)) - except FileNotFoundError as e: - prbad("Error 08: One or more files are missing!") - prinfo("Please re-extract the MSET9 zip file to the root of your SD card, overwriting any existing files when prompted.") - exitOnEnter() + except FileNotFoundError: + # prbad("Error 08: One or more files are missing!") + # prinfo("Please re-extract the MSET9 zip file to the root of your SD card, overwriting any existing files when prompted.") + # exitOnEnter() + pass + # The sanity checks will deal with that. I just don't want the exception to terminate the script. scriptroot = root From f7f18bc38e2ea6fae4ff529e2489bdbdaa94eb01 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Sun, 16 Jun 2024 20:49:08 -0500 Subject: [PATCH 04/30] Drop a note for the user about the SD root --- MSET9_installer_script/mset9.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 5d4d543..0573476 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -545,7 +545,7 @@ def print_root(self): scriptroot = os.path.dirname(thisfile) # I hate python ternary operator i hate python ternary operator - systemroot = os.environ["SYSTEMDRIVE"] if osver == "Windows" else "/" + systemroot = os.environ["SYSTEMDRIVE"] if osver == "Windows" else "/" # Never hardcode C:. My Windows drive letter is E:, my SD card or USB drive is often C:. if os.stat(scriptroot).st_dev == os.stat(systemroot).st_dev: prbad("Error 01: Script is not running on your SD card!") prinfo(f"Current location: {scriptroot}") @@ -556,16 +556,23 @@ def print_root(self): while not os.path.ismount(root) and root != os.path.dirname(root): root = os.path.dirname(root) - try: - for f in ["SafeB9S.bin", "b9", "boot.firm", "boot.3dsx", "boot9strap/", "mset9.py", "mset9.bat", "mset9.command"]: + for f in ["SafeB9S.bin", "b9", "boot.firm", "boot.3dsx", "boot9strap/", "mset9.py", "mset9.bat", "mset9.command", "_INSTRUCTIONS.txt", "errors.txt"]: + try: shutil.move(os.path.join(scriptroot, f), os.path.join(root, f)) - except FileNotFoundError: - # prbad("Error 08: One or more files are missing!") - # prinfo("Please re-extract the MSET9 zip file to the root of your SD card, overwriting any existing files when prompted.") - # exitOnEnter() - pass - # The sanity checks will deal with that. I just don't want the exception to terminate the script. + except FileNotFoundError: + pass # The sanity checks will deal with that. I just don't want the exception to terminate the script. + with open(os.path.join(scriptroot, "Note from MSET9.txt"), "w") as f: + f.write("Hey!\n") + f.write("All the MSET9 files have been moved to the root of your SD card.\n\n") + + f.write("- What is the 'root of my SD card' ...?\n") + f.write("The root is 'not inside any folder'.\n") + f.write("This is where you can see your 'Nintendo 3DS' folder. (It is not inside the Nintendo 3DS folder itself!)\n") + f.write("Reference image: https://3ds.hacks.guide/images/screenshots/onboarding/sdroot.png\n\n") + + f.write(f"At the time of writing, the root of your SD card is at: '{root}' Check it out!\n") + f.close() scriptroot = root fs = OSFS(scriptroot) @@ -713,7 +720,7 @@ def createHaxID1(): print("This is perfectly normal, and if everything goes right. it will re-appear") print("at the end of the process.") print() - print("In any case, you are free to make a backup of your SD card's contents.") + print("In any case, it is highly recommended to make a backup of your SD card's contents to a folder on your PC.") print("(Especially the 'Nintendo 3DS' folder.)") print() @@ -1021,7 +1028,7 @@ def is3DSID(name): # Ready (2) - Inject # Injected (3) - Remove inject option2label = { - 1: "Check for problems", + 1: "Perform sanity checks", 2: "Inject MSET9 trigger", 3: "Remove MSET9 trigger" } From 3e64c2311a0aebd788557737cc92129119cc0a0b Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Mon, 17 Jun 2024 09:55:15 -0500 Subject: [PATCH 05/30] better getInput function --- MSET9_installer_script/mset9.py | 77 +++++++++++++++------------------ 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 0573476..0f4022e 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -566,13 +566,15 @@ def print_root(self): f.write("Hey!\n") f.write("All the MSET9 files have been moved to the root of your SD card.\n\n") - f.write("- What is the 'root of my SD card' ...?\n") + f.write("\"What is the 'root of my SD card'...?\"\n") f.write("The root is 'not inside any folder'.\n") - f.write("This is where you can see your 'Nintendo 3DS' folder. (It is not inside the Nintendo 3DS folder itself!)\n") + f.write("This is where you can see your 'Nintendo 3DS' folder. (It is not inside the Nintendo 3DS folder itself!)\n\n") + f.write("Reference image: https://3ds.hacks.guide/images/screenshots/onboarding/sdroot.png\n\n") - f.write(f"At the time of writing, the root of your SD card is at: '{root}' Check it out!\n") + f.write(f"At the time of writing, the root of your SD card is at: '{root}'. Check it out!\n") f.close() + scriptroot = root fs = OSFS(scriptroot) @@ -584,7 +586,10 @@ def clearScreen(): os.system("clear") # -1: Cancelled -def getInput(maximum, minimum=1): +def getInput(options): + if type(options) == range: + options = [*options, (options[-1] + 1)] + while 1: try: opt = int(input(">>> ")) @@ -593,12 +598,10 @@ def getInput(maximum, minimum=1): except EOFError: return -1 except ValueError: - opt = 255 + opt = 0xFFFFFFFF - if opt > maximum or opt < minimum: - # prbad(f"Invalid input, try again. Valid inputs: {*range(minimum, maximum)}") :v - inputs = ', '.join(str(i) for i in range(minimum, maximum + 1)) - prbad(f"Invalid input, try again. Valid inputs: {inputs}") + if opt not in options: + prbad(f"Invalid input, try again. Valid inputs: {str.join(', ', (str(i) for i in options))}") continue return opt @@ -660,7 +663,7 @@ def writeProtectCheck(): 4: "FFFFFFFA119907488546696508A10122054B984768465946C0AA171C4346034CA047B84700900A08459E050881CC0408730064006D00630000900A0862003900" } -consoleIndex = getInput(maximum=4) +consoleIndex = getInput(range(1, 4)) if consoleIndex < 0: print() prgood("Goodbye!") @@ -731,7 +734,7 @@ def createHaxID1(): print("Input '1' again to confirm.") print("Input '2' to cancel.") time.sleep(3) - if getInput(maximum=2) != 1: + if getInput(range(1, 2)) != 1: print() prinfo("Cancelled.") exitOnEnter(remount=True) @@ -739,7 +742,7 @@ def createHaxID1(): hackedID1Path = ID0 + "/" + hackedID1 - if realID1BackupTag not in realID1Path: + if not realID1Path.endswith(realID1BackupTag): prinfo("Backing up original ID1...") fs.rename(realID1Path, realID1Path + realID1BackupTag) @@ -843,7 +846,7 @@ def remove(): print() prinfo("Enter '1' to only remove the MSET9 trigger.") prinfo("Enter '2' to remove the MSET9 ID1 entirely.") - resp = getInput(2) + resp = getInput(range(1, 2)) if resp < 0: return @@ -857,7 +860,7 @@ def remove(): prinfo("Deleting hacked ID1...") fs.rmtree(hackedID1Path) - if fs.exists(realID1Path) and realID1BackupTag in realID1Path: + if fs.exists(realID1Path) and realID1Path.endswith(realID1BackupTag): prinfo("Renaming original ID1...") fs.rename(realID1Path, ID0 + "/" + ID1[:32]) ID1 = ID1[:32] @@ -950,7 +953,7 @@ def is3DSID(name): prinfo(f"Found file in ID0 folder? '{dirname}'") continue - if is3DSID(dirname[:32]): + if is3DSID(dirname) or (dirname[32:] == realID1BackupTag and is3DSID(dirname[:32])): prinfo(f"Detected ID1: {dirname}") ID1 = dirname realID1Path = ID0 + "/" + ID1 @@ -965,7 +968,7 @@ def is3DSID(name): break if currentHaxID1index == 0 or (hackedID1Path and fs.exists(hackedID1Path)): # shouldn't happen - prbad("Unrecognized/duplicate hax ID1 in ID0 folder, removing!") + prbad("Unrecognized/duplicate hacked ID1 in ID0 folder, removing!") fs.rmtree(fullpath) elif currentHaxID1index != consoleIndex: prbad("Error 03: Don't change console model/version in the middle of MSET9!") @@ -974,29 +977,21 @@ def is3DSID(name): print() print("Please re-enter the number for your console model and version.") - while True: - try: - choice = int(input(">>> ")) - except KeyboardInterrupt: - prinfo("Cancelled.") - hackedID1Path = fullpath - remove() - exitOnEnter() - except ValueError: - choice = 255 - - if choice != consoleIndex and choice != currentHaxID1index: - prinfo(f"Invalid input, try again. Valid inputs: {consoleIndex}, {currentHaxID1index}") - continue - - elif choice == currentHaxID1index: - consoleIndex = currentHaxID1index - hackedID1 = dirname - break + choice = getInput([consoleIndex, currentHaxID1index]) + if choice < 0: + prinfo("Cancelled.") + hackedID1Path = fullpath + remove() + exitOnEnter() - elif choice == consoleIndex: - fs.rename(fullpath, ID0 + "/" + hackedID1) - break + elif choice == currentHaxID1index: + consoleIndex = currentHaxID1index + hackedID1 = dirname + break + + elif choice == consoleIndex: + fs.rename(fullpath, ID0 + "/" + hackedID1) + break hackedID1Path = ID0 + "/" + hackedID1 haxState = 1 # Created/Not ready. @@ -1040,11 +1035,11 @@ def is3DSID(name): while 1: - optSelect = getInput(maximum=4, minimum=0) + optSelect = getInput(range(0, 3)) fs.reload() # (?) - if optSelect <= 0: + if optSelect < 1: break elif optSelect == 1: @@ -1054,7 +1049,7 @@ def is3DSID(name): if haxState < 1: prbad("Can't do that now! Please create the MSET9 ID1 first!") continue - if haxState == 1: + elif haxState == 1: sanityReport() injection() From d87fd990047ed37ea9df6009eeb3e1ba228cdcfc Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Thu, 20 Jun 2024 22:46:36 -0500 Subject: [PATCH 06/30] print root on main MSET9 menu --- MSET9_installer_script/mset9.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 0f4022e..1647410 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -559,7 +559,7 @@ def print_root(self): for f in ["SafeB9S.bin", "b9", "boot.firm", "boot.3dsx", "boot9strap/", "mset9.py", "mset9.bat", "mset9.command", "_INSTRUCTIONS.txt", "errors.txt"]: try: shutil.move(os.path.join(scriptroot, f), os.path.join(root, f)) - except FileNotFoundError: + except: pass # The sanity checks will deal with that. I just don't want the exception to terminate the script. with open(os.path.join(scriptroot, "Note from MSET9.txt"), "w") as f: @@ -1012,6 +1012,7 @@ def is3DSID(name): print(f"Using {consoleNames[consoleIndex]}") print() print(f"Current MSET9 state: {haxStates[haxState]}") +fs.print_root(); print("\n-- Please type in a number then hit return --\n") From 3133ceb44d228af5231341e96b1fef53d1855f2c Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Sun, 23 Jun 2024 09:54:17 -0500 Subject: [PATCH 07/30] SD card storage does not matter as much --- MSET9_installer_script/mset9.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 1647410..494292a 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -546,6 +546,7 @@ def print_root(self): scriptroot = os.path.dirname(thisfile) # I hate python ternary operator i hate python ternary operator systemroot = os.environ["SYSTEMDRIVE"] if osver == "Windows" else "/" # Never hardcode C:. My Windows drive letter is E:, my SD card or USB drive is often C:. + # Can't stat / on Windows or else we get the current drive if os.stat(scriptroot).st_dev == os.stat(systemroot).st_dev: prbad("Error 01: Script is not running on your SD card!") prinfo(f"Current location: {scriptroot}") @@ -625,10 +626,10 @@ def writeProtectCheck(): prgood("SD card is writeable!") # Section: SD card free space -# ensure 16MB free space -if not fs.ensurespace(16 * 1024 * 1024): +# The 3DS is making the files not us, so let's just make sure we're not at the brim of space left +if not fs.ensurespace(0x10000): # 64KiB #prbad(f"Error 06: You need at least 16MB free space on your SD card, you have {(freeSpace / 1000000):.2f} bytes!") - prbad("Error 06: You need at least 16MB free space on your SD card!") + prbad("Error 06: SD card is out of free space!") prinfo("Please free up some space and try again.") exitOnEnter() From 34243d96666362e6aa788ffce6c6d7be3718b5ef Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Sun, 23 Jun 2024 10:06:46 -0500 Subject: [PATCH 08/30] Nvm, firm backups This reverts commit 3133ceb44d228af5231341e96b1fef53d1855f2c. --- MSET9_installer_script/mset9.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 494292a..1647410 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -546,7 +546,6 @@ def print_root(self): scriptroot = os.path.dirname(thisfile) # I hate python ternary operator i hate python ternary operator systemroot = os.environ["SYSTEMDRIVE"] if osver == "Windows" else "/" # Never hardcode C:. My Windows drive letter is E:, my SD card or USB drive is often C:. - # Can't stat / on Windows or else we get the current drive if os.stat(scriptroot).st_dev == os.stat(systemroot).st_dev: prbad("Error 01: Script is not running on your SD card!") prinfo(f"Current location: {scriptroot}") @@ -626,10 +625,10 @@ def writeProtectCheck(): prgood("SD card is writeable!") # Section: SD card free space -# The 3DS is making the files not us, so let's just make sure we're not at the brim of space left -if not fs.ensurespace(0x10000): # 64KiB +# ensure 16MB free space +if not fs.ensurespace(16 * 1024 * 1024): #prbad(f"Error 06: You need at least 16MB free space on your SD card, you have {(freeSpace / 1000000):.2f} bytes!") - prbad("Error 06: SD card is out of free space!") + prbad("Error 06: You need at least 16MB free space on your SD card!") prinfo("Please free up some space and try again.") exitOnEnter() From fa407c5af24038804f958e114a2136f33c50dedb Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Sun, 23 Jun 2024 17:06:40 -0500 Subject: [PATCH 09/30] Error handling for createHaxID1() https://github.com/Gruetzig/MSET9/commit/4f199e4c8dbcab4ee5a7a8ba0e80131f5eb9c127 --- MSET9_installer_script/mset9.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 1647410..a0bb23f 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -671,7 +671,7 @@ def writeProtectCheck(): ID0, ID0Count, ID1, ID1Count = "", 0, "", 0 -haxStates = ["\033[30;1m ID1 not created \033[0m", "\033[33;1m Not ready to inject \033[0m", "\033[32m Ready to inject \033[0m", "\033[32;1m Injected \033[0m"] +haxStates = ["\033[30;1mID1 not created\033[0m", "\033[33;1mNot ready to inject\033[0m", "\033[32mReady to inject\033[0m", "\033[32;1mInjected\033[0m"] haxState = 0 realID1Path = "" @@ -746,14 +746,28 @@ def createHaxID1(): prinfo("Backing up original ID1...") fs.rename(realID1Path, realID1Path + realID1BackupTag) - prinfo("Creating hacked ID1...") - fs.mkdir(hackedID1Path) - #fs.mkdir(hackedID1Path + "/extdata") - fs.mkdir(hackedID1Path + "/dbs") - fs.open (hackedID1Path + "/dbs/title.db", "w").close() - fs.open (hackedID1Path + "/dbs/import.db", "w").close() + try: + prinfo("Creating hacked ID1...") + fs.mkdir(hackedID1Path) + #fs.mkdir(hackedID1Path + "/extdata") + prinfo("Creating dummy databases...") + fs.mkdir(hackedID1Path + "/dbs") + fs.open (hackedID1Path + "/dbs/title.db", "w").close() + fs.open (hackedID1Path + "/dbs/import.db", "w").close() + except Exception as exc: + if isinstance(exc, OSError) and osver == "Windows" and osver.winerror == 234: # WinError 234 my love + prbad("Error 13: Locale settings are broken!") + prinfo("Consult https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 for instructions.") + prinfo("If you need help, join Nintendo Homebrew on Discord: https://discord.gg/nintendohomebrew") + + # Add linux errno 22 here? + else: + prbad("An unknown error occured!") + prbad(f"Error details: {str(exc)}") + prinfo("Join Nintendo Homebrew on Discord for help: https://discord.gg/nintendohomebrew") + else: + prgood("Created hacked ID1.") - prgood("Created hacked ID1.") exitOnEnter() titleDatabasesGood = False From d36f151b5b85bee0c2534f6a8d1769f5268eac06 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 25 Jun 2024 19:18:44 -0500 Subject: [PATCH 10/30] stub title databases in sanity() (again) --- MSET9_installer_script/mset9.py | 54 +++++++++++---------------------- 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index a0bb23f..8869991 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -686,28 +686,6 @@ def writeProtectCheck(): trigger = "002F003A.txt" # all 3ds ":/" in hex format triggerFilePath = "" - -# 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 fs, haxState, RealID1Path, id0, id1, homeDataPath, miiDataPath, homeHex, miiHex -# -# print() -# prinfo("Performing sanity checks...") -# -# writeProtectCheck() -# - - def createHaxID1(): global fs, ID0, hackedID1Path, realID1Path, realID1BackupTag @@ -720,7 +698,7 @@ def createHaxID1(): print() print("This process will temporarily reset all your 3DS data.") print("All your applications and themes will disappear.") - print("This is perfectly normal, and if everything goes right. it will re-appear") + print("This is perfectly normal, and if everything goes right, it will re-appear") print("at the end of the process.") print() print("In any case, it is highly recommended to make a backup of your SD card's contents to a folder on your PC.") @@ -756,7 +734,7 @@ def createHaxID1(): fs.open (hackedID1Path + "/dbs/import.db", "w").close() except Exception as exc: if isinstance(exc, OSError) and osver == "Windows" and osver.winerror == 234: # WinError 234 my love - prbad("Error 13: Locale settings are broken!") + prbad("Error 18: Windows locale settings are broken!") prinfo("Consult https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 for instructions.") prinfo("If you need help, join Nintendo Homebrew on Discord: https://discord.gg/nintendohomebrew") @@ -778,9 +756,13 @@ def sanity(): global fs, hackedID1Path, titleDatabasesGood, menuExtdataGood, miiExtdataGood prinfo("Checking databases...") - checkTitledb = softcheck(hackedID1Path + "/dbs/title.db", 0x31E400, 0, 1) - checkImportdb = softcheck(hackedID1Path + "/dbs/import.db", 0x31E400, 0, 1) + checkTitledb = softcheck(hackedID1Path + "/dbs/title.db", 0x31E400) + checkImportdb = softcheck(hackedID1Path + "/dbs/import.db", 0x31E400) titleDatabasesGood = not (checkTitledb or checkImportdb) + if not titleDatabasesGood: + # Stub them both. I'm not sure how the console acts if title.db is fine but not import. Someone had that happen, once + fs.open(hackedID1Path + "/dbs/title.db", "w").close() + fs.open(hackedID1Path + "/dbs/import.db", "w").close() # if fs.exists(hackedID1Path + "/extdata/" + trigger): # prinfo("Removing stale trigger...") @@ -882,21 +864,21 @@ def remove(): prgood("Successfully removed MSET9!") -def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): +def softcheck(keyfile, expectedSize = None, crc32 = None): global fs filename = keyfile.rsplit("/")[-1] if not fs.exists(keyfile): prbad(f"{filename} does not exist on SD card!") - return retval + return 1 fileSize = fs.getsize(keyfile) if not fileSize: prbad(f"{filename} is an empty file!") - return retval + return 1 elif expectedSize and fileSize != expectedSize: prbad(f"{filename} is size {fileSize:,} bytes, not expected {expectedSize:,} bytes") - return retval + return 1 if crc32: with fs.open(keyfile, "rb") as f: @@ -904,7 +886,7 @@ def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): f.close() if crc32 != checksum: prbad(f"{filename} was not recognized as the correct file") - return retval + return 1 prgood(f"{filename} looks good!") return 0 @@ -927,11 +909,11 @@ def is3DSID(name): 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) +fileSanity += softcheck("boot9strap/boot9strap.firm", crc32=0x08129C1F) +fileSanity += softcheck("boot.firm") +fileSanity += softcheck("boot.3dsx") +fileSanity += softcheck("b9") +fileSanity += softcheck("SafeB9S.bin") if fileSanity > 0: prbad("Error 08: One or more files are missing or malformed!") From cbfbea624ef8717ec8a4fff12fe0e561c675274b Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 25 Jun 2024 19:50:17 -0500 Subject: [PATCH 11/30] Hide option 3 when trigger is injected --- MSET9_installer_script/mset9.py | 37 +++++++++++++-------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 8869991..e99dafb 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -671,7 +671,7 @@ def writeProtectCheck(): ID0, ID0Count, ID1, ID1Count = "", 0, "", 0 -haxStates = ["\033[30;1mID1 not created\033[0m", "\033[33;1mNot ready to inject\033[0m", "\033[32mReady to inject\033[0m", "\033[32;1mInjected\033[0m"] +haxStates = ["\033[30;1mID1 not created\033[0m", "\033[33;1mNot ready - sanity check failed\033[0m", "\033[32mReady\033[0m", "\033[32;1mInjected\033[0m"] haxState = 0 realID1Path = "" @@ -815,8 +815,6 @@ def sanityReport(): print() - exitOnEnter() - def injection(): global fs, haxState, hackedID1Path, trigger @@ -824,7 +822,7 @@ def injection(): if fs.exists(triggerFilePath): fs.remove(triggerFilePath) - prinfo("Removed trigger file.") + prgood("Removed trigger file.") return prinfo("Injecting trigger file...") @@ -833,23 +831,11 @@ def injection(): f.write("pls be haxxed mister arm9, thx") f.close() prgood("MSET9 successfully injected!") + exitOnEnter() def remove(): global fs, ID0, ID1, hackedID1Path, realID1Path, realID1BackupTag - if haxState == 3: - prinfo("MSET9 trigger is still injected!") - print() - prinfo("Enter '1' to only remove the MSET9 trigger.") - prinfo("Enter '2' to remove the MSET9 ID1 entirely.") - resp = getInput(range(1, 2)) - if resp < 0: - return - - elif resp == 1: - injection() - return - prinfo("Removing MSET9...") if hackedID1Path and fs.exists(hackedID1Path): @@ -1026,7 +1012,8 @@ def is3DSID(name): } print(f"2. {option2label[haxState]}") - print("3. Remove MSET9") + if haxState != 3: + print("3. Remove MSET9") print("0. Exit") @@ -1036,23 +1023,27 @@ def is3DSID(name): fs.reload() # (?) - if optSelect < 1: + if optSelect <= 0: break elif optSelect == 1: createHaxID1() elif optSelect == 2: - if haxState < 1: + if haxState <= 0: prbad("Can't do that now! Please create the MSET9 ID1 first!") continue elif haxState == 1: sanityReport() - - injection() - exitOnEnter() + exitOnEnter() + else: + injection() elif optSelect == 3: + if haxState == 3: + prbad("Can't do that now!") + continue + remove() remove_extra() # (?) exitOnEnter(remount=True) From 07da99b38e141eb39fb7920a3cbc4342a4b7a815 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 26 Jun 2024 11:01:35 -0500 Subject: [PATCH 12/30] Hide option 3 when trigger is injected (2) --- MSET9_installer_script/mset9.py | 115 +++++++++++++++++--------------- 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index e99dafb..6b0b6cb 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -671,7 +671,7 @@ def writeProtectCheck(): ID0, ID0Count, ID1, ID1Count = "", 0, "", 0 -haxStates = ["\033[30;1mID1 not created\033[0m", "\033[33;1mNot ready - sanity check failed\033[0m", "\033[32mReady\033[0m", "\033[32;1mInjected\033[0m"] +haxStates = ["\033[30;1mID1 not created\033[0m", "\033[33;1mNot ready - sanity check failed\033[0m", "\033[32mReady\033[0m", "\033[32;1mInjected\033[0m", "\033[32mRemoved trigger file\033[0m"] haxState = 0 realID1Path = "" @@ -689,11 +689,6 @@ def writeProtectCheck(): def createHaxID1(): global fs, ID0, hackedID1Path, realID1Path, realID1BackupTag - if hackedID1Path and fs.exists(hackedID1Path): - prinfo("Hacked ID1 already exists!") - # prinfo("Are you looking to do the sanity checks? Enter '2' instead.") - return - print("\033[0;33m=== DISCLAIMER ===\033[0m") # 5;33m? The blinking is awesome but I also don't want to frighten users lol print() print("This process will temporarily reset all your 3DS data.") @@ -822,6 +817,7 @@ def injection(): if fs.exists(triggerFilePath): fs.remove(triggerFilePath) + haxState = 4 prgood("Removed trigger file.") return @@ -848,6 +844,7 @@ def remove(): ID1 = ID1[:32] realID1Path = ID0 + "/" + ID1 + haxState = 0 prgood("Successfully removed MSET9!") def softcheck(keyfile, expectedSize = None, crc32 = None): @@ -989,65 +986,79 @@ def is3DSID(name): prinfo("Consult: https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 for help!") exitOnEnter() -clearScreen() -print(f"MSET9 {VERSION} SETUP by zoogie, Aven and DannyAAM") -print(f"Using {consoleNames[consoleIndex]}") -print() -print(f"Current MSET9 state: {haxStates[haxState]}") -fs.print_root(); - -print("\n-- Please type in a number then hit return --\n") - -print("↓ Input one of these numbers!") -print("1. Create MSET9 ID1") +def mainMenu(): + clearScreen() + print(f"MSET9 {VERSION} SETUP by zoogie, Aven and DannyAAM") + print(f"Using {consoleNames[consoleIndex]}") + print() + print(f"Current MSET9 state: {haxStates[haxState]}") + fs.print_root(); -if haxState > 0: - # Not ready (1) - Check for problems - # Ready (2) - Inject - # Injected (3) - Remove inject - option2label = { - 1: "Perform sanity checks", - 2: "Inject MSET9 trigger", - 3: "Remove MSET9 trigger" - } + print("\n-- Please type in a number then hit return --\n") - print(f"2. {option2label[haxState]}") - if haxState != 3: - print("3. Remove MSET9") + print("↓ Input one of these numbers!") + print("1. Create MSET9 ID1") -print("0. Exit") + if haxState > 0: + # Not ready (1) - Check for problems + # Ready (2) - Inject + # Injected (3) - Remove inject + option2label = { + 1: "Perform sanity checks", + 2: "Inject MSET9 trigger", + 3: "Remove MSET9 trigger", + 4: "Remove MSET9 trigger" + } + print(f"2. {option2label[haxState]}") + if haxState != 3: + print("3. Remove MSET9") -while 1: - optSelect = getInput(range(0, 3)) + print("0. Exit") - fs.reload() # (?) + while 1: + optSelect = getInput(range(0, 3)) - if optSelect <= 0: - break + fs.reload() # (?) - elif optSelect == 1: - createHaxID1() + if optSelect <= 0: + break - elif optSelect == 2: - if haxState <= 0: - prbad("Can't do that now! Please create the MSET9 ID1 first!") - continue - elif haxState == 1: - sanityReport() + elif optSelect == 1: + if haxState > 0: + prinfo("Hacked ID1 already exists!") + continue + createHaxID1() exitOnEnter() - else: - injection() - elif optSelect == 3: - if haxState == 3: - prbad("Can't do that now!") - continue + elif optSelect == 2: + if haxState <= 0: + prbad("Can't do that now! Please create the MSET9 ID1 first!") + continue + elif haxState == 1: + sanityReport() + exitOnEnter() + elif haxState == 2: + injection() + exitOnEnter() + elif haxState == 3: + injection() + time.sleep(3) + return mainMenu() + elif haxState == 4: + prinfo("Already removed trigger file.") + continue - remove() - remove_extra() # (?) - exitOnEnter(remount=True) + elif optSelect == 3: + if haxState == 3: + prbad("Can't do that now!") + continue + + remove() + remove_extra() # (?) + exitOnEnter(remount=True) +mainMenu() cleanup(remount=True) prgood("Goodbye!") time.sleep(2) From 05ef9660776d7771150231b3772e3c8492d91984 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 26 Jun 2024 11:36:16 -0500 Subject: [PATCH 13/30] stil suggest reformatting SD for no home menu extdata --- MSET9_installer_script/mset9.py | 1 + 1 file changed, 1 insertion(+) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 6b0b6cb..3d892cd 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -788,6 +788,7 @@ def sanityReport(): prbad("HOME menu extdata: Missing!") prinfo("Please power on your console with your SD inserted, then check again.") prinfo("If this does not work, power it on while holding L+R+Down+B.") + prinfo("If this still does not work, your SD card may need to be reformatted.") else: prgood("HOME menu extdata: OK!") From 631c7bd1bb83a48b375c035cc1b91b3f7f649164 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 26 Jun 2024 12:18:53 -0500 Subject: [PATCH 14/30] Fix hax id1 swap being wack --- MSET9_installer_script/mset9.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 3d892cd..7476514 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -758,26 +758,18 @@ def sanity(): # Stub them both. I'm not sure how the console acts if title.db is fine but not import. Someone had that happen, once fs.open(hackedID1Path + "/dbs/title.db", "w").close() fs.open(hackedID1Path + "/dbs/import.db", "w").close() - - # if fs.exists(hackedID1Path + "/extdata/" + trigger): - # prinfo("Removing stale trigger...") - # fs.remove(hackedID1Path + "/extdata/" + trigger) - - extdataRoot = hackedID1Path + "/extdata/00000000" prinfo("Checking for HOME Menu extdata...") for i in homeMenuExtdata: - extdataRegionCheck = extdataRoot + f"/{i:08X}" + extdataRegionCheck = hackedID1Path + f"/extdata/00000000/{i:08X}" if fs.exists(extdataRegionCheck): - # prgood(f"Detected {regionTable[i]} HOME Menu data!") menuExtdataGood = True break prinfo("Checking for Mii Maker extdata...") for i in miiMakerExtdata: - extdataRegionCheck = extdataRoot + f"/{i:08X}" + extdataRegionCheck = hackedID1Path + f"/extdata/00000000/{i:08X}" if fs.exists(extdataRegionCheck): - # prgood("Found Mii Maker data!") miiExtdataGood = True break @@ -967,14 +959,12 @@ def is3DSID(name): elif choice == currentHaxID1index: consoleIndex = currentHaxID1index hackedID1 = dirname - break elif choice == consoleIndex: fs.rename(fullpath, ID0 + "/" + hackedID1) - break hackedID1Path = ID0 + "/" + hackedID1 - haxState = 1 # Created/Not ready. + haxState = 1 # Not ready. if fs.exists(hackedID1Path + "/extdata/" + trigger): triggerFilePath = hackedID1Path + "/extdata/" + trigger @@ -1051,6 +1041,9 @@ def mainMenu(): continue elif optSelect == 3: + if haxState <= 0: + prinfo("Nothing to do.") + continue if haxState == 3: prbad("Can't do that now!") continue From b50173a325e4b33843a57803c5cbb58bce60e2b4 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Thu, 27 Jun 2024 21:14:45 -0500 Subject: [PATCH 15/30] Separate number for each option --- MSET9_installer_script/mset9.py | 83 +++++++++++++-------------------- 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 7476514..2cd39e0 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -50,9 +50,6 @@ def rmtree(self, path): def copytree(self, src, dst): pass @abc.abstractmethod - def walk(self, path, topdown=False): - pass - @abc.abstractmethod def is_writable(self): pass @abc.abstractmethod @@ -427,9 +424,6 @@ def copytree(self, src, dst): self.fs.copydir(src, dst, create=True) def listdir(self, path): return self.fs.listdir(path) - def walk(self, path, topdown=False): # topdown is ignored - for dir_path, dirs, files in self.fs.walk(path): - yield dir_path, list(map(lambda x: x.name, dirs)), list(map(lambda x: x.name, files)) def is_writable(self): try: with self.open("test.txt", "w") as f: @@ -517,8 +511,6 @@ def copytree(self, src, dst): shutil.copytree(self.abs(src), self.abs(dst)) def listdir(self, path): return os.listdir(path) - def walk(self, path, topdown=False): - return os.walk(self.abs(path), topdown=topdown) def is_writable(self): writable = os.access(self.root, os.W_OK) try: # Bodge for windows @@ -594,8 +586,10 @@ def getInput(options): try: opt = int(input(">>> ")) except KeyboardInterrupt: + print() return -1 except EOFError: + print() return -1 except ValueError: opt = 0xFFFFFFFF @@ -665,7 +659,6 @@ def writeProtectCheck(): consoleIndex = getInput(range(1, 4)) if consoleIndex < 0: - print() prgood("Goodbye!") exitOnEnter(remount=True) @@ -680,8 +673,7 @@ def writeProtectCheck(): hackedID1 = bytes.fromhex(encodedID1s[consoleIndex]).decode("utf-16le") # ID1 - arm injected payload in readable f hackedID1Path = "" -extdataRoot = "" -homeMenuExtdata = [0x8F, 0x98, 0x82, 0xA1, 0xA9, 0xB1] # us,eu,jp,ch,kr,tw +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 trigger = "002F003A.txt" # all 3ds ":/" in hex format triggerFilePath = "" @@ -988,59 +980,50 @@ def mainMenu(): print("\n-- Please type in a number then hit return --\n") print("↓ Input one of these numbers!") - print("1. Create MSET9 ID1") - - if haxState > 0: - # Not ready (1) - Check for problems - # Ready (2) - Inject - # Injected (3) - Remove inject - option2label = { - 1: "Perform sanity checks", - 2: "Inject MSET9 trigger", - 3: "Remove MSET9 trigger", - 4: "Remove MSET9 trigger" - } - - print(f"2. {option2label[haxState]}") - if haxState != 3: - print("3. Remove MSET9") + + # Not ready (1) - Check for problems + # Ready (2) - Inject + # Injected (3) - Remove inject + optionlabel = { + 0: "Create MSET9 ID1", + 1: "Perform sanity checks", + 2: "Inject MSET9 trigger", + 3: "Remove MSET9 trigger", + } + + if haxState != 4: + print(f"{haxState + 1}. {optionlabel[haxState]}") + if haxState > 0 and haxState != 3: + print("5. Remove MSET9") print("0. Exit") while 1: - optSelect = getInput(range(0, 3)) + optSelect = getInput([haxState + 1, 5, 0]) fs.reload() # (?) if optSelect <= 0: break - elif optSelect == 1: - if haxState > 0: - prinfo("Hacked ID1 already exists!") - continue + elif optSelect == 1: # Create hacked ID1 createHaxID1() exitOnEnter() - elif optSelect == 2: - if haxState <= 0: - prbad("Can't do that now! Please create the MSET9 ID1 first!") - continue - elif haxState == 1: - sanityReport() - exitOnEnter() - elif haxState == 2: - injection() - exitOnEnter() - elif haxState == 3: - injection() - time.sleep(3) - return mainMenu() - elif haxState == 4: - prinfo("Already removed trigger file.") - continue + elif optSelect == 2: # Perform sanity checks + sanityReport() + exitOnEnter() + + elif optSelect == 3: # Inject trigger file + injection() + exitOnEnter() + + elif optSelect == 4: # Remove trigger file + injection() + time.sleep(3) + return mainMenu() - elif optSelect == 3: + elif optSelect == 5: # Remove MSET9 if haxState <= 0: prinfo("Nothing to do.") continue From 0071b18a2314af0e54e0829eb79abdd066baa4b9 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Fri, 28 Jun 2024 06:21:20 -0500 Subject: [PATCH 16/30] SD root magic for macOS (hopefully) --- MSET9_installer_script/mset9.py | 80 ++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 2cd39e0..f109b04 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -70,8 +70,46 @@ def remove_extra(): osver = platform.system() thisfile = os.path.abspath(__file__) +scriptroot = os.path.dirname(thisfile) systmp = None +# I hate python ternary operator i hate python ternary operator +systemroot = os.environ["SYSTEMDRIVE"] if osver == "Windows" else "/" # Never hardcode C:. My Windows drive letter is E:, my SD card or USB drive is often C:. +if os.stat(scriptroot).st_dev == os.stat(systemroot).st_dev: + prbad("Error 01: Script is not running on your SD card!") + prinfo(f"Current location: {scriptroot}") + exitOnEnter() + +def dig_for_root(): + global thisfile, scriptroot + + if not os.path.ismount(scriptroot): + root = scriptroot + while not os.path.ismount(root) and root != os.path.dirname(root): + root = os.path.dirname(root) + + for f in ["SafeB9S.bin", "b9", "boot.firm", "boot.3dsx", "boot9strap/", "mset9.py", "mset9.bat", "mset9.command", "_INSTRUCTIONS.txt", "errors.txt"]: + try: + shutil.move(os.path.join(scriptroot, f), os.path.join(root, f)) + except: + pass # The sanity checks will deal with that. I just don't want the exception to terminate the script. + + with open(os.path.join(scriptroot, "Note from MSET9.txt"), "w") as f: + f.write("Hey!\n") + f.write("All the MSET9 files have been moved to the root of your SD card.\n\n") + + f.write("\"What is the 'root of my SD card'...?\"\n") + f.write("The root is 'not inside any folder'.\n") + f.write("This is where you can see your 'Nintendo 3DS' folder. (It is not inside the Nintendo 3DS folder itself!)\n\n") + + f.write("Reference image: https://3ds.hacks.guide/images/screenshots/onboarding/sdroot.png\n\n") + + f.write(f"At the time of writing, the root of your SD card is at: '{root}'. Check it out!\n") + f.close() + + scriptroot = root + thisfile = os.path.join(scriptroot, "mset9.py") + if osver == "Darwin": # ======== macOS / iOS? ======== import sys @@ -100,13 +138,15 @@ def run_diskutil_and_wait(command, dev): return subprocess.run(["diskutil", *command, dev], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode if len(sys.argv) < 2: - if not thisfile.startswith("/Volumes/"): + if not scriptroot.startswith("/Volumes/"): prbad("Error 01: Couldn't find Nintendo 3DS folder! Ensure that you are running this script from the root of the SD card.") # should we add some macos specific message? exitOnEnter() + + dig_for_root() prinfo("Resolving device...") device = None - devid = os.stat(thisfile).st_dev + devid = os.stat(scriptroot).st_dev for devname in os.listdir("/dev"): if not devname.startswith("disk"): continue @@ -534,41 +574,7 @@ def reload(self): def print_root(self): prinfo(f"Current dir: {self.root}") - - scriptroot = os.path.dirname(thisfile) - # I hate python ternary operator i hate python ternary operator - systemroot = os.environ["SYSTEMDRIVE"] if osver == "Windows" else "/" # Never hardcode C:. My Windows drive letter is E:, my SD card or USB drive is often C:. - if os.stat(scriptroot).st_dev == os.stat(systemroot).st_dev: - prbad("Error 01: Script is not running on your SD card!") - prinfo(f"Current location: {scriptroot}") - exitOnEnter() - - if not os.path.ismount(scriptroot): - root = scriptroot - while not os.path.ismount(root) and root != os.path.dirname(root): - root = os.path.dirname(root) - - for f in ["SafeB9S.bin", "b9", "boot.firm", "boot.3dsx", "boot9strap/", "mset9.py", "mset9.bat", "mset9.command", "_INSTRUCTIONS.txt", "errors.txt"]: - try: - shutil.move(os.path.join(scriptroot, f), os.path.join(root, f)) - except: - pass # The sanity checks will deal with that. I just don't want the exception to terminate the script. - - with open(os.path.join(scriptroot, "Note from MSET9.txt"), "w") as f: - f.write("Hey!\n") - f.write("All the MSET9 files have been moved to the root of your SD card.\n\n") - - f.write("\"What is the 'root of my SD card'...?\"\n") - f.write("The root is 'not inside any folder'.\n") - f.write("This is where you can see your 'Nintendo 3DS' folder. (It is not inside the Nintendo 3DS folder itself!)\n\n") - - f.write("Reference image: https://3ds.hacks.guide/images/screenshots/onboarding/sdroot.png\n\n") - - f.write(f"At the time of writing, the root of your SD card is at: '{root}'. Check it out!\n") - f.close() - - scriptroot = root - + dig_for_root() fs = OSFS(scriptroot) def clearScreen(): From 0c5f63f4fd24040753f774f7bc185d2b496ee01b Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 9 Jul 2024 10:06:20 -0500 Subject: [PATCH 17/30] (Add linux errno 22 here) --- MSET9_installer_script/mset9.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index f109b04..ccf98ab 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -81,6 +81,7 @@ def remove_extra(): exitOnEnter() def dig_for_root(): + import shutil global thisfile, scriptroot if not os.path.ismount(scriptroot): @@ -676,7 +677,7 @@ def writeProtectCheck(): realID1Path = "" realID1BackupTag = "_user-id1" -hackedID1 = bytes.fromhex(encodedID1s[consoleIndex]).decode("utf-16le") # ID1 - arm injected payload in readable f +hackedID1 = bytes.fromhex(encodedID1s[consoleIndex]).decode("utf-16le") # ID1 - arm injected payload in readable format hackedID1Path = "" homeMenuExtdata = [0x8F, 0x98, 0x82, 0xA1, 0xA9, 0xB1] # us,eu,jp,ch,kr,tw @@ -710,35 +711,36 @@ def createHaxID1(): prinfo("Cancelled.") exitOnEnter(remount=True) - hackedID1Path = ID0 + "/" + hackedID1 - if not realID1Path.endswith(realID1BackupTag): - prinfo("Backing up original ID1...") - fs.rename(realID1Path, realID1Path + realID1BackupTag) - try: prinfo("Creating hacked ID1...") fs.mkdir(hackedID1Path) - #fs.mkdir(hackedID1Path + "/extdata") prinfo("Creating dummy databases...") fs.mkdir(hackedID1Path + "/dbs") fs.open (hackedID1Path + "/dbs/title.db", "w").close() fs.open (hackedID1Path + "/dbs/import.db", "w").close() except Exception as exc: - if isinstance(exc, OSError) and osver == "Windows" and osver.winerror == 234: # WinError 234 my love + if isinstance(exc, OSError) and osver == "Windows" and exc.winerror == 234: # WinError 234 my love prbad("Error 18: Windows locale settings are broken!") prinfo("Consult https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 for instructions.") prinfo("If you need help, join Nintendo Homebrew on Discord: https://discord.gg/nintendohomebrew") - - # Add linux errno 22 here? + elif isinstance(exc, OSError) and osver == "Linux" and exc.errno == 22: # Don't want this message to display on Windows if it ever manages to + prbad("Failed to create hacked ID1!") # Give this an error number? + prbad(f"Error details: {str(exc)}") + prinfo("Please unmount your SD card and remount it with the 'utf8' option.") # Should we do this ourself? Like look at macOS else: prbad("An unknown error occured!") prbad(f"Error details: {str(exc)}") prinfo("Join Nintendo Homebrew on Discord for help: https://discord.gg/nintendohomebrew") - else: - prgood("Created hacked ID1.") + exitOnEnter() + + if not realID1Path.endswith(realID1BackupTag): + prinfo("Backing up original ID1...") + fs.rename(realID1Path, realID1Path + realID1BackupTag) + + prgood("Created hacked ID1.") exitOnEnter() titleDatabasesGood = False @@ -777,8 +779,7 @@ def sanityReport(): if not menuExtdataGood: prbad("HOME menu extdata: Missing!") prinfo("Please power on your console with your SD inserted, then check again.") - prinfo("If this does not work, power it on while holding L+R+Down+B.") - prinfo("If this still does not work, your SD card may need to be reformatted.") + prinfo("If this does not work, your SD card may need to be reformatted.") else: prgood("HOME menu extdata: OK!") From bb9e56213a2605beb358c73993784cc0836227a3 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 9 Jul 2024 10:12:13 -0500 Subject: [PATCH 18/30] Update error numbers --- MSET9_installer_script/errors.txt | 13 ++++++------- MSET9_installer_script/mset9.py | 9 +++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/MSET9_installer_script/errors.txt b/MSET9_installer_script/errors.txt index cfbfafe..021d401 100644 --- a/MSET9_installer_script/errors.txt +++ b/MSET9_installer_script/errors.txt @@ -1,18 +1,17 @@ Error 01: Not running on the SD Card root. /Nintendo 3DS/ not found. Error 02: Write protected SD Card. Ensure switch is facing up. Error 03: Attempting to change target console version during exploit. -Error 04: No Home Menu Extdata. SD needs reformatting, follow MSET9 Troubleshooting page. -Error 05: No Mii Maker Data, follow MSET9 Troubleshooting page. +Error 04: Multiple ID0s, follow MSET9 Troubleshooting page. +Error 05: Multiple ID1s, follow MSET9 Troubleshooting page. Error 06: Not enough free space on disk. Clean out unused files. -Error 07: Multiple ID0s, follow MSET9 Troubleshooting page. -Error 08: Missing/Malformed file. Reextract MSET9 zip file. -Error 09: Could not change back into SD directory. Ensure the SD Card has been reinserted. -Error 10: Database problem. Follow the troubleshooting to reset the DBs. -Error 12: Multiple ID1s, follow MSET9 Troubleshooting page. +Error 07: Missing/Malformed file. Reextract MSET9 zip file. +Error 08: Could not change back into SD directory. Ensure the SD Card has been reinserted. + Error 13: Device doesn't exist. Error 14: Can't open device. Error 15: Not FAT32 formatted or corrupted filesystem. Error 16: Unable to umount SD card. Error 17: Root privilege is required. +Error 18: Winerror 234, Windows UTF Settings needs adjustment, follow MSET9 Troubleshooting page MSET9 Troubleshooting Page: https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index ccf98ab..d23d6bc 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -570,7 +570,7 @@ def reload(self): try: os.chdir(self.root) except Exception: - prbad("Error 09: Couldn't reapply working directory, is SD card reinserted?") + prbad("Error 08: Couldn't reapply working directory, is SD card reinserted?") exitOnEnter() def print_root(self): prinfo(f"Current dir: {self.root}") @@ -818,6 +818,7 @@ def injection(): #f.write("not so useless FAT-fs null deref") f.write("pls be haxxed mister arm9, thx") f.close() + prgood("MSET9 successfully injected!") exitOnEnter() @@ -891,7 +892,7 @@ def is3DSID(name): fileSanity += softcheck("SafeB9S.bin") if fileSanity > 0: - prbad("Error 08: One or more files are missing or malformed!") + prbad("Error 07: One or more files are missing or malformed!") prinfo("Please re-extract the MSET9 zip file, overwriting any existing files when prompted.") exitOnEnter() @@ -913,7 +914,7 @@ def is3DSID(name): ID0Count += 1 if ID0Count != 1: - prbad(f"Error 07: You don't have 1 ID0 in your Nintendo 3DS folder, you have {ID0Count}!") + prbad(f"Error 04: 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() @@ -972,7 +973,7 @@ def is3DSID(name): haxState = 2 # Ready! if ID1Count != 1: - prbad(f"Error 12: You don't have 1 ID1 in your Nintendo 3DS folder, you have {ID1Count}!") + prbad(f"Error 05: 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() From e625ee5455842495d4a9068168bbd44cf5ab8c81 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 9 Jul 2024 15:50:33 -0500 Subject: [PATCH 19/30] Copy title dbs to user ID1 after removal --- MSET9_installer_script/mset9.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index d23d6bc..86dbcdf 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -823,11 +823,15 @@ def injection(): exitOnEnter() def remove(): - global fs, ID0, ID1, hackedID1Path, realID1Path, realID1BackupTag + global fs, ID0, ID1, hackedID1Path, realID1Path, realID1BackupTag, titleDatabasesGood prinfo("Removing MSET9...") if hackedID1Path and fs.exists(hackedID1Path): + if not fs.exists(realID1Path + "/dbs") and titleDatabasesGood: + prinfo("Moving databases to user ID1...") + fs.rename(hackedID1Path + "/dbs", realID1Path + "/dbs") + prinfo("Deleting hacked ID1...") fs.rmtree(hackedID1Path) From 5a234d2a874bc5ac137220a23b18da368f5088bc Mon Sep 17 00:00:00 2001 From: Anonymous941 <36797492+Anonymous941@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:23:46 -0400 Subject: [PATCH 20/30] Improve root detection (#1) This will cause it to not fail on Cygwin --- MSET9_installer_script/mset9.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 86dbcdf..ef70d97 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -1,5 +1,5 @@ #!/usr/bin/python3 -import abc, os, platform, time, binascii +import abc, sys, os, platform, time, pathlib, binascii VERSION = "v1.2c" @@ -73,8 +73,7 @@ def remove_extra(): scriptroot = os.path.dirname(thisfile) systmp = None -# I hate python ternary operator i hate python ternary operator -systemroot = os.environ["SYSTEMDRIVE"] if osver == "Windows" else "/" # Never hardcode C:. My Windows drive letter is E:, my SD card or USB drive is often C:. +systemroot = pathlib.Path(sys.executable).anchor # Never hardcode C:. My Windows drive letter is E:, my SD card or USB drive is often C:. if os.stat(scriptroot).st_dev == os.stat(systemroot).st_dev: prbad("Error 01: Script is not running on your SD card!") prinfo(f"Current location: {scriptroot}") @@ -113,7 +112,6 @@ def dig_for_root(): if osver == "Darwin": # ======== macOS / iOS? ======== - import sys tmpprefix = "mset9-macos-run-" From 2e755f1471a93456f5714d65f99497a6ee5e931e Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 16 Jul 2024 18:43:44 -0500 Subject: [PATCH 21/30] User friendly messages for unavailable options --- MSET9_installer_script/mset9.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index ef70d97..4d216ec 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -800,11 +800,15 @@ def sanityReport(): print() -def injection(): +def injection(create=True): global fs, haxState, hackedID1Path, trigger triggerFilePath = hackedID1Path + "/extdata/" + trigger + if not fs.exists(triggerFilePath) ^ create: + prbad(f"Trigger file already {'injected' if create else 'removed'}!") + return + if fs.exists(triggerFilePath): fs.remove(triggerFilePath) haxState = 4 @@ -813,7 +817,6 @@ def injection(): prinfo("Injecting trigger file...") with fs.open(triggerFilePath, 'w') as f: - #f.write("not so useless FAT-fs null deref") f.write("pls be haxxed mister arm9, thx") f.close() @@ -966,13 +969,15 @@ def is3DSID(name): fs.rename(fullpath, ID0 + "/" + hackedID1) hackedID1Path = ID0 + "/" + hackedID1 - haxState = 1 # Not ready. + sanityOK = sanity() if fs.exists(hackedID1Path + "/extdata/" + trigger): triggerFilePath = hackedID1Path + "/extdata/" + trigger haxState = 3 # Injected. - elif sanity(): + elif sanityOK: haxState = 2 # Ready! + else: + haxState = 1 # Not ready... if ID1Count != 1: prbad(f"Error 05: You don't have 1 ID1 in your Nintendo 3DS folder, you have {ID1Count}!") @@ -1009,7 +1014,7 @@ def mainMenu(): print("0. Exit") while 1: - optSelect = getInput([haxState + 1, 5, 0]) + optSelect = getInput(range(0, 5)) fs.reload() # (?) @@ -1017,6 +1022,9 @@ def mainMenu(): break elif optSelect == 1: # Create hacked ID1 + if hackedID1Path and fs.exists(hackedID1Path): + prinfo("Hacked ID1 already exists.") + continue createHaxID1() exitOnEnter() @@ -1025,11 +1033,14 @@ def mainMenu(): exitOnEnter() elif optSelect == 3: # Inject trigger file - injection() - exitOnEnter() + if haxState == 4: + prbad("Can't do that now!") + continue + injection(create=True) + # exitOnEnter() # has it's own elif optSelect == 4: # Remove trigger file - injection() + injection(create=False) time.sleep(3) return mainMenu() From 8ae4f74bc2f6757f2ed5a15d33187c60b5446c76 Mon Sep 17 00:00:00 2001 From: thepikachugamer <44107089+Naim2000@users.noreply.github.com> Date: Wed, 17 Jul 2024 07:21:43 -0500 Subject: [PATCH 22/30] Create dbs folder if it does not exist somehow --- MSET9_installer_script/mset9.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 4d216ec..dd1a0ff 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -753,6 +753,8 @@ def sanity(): checkImportdb = softcheck(hackedID1Path + "/dbs/import.db", 0x31E400) titleDatabasesGood = not (checkTitledb or checkImportdb) if not titleDatabasesGood: + if not fs.exists(hackedID1Path + "/dbs"): + fs.mkdir(hackedID1Path + "/dbs") # Stub them both. I'm not sure how the console acts if title.db is fine but not import. Someone had that happen, once fs.open(hackedID1Path + "/dbs/title.db", "w").close() fs.open(hackedID1Path + "/dbs/import.db", "w").close() From ebf9bcd59fbf4012b542fad763163b8076757c36 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 17 Jul 2024 12:13:38 -0500 Subject: [PATCH 23/30] remove fs.print_root() from mainMenu() --- MSET9_installer_script/mset9.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index dd1a0ff..05a17ae 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -992,7 +992,7 @@ def mainMenu(): print(f"Using {consoleNames[consoleIndex]}") print() print(f"Current MSET9 state: {haxStates[haxState]}") - fs.print_root(); +# fs.print_root(); # <-- Why did I add a semicolon here print("\n-- Please type in a number then hit return --\n") From a166d59bcba0bb92da41b02da3feef3908fabd07 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 17 Jul 2024 16:55:04 -0500 Subject: [PATCH 24/30] Show all options at all times* + print root in status check --- MSET9_installer_script/mset9.py | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 05a17ae..7988244 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -748,6 +748,8 @@ def createHaxID1(): def sanity(): global fs, hackedID1Path, titleDatabasesGood, menuExtdataGood, miiExtdataGood + fs.print_root() + prinfo("Checking databases...") checkTitledb = softcheck(hackedID1Path + "/dbs/title.db", 0x31E400) checkImportdb = softcheck(hackedID1Path + "/dbs/import.db", 0x31E400) @@ -998,22 +1000,15 @@ def mainMenu(): print("↓ Input one of these numbers!") - # Not ready (1) - Check for problems - # Ready (2) - Inject - # Injected (3) - Remove inject - optionlabel = { - 0: "Create MSET9 ID1", - 1: "Perform sanity checks", - 2: "Inject MSET9 trigger", - 3: "Remove MSET9 trigger", - } - - if haxState != 4: - print(f"{haxState + 1}. {optionlabel[haxState]}") - if haxState > 0 and haxState != 3: + print("1. Create MSET9 ID1") + print("2. Check MSET9 status") + print("3. Inject trigger file") + print("4. Remove trigger file") + + if haxState != 3: print("5. Remove MSET9") - print("0. Exit") + print("\n0. Exit") while 1: optSelect = getInput(range(0, 5)) @@ -1024,24 +1019,29 @@ def mainMenu(): break elif optSelect == 1: # Create hacked ID1 - if hackedID1Path and fs.exists(hackedID1Path): + if haxState > 0: prinfo("Hacked ID1 already exists.") continue createHaxID1() exitOnEnter() - elif optSelect == 2: # Perform sanity checks + elif optSelect == 2: # Check status + if haxState == 0: # MSET9 ID1 not present + prbad("Can't do that now!") + continue sanityReport() exitOnEnter() elif optSelect == 3: # Inject trigger file - if haxState == 4: + if haxState != 2: # Ready to inject prbad("Can't do that now!") continue injection(create=True) # exitOnEnter() # has it's own elif optSelect == 4: # Remove trigger file + if haxState < 2: + prbad("Can't do that now!") injection(create=False) time.sleep(3) return mainMenu() From bd4e9eccbb0f8afe940d79ed869f42a0cefd16fa Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 17 Jul 2024 17:17:09 -0500 Subject: [PATCH 25/30] change not ready message --- MSET9_installer_script/mset9.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 7988244..cd4f60b 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -669,7 +669,7 @@ def writeProtectCheck(): ID0, ID0Count, ID1, ID1Count = "", 0, "", 0 -haxStates = ["\033[30;1mID1 not created\033[0m", "\033[33;1mNot ready - sanity check failed\033[0m", "\033[32mReady\033[0m", "\033[32;1mInjected\033[0m", "\033[32mRemoved trigger file\033[0m"] +haxStates = ["\033[30;1mID1 not created\033[0m", "\033[33;1mNot ready - check MSET9 status for more details\033[0m", "\033[32mReady\033[0m", "\033[32;1mInjected\033[0m", "\033[32mRemoved trigger file\033[0m"] haxState = 0 realID1Path = "" From eed735f19c431a0cb8c7832c17325649c508d047 Mon Sep 17 00:00:00 2001 From: Blueness Date: Sat, 20 Jul 2024 13:00:50 -0600 Subject: [PATCH 26/30] Update the credits --- MSET9_installer_script/mset9.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index cd4f60b..9b0a39a 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -632,7 +632,7 @@ def writeProtectCheck(): exitOnEnter() clearScreen() -print(f"MSET9 {VERSION} SETUP by zoogie, Aven and DannyAAM") +print(f"MSET9 {VERSION} SETUP by zoogie, Aven, DannyAAM and thepikachugamer") 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)") From bb91d1b6d89952afbeda6dfb59d30b883a30008a Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Mon, 22 Jul 2024 18:53:54 -0500 Subject: [PATCH 27/30] mset9.bat -> MSET9-Windows.bat --- MSET9_installer_script/{mset9.bat => MSET9-Windows.bat} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename MSET9_installer_script/{mset9.bat => MSET9-Windows.bat} (100%) diff --git a/MSET9_installer_script/mset9.bat b/MSET9_installer_script/MSET9-Windows.bat similarity index 100% rename from MSET9_installer_script/mset9.bat rename to MSET9_installer_script/MSET9-Windows.bat From 67c12d0c7441a0d86100a792706679e55b79a8f6 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Mon, 22 Jul 2024 18:54:30 -0500 Subject: [PATCH 28/30] mset9.command -> MSET9-macOS.command --- MSET9_installer_script/{mset9.command => MSET9-macOS.command} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename MSET9_installer_script/{mset9.command => MSET9-macOS.command} (100%) diff --git a/MSET9_installer_script/mset9.command b/MSET9_installer_script/MSET9-macOS.command similarity index 100% rename from MSET9_installer_script/mset9.command rename to MSET9_installer_script/MSET9-macOS.command From 00da5c9592ffba46a9547304be85bd7e1dcf69f2 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Thu, 25 Jul 2024 14:06:06 -0500 Subject: [PATCH 29/30] credits mishap --- MSET9_installer_script/mset9.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 9b0a39a..03496df 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -990,11 +990,10 @@ def is3DSID(name): def mainMenu(): clearScreen() - print(f"MSET9 {VERSION} SETUP by zoogie, Aven and DannyAAM") + print(f"MSET9 {VERSION} SETUP by zoogie, Aven, DannyAAM and thepikachugamer") print(f"Using {consoleNames[consoleIndex]}") print() print(f"Current MSET9 state: {haxStates[haxState]}") -# fs.print_root(); # <-- Why did I add a semicolon here print("\n-- Please type in a number then hit return --\n") From 45438060bbd32a0aba38b97ba31aa88cf83a95da Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Thu, 25 Jul 2024 14:06:26 -0500 Subject: [PATCH 30/30] fs.print_root() was in the wrong sanity function --- MSET9_installer_script/mset9.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 03496df..2ce8975 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -748,8 +748,6 @@ def createHaxID1(): def sanity(): global fs, hackedID1Path, titleDatabasesGood, menuExtdataGood, miiExtdataGood - fs.print_root() - prinfo("Checking databases...") checkTitledb = softcheck(hackedID1Path + "/dbs/title.db", 0x31E400) checkImportdb = softcheck(hackedID1Path + "/dbs/import.db", 0x31E400) @@ -778,6 +776,8 @@ def sanity(): return menuExtdataGood and miiExtdataGood and titleDatabasesGood def sanityReport(): + fs.print_root() + if not menuExtdataGood: prbad("HOME menu extdata: Missing!") prinfo("Please power on your console with your SD inserted, then check again.")