From 25bb96379b8ea855cd31c2d066056b3ef9d0e639 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 2 Apr 2025 15:48:04 -0500 Subject: [PATCH 01/15] call `sync(2)` on Linux on exit https://github.com/zoogie/MSET9/issues/35#issuecomment-2294239128 --- MSET9_installer_script/mset9.py | 1 - 1 file changed, 1 deletion(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index cd7fc22..bb46c8a 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -134,7 +134,6 @@ def append_syllable(): verify_device() dig_for_root() - try_chdir() def clearScreen(): From a63c87b6e5df7e61f54934220e7f239c4589085a Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 2 Apr 2025 15:49:08 -0500 Subject: [PATCH 02/15] check if the user is opening StreetPass Mii Plaza --- MSET9_installer_script/mset9.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index bb46c8a..8602df4 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -305,9 +305,10 @@ def createHaxID1(): titleDatabasesGood = False menuExtdataGood = False miiExtdataGood = False +miiPlazaExtdata = False def sanity(): - global fs, hackedID1Path, titleDatabasesGood, menuExtdataGood, miiExtdataGood + global fs, hackedID1Path, titleDatabasesGood, menuExtdataGood, miiExtdataGood, miiPlazaExtdata prinfo("Checking databases...") checkTitledb = softcheck(hackedID1Path + "/dbs/title.db", 0x31E400) @@ -322,15 +323,16 @@ def sanity(): prinfo("Checking for HOME Menu extdata...") for i in homeMenuExtdata: - extdataRegionCheck = hackedID1Path + f"/extdata/00000000/{i:08X}" - if os.path.exists(abs(extdataRegionCheck)): + if os.path.exists(abs(hackedID1Path + f"/extdata/00000000/{i:08X}")): menuExtdataGood = True break prinfo("Checking for Mii Maker extdata...") for i in miiMakerExtdata: - extdataRegionCheck = hackedID1Path + f"/extdata/00000000/{i:08X}" - if os.path.exists(abs(extdataRegionCheck)): + if os.path.exists(abs(hackedID1Path + f"/extdata/00000000/{i+1:08X}")): + miiPlazaExtdata = True + + if os.path.exists(abs(hackedID1Path + f"/extdata/00000000/{i:08X}")): miiExtdataGood = True break @@ -351,6 +353,9 @@ def sanityReport(): if not miiExtdataGood: prbad("Mii Maker extdata: Missing!") prinfo("Please power on your console with your SD inserted, then launch Mii Maker.") + # * + if miiPlazaExtdata: + prinfo("Reminder: Mii Maker is the app with the \"Mii\" icon; StreetPass Mii Plaza is different!") else: prgood("Mii Maker extdata: OK!") From 47847dfdcf8a47dd71d4213d4d28f3204df5887c Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 2 Apr 2025 15:49:50 -0500 Subject: [PATCH 03/15] tell the user how to make the Nintendo 3DS folder --- MSET9_installer_script/mset9.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 8602df4..b5faabe 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -167,9 +167,18 @@ def getInput(options): # Section: insureRoot if not os.path.exists(abs("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.") - prbad("If that doesn't work, eject the SD card, and put it back in your console. Turn it on and off again, then rerun this script.") - prinfo(f"Current dir: {scriptroot}") + prbad("Error 01: Nintendo 3DS folder not found!") + prinfo(f"Current location: {scriptroot}") + print() + + prinfo("How to generate the Nintendo 3DS folder:") + prinfo("1. Safely eject your SD card from your PC.") + prinfo("2. Insert your SD card into your 3DS.") + prinfo("3. Power on your 3DS, and wait for it to reach the HOME menu.") + prinfo("4. Press the POWER button to turn off your 3DS.") + prinfo("5. Re-insert your SD card into your PC.") + prinfo("The Nintendo 3DS folder should appear on the SD card.") + exitOnEnter() # Section: sdWritable From 66c0a8735172a896840b7e235e135f6dbf468a48 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 2 Apr 2025 15:50:55 -0500 Subject: [PATCH 04/15] check free space on inject only --- MSET9_installer_script/mset9.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index b5faabe..3e2adab 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -183,7 +183,6 @@ def getInput(options): # Section: sdWritable def writeProtectCheck(): - global fs prinfo("Checking if SD card is writeable...") if not is_writable(): prbad("Error 02: Your SD card is write protected! If using a full size SD card, ensure that the lock switch is facing upwards.") @@ -192,15 +191,6 @@ def writeProtectCheck(): else: prgood("SD card is writeable!") -# Section: SD card free space -# ensure 16MB free space -freeSpace = shutil.disk_usage(scriptroot).free -if not freeSpace >= 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: You need at least 16MB free space on your SD card!") - prinfo("Please free up some space and try again.") - exitOnEnter() - clearScreen() print(f"MSET9 {VERSION} SETUP by zoogie, Aven, DannyAAM and thepikachugamer") print("What is your console model and version?") @@ -254,7 +244,7 @@ def writeProtectCheck(): triggerFilePath = "" def createHaxID1(): - global fs, ID0, hackedID1Path, realID1Path, realID1BackupTag + global ID0, hackedID1Path, realID1Path, realID1BackupTag print("\033[0;33m=== DISCLAIMER ===\033[0m") # 5;33m? The blinking is awesome but I also don't want to frighten users lol print() @@ -317,7 +307,7 @@ def createHaxID1(): miiPlazaExtdata = False def sanity(): - global fs, hackedID1Path, titleDatabasesGood, menuExtdataGood, miiExtdataGood, miiPlazaExtdata + global hackedID1Path, titleDatabasesGood, menuExtdataGood, miiExtdataGood, miiPlazaExtdata prinfo("Checking databases...") checkTitledb = softcheck(hackedID1Path + "/dbs/title.db", 0x31E400) @@ -380,7 +370,7 @@ def sanityReport(): print() def injection(create=True): - global fs, haxState, hackedID1Path, trigger + global haxState, hackedID1Path, trigger triggerFilePath = hackedID1Path + "/extdata/" + trigger @@ -392,6 +382,11 @@ def injection(create=True): os.remove(abs(triggerFilePath)) haxState = 4 prgood("Removed trigger file.") + freeSpace = shutil.disk_usage(scriptroot).free + if freeSpace < 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: You need at least 16MB free space on your SD card!") + prinfo("Please free up some space and try again.") return prinfo("Injecting trigger file...") @@ -403,7 +398,7 @@ def injection(create=True): exitOnEnter() def remove(): - global fs, ID0, ID1, hackedID1Path, realID1Path, realID1BackupTag, titleDatabasesGood + global ID0, ID1, hackedID1Path, realID1Path, realID1BackupTag, titleDatabasesGood prinfo("Removing MSET9...") @@ -425,7 +420,6 @@ def remove(): prgood("Successfully removed MSET9!") def softcheck(keyfile, expectedSize = None, crc32 = None): - global fs filename = keyfile.rsplit("/")[-1] if not os.path.exists(abs(keyfile)): From 4a77c7da3764eeb2e6923e55ca617cef1d9240f8 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Wed, 2 Apr 2025 15:53:56 -0500 Subject: [PATCH 05/15] automatically remove trigger file, change option 1.. ..to change console version. Console version will only be selected there --- MSET9_installer_script/mset9.py | 303 ++++++++++++++------------------ 1 file changed, 134 insertions(+), 169 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 3e2adab..a07bfb8 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -191,29 +191,13 @@ def writeProtectCheck(): else: prgood("SD card is writeable!") -clearScreen() -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)") - -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!") -for i in consoleNames: - print(f"Enter {i} for: {consoleNames[i]}") - -# 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") +consoleIndex = 0 encodedID1s = { 1: "01C08FE21CFF2FE111990B488546696507A10122044B984768465946C0AA171C4346024CA047B84771A0050899CE0408730064006D00630000900A0862003900", @@ -222,20 +206,21 @@ def writeProtectCheck(): 4: "01C08FE21CFF2FE111990B488546696507A10122044B984768465946C0AA171C4346024CA047B847459E050881CC0408730064006D00630000900A0862003900" } -consoleIndex = getInput(range(1, 4)) -if consoleIndex < 0: - prgood("Goodbye!") - exitOnEnter() - ID0, ID0Count, ID1, ID1Count = "", 0, "", 0 -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"] +haxStates = { + 0: "\033[30;1mID1 not created\033[0m", + 1: "\033[33;1mNot ready - check MSET9 status for more details\033[0m", + 2: "\033[32mReady\033[0m", + # "\033[32;1mInjected\033[0m", # you must go + 3: "\033[32mRemoved trigger file\033[0m" +} haxState = 0 realID1Path = "" realID1BackupTag = "_user-id1" -hackedID1 = bytes.fromhex(encodedID1s[consoleIndex]).decode("utf-16le") # ID1 - arm injected payload in readable format +hackedID1 = "" hackedID1Path = "" homeMenuExtdata = [0x8F, 0x98, 0x82, 0xA1, 0xA9, 0xB1] # us,eu,jp,ch,kr,tw @@ -244,62 +229,85 @@ def writeProtectCheck(): triggerFilePath = "" def createHaxID1(): - global ID0, hackedID1Path, realID1Path, realID1BackupTag + global consoleIndex, ID0, hackedID1Path, realID1Path, realID1BackupTag - 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, 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() + clearScreen() + 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)") - 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("\n-- Please type in a number then hit return --\n") - print("Input '1' again to confirm.") - print("Input '2' to cancel.") - time.sleep(3) - if getInput(range(1, 2)) != 1: + print("Enter one of these four numbers!") + for i in consoleNames: + print(f"Enter {i} for: {consoleNames[i]}") + + selectedIndex = getInput(range(1, 4)) + if selectedIndex < 0: + prgood("Goodbye!") + exitOnEnter(remount=True) + + hackedID1 = bytes.fromhex(encodedID1s[selectedIndex]).decode("utf-16le") + + if consoleIndex == 0: + 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, 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() - prinfo("Cancelled.") - exitOnEnter() - hackedID1Path = ID0 + "/" + hackedID1 + print("Input '1' again to confirm.") + print("Input '2' to cancel.") + time.sleep(3) + if getInput(range(1, 2)) != 1: + print() + prinfo("Cancelled.") + return - try: - prinfo("Creating hacked ID1...") - os.mkdir(abs(hackedID1Path)) - prinfo("Creating dummy databases...") - os.mkdir(abs(hackedID1Path + "/dbs")) - open(abs(hackedID1Path + "/dbs/title.db"), "w").close() - open(abs(hackedID1Path + "/dbs/import.db"), "w").close() - except Exception as exc: - 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-mset9.html for instructions.") - prinfo("If you need help, join Nintendo Homebrew on Discord: https://discord.gg/nintendohomebrew") - 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") + if not realID1Path.endswith(realID1BackupTag): + prinfo("Backing up original ID1...") + os.rename(abs(realID1Path), abs(realID1Path + realID1BackupTag)) + try: + prinfo("Creating hacked ID1...") + os.mkdir(abs(hackedID1Path)) + prinfo("Creating dummy databases...") + os.mkdir(abs(hackedID1Path + "/dbs")) + open(abs(hackedID1Path + "/dbs/title.db"), "w").close() + open(abs(hackedID1Path + "/dbs/import.db"), "w").close() + except Exception as exc: + 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-mset9.html for instructions.") + prinfo("If you need help, join Nintendo Homebrew on Discord: https://discord.gg/nintendohomebrew") + 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") - exitOnEnter() + exitOnEnter() - if not realID1Path.endswith(realID1BackupTag): - prinfo("Backing up original ID1...") - os.rename(abs(realID1Path), abs(realID1Path + realID1BackupTag)) + prgood("Created hacked ID1.") + return - prgood("Created hacked ID1.") - exitOnEnter() + elif selectedIndex == consoleIndex: + prinfo("No change made.") + return + + else: + os.rename(abs(hackedID1Path), abs(ID0 + "/" + hackedID1)) + hackedID1Path = ID0 + "/" + hackedID1 + prinfo(f"Switched to \"{consoleNames[selectedIndex]}\".") + return titleDatabasesGood = False menuExtdataGood = False @@ -369,23 +377,14 @@ def sanityReport(): print() -def injection(create=True): +def injection(): global haxState, hackedID1Path, trigger triggerFilePath = hackedID1Path + "/extdata/" + trigger - if not os.path.exists(abs(triggerFilePath)) ^ create: - prbad(f"Trigger file already {'injected' if create else 'removed'}!") - return - - if os.path.exists(abs(triggerFilePath)): - os.remove(abs(triggerFilePath)) - haxState = 4 - prgood("Removed trigger file.") freeSpace = shutil.disk_usage(scriptroot).free - if freeSpace < 16 * 1024 * 1024: + if freeSpace < 16 * 1024 * 1024: # This is a good time to actually check the space 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!") prinfo("Please free up some space and try again.") return @@ -395,7 +394,6 @@ def injection(create=True): f.close() prgood("MSET9 successfully injected!") - exitOnEnter() def remove(): global ID0, ID1, hackedID1Path, realID1Path, realID1BackupTag, titleDatabasesGood @@ -466,8 +464,8 @@ def is3DSID(name): fileSanity += softcheck("boot9strap/boot9strap.firm", crc32=0x08129C1F) fileSanity += softcheck("boot.firm") fileSanity += softcheck("boot.3dsx") -fileSanity += softcheck("b9") -fileSanity += softcheck("SafeB9S.bin") +fileSanity += softcheck("b9", crc32=0xD59F0CAD) +fileSanity += softcheck("SafeB9S.bin", crc32=0x93CDC5A5) if fileSanity > 0: prbad("Error 07: One or more files are missing or malformed!") @@ -515,43 +513,23 @@ def is3DSID(name): 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 + consoleIndex = haxID1index break - if currentHaxID1index == 0 or (hackedID1Path and os.path.exists(abs(hackedID1Path))): # shouldn't happen - prbad("Unrecognized/duplicate hacked ID1 in ID0 folder, removing!") + if consoleIndex == 0: # shouldn't happen + prbad("Unrecognized hacked ID1 in ID0 folder, removing!") shutil.rmtree(abs(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("Please re-enter the number for your console model and version.") - choice = getInput([consoleIndex, currentHaxID1index]) - if choice < 0: - prinfo("Cancelled.") - hackedID1Path = fullpath - remove() - exitOnEnter() - - elif choice == currentHaxID1index: - consoleIndex = currentHaxID1index - hackedID1 = dirname - - elif choice == consoleIndex: - os.rename(abs(fullpath), abs(ID0 + "/" + hackedID1)) - - hackedID1Path = ID0 + "/" + hackedID1 + hackedID1Path = fullpath + triggerFilePath = abs(hackedID1Path + "/extdata/" + trigger) sanityOK = sanity() - if os.path.exists(abs(hackedID1Path + "/extdata/" + trigger)): - triggerFilePath = hackedID1Path + "/extdata/" + trigger - haxState = 3 # Injected. + if os.path.exists(triggerFilePath): + os.remove(triggerFilePath) + haxState = 3 # Removed elif sanityOK: haxState = 2 # Ready! else: @@ -562,74 +540,61 @@ def is3DSID(name): prinfo("Consult: https://3ds.hacks.guide/troubleshooting-mset9.html for help!") exitOnEnter() -def mainMenu(): - clearScreen() - print(f"MSET9 {VERSION} SETUP by zoogie, Aven, DannyAAM and thepikachugamer") - print(f"Using {consoleNames[consoleIndex]}") - print() - print(f"Current MSET9 state: {haxStates[haxState]}") +if haxState != 0 and not realID1Path.endswith(realID1BackupTag): # ? + os.rename(abs(realID1Path), abs(realID1Path + realID1BackupTag)) - print("\n-- Please type in a number then hit return --\n") +clearScreen() +print(f"MSET9 {VERSION} SETUP by zoogie, Aven, DannyAAM and thepikachugamer") +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("↓ Input one of these numbers!") +if haxState == 0: print("1. Create MSET9 ID1") +else: + print(f"1. Change console version (Current: {consoleNames[consoleIndex]})") print("2. Check MSET9 status") print("3. Inject trigger file") - print("4. Remove trigger file") + print("4. Remove MSET9") - if haxState != 3: - print("5. Remove MSET9") +print("\n0. Exit") - print("\n0. Exit") +while 1: + try_chdir() # (?) - while 1: - optSelect = getInput(range(0, 5)) - - try_chdir() # (?) + optSelect = getInput(range(0, 5)) + if optSelect <= 0: + break + elif optSelect == 1: # Create hacked ID1 + createHaxID1() + exitOnEnter() - if optSelect <= 0: - break + elif optSelect == 2: # Check status + if haxState == 0: # MSET9 ID1 not present + prbad("Can't do that now!") + continue + sanityReport() + exitOnEnter() - elif optSelect == 1: # Create hacked ID1 - if haxState > 0: - prinfo("Hacked ID1 already exists.") - continue - createHaxID1() - exitOnEnter() + elif optSelect == 3: # Inject trigger file + if haxState != 2: # Ready to inject + prbad("Can't do that now!") + continue + injection() + exitOnEnter() - elif optSelect == 2: # Check status - if haxState == 0: # MSET9 ID1 not present - prbad("Can't do that now!") - continue - sanityReport() - exitOnEnter() + elif optSelect == 4: # Remove MSET9 + if haxState == 0: + prinfo("Nothing to do.") + continue - elif optSelect == 3: # Inject trigger file - 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() - - elif optSelect == 5: # Remove MSET9 - if haxState <= 0: - prinfo("Nothing to do.") - continue - if haxState == 3: - prbad("Can't do that now!") - continue - - remove() - exitOnEnter() + remove() + remove_extra() + exitOnEnter(remount=True) -mainMenu() -prgood("Goodbye!") +cleanup(remount=True) +prgood("See ya later, alligator...") time.sleep(2) From 359d94b08ff132c3c82a5625174ab4f4b359664b Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Mon, 7 Jul 2025 22:28:46 -0500 Subject: [PATCH 06/15] Actually call `sync(2)` lol.. --- MSET9_installer_script/mset9.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index a07bfb8..3dae36d 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -14,15 +14,19 @@ def prbad(content): def prinfo(content): print(f"[--] {content}") -def exitOnEnter(errCode = 0): - input("[--] Press Enter to exit...") - exit(errCode) osver = platform.system() thisfile = os.path.abspath(__file__) scriptroot = os.path.dirname(thisfile) systmp = None +def exitOnEnter(errCode = 0): + if osver == "Linux": + os.sync() + + input("[--] Press Enter to exit...") + exit(errCode) + def need_hangul_fix(): if osver == "Darwin": return True From 867eddf8f3d854681fce66608b39e7bbe3ee1a69 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 8 Jul 2025 08:55:03 -0500 Subject: [PATCH 07/15] note about formatting the SD --- MSET9_installer_script/mset9.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 3dae36d..e3b2f57 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -182,6 +182,10 @@ def getInput(options): prinfo("4. Press the POWER button to turn off your 3DS.") prinfo("5. Re-insert your SD card into your PC.") prinfo("The Nintendo 3DS folder should appear on the SD card.") + print() + prinfo("If the folder still does not appear, or your 3DS complains that the SD card") + prinfo("could not be detected/could not be accessed, you may need to format the SD card to FAT32.") + prinfo("Consult: https://wiki.hacks.guide/wiki/Formatting_an_SD_card") exitOnEnter() From 35904b53442adac53e33aed1dfd698c8b1c7886e Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 8 Jul 2025 09:52:38 -0500 Subject: [PATCH 08/15] almost fell victim to abs("") again --- MSET9_installer_script/mset9.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index e3b2f57..d354d43 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -237,7 +237,7 @@ def writeProtectCheck(): triggerFilePath = "" def createHaxID1(): - global consoleIndex, ID0, hackedID1Path, realID1Path, realID1BackupTag + global consoleIndex, ID0, hackedID1, hackedID1Path, realID1Path, realID1BackupTag clearScreen() print(f"MSET9 {VERSION} SETUP by zoogie, Aven, DannyAAM and thepikachugamer") @@ -281,7 +281,9 @@ def createHaxID1(): if not realID1Path.endswith(realID1BackupTag): prinfo("Backing up original ID1...") os.rename(abs(realID1Path), abs(realID1Path + realID1BackupTag)) + try: + hackedID1Path = ID0 + "/" + hackedID1 prinfo("Creating hacked ID1...") os.mkdir(abs(hackedID1Path)) prinfo("Creating dummy databases...") @@ -576,6 +578,7 @@ def is3DSID(name): optSelect = getInput(range(0, 5)) if optSelect <= 0: break + elif optSelect == 1: # Create hacked ID1 createHaxID1() exitOnEnter() @@ -601,8 +604,7 @@ def is3DSID(name): remove() remove_extra() - exitOnEnter(remount=True) + exitOnEnter() -cleanup(remount=True) prgood("See ya later, alligator...") time.sleep(2) From fd19ccac89f32a9dc607e6db76fcae13043e278b Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 8 Jul 2025 09:53:48 -0500 Subject: [PATCH 09/15] truncate the sanity() output a bit --- MSET9_installer_script/mset9.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index d354d43..9d2d1db 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -327,9 +327,9 @@ def createHaxID1(): def sanity(): global hackedID1Path, titleDatabasesGood, menuExtdataGood, miiExtdataGood, miiPlazaExtdata - prinfo("Checking databases...") - checkTitledb = softcheck(hackedID1Path + "/dbs/title.db", 0x31E400) - checkImportdb = softcheck(hackedID1Path + "/dbs/import.db", 0x31E400) + # prinfo("Checking databases...") + checkTitledb = softcheck(hackedID1Path + "/dbs/title.db", 0x31E400, silent=True) + checkImportdb = softcheck(hackedID1Path + "/dbs/import.db", 0x31E400, silent=True) titleDatabasesGood = not (checkTitledb or checkImportdb) if not titleDatabasesGood: if not os.path.exists(abs(hackedID1Path + "/dbs")): @@ -338,13 +338,13 @@ def sanity(): open(abs(hackedID1Path + "/dbs/title.db"), "w").close() open(abs(hackedID1Path + "/dbs/import.db"), "w").close() - prinfo("Checking for HOME Menu extdata...") + # prinfo("Checking for HOME Menu extdata...") for i in homeMenuExtdata: if os.path.exists(abs(hackedID1Path + f"/extdata/00000000/{i:08X}")): menuExtdataGood = True break - - prinfo("Checking for Mii Maker extdata...") + + # prinfo("Checking for Mii Maker extdata...") for i in miiMakerExtdata: if os.path.exists(abs(hackedID1Path + f"/extdata/00000000/{i+1:08X}")): miiPlazaExtdata = True @@ -427,19 +427,19 @@ def remove(): haxState = 0 prgood("Successfully removed MSET9!") -def softcheck(keyfile, expectedSize = None, crc32 = None): +def softcheck(keyfile, expectedSize = None, crc32 = None, silent = False): filename = keyfile.rsplit("/")[-1] if not os.path.exists(abs(keyfile)): - prbad(f"{filename} does not exist on SD card!") + silent or prbad(f"{filename} does not exist on SD card!") return 1 fileSize = os.path.getsize(abs(keyfile)) if not fileSize: - prbad(f"{filename} is an empty file!") + silent or prbad(f"{filename} is an empty file!") return 1 elif expectedSize and fileSize != expectedSize: - prbad(f"{filename} is size {fileSize:,} bytes, not expected {expectedSize:,} bytes") + silent or prbad(f"{filename} is size {fileSize:,} bytes, not expected {expectedSize:,} bytes") return 1 if crc32: @@ -447,10 +447,10 @@ def softcheck(keyfile, expectedSize = None, crc32 = None): checksum = binascii.crc32(f.read()) f.close() if crc32 != checksum: - prbad(f"{filename} was not recognized as the correct file") + silent or prbad(f"{filename} was not recognized as the correct file") return 1 - prgood(f"{filename} looks good!") + silent or prgood(f"{filename} looks good!") return 0 def is3DSID(name): @@ -533,6 +533,7 @@ def is3DSID(name): prbad("Unrecognized hacked ID1 in ID0 folder, removing!") shutil.rmtree(abs(fullpath)) + prinfo(f"Detected hacked ID1 for {consoleNames[consoleIndex]}") hackedID1Path = fullpath triggerFilePath = abs(hackedID1Path + "/extdata/" + trigger) sanityOK = sanity() From 76bb26c9af07007a90052e33b38ee30eefe1dedc Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 8 Jul 2025 09:54:31 -0500 Subject: [PATCH 10/15] we can't checksum SafeB9S.bin --- 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 9d2d1db..0ef2c7b 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -475,7 +475,7 @@ def is3DSID(name): fileSanity += softcheck("boot.firm") fileSanity += softcheck("boot.3dsx") fileSanity += softcheck("b9", crc32=0xD59F0CAD) -fileSanity += softcheck("SafeB9S.bin", crc32=0x93CDC5A5) +fileSanity += softcheck("SafeB9S.bin") if fileSanity > 0: prbad("Error 07: One or more files are missing or malformed!") From 102d3cbbcaf0116c40bc65a4d73ee3bdf8b103f7 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 8 Jul 2025 09:59:23 -0500 Subject: [PATCH 11/15] error 08 vanishes --- MSET9_installer_script/mset9.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 0ef2c7b..8c66708 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -86,8 +86,10 @@ def try_chdir(): global scriptroot try: os.chdir(scriptroot) - except Exception: - prbad("Error 08: Couldn't reapply working directory, is SD card reinserted?") + except Exception as exc: + # prbad("Error 08: Couldn't reapply working directory, is SD card reinserted?") Wasn't in the troubleshooting section, doesn't really need a number IMO + prbad("Failed to change directory to SD card. is it inserted?") + prbad(f"Error details: {str(exc)}") exitOnEnter() def is_writable(): From a022ad1509660ac15c289ea98a1b6819bc837710 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 8 Jul 2025 09:59:50 -0500 Subject: [PATCH 12/15] error 07->03 --- MSET9_installer_script/errors.txt | 9 +-------- MSET9_installer_script/mset9.py | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/MSET9_installer_script/errors.txt b/MSET9_installer_script/errors.txt index 021d401..9078009 100644 --- a/MSET9_installer_script/errors.txt +++ b/MSET9_installer_script/errors.txt @@ -1,17 +1,10 @@ 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 03: Missing/Malformed file. Reextract MSET9 zip file. 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: 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 8c66708..7af55d6 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -480,7 +480,7 @@ def is3DSID(name): fileSanity += softcheck("SafeB9S.bin") if fileSanity > 0: - prbad("Error 07: One or more files are missing or malformed!") + prbad("Error 03: One or more files are missing or malformed!") prinfo("Please re-extract the MSET9 zip file, overwriting any existing files when prompted.") exitOnEnter() From 905e136f84970e19ce4cdf334d81bfe54b90de03 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Tue, 8 Jul 2025 10:04:20 -0500 Subject: [PATCH 13/15] "remove_extra" is not a thing anymore --- MSET9_installer_script/mset9.py | 1 - 1 file changed, 1 deletion(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 7af55d6..aa9b6e4 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -606,7 +606,6 @@ def is3DSID(name): continue remove() - remove_extra() exitOnEnter() prgood("See ya later, alligator...") From 25ff9e2e8910160e8add21fa69f139dc82494309 Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Sat, 2 Aug 2025 12:27:50 -0500 Subject: [PATCH 14/15] split up relevant firmware versions from the rest --- MSET9_installer_script/mset9.py | 43 +++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index aa9b6e4..abe4244 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -207,6 +207,10 @@ def writeProtectCheck(): 3: "Old 3DS/2DS, 11.4.0 to 11.7.0", 4: "New 3DS/2DS, 11.4.0 to 11.7.0" } + +primaryConsoleVersions = [ 1, 2 ] +secondaryConsoleVersions = [ 3, 4 ] + consoleIndex = 0 encodedID1s = { @@ -238,9 +242,7 @@ def writeProtectCheck(): trigger = "002F003A.txt" # all 3ds ":/" in hex format triggerFilePath = "" -def createHaxID1(): - global consoleIndex, ID0, hackedID1, hackedID1Path, realID1Path, realID1BackupTag - +def pickConsoleVersion(): clearScreen() print(f"MSET9 {VERSION} SETUP by zoogie, Aven, DannyAAM and thepikachugamer") print("What is your console model and version?") @@ -249,14 +251,39 @@ def createHaxID1(): print("\n-- Please type in a number then hit return --\n") - print("Enter one of these four numbers!") - for i in consoleNames: - print(f"Enter {i} for: {consoleNames[i]}") + print("Enter one of these numbers!") + for i in primaryConsoleVersions: + print(f"{i}: {consoleNames[i]}") + + print("9: Other firmware versions") + + selectedIndex = getInput([*primaryConsoleVersions, 9]) + if selectedIndex < 0: + prgood("Goodbye!") + exitOnEnter() + + if selectedIndex != 9: + return selectedIndex + + for i in secondaryConsoleVersions: + print(f"{i}: {consoleNames[i]}") - selectedIndex = getInput(range(1, 4)) + print("0: Back") + selectedIndex = getInput([*secondaryConsoleVersions, 0]) if selectedIndex < 0: prgood("Goodbye!") - exitOnEnter(remount=True) + exitOnEnter() + + if selectedIndex != 0: + return selectedIndex + + return pickConsoleVersion() + + +def createHaxID1(): + global consoleIndex, ID0, hackedID1, hackedID1Path, realID1Path, realID1BackupTag + + selectedIndex = pickConsoleVersion() hackedID1 = bytes.fromhex(encodedID1s[selectedIndex]).decode("utf-16le") From 90c916fe06eef91d42fd4216b2bb8bb39d7e5c8a Mon Sep 17 00:00:00 2001 From: Naim2000 Date: Sat, 2 Aug 2025 12:29:34 -0500 Subject: [PATCH 15/15] made an enum for the haxState variable see: https://github.com/hacks-guide/MSET9/pull/16 (sorry @dg4l) --- MSET9_installer_script/mset9.py | 39 +++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index abe4244..a574801 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -222,14 +222,21 @@ def writeProtectCheck(): ID0, ID0Count, ID1, ID1Count = "", 0, "", 0 -haxStates = { - 0: "\033[30;1mID1 not created\033[0m", - 1: "\033[33;1mNot ready - check MSET9 status for more details\033[0m", - 2: "\033[32mReady\033[0m", +class haxStates: + ID1_NOT_PRESENT = 0 + NOT_READY = 1 + READY_TO_INJECT = 2 + TRIGGER_FILE_REMOVED = 3 + +haxStateLabel = { + haxStates.ID1_NOT_PRESENT: "\033[30;1mID1 not created\033[0m", + haxStates.NOT_READY: "\033[33;1mNot ready - check MSET9 status for more details\033[0m", + haxStates.READY_TO_INJECT: "\033[32mReady\033[0m", # "\033[32;1mInjected\033[0m", # you must go - 3: "\033[32mRemoved trigger file\033[0m" + haxStates.TRIGGER_FILE_REMOVED: "\033[32mRemoved trigger file\033[0m" } -haxState = 0 + +haxState = haxStates.ID1_NOT_PRESENT realID1Path = "" realID1BackupTag = "_user-id1" @@ -453,7 +460,7 @@ def remove(): ID1 = ID1[:32] realID1Path = ID0 + "/" + ID1 - haxState = 0 + haxState = haxStates.ID1_NOT_PRESENT prgood("Successfully removed MSET9!") def softcheck(keyfile, expectedSize = None, crc32 = None, silent = False): @@ -569,30 +576,30 @@ def is3DSID(name): if os.path.exists(triggerFilePath): os.remove(triggerFilePath) - haxState = 3 # Removed + haxState = haxStates.TRIGGER_FILE_REMOVED elif sanityOK: - haxState = 2 # Ready! + haxState = haxStates.READY_TO_INJECT else: - haxState = 1 # Not ready... + haxState = haxStates.NOT_READY if ID1Count != 1: 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-mset9.html for help!") exitOnEnter() -if haxState != 0 and not realID1Path.endswith(realID1BackupTag): # ? +if haxState != haxStates.ID1_NOT_PRESENT and not realID1Path.endswith(realID1BackupTag): # ? os.rename(abs(realID1Path), abs(realID1Path + realID1BackupTag)) clearScreen() print(f"MSET9 {VERSION} SETUP by zoogie, Aven, DannyAAM and thepikachugamer") print() -print(f"Current MSET9 state: {haxStates[haxState]}") +print(f"Current MSET9 state: {haxStateLabel[haxState]}") print("\n-- Please type in a number then hit return --\n") print("↓ Input one of these numbers!") -if haxState == 0: +if haxState == haxStates.ID1_NOT_PRESENT: print("1. Create MSET9 ID1") else: print(f"1. Change console version (Current: {consoleNames[consoleIndex]})") @@ -614,21 +621,21 @@ def is3DSID(name): exitOnEnter() elif optSelect == 2: # Check status - if haxState == 0: # MSET9 ID1 not present + if haxState == haxStates.ID1_NOT_PRESENT: prbad("Can't do that now!") continue sanityReport() exitOnEnter() elif optSelect == 3: # Inject trigger file - if haxState != 2: # Ready to inject + if haxState != haxStates.READY_TO_INJECT: prbad("Can't do that now!") continue injection() exitOnEnter() elif optSelect == 4: # Remove MSET9 - if haxState == 0: + if haxState == haxStates.ID1_NOT_PRESENT: prinfo("Nothing to do.") continue