Exemplo n.º 1
0
 def restore_old_backup(self, params=None):
     if (util.get_cemu_dir() / "bcml_backups").exists():
         open_dir = util.get_cemu_dir() / "bcml_backups"
     else:
         open_dir = Path.home()
     try:
         file = Path(
             self.window.create_file_dialog(
                 directory=str(open_dir),
                 file_types=("BCML Backups (*.7z)", "All Files (*.*)"),
             )[0])
     except IndexError:
         return
     tmp_dir = Path(mkdtemp())
     x_args = [ZPATH, "x", str(file), f"-o{str(tmp_dir)}"]
     if system() == "Windows":
         run(
             x_args,
             capture_output=True,
             creationflags=util.CREATE_NO_WINDOW,
             check=True,
         )
     else:
         run(x_args, capture_output=True, check=True)
     upgrade.convert_old_mods(tmp_dir)
Exemplo n.º 2
0
 def launch_cemu(self, params=None):
     if not params:
         params = {"run_game": True}
     cemu = next(
         iter({
             f
             for f in util.get_cemu_dir().glob("*.exe")
             if "cemu" in f.name.lower()
         }))
     uking = util.get_game_dir().parent / "code" / "U-King.rpx"
     try:
         assert uking.exists()
     except AssertionError:
         raise FileNotFoundError("Your BOTW executable could not be found")
     cemu_args: List[str]
     if SYSTEM == "Windows":
         cemu_args = [str(cemu)]
         if params["run_game"]:
             cemu_args.extend(("-g", str(uking)))
     else:
         cemu_args = ["wine", str(cemu)]
         if params["run_game"]:
             cemu_args.extend((
                 "-g",
                 "Z:\\" + str(uking).replace("/", "\\"),
             ))
     Popen(cemu_args, cwd=str(util.get_cemu_dir()))
Exemplo n.º 3
0
def enable_bcml_gfx():
    if util.get_settings("no_cemu"):
        return

    settings = util.parse_cemu_settings()
    try:
        gpack = settings.getElementsByTagName("GraphicPack")[0]
    except IndexError:
        gpack = settings.createElement("GraphicPack")
        settings.appendChild(gpack)
    new_cemu = True

    def create_entry(path: str):
        def entry_matches(entry):
            try:
                return (path == entry.getElementsByTagName("filename")
                        [0].childNodes[0].data)
            except IndexError:
                return path == entry.getAttribute("filename")

        if any(
                entry_matches(entry)
                for entry in gpack.getElementsByTagName("Entry")):
            return
        entry: minidom.Element = settings.createElement("Entry")
        if new_cemu:
            entry.setAttribute("filename", path)
        else:
            entryfile = settings.createElement("filename")
            entryfile.appendChild(settings.createTextNode(path))
            entry.appendChild(entryfile)
        entrypreset = settings.createElement("preset")
        entrypreset.appendChild(settings.createTextNode(""))
        entry.appendChild(entrypreset)
        gpack.appendChild(entry)

    create_entry("graphicPacks\\BreathOfTheWild_BCML\\rules.txt")

    if (util.get_cemu_dir() / "graphicPacks" / "bcmlPatches").exists():
        for rules in (util.get_cemu_dir() / "graphicPacks" /
                      "bcmlPatches").rglob("rules.txt"):
            create_entry(str(rules.relative_to(util.get_cemu_dir())))

        settings.writexml(
            (util.get_cemu_dir() / "settings.xml").open("w", encoding="utf-8"),
            addindent="    ",
            newl="\n",
        )
Exemplo n.º 4
0
def restore_backup(backup: Union[str, Path]):
    """
    Restores a BCML mod configuration backup

    :param backup: The backup to restore, either by name or by path
    :type backup: Union[str, Path]
    """
    if isinstance(backup, str):
        backup = util.get_cemu_dir() / 'bcml_backups' / f'{backup}.7z'
    if not backup.exists():
        raise FileNotFoundError(f'The backup "{backup.name}" does not exist.')
    print('Clearing installed mods...')
    for folder in [
            item for item in util.get_modpack_dir().glob('*') if item.is_dir()
    ]:
        shutil.rmtree(str(folder))
    print('Extracting backup...')
    x_args = [
        str(util.get_exec_dir() / 'helpers' / '7z.exe'), 'x',
        str(backup), f'-o{str(util.get_modpack_dir())}'
    ]
    subprocess.run(x_args,
                   stdout=subprocess.PIPE,
                   stderr=subprocess.PIPE,
                   creationflags=util.CREATE_NO_WINDOW)
    print('Re-enabling mods in Cemu...')
    refresh_cemu_mods()
    print(f'Backup "{backup.name}" restored')
Exemplo n.º 5
0
def create_backup(name: str = ''):
    """
    Creates a backup of the current mod installations. Saves it as a 7z file in
    `CEMU_DIR/bcml_backups`.

    :param name: The name to give the backup, defaults to "BCML_Backup_YYYY-MM-DD"
    :type name: str, optional
    """
    import re
    if not name:
        name = f'BCML_Backup_{datetime.datetime.now().strftime("%Y-%m-%d")}'
    else:
        name = re.sub(r'(?u)[^-\w.]', '', name.strip().replace(' ', '_'))
    output = util.get_cemu_dir() / 'bcml_backups' / f'{name}.7z'
    output.parent.mkdir(parents=True, exist_ok=True)
    print(f'Saving backup {name}...')
    x_args = [
        str(util.get_exec_dir() / 'helpers' / '7z.exe'), 'a',
        str(output), f'{str(util.get_modpack_dir() / "*")}'
    ]
    subprocess.run(x_args,
                   stdout=subprocess.PIPE,
                   stderr=subprocess.PIPE,
                   creationflags=util.CREATE_NO_WINDOW)
    print(f'Backup "{name}" created')
Exemplo n.º 6
0
 def uninstall_all(self):
     for folder in {d for d in util.get_modpack_dir().glob("*") if d.is_dir()}:
         rmtree(folder, onerror=install.force_del)
     if not util.get_settings("no_cemu"):
         shutil.rmtree(
             util.get_cemu_dir() / "graphicPacks" / "bcmlPatches", ignore_errors=True
         )
Exemplo n.º 7
0
def disable_bcml_gfx():
    if not util.get_settings("no_cemu"):
        settings = util.parse_cemu_settings()
        try:
            gpack = settings.getElementsByTagName("GraphicPack")[0]
        except IndexError:
            gpack = settings.createElement("GraphicPack")
            settings.appendChild(gpack)
        new_cemu = True
        entry: minidom.Element
        for entry in gpack.getElementsByTagName("Entry"):
            if new_cemu and entry.getElementsByTagName("filename"):
                new_cemu = False
            try:
                if ("bcml" in entry.getElementsByTagName("filename")
                    [0].childNodes[0].data.lower()):
                    gpack.removeChild(entry)
            except IndexError:
                if "bcml" in entry.getAttribute("filename").lower():
                    gpack.removeChild(entry)
        settings.writexml(
            (util.get_cemu_dir() / "settings.xml").open("w", encoding="utf-8"),
            addindent="    ",
            newl="\n",
        )
Exemplo n.º 8
0
def uninstall_mod(mod: BcmlMod, wait_merge: bool = False):
    has_patches = (mod.path / "patches").exists()
    try:
        shutil.rmtree(str(mod.path), onerror=force_del)
    except (OSError, PermissionError, WindowsError) as err:
        raise RuntimeError(
            f"The folder for {mod.name} could not be removed. "
            "You may need to delete it manually and remerge, or "
            "close all open programs (including BCML and Windows Explorer) "
            "and try again. The location of the folder is "
            f"<code>{str(mod.path)}</code>.") from err

    for fall_mod in [
            m for m in util.get_installed_mods(True)
            if m.priority > mod.priority
    ]:
        fall_mod.change_priority(fall_mod.priority - 1)

    if not util.get_installed_mods():
        shutil.rmtree(util.get_master_modpack_dir())
        util.create_bcml_graphicpack_if_needed()
    else:
        if not wait_merge:
            refresh_merges()

    if has_patches and not util.get_settings("no_cemu"):
        shutil.rmtree(
            util.get_cemu_dir() / "graphicPacks" / "bcmlPatches" /
            util.get_safe_pathname(mod.name),
            ignore_errors=True,
        )

    print(f"{mod.name} has been uninstalled.")
Exemplo n.º 9
0
 def get_old_mods(self):
     return len(
         {
             d
             for d in (util.get_cemu_dir() / "graphicPacks" / "BCML").glob("*")
             if d.is_dir()
         }
     )
Exemplo n.º 10
0
def link_master_mod(output: Path = None):
    util.create_bcml_graphicpack_if_needed()
    if not output:
        if util.get_settings("no_cemu"):
            return
        output = util.get_cemu_dir() / "graphicPacks" / "BreathOfTheWild_BCML"
    if output.exists():
        shutil.rmtree(output, ignore_errors=True)
    try:
        output.mkdir(parents=True, exist_ok=True)
        if not util.get_settings("no_cemu"):
            shutil.copy(util.get_master_modpack_dir() / "rules.txt",
                        output / "rules.txt")
    except (OSError, PermissionError, FileExistsError,
            FileNotFoundError) as err:
        raise RuntimeError(
            "There was a problem creating the master BCML graphic pack. "
            "It may be a one time fluke, so try remerging and/or restarting BCML. "
            "This can also happen if BCML and/or Cemu are installed into Program "
            "Files or any folder which requires administrator (or root) permissions. "
            "You can try running BCML as administrator or root, but bear in mind this "
            "is not officially supported. If the problem persists, good luck, because "
            "it's something wonky about your PC, I guess.") from err

    mod_folders: List[Path] = sorted(
        [
            item for item in util.get_modpack_dir().glob("*")
            if item.is_dir() and not (item / ".disabled").exists()
        ],
        reverse=True,
    )
    util.vprint(mod_folders)
    link_or_copy: Any = os.link if not util.get_settings(
        "no_hardlinks") else copyfile
    for mod_folder in mod_folders:
        for item in mod_folder.rglob("**/*"):
            rel_path = item.relative_to(mod_folder)
            exists = (output / rel_path).exists()
            is_log = str(rel_path).startswith("logs")
            is_opt = str(rel_path).startswith("options")
            is_meta = str(rel_path).startswith("meta")
            is_extra = (len(rel_path.parts) == 1 and rel_path.suffix != ".txt"
                        and not item.is_dir())
            if exists or is_log or is_extra or is_meta or is_opt:
                continue
            if item.is_dir():
                (output / rel_path).mkdir(parents=True, exist_ok=True)
            elif item.is_file():
                try:
                    link_or_copy(str(item), str(output / rel_path))
                except OSError:
                    if link_or_copy is os.link:
                        link_or_copy = copyfile
                        link_or_copy(str(item), str(output / rel_path))
                    else:
                        raise
Exemplo n.º 11
0
def refresh_cemu_mods():
    """ Updates Cemu's enabled graphic packs """
    setpath = util.get_cemu_dir() / 'settings.xml'
    if not setpath.exists():
        raise FileNotFoundError('The Cemu settings file could not be found.')
    setread = ''
    with setpath.open('r') as setfile:
        for line in setfile:
            setread += line.strip()
    settings = minidom.parseString(setread)
    try:
        gpack = settings.getElementsByTagName('GraphicPack')[0]
    except IndexError:
        gpack = settings.createElement('GraphicPack')
        settings.appendChild(gpack)
    # Issue #33 - check for new cemu Entry layout
    new_cemu_version = False
    for entry in gpack.getElementsByTagName('Entry'):
        if len(entry.getElementsByTagName('filename')) == 0:
            new_cemu_version = True
            break
    # Issue #33 - end Entry layout check
    for entry in gpack.getElementsByTagName('Entry'):
        # Issue #33 - handle BCML node by Cemu version
        if new_cemu_version:
            if 'BCML' in entry.getAttribute('filename'):
                gpack.removeChild(entry)
        else:
            try:
                if 'BCML' in entry.getElementsByTagName(
                        'filename')[0].childNodes[0].data:
                    gpack.removeChild(entry)
            except IndexError:
                pass
    bcmlentry = create_settings_mod_node(settings, new_cemu_version)
    # Issue #33 - end BCML node
    gpack.appendChild(bcmlentry)
    for mod in util.get_installed_mods():
        # Issue #33 - handle new mod nodes by Cemu version
        modentry = create_settings_mod_node(settings, new_cemu_version, mod)
        # Issue #33 - end handling new mod nodes
        gpack.appendChild(modentry)
    settings.writexml(setpath.open('w', encoding='utf-8'),
                      addindent='    ',
                      newl='\n')
Exemplo n.º 12
0
def refresh_master_export():
    print("Exporting merged mod pack...")
    link_master_mod()
    if not util.get_settings("no_cemu"):
        settings = util.parse_cemu_settings()
        try:
            gpack = settings.getElementsByTagName("GraphicPack")[0]
        except IndexError:
            gpack = settings.createElement("GraphicPack")
            settings.appendChild(gpack)
        new_cemu = True
        entry: minidom.Element
        for entry in gpack.getElementsByTagName("Entry"):
            if new_cemu and entry.getElementsByTagName("filename"):
                new_cemu = False
            try:
                if "BCML" in entry.getElementsByTagName(
                        "filename")[0].childNodes[0].data:
                    break
            except IndexError:
                if "BCML" in entry.getAttribute("filename"):
                    break
        else:
            bcmlentry = settings.createElement("Entry")
            if new_cemu:
                bcmlentry.setAttribute(
                    "filename",
                    "graphicPacks\\BreathOfTheWild_BCML\\rules.txt")
            else:
                entryfile = settings.createElement("filename")
                entryfile.appendChild(
                    settings.createTextNode(
                        "graphicPacks\\BreathOfTheWild_BCML\\rules.txt"))
                bcmlentry.appendChild(entryfile)
            entrypreset = settings.createElement("preset")
            entrypreset.appendChild(settings.createTextNode(""))
            bcmlentry.appendChild(entrypreset)
            gpack.appendChild(bcmlentry)
            settings.writexml(
                (util.get_cemu_dir() / "settings.xml").open("w",
                                                            encoding="utf-8"),
                addindent="    ",
                newl="\n",
            )
Exemplo n.º 13
0
def link_master_mod(output: Path = None):
    if not output:
        output = util.get_cemu_dir() / 'graphicPacks' / 'BreathOfTheWild_BCML'
    if output.exists():
        shutil.rmtree(str(output), ignore_errors=True)
    output.mkdir(parents=True, exist_ok=True)
    mod_folders: List[Path] = sorted(
        [item for item in util.get_modpack_dir().glob('*') if item.is_dir()],
        reverse=True)
    shutil.copy(str(util.get_master_modpack_dir() / 'rules.txt'),
                str(output / 'rules.txt'))
    for mod_folder in mod_folders:
        for item in mod_folder.rglob('**/*'):
            rel_path = item.relative_to(mod_folder)
            if (output / rel_path).exists()\
               or (str(rel_path).startswith('logs'))\
               or (len(rel_path.parts) == 1 and rel_path.suffix != '.txt'):
                continue
            if item.is_dir():
                (output / rel_path).mkdir(parents=True, exist_ok=True)
            elif item.is_file():
                os.link(str(item), str(output / rel_path))
Exemplo n.º 14
0
def convert_old_mods(source: Path = None):
    mod_dir = util.get_modpack_dir()
    old_path = source or util.get_cemu_dir() / "graphicPacks" / "BCML"
    print("Copying old mods...")
    shutil.rmtree(mod_dir, ignore_errors=True)
    shutil.copytree(old_path, mod_dir)
    print("Converting old mods...")
    for i, mod in enumerate(
            sorted({
                d
                for d in mod_dir.glob("*")
                if d.is_dir() and d.name != "9999_BCML"
            })):
        print(f"Converting {mod.name[4:]}")
        try:
            convert_old_mod(mod, True)
        except Exception as err:
            shutil.rmtree(mod)
            install.refresh_merges()
            raise RuntimeError(
                f"BCML was unable to convert {mod.name[4:]}. Error: {str(err)}. Your old "
                f"mods have not been modified. {i} mod(s) were successfully imported."
            ) from err
    shutil.rmtree(old_path, ignore_errors=True)
Exemplo n.º 15
0
def install_mod(
    mod: Path,
    options: dict = None,
    selects: dict = None,
    pool: Optional[multiprocessing.pool.Pool] = None,
    insert_priority: int = 0,
    merge_now: bool = False,
    updated: bool = False,
):
    if not insert_priority:
        insert_priority = get_next_priority()

    try:
        if isinstance(mod, str):
            mod = Path(mod)
        if mod.is_file():
            print("Opening mod...")
            tmp_dir = open_mod(mod)
        elif mod.is_dir():
            if not ((mod / "rules.txt").exists() or
                    (mod / "info.json").exists()):
                print(
                    f"Cannot open mod at {str(mod)}, no rules.txt or info.json found"
                )
                return
            print(f"Loading mod from {str(mod)}...")
            tmp_dir = Path(mkdtemp())
            if tmp_dir.exists():
                shutil.rmtree(tmp_dir)
            shutil.copytree(str(mod), str(tmp_dir))
            if (mod /
                    "rules.txt").exists() and not (mod / "info.json").exists():
                print("Upgrading old mod format...")
                upgrade.convert_old_mod(mod, delete_old=True)
        else:
            print(f"Error: {str(mod)} is neither a valid file nor a directory")
            return
    except Exception as err:  # pylint: disable=broad-except
        raise util.InstallError(err) from err

    if not options:
        options = {"options": {}, "disable": []}

    this_pool: Optional[multiprocessing.pool.Pool] = None  # type: ignore
    try:
        rules = json.loads((tmp_dir / "info.json").read_text("utf-8"))
        mod_name = rules["name"].strip(" '\"").replace("_", "")
        print(f"Identified mod: {mod_name}")
        if rules["depends"]:
            try:
                installed_metas = {
                    v[0]: v[1]
                    for m in util.get_installed_mods()
                    for v in util.BcmlMod.meta_from_id(m.id)
                }
            except (IndexError, TypeError) as err:
                raise RuntimeError(
                    f"This BNP has invalid or corrupt dependency data.")
            for depend in rules["depends"]:
                depend_name, depend_version = util.BcmlMod.meta_from_id(depend)
                if (depend_name not in installed_metas) or (
                        depend_name in installed_metas
                        and depend_version > installed_metas[depend_name]):
                    raise RuntimeError(
                        f"{mod_name} requires {depend_name} version {depend_version}, "
                        f"but it is not installed. Please install {depend_name} and "
                        "try again.")
        friendly_plaform = lambda p: "Wii U" if p == "wiiu" else "Switch"
        user_platform = "wiiu" if util.get_settings("wiiu") else "switch"
        if rules["platform"] != user_platform:
            raise ValueError(
                f'"{mod_name}" is for {friendly_plaform(rules["platform"])}, not '
                f" {friendly_plaform(user_platform)}.'")
        if "priority" in rules and rules["priority"] == "base":
            insert_priority = 100

        logs = tmp_dir / "logs"
        if logs.exists():
            print("Loading mod logs...")
            for merger in [
                    merger()  # type: ignore
                    for merger in mergers.get_mergers()
                    if merger.NAME in options["disable"]
            ]:
                if merger.is_mod_logged(BcmlMod(tmp_dir)):
                    (tmp_dir / "logs" / merger.log_name).unlink()
        else:
            this_pool = pool or Pool(maxtasksperchild=500)
            dev._pack_sarcs(tmp_dir,
                            util.get_hash_table(util.get_settings("wiiu")),
                            this_pool)
            generate_logs(tmp_dir=tmp_dir, options=options, pool=this_pool)
            if not util.get_settings("strip_gfx"):
                (tmp_dir / ".processed").touch()
    except Exception as err:  # pylint: disable=broad-except
        try:
            name = mod_name
        except NameError:
            name = "your mod, the name of which could not be detected"
        raise util.InstallError(err, name) from err

    if selects:
        for opt_dir in {
                d
                for d in (tmp_dir / "options").glob("*") if d.is_dir()
        }:
            if opt_dir.name not in selects:
                shutil.rmtree(opt_dir, ignore_errors=True)
            else:
                file: Path
                for file in {
                        f
                        for f in opt_dir.rglob("**/*")
                        if ("logs" not in f.parts and f.is_file())
                }:
                    out = tmp_dir / file.relative_to(opt_dir)
                    out.parent.mkdir(parents=True, exist_ok=True)
                    try:
                        os.link(file, out)
                    except FileExistsError:
                        if file.suffix in util.SARC_EXTS:
                            try:
                                old_sarc = oead.Sarc(
                                    util.unyaz_if_needed(out.read_bytes()))
                            except (ValueError, oead.InvalidDataError,
                                    RuntimeError):
                                out.unlink()
                                os.link(file, out)
                            try:
                                link_sarc = oead.Sarc(
                                    util.unyaz_if_needed(file.read_bytes()))
                            except (ValueError, oead.InvalidDataError,
                                    RuntimeError):
                                del old_sarc
                                continue
                            new_sarc = oead.SarcWriter.from_sarc(link_sarc)
                            link_files = {
                                f.name
                                for f in link_sarc.get_files()
                            }
                            for sarc_file in old_sarc.get_files():
                                if sarc_file.name not in link_files:
                                    new_sarc.files[sarc_file.name] = bytes(
                                        sarc_file.data)
                            del old_sarc
                            del link_sarc
                            out.write_bytes(new_sarc.write()[1])
                            del new_sarc
                        else:
                            out.unlink()
                            os.link(file, out)

    rstb_path = (tmp_dir / util.get_content_path() / "System" / "Resource" /
                 "ResourceSizeTable.product.srsizetable")
    if rstb_path.exists():
        rstb_path.unlink()

    priority = insert_priority
    print(f"Assigned mod priority of {priority}")
    mod_id = util.get_mod_id(mod_name, priority)
    mod_dir = util.get_modpack_dir() / mod_id

    try:
        if not updated:
            for existing_mod in util.get_installed_mods(True):
                if existing_mod.priority >= priority:
                    existing_mod.change_priority(existing_mod.priority + 1)

        if (tmp_dir / "patches").exists() and not util.get_settings("no_cemu"):
            patch_dir = (util.get_cemu_dir() / "graphicPacks" /
                         f"bcmlPatches" /
                         util.get_safe_pathname(rules["name"]))
            patch_dir.mkdir(parents=True, exist_ok=True)
            for file in {
                    f
                    for f in (tmp_dir / "patches").rglob("*") if f.is_file()
            }:
                out = patch_dir / file.relative_to(tmp_dir / "patches")
                out.parent.mkdir(parents=True, exist_ok=True)
                shutil.copyfile(file, out)

        mod_dir.parent.mkdir(parents=True, exist_ok=True)
        print(f"Moving mod to {str(mod_dir)}...")
        if mod.is_file():
            try:
                shutil.move(str(tmp_dir), str(mod_dir))
            except Exception:  # pylint: disable=broad-except
                try:
                    shutil.rmtree(str(mod_dir))
                    shutil.copytree(str(tmp_dir), str(mod_dir))
                    shutil.rmtree(str(tmp_dir), ignore_errors=True)
                except Exception:  # pylint: disable=broad-except
                    raise OSError(
                        "BCML could not transfer your mod from the temp directory to the"
                        " BCML directory.")
        elif mod.is_dir():
            shutil.copytree(str(tmp_dir), str(mod_dir))

        shutil.rmtree(tmp_dir, ignore_errors=True)

        rules["priority"] = priority
        (mod_dir / "info.json").write_text(json.dumps(rules,
                                                      ensure_ascii=False,
                                                      indent=2),
                                           encoding="utf-8")
        (mod_dir / "options.json").write_text(json.dumps(options,
                                                         ensure_ascii=False,
                                                         indent=2),
                                              encoding="utf-8")

        output_mod = BcmlMod(mod_dir)
        try:
            util.get_mod_link_meta(rules)
            util.get_mod_preview(output_mod)
        except Exception:  # pylint: disable=broad-except
            pass
    except Exception as err:  # pylint: disable=broad-except
        if mod_dir.exists():
            try:
                uninstall_mod(mod_dir, wait_merge=True)
            except Exception:  # pylint: disable=broad-except
                shutil.rmtree(str(mod_dir))
        raise util.InstallError(err, mod_name) from err

    try:
        if merge_now:
            for merger in [m() for m in mergers.get_mergers()]:
                if this_pool or pool:
                    merger.set_pool(this_pool or pool)
                if merger.NAME in options["options"]:
                    merger.set_options(options["options"][merger.NAME])
                merger.perform_merge()
    except Exception as err:  # pylint: disable=broad-except
        raise util.MergeError(err) from err

    if this_pool and not pool:
        this_pool.close()
        this_pool.join()
    return output_mod
Exemplo n.º 16
0
def get_backups() -> List[Path]:
    """ Gets a list of BCML mod configuration backups """
    return list((util.get_cemu_dir() / 'bcml_backups').glob('*.7z'))
Exemplo n.º 17
0
 def delete_old_mods(self):
     rmtree(util.get_cemu_dir() / "graphicPacks" / "BCML")