From d81e2ee6705182cff825eeb0424adac12c639fa8 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Mon, 30 Oct 2023 14:12:35 +0800 Subject: [PATCH 01/17] prepare for macos support --- MSET9_installer_script/mset9.py | 222 ++++++++++++++++++++++---------- 1 file changed, 157 insertions(+), 65 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 4394290..7e5ccea 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -1,5 +1,5 @@ #!/usr/bin/python3 -import os, platform, time, shutil, binascii +import abc, os, platform, time, binascii VERSION = "v1.1" @@ -16,12 +16,132 @@ def exitOnEnter(errCode = 0): input("[*] Press Enter to exit...") exit(errCode) +# wrapper for fs operations. can use pyfilesystem2 directly, +# but try to avoid extra dependency on non-darwin system +class FSWrapper(metaclass=abc.ABCMeta): + @abc.abstractmethod + def exists(self, path): + pass + @abc.abstractmethod + def mkdir(self, path): + pass + @abc.abstractmethod + def open(self, path, mode='r'): + pass + @abc.abstractmethod + def getsize(self, path): + pass + @abc.abstractmethod + def remove(self, path): + pass + @abc.abstractmethod + def rename(self, src, dst): + pass + @abc.abstractmethod + def rmtree(self, path): + pass + @abc.abstractmethod + def copytree(self, src, dst): + pass + @abc.abstractmethod + def walk(self, path, topdown=False): + pass + @abc.abstractmethod + def is_writable(self): + pass + @abc.abstractmethod + def freespace(self): + pass + @abc.abstractmethod + def reload(self): + pass + osver = platform.system() if osver == "Darwin": + # ======== macOS / iOS ======== prbad("Error 11: macOS is not supported!") prinfo("Please use a Windows or Linux computer.") exitOnEnter() + from pyfatfs.PyFatFS import PyFatFS + from pyfatfs.EightDotThree import EightDotThree + + class FatFS(FSWrapper): + def __init__(self, device): + pass + def exists(self, path): + pass + def mkdir(self, path): + pass + def open(self, path, mode='r'): + pass + def getsize(self, path): + pass + def remove(self, path): + pass + def rename(self, src, dst): + pass + def rmtree(self, path): + pass + def copytree(self, src, dst): + pass + def walk(self, path, topdown=False): + pass + def is_writable(self): + pass + def freespace(self): + pass + def reload(self): + pass + +else: + # ======== Windows / Linux ======== + import shutil + + class OSFS(FSWrapper): + def __init__(self, root): + self.root = root + self.reload() + def abs(self, path): + return os.path.join(self.root, path) + def exists(self, path): + return os.path.exists(self.abs(path)) + def mkdir(self, path): + os.mkdir(self.abs(path)) + def open(self, path, mode='r'): + return open(self.abs(path), mode) + def getsize(self, path): + return os.path.getsize(self.abs(path)) + def remove(self, path): + os.remove(self.abs(path)) + def rename(self, src, dst): + os.rename(self.abs(src), self.abs(dst)) + def rmtree(self, path): + shutil.rmtree(self.abs(path)) + def copytree(self, src, dst): + shutil.copytree(self.abs(src), self.abs(dst)) + 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 + with open("test.txt", "w") as f: + f.write("test") + f.close() + os.remove("test.txt") + except: + writable = False + return writable + def freespace(self): + return shutil.disk_usage(self.root).free + def reload(self): + try: + os.chdir(self.root) + except Exception: + prbad("Error 09: Couldn't reapply working directory, is SD card reinserted?") + exitOnEnter() + + fs = OSFS(os.path.dirname(os.path.abspath(__file__))) def clearScreen(): if osver == "Windows": @@ -29,16 +149,8 @@ def clearScreen(): else: os.system("clear") -cwd = os.path.dirname(os.path.abspath(__file__)) -try: - os.chdir(cwd) -except Exception: - prbad("Failed to set cwd: " + cwd) - prbad("This should pretty much never happen. Try running the script again.") - exitOnEnter() - # Section: insureRoot -if not os.path.exists("Nintendo 3DS/"): +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.") 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: {cwd}") @@ -46,17 +158,9 @@ def clearScreen(): # Section: sdWritable def writeProtectCheck(): + global fs prinfo("Checking if SD card is writeable...") - writeable = os.access(cwd, os.W_OK) - try: # Bodge for windows - with open("test.txt", "w") as f: - f.write("test") - f.close() - os.remove("test.txt") - except: - writeable = False - - if not writeable: + if not fs.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.") prinfo("Visual aid: https://nintendohomebrew.com/assets/img/nhmemes/sdlock.png") exitOnEnter() @@ -65,8 +169,7 @@ def writeProtectCheck(): # Section: SD card free space # ensure 16MB free space -freeSpace = shutil.disk_usage(cwd).free -if freeSpace < 16777216: +if fs.freespace() < 16777216: prbad(f"Error 06: You need at least 16MB free space on your SD card, you have {(freeSpace / 1000000):.2f} bytes!") prinfo("Please free up some space and try again.") exitOnEnter() @@ -157,7 +260,7 @@ def writeProtectCheck(): homeDataPath, miiDataPath, homeHex, miiHex = "", "", 0x0, 0x0 def sanity(): - global haxState, realId1Path, id0, id1, homeDataPath, miiDataPath, homeHex, miiHex + global fs, haxState, realId1Path, id0, id1, homeDataPath, miiDataPath, homeHex, miiHex menuExtdataGood = False miiExtdataGood = False @@ -185,11 +288,11 @@ def sanity(): if checkTitledb or checkImportdb: prbad("Error 10: Database(s) malformed or missing!") if not ( - os.path.exists(realId1Path + "/dbs/import.db") - or os.path.exists(realId1Path + "/dbs/title.db") + fs.exists(realId1Path + "/dbs/import.db") + or fs.exists(realId1Path + "/dbs/title.db") ): - if not os.path.exists(realId1Path + "/dbs"): - os.mkdir(realId1Path + "/dbs") + if not fs.exists(realId1Path + "/dbs"): + fs.mkdir(realId1Path + "/dbs") if checkTitledb: open(realId1Path + "/dbs/title.db", "x").close() if checkImportdb: @@ -202,16 +305,16 @@ def sanity(): else: prgood("Databases look good!") - if os.path.exists(realId1Path + "/extdata/" + trigger): + if fs.exists(realId1Path + "/extdata/" + trigger): prinfo("Removing stale trigger...") - os.remove(realId1Path + "/extdata/" + trigger) + fs.remove(realId1Path + "/extdata/" + trigger) extdataRoot = realId1Path + "/extdata/00000000" prinfo("Checking for HOME Menu extdata...") for i in homeMenuExtdata: extdataRegionCheck = extdataRoot + f"/{i:08X}" - if os.path.exists(extdataRegionCheck): + if fs.exists(extdataRegionCheck): prgood(f"Detected {regionTable[i]} HOME Menu data!") homeHex = i homeDataPath = extdataRegionCheck @@ -228,7 +331,7 @@ def sanity(): prinfo("Checking for Mii Maker extdata...") for i in miiMakerExtdata: extdataRegionCheck = extdataRoot + f"/{i:08X}" - if os.path.exists(extdataRegionCheck): + if fs.exists(extdataRegionCheck): prgood("Found Mii Maker data!") miiHex = i miiDataPath = extdataRegionCheck @@ -241,38 +344,38 @@ def sanity(): exitOnEnter() def injection(): - global realId1Path, id1 + global fs, realId1Path, id1 - if not os.path.exists(id0 + "/" + hackedId1): + if not fs.exists(id0 + "/" + hackedId1): prinfo("Creating hacked ID1...") hackedId1Path = id0 + "/" + hackedId1 - os.mkdir(hackedId1Path) - os.mkdir(hackedId1Path + "/extdata") - os.mkdir(hackedId1Path + "/extdata/00000000") + fs.mkdir(hackedId1Path) + fs.mkdir(hackedId1Path + "/extdata") + fs.mkdir(hackedId1Path + "/extdata/00000000") else: prinfo("Reusing existing hacked ID1...") hackedId1Path = id0 + "/" + hackedId1 - if not os.path.exists(hackedId1Path + "/dbs"): + if not fs.exists(hackedId1Path + "/dbs"): prinfo("Copying databases to hacked ID1...") shutil.copytree(realId1Path + "/dbs", hackedId1Path + "/dbs") prinfo("Copying extdata to hacked ID1...") - if not os.path.exists(hackedId1Path + f"/extdata/00000000/{homeHex:08X}"): + if not fs.exists(hackedId1Path + f"/extdata/00000000/{homeHex:08X}"): shutil.copytree(homeDataPath, hackedId1Path + f"/extdata/00000000/{homeHex:08X}") - if not os.path.exists(hackedId1Path + f"/extdata/00000000/{miiHex:08X}"): + if not fs.exists(hackedId1Path + f"/extdata/00000000/{miiHex:08X}"): shutil.copytree(miiDataPath, hackedId1Path + f"/extdata/00000000/{miiHex:08X}") prinfo("Injecting trigger file...") triggerFilePath = id0 + "/" + hackedId1 + "/extdata/" + trigger - if not os.path.exists(triggerFilePath): - with open(triggerFilePath, "w") as f: + if not fs.exists(triggerFilePath): + with fs.open(triggerFilePath, "w") as f: f.write("plz be haxxed mister arm9, thx") f.close() - if os.path.exists(realId1Path) and realId1BackupTag not in realId1Path: + if fs.exists(realId1Path) and realId1BackupTag not in realId1Path: prinfo("Backing up real ID1...") - os.rename(realId1Path, realId1Path + realId1BackupTag) + fs.rename(realId1Path, realId1Path + realId1BackupTag) id1 += realId1BackupTag realId1Path = f"{id0}/{id1}" else: @@ -282,12 +385,12 @@ def injection(): prgood("MSET9 successfully injected!") def remove(): - global realId1Path, id0, id1 + global fs, realId1Path, id0, id1 prinfo("Removing MSET9...") - if os.path.exists(realId1Path) and realId1BackupTag in realId1Path: + if fs.exists(realId1Path) and realId1BackupTag in realId1Path: prinfo("Renaming original Id1...") - os.rename(realId1Path, id0 + "/" + id1[:32]) + fs.rename(realId1Path, id0 + "/" + id1[:32]) else: prgood("Nothing to remove!") return @@ -295,7 +398,7 @@ def remove(): # 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 os.path.exists(id0 + "/" + maybeHackedId): + if fs.exists(id0 + "/" + maybeHackedId): prinfo("Deleting hacked ID1...") shutil.rmtree(id0 + "/" + maybeHackedId) id1 = id1[:32] @@ -303,12 +406,13 @@ def remove(): prgood("Successfully removed MSET9!") def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): + global fs shortname = keyfile.rsplit("/")[-1] - if not os.path.exists(keyfile): + if not fs.exists(keyfile): prbad(f"{shortname} does not exist on SD card!") return retval elif expectedSize: - fileSize = os.path.getsize(keyfile) + fileSize = fs.getsize(keyfile) if expectedSize != fileSize: prbad(f"{shortname} is size {fileSize:,} bytes, not expected {expectedSize:,} bytes") return retval @@ -323,16 +427,8 @@ def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): prgood(f"{shortname} looks good!") return 0 -def reapplyWorkingDir(): - try: - os.chdir(cwd) - return True - except Exception: - prbad("Error 09: Couldn't reapply working directory, is SD card reinserted?") - return False - # Section: sdwalk -for root, dirs, files in os.walk("Nintendo 3DS/", topdown=True): +for root, dirs, files in fs.walk("Nintendo 3DS/", topdown=True): for name in dirs: # If the name doesn't contain sdmc (Ignores MSET9 exploit folder) @@ -345,7 +441,7 @@ def reapplyWorkingDir(): 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 os.path.exists(os.path.join(root, name) + "/extdata"): + if fs.exists(os.path.join(root, name) + "/extdata"): id1Count += 1 id1 = name id0 = root @@ -402,11 +498,7 @@ def reapplyWorkingDir(): except: sysModelVerSelect = 42 - try: - os.chdir(cwd) - except Exception: - prbad("Error 09: Couldn't reapply working directory, is SD card reinserted?") - exitOnEnter() + fs.reload() if sysModelVerSelect == 1: sanity() @@ -425,4 +517,4 @@ def reapplyWorkingDir(): else: prinfo("Invalid input, try again. Valid inputs: 1, 2, 3, 4") -time.sleep(2) \ No newline at end of file +time.sleep(2) From 642e6a64cf80ee60691b33d85abaee9616a1638e Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Mon, 30 Oct 2023 21:12:06 +0800 Subject: [PATCH 02/17] inital proper? macOS support --- MSET9_installer_script/errors.txt | 4 +- MSET9_installer_script/mset9.py | 165 ++++++++++++++++++++++++------ 2 files changed, 136 insertions(+), 33 deletions(-) diff --git a/MSET9_installer_script/errors.txt b/MSET9_installer_script/errors.txt index 5256d33..e103436 100644 --- a/MSET9_installer_script/errors.txt +++ b/MSET9_installer_script/errors.txt @@ -10,5 +10,7 @@ Error 09: Could not change back into SD directory. Ensure the SD Card has been r Error 10: Database problem. Follow the troubleshooting to reset the DBs. Error 11: Running as MacOS. MacOS is not supported. Error 12: Multiple ID1s, follow MSET9 Troublshooting page. +Error 13: Device doesn't exist. +Error 14: Can't open device. -MSET9 Troublshooting Page: https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 \ No newline at end of file +MSET9 Troublshooting Page: https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 7e5ccea..21fc738 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -12,8 +12,12 @@ def prbad(content): def prinfo(content): print(f"[*] {content}") +def cleanup(): + pass + def exitOnEnter(errCode = 0): input("[*] Press Enter to exit...") + cleanup() exit(errCode) # wrapper for fs operations. can use pyfilesystem2 directly, @@ -53,46 +57,133 @@ def is_writable(self): def freespace(self): pass @abc.abstractmethod + def close(self): + pass + @abc.abstractmethod def reload(self): pass osver = platform.system() if osver == "Darwin": - # ======== macOS / iOS ======== - prbad("Error 11: macOS is not supported!") - prinfo("Please use a Windows or Linux computer.") - exitOnEnter() + # ======== macOS / iOS? ======== + import sys + + if len(sys.argv) < 2: + prbad("Error 11: macOS is not supported!") + prinfo("Please use a Windows or Linux computer.") + exitOnEnter() + + device = sys.argv[1] + if not os.path.exists(device): + prbad("Error 13: Device doesn't exist.") + prinfo("Make sure your sd card is sitted properly.") + exitOnEnter() + from pyfatfs.PyFatFS import PyFatFS from pyfatfs.EightDotThree import EightDotThree + from pyfatfs._exceptions import PyFATException + import struct + + def make_8dot3_name(dir_name, parent_dir_entry): + dirs, files, _ = parent_dir_entry.get_entries() + dir_entries = [e.get_short_name() for e in dirs + files] + extsep = "." + def map_chars(name: bytes) -> bytes: + _name: bytes = b'' + for b in struct.unpack(f"{len(name)}c", name): + if b == b' ': + _name += b'' + elif ord(b) in EightDotThree.INVALID_CHARACTERS: + _name += b'_' + else: + _name += b + return _name + dir_name = dir_name.upper() + # Shorten to 8 chars; strip invalid characters + basename = os.path.splitext(dir_name)[0][0:8].strip() + if basename.isascii(): + basename = basename.encode("ascii", errors="replace") + basename = map_chars(basename).decode("ascii") + else: + basename = "HAX8D3FN" + # Shorten to 3 chars; strip invalid characters + extname = os.path.splitext(dir_name)[1][1:4].strip() + if basename.isascii(): + extname = extname.encode("ascii", errors="replace") + extname = map_chars(extname).decode("ascii") + else: + extname = "HAX" + if len(extname) == 0: + extsep = "" + # Loop until suiting name is found + i = 0 + while len(str(i)) + 1 <= 7: + if i > 0: + maxlen = 8 - (1 + len(str(i))) + basename = f"{basename[0:maxlen]}~{i}" + short_name = f"{basename}{extsep}{extname}" + if short_name not in dir_entries: + return short_name + i += 1 + raise PyFATException("Cannot generate 8dot3 filename, " + "unable to find suiting short file name.", + errno=errno.EEXIST) + EightDotThree.make_8dot3_name = staticmethod(make_8dot3_name) class FatFS(FSWrapper): def __init__(self, device): - pass + self.device = device + self.reload() def exists(self, path): - pass + return self.fs.exists(path) def mkdir(self, path): - pass + self.fs.makedir(path) def open(self, path, mode='r'): - pass + return self.fs.open(path, mode) def getsize(self, path): - pass + return self.fs.getsize(path) def remove(self, path): - pass + self.fs.remove(path) def rename(self, src, dst): - pass + self.fs.movedir(src, dst, create=True) # TODO: implement real fat rename... def rmtree(self, path): - pass + self.fs.removetree(path) def copytree(self, src, dst): - pass - def walk(self, path, topdown=False): - pass + self.fs.copydir(src, dst, create=True) + 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): - pass + try: + with self.open("test.txt", "w") as f: + f.write("test") + f.close() + self.remove("test.txt") + return True + except: + return False def freespace(self): - pass + return 16777216 # TODO: implement proper freespace + def close(self): + try: + self.fs.close() + except AttributeError: + pass def reload(self): - pass + self.close() + self.fs = PyFatFS(filename=self.device) + + try: + fs = FatFS(device) + except PyFATException: + prbad("Error 14: Can't open device.") + prinfo("Make sure your sd card is unmounted in disk utility.") + exitOnEnter() + + def cleanup(): + global fs + fs.close() else: # ======== Windows / Linux ======== @@ -134,6 +225,8 @@ def is_writable(self): return writable def freespace(self): return shutil.disk_usage(self.root).free + def close(self): + pass def reload(self): try: os.chdir(self.root) @@ -294,9 +387,9 @@ def sanity(): if not fs.exists(realId1Path + "/dbs"): fs.mkdir(realId1Path + "/dbs") if checkTitledb: - open(realId1Path + "/dbs/title.db", "x").close() + fs.open(realId1Path + "/dbs/title.db", "x").close() if checkImportdb: - open(realId1Path + "/dbs/import.db", "x").close() + fs.open(realId1Path + "/dbs/import.db", "x").close() prinfo("Created empty databases.") prinfo("Please initialize the title database by navigating to System Settings -> Data Management -> Nintendo 3DS -> Software -> Reset, then rerun this script.") @@ -358,13 +451,13 @@ def injection(): if not fs.exists(hackedId1Path + "/dbs"): prinfo("Copying databases to hacked ID1...") - shutil.copytree(realId1Path + "/dbs", hackedId1Path + "/dbs") + fs.copytree(realId1Path + "/dbs", hackedId1Path + "/dbs") prinfo("Copying extdata to hacked ID1...") if not fs.exists(hackedId1Path + f"/extdata/00000000/{homeHex:08X}"): - shutil.copytree(homeDataPath, 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}"): - shutil.copytree(miiDataPath, hackedId1Path + f"/extdata/00000000/{miiHex:08X}") + fs.copytree(miiDataPath, hackedId1Path + f"/extdata/00000000/{miiHex:08X}") prinfo("Injecting trigger file...") triggerFilePath = id0 + "/" + hackedId1 + "/extdata/" + trigger @@ -400,31 +493,38 @@ def remove(): maybeHackedId = bytes.fromhex(encodedId1s[id1Index]).decode("utf-16le") if fs.exists(id0 + "/" + maybeHackedId): prinfo("Deleting hacked ID1...") - shutil.rmtree(id0 + "/" + maybeHackedId) + fs.rmtree(id0 + "/" + maybeHackedId) id1 = id1[:32] realId1Path = id0 + "/" + id1 prgood("Successfully removed MSET9!") def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): global fs - shortname = keyfile.rsplit("/")[-1] + split = keyfile.rsplit("/", 1) + if len(split) == 1: + dirname = "/" + filename = split[0] + else: + dirname, filename = split if not fs.exists(keyfile): - prbad(f"{shortname} does not exist on SD card!") - return retval - elif expectedSize: + keyfile = os.path.join(dirname, filename.upper()) # this is literally for b9 + 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"{shortname} is size {fileSize:,} bytes, not expected {expectedSize:,} bytes") + prbad(f"{filename} is size {fileSize:,} bytes, not expected {expectedSize:,} bytes") return retval elif crc32: - with open(keyfile, "rb") as f: + with fs.open(keyfile, "rb") as f: checksum = binascii.crc32(f.read()) if crc32 != checksum: - prbad(f"{shortname} was not recognized as the correct file") + prbad(f"{filename} was not recognized as the correct file") f.close() return retval f.close() - prgood(f"{shortname} looks good!") + prgood(f"{filename} looks good!") return 0 # Section: sdwalk @@ -517,4 +617,5 @@ def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): else: prinfo("Invalid input, try again. Valid inputs: 1, 2, 3, 4") +cleanup() time.sleep(2) From 72353f5d358bc79706b06adc5c7a411fff009ace Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Mon, 30 Oct 2023 22:04:35 +0800 Subject: [PATCH 03/17] self elevate --- MSET9_installer_script/mset9.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 21fc738..fe9de08 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -64,6 +64,7 @@ def reload(self): pass osver = platform.system() +thisfile = os.path.abspath(__file__) if osver == "Darwin": # ======== macOS / iOS? ======== @@ -80,6 +81,28 @@ def reload(self): prinfo("Make sure your sd card is sitted properly.") exitOnEnter() + # self elevate + if os.getuid() != 0: + # run with osascript won't have raw disk access by default... + # thanks for the perfect security of macos + #args = [sys.executable, thisfile, device] + #escaped_args = map(lambda x: f"\\\"{x}\\\"", args) + #cmd = " ".join(escaped_args) + #osascript = " ".join([ + # f"do shell script \"{cmd}\"", + # "with administrator privileges", + # "without altering line endings" + #]) + #try: + # os.execlp("osascript", "osascript", "-e", osascript) + prinfo("Input the password of your computer if prompted.") + prinfo("(It won't show anything while you're typing, just type it blindly)") + try: + os.execlp("sudo", "sudo", sys.executable, thisfile, device) + except: + printfo("Root privilege is required") + exitOnEnter() + from pyfatfs.PyFatFS import PyFatFS from pyfatfs.EightDotThree import EightDotThree from pyfatfs._exceptions import PyFATException @@ -234,7 +257,7 @@ def reload(self): prbad("Error 09: Couldn't reapply working directory, is SD card reinserted?") exitOnEnter() - fs = OSFS(os.path.dirname(os.path.abspath(__file__))) + fs = OSFS(os.path.dirname(thisfile)) def clearScreen(): if osver == "Windows": From 72d558e459d35a78c3fab58d027f63133dafc9a5 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 02:41:34 +0800 Subject: [PATCH 04/17] make it able to run on sd card --- MSET9_installer_script/mset9.py | 112 ++++++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 13 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index fe9de08..9dba0c0 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -12,12 +12,12 @@ def prbad(content): def prinfo(content): print(f"[*] {content}") -def cleanup(): +def cleanup(remount=False): pass -def exitOnEnter(errCode = 0): +def exitOnEnter(errCode = 0, remount=False): + cleanup(remount) input("[*] Press Enter to exit...") - cleanup() exit(errCode) # wrapper for fs operations. can use pyfilesystem2 directly, @@ -63,6 +63,9 @@ def close(self): def reload(self): pass +def remove_extra(): + pass + osver = platform.system() thisfile = os.path.abspath(__file__) @@ -70,15 +73,87 @@ def reload(self): # ======== macOS / iOS? ======== import sys + tmpprefix = "mset9-macos-run-" + + def tmp_cleanup(): + global tmpprefix + prinfo("Removing temporary folders...") + import tempfile, shutil + systmp = tempfile.gettempdir() + for dirname in os.listdir(systmp): + if dirname.startswith(tmpprefix): + shutil.rmtree(f"{systmp}/{dirname}") + prinfo("Temporary folders removed!") + + def run_diskutil_and_wait(command, dev): + import subprocess + return subprocess.run(["diskutil", command, dev], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode + if len(sys.argv) < 2: - prbad("Error 11: macOS is not supported!") - prinfo("Please use a Windows or Linux computer.") - exitOnEnter() + if not thisfile.startswith("/Volumes/"): + #prbad("Error :") + prbad("You should run this file from sd card, or specifiy device name manually") + exitOnEnter() + prinfo("Resolving device...") + device = None + devid = os.stat(thisfile).st_dev + for devname in os.listdir("/dev"): + if not devname.startswith("disk"): + continue + devpath = f"/dev/{devname}" + if os.stat(devpath).st_rdev == devid: + device = devpath + break + if device is None: + #prbad("Error :") + prbad("Can't find matching device, this shouldn't happen...") + exitOnEnter() + + prinfo("Finding previous temporary folder...") + import shutil, tempfile, time + systmp = tempfile.gettempdir() + tmpdir = None + for dirname in os.listdir(systmp): + if dirname.startswith(tmpprefix): + dirpath = f"{systmp}/{dirname}" + script = f"{dirpath}/mset9.py" + if not os.path.exists(script): + continue + elif os.stat(script).st_mtime > os.stat(thisfile).st_mtime: + tmpdir = dirpath + break + else: + shutil.rmtree(dirpath) + if tmpdir is None: + prinfo("Creating temporary folder...") + tmpdir = tempfile.mkdtemp(prefix=tmpprefix) + shutil.copyfile(thisfile, f"{tmpdir}/mset9.py") + + prinfo("Trying to unmount sd card...") + ret = 1 + count = 0 + while count < 5: + ret = run_diskutil_and_wait("umount", device) + if ret == 0: + break + else: + count += 1 + time.sleep(1) + + if ret == 1: + #prbad("Error : ") + prbad("Can't umount sd card!") + #tmp_cleanup() + exitOnEnter() + + os.execlp(sys.executable, sys.executable, f"{tmpdir}/mset9.py", device) + prbad("WTF???") device = sys.argv[1] if not os.path.exists(device): prbad("Error 13: Device doesn't exist.") prinfo("Make sure your sd card is sitted properly.") + #tmp_cleanup() exitOnEnter() # self elevate @@ -100,8 +175,9 @@ def reload(self): try: os.execlp("sudo", "sudo", sys.executable, thisfile, device) except: - printfo("Root privilege is required") - exitOnEnter() + prbad("Root privilege is required") + #tmp_cleanup() + exitOnEnter(remount=True) from pyfatfs.PyFatFS import PyFatFS from pyfatfs.EightDotThree import EightDotThree @@ -202,11 +278,20 @@ def reload(self): except PyFATException: prbad("Error 14: Can't open device.") prinfo("Make sure your sd card is unmounted in disk utility.") + #tmp_cleanup() exitOnEnter() - def cleanup(): - global fs + def remove_extra(): + tmp_cleanup() + + def cleanup(remount=False): + global fs, device fs.close() + if remount: + prinfo("Trying to remount sd card...") + run_diskutil_and_wait("mount", device) + #tmp_cleanup() + else: # ======== Windows / Linux ======== @@ -520,6 +605,7 @@ def remove(): id1 = id1[:32] realId1Path = id0 + "/" + id1 prgood("Successfully removed MSET9!") + remove_extra() def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): global fs @@ -633,12 +719,12 @@ def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): exitOnEnter() elif sysModelVerSelect == 3: remove() - exitOnEnter() + exitOnEnter(remount=True) elif sysModelVerSelect == 4 or "exit": - prgood("Goodbye!") break else: prinfo("Invalid input, try again. Valid inputs: 1, 2, 3, 4") -cleanup() +cleanup(remount=True) +prgood("Goodbye!") time.sleep(2) From 326924ad43959f04b4a24c372de4d43971b41dd1 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 03:24:55 +0800 Subject: [PATCH 05/17] auto venv --- MSET9_installer_script/mset9.py | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 9dba0c0..a2ab9d8 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -156,6 +156,41 @@ def run_diskutil_and_wait(command, dev): #tmp_cleanup() exitOnEnter() + # auto venv + venv_path = os.path.dirname(thisfile) + venv_bin = f"{venv_path}/bin" + venv_py = f"{venv_bin}/python3" + + def activate_venv(): + global venv_path, venv_bin, venv_py, device + #import site + os.environ["PATH"] = os.pathsep.join([venv_bin, *os.environ.get("PATH", "").split(os.pathsep)]) + os.environ["VIRTUAL_ENV"] = venv_path + os.environ["VIRTUAL_ENV_PROMPT"] = "(mset9)" + + os.execlp(venv_py, venv_py, __file__, device) + + #prev_length = len(sys.path) + #for lib in "__LIB_FOLDERS__".split(os.pathsep): + # path = os.path.realpath(os.path.join(venv_bin, lib)) + # site.addsitedir(path) + # sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length] + #sys.real_prefix = sys.prefix + #sys.prefix = venv_path + + if "VIRTUAL_ENV" not in os.environ and os.path.exists(venv_py): + prinfo("venv found, activate it...") + activate_venv() + + try: + from pyfatfs.PyFatFS import PyFatFS + except ModuleNotFoundError: + prinfo("PyFatFS not found, setting up venv for installing automatically...") + import venv, subprocess + venv.create(venv_path, with_pip=True) + subprocess.run(["bin/pip", "install", "pyfatfs"], cwd=venv_path) + activate_venv() + # self elevate if os.getuid() != 0: # run with osascript won't have raw disk access by default... From cf313b8cf32498435d503f498944d752601f4de1 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 04:07:10 +0800 Subject: [PATCH 06/17] add .command --- MSET9_installer_script/mset9.command | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100755 MSET9_installer_script/mset9.command diff --git a/MSET9_installer_script/mset9.command b/MSET9_installer_script/mset9.command new file mode 100755 index 0000000..ff5ea17 --- /dev/null +++ b/MSET9_installer_script/mset9.command @@ -0,0 +1,11 @@ +#!/bin/sh +if which python3 >/dev/null; then + # use exec here to release shell and thus sd card, allow it to be umounted + exec python3 "$(cd "$(dirname "$0")" && pwd)/mset9.py" +else + echo "Python 3 is not installed." + echo "Please install Python 3 and try again." + echo "https://www.python.org/downloads/" + echo "Press ENTER to exit ..." + read DUMMY +fi From 88def89c9998c810dd2994c56863673da4f21a90 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 19:08:51 +0800 Subject: [PATCH 07/17] proper rename and ensure space --- MSET9_installer_script/mset9.py | 35 ++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index a2ab9d8..61456bc 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -54,7 +54,7 @@ def walk(self, path, topdown=False): def is_writable(self): pass @abc.abstractmethod - def freespace(self): + def ensurespace(self, size): pass @abc.abstractmethod def close(self): @@ -215,6 +215,7 @@ def activate_venv(): exitOnEnter(remount=True) from pyfatfs.PyFatFS import PyFatFS + from pyfatfs.FATDirectoryEntry import FATDirectoryEntry, make_lfn_entry from pyfatfs.EightDotThree import EightDotThree from pyfatfs._exceptions import PyFATException import struct @@ -280,7 +281,22 @@ def getsize(self, path): def remove(self, path): self.fs.remove(path) def rename(self, src, dst): - self.fs.movedir(src, dst, create=True) # TODO: implement real fat rename... + srcdir = os.path.dirname(src) + srcname = os.path.basename(src) + dstdir = os.path.dirname(dst) + dstname = os.path.basename(dst) + if srcdir == dstdir and all(not EightDotThree.is_8dot3_conform(n) for n in [srcname, dstname]): + # cursed rename, lfn and same folder only + pdentry = self.fs._get_dir_entry(srcdir) + dentry = pdentry._search_entry(srcname) + lfn_entry = make_lfn_entry(dstname, dentry.name) + dentry.set_lfn_entry(lfn_entry) + self.fs.fs.update_directory_entry(pdentry) + self.fs.fs.flush_fat() + elif self.fs.getinfo(src).is_dir: + self.fs.movedir(src, dst, create=True) + else: + self.fs.move(src, dst, create=True) def rmtree(self, path): self.fs.removetree(path) def copytree(self, src, dst): @@ -297,8 +313,13 @@ def is_writable(self): return True except: return False - def freespace(self): - return 16777216 # TODO: implement proper freespace + def ensurespace(self, size): + try: + first = self.fs.fs.allocate_bytes(size)[0] + self.fs.fs.free_cluster_chain(first) + return True + except PyFATException: + return False def close(self): try: self.fs.close() @@ -366,8 +387,8 @@ def is_writable(self): except: writable = False return writable - def freespace(self): - return shutil.disk_usage(self.root).free + def ensurespace(self, size): + return shutil.disk_usage(self.root).free >= size def close(self): pass def reload(self): @@ -405,7 +426,7 @@ def writeProtectCheck(): # Section: SD card free space # ensure 16MB free space -if fs.freespace() < 16777216: +if not fs.ensurespace(16777216): prbad(f"Error 06: You need at least 16MB free space on your SD card, you have {(freeSpace / 1000000):.2f} bytes!") prinfo("Please free up some space and try again.") exitOnEnter() From 187c4ebe6371b03d4377eca0d21a1a6f9863bef3 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 19:53:28 +0800 Subject: [PATCH 08/17] proper errors --- MSET9_installer_script/errors.txt | 3 +++ MSET9_installer_script/mset9.py | 22 ++++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/MSET9_installer_script/errors.txt b/MSET9_installer_script/errors.txt index e103436..697dce8 100644 --- a/MSET9_installer_script/errors.txt +++ b/MSET9_installer_script/errors.txt @@ -12,5 +12,8 @@ Error 11: Running as MacOS. MacOS is not supported. Error 12: Multiple ID1s, follow MSET9 Troublshooting page. 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. MSET9 Troublshooting Page: https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 61456bc..626b11f 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -91,8 +91,8 @@ def run_diskutil_and_wait(command, dev): if len(sys.argv) < 2: if not thisfile.startswith("/Volumes/"): - #prbad("Error :") - prbad("You should run this file from sd card, or specifiy device name manually") + prbad("Error 01: Not running on the SD Card root. /Nintendo 3DS/ not found.") + # should we add some macos specific message? exitOnEnter() prinfo("Resolving device...") device = None @@ -141,8 +141,8 @@ def run_diskutil_and_wait(command, dev): time.sleep(1) if ret == 1: - #prbad("Error : ") - prbad("Can't umount sd card!") + prbad("Error 16: Unable to umount sd card.") + prinfo("Please make sure there's no other app using your sd card.") #tmp_cleanup() exitOnEnter() @@ -210,7 +210,7 @@ def activate_venv(): try: os.execlp("sudo", "sudo", sys.executable, thisfile, device) except: - prbad("Root privilege is required") + prbad("Error 17: Root privilege is required.") #tmp_cleanup() exitOnEnter(remount=True) @@ -331,9 +331,15 @@ def reload(self): try: fs = FatFS(device) - except PyFATException: - prbad("Error 14: Can't open device.") - prinfo("Make sure your sd card is unmounted in disk utility.") + except PyFATException as e: + msg = str(e) + if "Cannot open" in msg: + prbad("Error 14: Can't open device.") + prinfo("Please make sure your sd card is unmounted in disk utility.") + elif "Invalid" in msg: + prbad("Error 15: Not FAT32 formatted or corrupted filesystem.") + prinfo("Please make sure your sd card is properly formatted") + prinfo("Consult: https://wiki.hacks.guide/wiki/Formatting_an_SD_card") #tmp_cleanup() exitOnEnter() From 6c0f69421261b2932ed1a50a8e07b7baa1909622 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 19:55:22 +0800 Subject: [PATCH 09/17] remount --- 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 626b11f..a25a6ee 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -466,7 +466,7 @@ def writeProtectCheck(): except KeyboardInterrupt: print() prgood("Goodbye!") - exitOnEnter() + exitOnEnter(remount=True) except: sysModelVerSelect = 42 if sysModelVerSelect == 1: From ce426d57ffec1a5f27609aff52826ee1384111ae Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 20:08:43 +0800 Subject: [PATCH 10/17] bump version --- MSET9_installer_script/mset9.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index a25a6ee..b0bf6cf 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 import abc, os, platform, time, binascii -VERSION = "v1.1" +VERSION = "v1.2" def prgood(content): print(f"[\033[0;32m✓\033[0m] {content}") @@ -438,7 +438,7 @@ def writeProtectCheck(): exitOnEnter() clearScreen() -print(f"MSET9 {VERSION} SETUP by zoogie and Aven") +print(f"MSET9 {VERSION} SETUP by zoogie, Aven and DannyAAM") 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)") @@ -750,7 +750,7 @@ def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): exitOnEnter() clearScreen() -print(f"MSET9 {VERSION} SETUP by zoogie and Aven") +print(f"MSET9 {VERSION} SETUP by zoogie, Aven and DannyAAM") print(f"Using {consoleModel} {consoleFirmware}") print("\n-- Please type in a number then hit return --\n") From c2847d3cf0b7188a07e6047a43a31877a03dd3a0 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 20:36:01 +0800 Subject: [PATCH 11/17] fix tmp folder cleanup --- MSET9_installer_script/mset9.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index b0bf6cf..0b29511 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -68,6 +68,7 @@ def remove_extra(): osver = platform.system() thisfile = os.path.abspath(__file__) +systmp = None if osver == "Darwin": # ======== macOS / iOS? ======== @@ -76,10 +77,11 @@ def remove_extra(): tmpprefix = "mset9-macos-run-" def tmp_cleanup(): - global tmpprefix + global tmpprefix, systmp prinfo("Removing temporary folders...") import tempfile, shutil - systmp = tempfile.gettempdir() + if systmp is None: + systmp = tempfile.gettempdir() for dirname in os.listdir(systmp): if dirname.startswith(tmpprefix): shutil.rmtree(f"{systmp}/{dirname}") @@ -150,6 +152,8 @@ def run_diskutil_and_wait(command, dev): prbad("WTF???") device = sys.argv[1] + if len(sys.argv) == 3: + systmp = sys.argv[2] if not os.path.exists(device): prbad("Error 13: Device doesn't exist.") prinfo("Make sure your sd card is sitted properly.") @@ -162,13 +166,16 @@ def run_diskutil_and_wait(command, dev): venv_py = f"{venv_bin}/python3" def activate_venv(): - global venv_path, venv_bin, venv_py, device + global venv_path, venv_bin, venv_py, device, systmp #import site os.environ["PATH"] = os.pathsep.join([venv_bin, *os.environ.get("PATH", "").split(os.pathsep)]) os.environ["VIRTUAL_ENV"] = venv_path os.environ["VIRTUAL_ENV_PROMPT"] = "(mset9)" - os.execlp(venv_py, venv_py, __file__, device) + if systmp is None: + os.execlp(venv_py, venv_py, __file__, device) + else: + os.execlp(venv_py, venv_py, __file__, device, systmp) #prev_length = len(sys.path) #for lib in "__LIB_FOLDERS__".split(os.pathsep): @@ -208,7 +215,8 @@ def activate_venv(): prinfo("Input the password of your computer if prompted.") prinfo("(It won't show anything while you're typing, just type it blindly)") try: - os.execlp("sudo", "sudo", sys.executable, thisfile, device) + import tempfile + os.execlp("sudo", "sudo", sys.executable, thisfile, device, tempfile.gettempdir()) except: prbad("Error 17: Root privilege is required.") #tmp_cleanup() @@ -667,7 +675,6 @@ def remove(): id1 = id1[:32] realId1Path = id0 + "/" + id1 prgood("Successfully removed MSET9!") - remove_extra() def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): global fs @@ -781,6 +788,7 @@ def softcheck(keyfile, expectedSize = None, crc32 = None, retval = 0): exitOnEnter() elif sysModelVerSelect == 3: remove() + remove_extra() exitOnEnter(remount=True) elif sysModelVerSelect == 4 or "exit": break From c98da5dd5a7bc5b9e2784cb651f9da7d3529922d Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 20:57:14 +0800 Subject: [PATCH 12/17] increase umount retry count --- 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 0b29511..c033b41 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -134,7 +134,7 @@ def run_diskutil_and_wait(command, dev): prinfo("Trying to unmount sd card...") ret = 1 count = 0 - while count < 5: + while count < 10: ret = run_diskutil_and_wait("umount", device) if ret == 0: break From 849885f8ea2b6de7d29ed12718164f89a7ba98da Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 21:49:39 +0800 Subject: [PATCH 13/17] text fix --- MSET9_installer_script/errors.txt | 2 +- MSET9_installer_script/mset9.py | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/MSET9_installer_script/errors.txt b/MSET9_installer_script/errors.txt index 697dce8..74d6c43 100644 --- a/MSET9_installer_script/errors.txt +++ b/MSET9_installer_script/errors.txt @@ -13,7 +13,7 @@ Error 12: Multiple ID1s, follow MSET9 Troublshooting page. 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 16: Unable to umount SD card. Error 17: Root privilege is required. MSET9 Troublshooting Page: https://3ds.hacks.guide/troubleshooting#installing-boot9strap-mset9 diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index c033b41..343ccb3 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -93,7 +93,7 @@ def run_diskutil_and_wait(command, dev): if len(sys.argv) < 2: if not thisfile.startswith("/Volumes/"): - prbad("Error 01: Not running on the SD Card root. /Nintendo 3DS/ not found.") + prbad("Error 02: Your SD card is write protected! If using a full size SD card, ensure that the lock switch is facing upwards.") # should we add some macos specific message? exitOnEnter() prinfo("Resolving device...") @@ -131,7 +131,7 @@ def run_diskutil_and_wait(command, dev): tmpdir = tempfile.mkdtemp(prefix=tmpprefix) shutil.copyfile(thisfile, f"{tmpdir}/mset9.py") - prinfo("Trying to unmount sd card...") + prinfo("Trying to unmount SD card...") ret = 1 count = 0 while count < 10: @@ -143,8 +143,8 @@ def run_diskutil_and_wait(command, dev): time.sleep(1) if ret == 1: - prbad("Error 16: Unable to umount sd card.") - prinfo("Please make sure there's no other app using your sd card.") + prbad("Error 16: Unable to umount SD card.") + prinfo("Please ensure there's no other app using your SD card.") #tmp_cleanup() exitOnEnter() @@ -156,7 +156,8 @@ def run_diskutil_and_wait(command, dev): systmp = sys.argv[2] if not os.path.exists(device): prbad("Error 13: Device doesn't exist.") - prinfo("Make sure your sd card is sitted properly.") + prinfo("Ensure your SD card is inserted properly.") + prinfo("Also, don't eject SD card itself in disk utility, unmount the partition only.") #tmp_cleanup() exitOnEnter() @@ -343,10 +344,10 @@ def reload(self): msg = str(e) if "Cannot open" in msg: prbad("Error 14: Can't open device.") - prinfo("Please make sure your sd card is unmounted in disk utility.") + prinfo("Please ensure your SD card is unmounted in disk utility.") elif "Invalid" in msg: prbad("Error 15: Not FAT32 formatted or corrupted filesystem.") - prinfo("Please make sure your sd card is properly formatted") + prinfo("Please ensure your SD card is properly formatted") prinfo("Consult: https://wiki.hacks.guide/wiki/Formatting_an_SD_card") #tmp_cleanup() exitOnEnter() @@ -358,7 +359,7 @@ def cleanup(remount=False): global fs, device fs.close() if remount: - prinfo("Trying to remount sd card...") + prinfo("Trying to remount SD card...") run_diskutil_and_wait("mount", device) #tmp_cleanup() From ff0eef85b6ecc8215768e7a0675fe3e95465f0e7 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 21:53:43 +0800 Subject: [PATCH 14/17] wtf? --- 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 343ccb3..7b79632 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -93,7 +93,7 @@ def run_diskutil_and_wait(command, dev): if len(sys.argv) < 2: if not thisfile.startswith("/Volumes/"): - prbad("Error 02: Your SD card is write protected! If using a full size SD card, ensure that the lock switch is facing upwards.") + 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() prinfo("Resolving device...") From f91a4e13990c409d897c9cdec4376c196667da40 Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 22:05:00 +0800 Subject: [PATCH 15/17] fix missed tmp cleanup --- MSET9_installer_script/mset9.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/MSET9_installer_script/mset9.py b/MSET9_installer_script/mset9.py index 7b79632..f70cc43 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -119,9 +119,7 @@ def run_diskutil_and_wait(command, dev): if dirname.startswith(tmpprefix): dirpath = f"{systmp}/{dirname}" script = f"{dirpath}/mset9.py" - if not os.path.exists(script): - continue - elif os.stat(script).st_mtime > os.stat(thisfile).st_mtime: + if os.path.exists(script) and os.stat(script).st_mtime > os.stat(thisfile).st_mtime: tmpdir = dirpath break else: From 8d3f0d348292f27ba18132a8e3e974a5d87013ac Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 22:06:48 +0800 Subject: [PATCH 16/17] don't gen extension name if there's none --- 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 f70cc43..0e4ba26 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -254,7 +254,7 @@ def map_chars(name: bytes) -> bytes: if basename.isascii(): extname = extname.encode("ascii", errors="replace") extname = map_chars(extname).decode("ascii") - else: + elif len(extname) != 0: extname = "HAX" if len(extname) == 0: extsep = "" From 879f82a987618a0263d921fde777c108ec5ca89e Mon Sep 17 00:00:00 2001 From: DannyAAM Date: Tue, 31 Oct 2023 22:35:27 +0800 Subject: [PATCH 17/17] not really necessary fix? --- 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 0e4ba26..7ded25c 100644 --- a/MSET9_installer_script/mset9.py +++ b/MSET9_installer_script/mset9.py @@ -288,10 +288,8 @@ def getsize(self, path): def remove(self, path): self.fs.remove(path) def rename(self, src, dst): - srcdir = os.path.dirname(src) - srcname = os.path.basename(src) - dstdir = os.path.dirname(dst) - dstname = os.path.basename(dst) + srcdir, srcname = f"/{src}".rstrip("/").rsplit("/", 1) + dstdir, dstname = f"/{dst}".rstrip("/").rsplit("/", 1) if srcdir == dstdir and all(not EightDotThree.is_8dot3_conform(n) for n in [srcname, dstname]): # cursed rename, lfn and same folder only pdentry = self.fs._get_dir_entry(srcdir)