def extract_refs(language: str, tmp_dir: Path, files: set = None): x_args = [ get_7z_path(), "x", str(util.get_exec_dir() / "data" / "text_refs.7z"), f'-o{str(tmp_dir / "refs")}', ] if files: x_args.extend(files) else: x_args.append(language) result: subprocess.CompletedProcess if system() == "Windows": result = subprocess.run( x_args, capture_output=True, creationflags=util.CREATE_NO_WINDOW, check=False, text=True, ) else: result = subprocess.run(x_args, capture_output=True, text=True, check=False) if result.stderr: raise RuntimeError(result.stderr)
def update_bcml(self): if util.get_latest_bcml() <= VERSION: return exe = util.get_python_exe().replace("pythonw", "python") args = [ exe, "-m", "pip", "install", "--disable-pip-version-check", "--no-warn-script-location", "--upgrade", "--pre" if DEBUG else "", "bcml", ] parent = util.get_exec_dir().parent if parent.name == "pkgs": (parent / "bcml").rename(parent / "bcml.bak") if SYSTEM == "Windows": result = run( args, creationflags=util.CREATE_NO_WINDOW, capture_output=True, check=False, text=True, ) else: result = run( args, capture_output=True, check=False, text=True, ) if result.stderr: raise RuntimeError(result.stderr)
def __init__(self) -> None: self._gameid = "5866" if util.get_settings("wiiu") else "6386" if not GB_DATA.exists(): GB_DATA.write_bytes( util.decompress( (util.get_exec_dir() / "data" / "gb.sjson").read_bytes())) self._data = json.loads(GB_DATA.read_text("utf-8"))
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')
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')
def get_text_hashes(language: str = None) -> {}: hashes = json.loads( util.decompress((util.get_exec_dir() / "data" / "hashes" / "msyts.sjson").read_bytes()).decode("utf8")) if language: return hashes[language if not language.endswith("en") else "XXen"] else: return hashes
def _win_create_handler(): # pylint: disable=import-error,import-outside-toplevel,undefined-variable import winreg if (util.get_exec_dir().parent.parent.parent / "Scripts" / "bcml.exe").exists(): exec_path = ('"' + str((util.get_exec_dir().parent.parent.parent / "Scripts" / "bcml.exe").resolve()) + '"') elif (util.get_exec_dir().parent.parent / "bin" / "bcml.exe").exists(): exec_path = ('"' + str( (util.get_exec_dir().parent.parent / "bin" / "bcml.exe").resolve()) + '"') else: exec_path = f'"{sys.executable}" -m bcml' with winreg.CreateKey(winreg.HKEY_CURRENT_USER, r"Software\Classes\bcml") as key: try: with winreg.OpenKey( winreg.HKEY_CURRENT_USER, r"Software\Classes\bcml\shell\open\command", 0, winreg.KEY_READ, ) as okey: assert exec_path in winreg.QueryValueEx(okey, "")[0] except (WindowsError, OSError, AssertionError): winreg.SetValueEx(key, "URL Protocol", 0, winreg.REG_SZ, "") with winreg.CreateKey(key, r"shell\open\command") as key2: winreg.SetValueEx(key2, "", 0, winreg.REG_SZ, f'{exec_path} "%1"') with winreg.CreateKey(winreg.HKEY_CURRENT_USER, r"Software\Classes\.bnp") as key: try: with winreg.OpenKey( winreg.HKEY_CURRENT_USER, r"Software\Classes\.bnp", 0, winreg.KEY_READ, ) as okey: assert winreg.QueryValueEx(okey, "")[0] == "bcml" except (WindowsError, OSError, AssertionError): winreg.SetValueEx(key, "", 0, winreg.REG_SZ, "bcml")
def open_mod(path: Path) -> Path: """ Extracts a provided mod and returns the root path of the graphicpack inside :param path: The path to the mod archive. :type path: class:`pathlib.Path` :returns: The path to the extracted root of the mod where the rules.txt file is found. :rtype: class:`pathlib.Path` """ if isinstance(path, str): path = Path(path) tmpdir = util.get_work_dir() / f'tmp_{xxhash.xxh32(str(path)).hexdigest()}' formats = ['.rar', '.zip', '.7z', '.bnp'] if tmpdir.exists(): shutil.rmtree(tmpdir, ignore_errors=True) if path.suffix.lower() in formats: x_args = [ str(util.get_exec_dir() / 'helpers' / '7z.exe'), 'x', str(path), f'-o{str(tmpdir)}' ] subprocess.run(x_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=util.CREATE_NO_WINDOW) else: raise Exception( 'The mod provided was not a supported archive (BNP, ZIP, RAR, or 7z).' ) if not tmpdir.exists(): raise Exception('No files were extracted.') rulesdir = tmpdir found_rules = (rulesdir / 'rules.txt').exists() if not found_rules: for subdir in tmpdir.rglob('*'): if (subdir / 'rules.txt').exists(): rulesdir = subdir found_rules = True break if not found_rules: raise FileNotFoundError( f'No rules.txt was found in "{path.name}".') return rulesdir
"EUen", "USfr", "USes", "EUde", "EUes", "EUfr", "EUit", "EUnl", "EUru", "CNzh", "JPja", "KRko", "TWzh", ] MSYT_PATH = str(util.get_exec_dir() / "helpers" / "msyt{}".format(".exe" if system() == "Windows" else "")) @lru_cache(2) def get_text_hashes(language: str = None) -> {}: hashes = json.loads( util.decompress((util.get_exec_dir() / "data" / "hashes" / "msyts.sjson").read_bytes()).decode("utf8")) if language: return hashes[language if not language.endswith("en") else "XXen"] else: return hashes def get_user_languages() -> set:
def main(debug: bool = False): set_start_method("spawn", True) global logger # pylint: disable=invalid-name,global-statement logger = None try: if SYSTEM != "Windows": chmod(util.get_exec_dir() / "helpers/msyt", int("755", 8)) chmod(util.get_exec_dir() / "helpers/7z", int("755", 8)) os.setpgrp() LOG.parent.mkdir(parents=True, exist_ok=True) for folder in util.get_work_dir().glob("*"): rmtree(folder) (util.get_data_dir() / "tmp_settings.json").unlink() except (FileNotFoundError, OSError, PermissionError): pass _oneclick.register_handlers() oneclick = Thread(target=_oneclick.listen) oneclick.daemon = True oneclick.start() server_port = util.get_open_port() server = Process(target=start_server, args=(server_port,)) server.daemon = True server.start() host = f"http://localhost:{server_port}" api = Api(host) gui = "cef" if SYSTEM == "Windows" else "qt" if not debug: debug = DEBUG or "bcml-debug" in sys.argv if SYSTEM == "Windows": configure_cef(debug) now = datetime.now() if ( now.month == 4 and now.day == 1 and not (util.get_data_dir() / ".fooled").exists() ): (util.get_data_dir() / ".fooled").write_bytes(b"") url = ( [ "https://www.youtube.com/embed/Lrj2Hq7xqQ8", "https://www.youtube.com/embed/8B1fu3AuDrQ", "https://www.youtube.com/embed/jRMHp7_kPec", "https://www.youtube.com/embed/N9qYF9DZPdw", "https://www.youtube.com/embed/j1FGaCNN1aw", ][randint(0, 4)] ) + "?autoplay=1" width, height = 640, 360 elif (util.get_data_dir() / "settings.json").exists(): url = f"{host}/index.html" width, height = 907, 680 else: url = f"{host}/index.html?firstrun=yes" width, height = 750, 600 api.window = webview.create_window( "BOTW Cross-Platform Mod Loader", url=url, js_api=api, text_select=DEBUG, width=width, height=height, min_size=(width if width == 750 else 820, 600), ) logger = Messager(api.window) api.window.closing += stop_it messager = Messager(api.window) with redirect_stderr(sys.stdout): with redirect_stdout(messager): # type: ignore sleep(0.5) webview.start( gui=gui, debug=debug, http_server=True, func=_oneclick.process_arg ) api.cleanup() stop_it(messager=messager)
def __init__(self, *args, **kwargs): super().__init__(*args, directory=str(get_exec_dir() / "assets"), **kwargs)
def main(debug: bool = False): set_start_method("spawn", True) global logger # pylint: disable=invalid-name,global-statement logger = None try: if SYSTEM != "Windows": chmod(util.get_exec_dir() / "helpers/msyt", int("755", 8)) chmod(util.get_exec_dir() / "helpers/7z", int("755", 8)) os.setpgrp() LOG.parent.mkdir(parents=True, exist_ok=True) for folder in util.get_work_dir().glob("*"): rmtree(folder) except (FileNotFoundError, OSError, PermissionError): pass _oneclick.register_handlers() oneclick = Thread(target=_oneclick.listen) oneclick.daemon = True oneclick.start() server_port = util.get_open_port() server = Process(target=start_server, args=(server_port, )) server.daemon = True server.start() host = f"http://localhost:{server_port}" api = Api(host) gui = "cef" if SYSTEM == "Windows" else "qt" if (util.get_data_dir() / "settings.json").exists(): url = f"{host}/index.html" width, height = 907, 680 else: url = f"{host}/index.html?firstrun=yes" width, height = 750, 600 api.window = webview.create_window( "BOTW Cross-Platform Mod Loader", url=url, js_api=api, text_select=DEBUG, width=width, height=height, min_size=(width if width == 750 else 820, 600), ) logger = Messager(api.window) api.window.closing += stop_it if not debug: debug = DEBUG or "bcml-debug" in sys.argv messager = Messager(api.window) with redirect_stderr(sys.stdout): with redirect_stdout(messager): sleep(0.5) webview.start(gui=gui, debug=debug, http_server=True, func=_oneclick.process_arg) stop_it(messager=messager)
def create_bnp_mod(mod: Path, output: Path, options: dict = None): """[summary] :param mod: [description] :type mod: Path :param output: [description] :type output: Path :param options: [description], defaults to {} :type options: dict, optional """ if isinstance(mod, str): mod = Path(mod) if mod.is_file(): print('Extracting mod...') tmp_dir: Path = open_mod(mod) elif mod.is_dir(): print(f'Loading mod from {str(mod)}...') tmp_dir: Path = util.get_work_dir() / \ f'tmp_{xxhash.xxh32(str(mod)).hexdigest()}' shutil.copytree(str(mod), str(tmp_dir)) else: print(f'Error: {str(mod)} is neither a valid file nor a directory') return print('Packing loose files...') pack_folders = sorted( { d for d in tmp_dir.rglob('**/*') if d.is_dir() and d.suffix in util.SARC_EXTS }, key=lambda d: len(d.parts), reverse=True) for folder in pack_folders: new_tmp: Path = folder.with_suffix(folder.suffix + '.tmp') shutil.move(folder, new_tmp) new_sarc = sarc.SARCWriter(be=True) for file in {f for f in new_tmp.rglob('**/*') if f.is_file()}: new_sarc.add_file( file.relative_to(new_tmp).as_posix(), file.read_bytes()) sarc_bytes = new_sarc.get_bytes() if str(folder.suffix).startswith('.s') and folder.suffix != '.sarc': sarc_bytes = util.compress(sarc_bytes) folder.write_bytes(sarc_bytes) shutil.rmtree(new_tmp) if not options: options = {} options['texts'] = {'user_only': False} pool = Pool(cpu_count()) logged_files = generate_logs(tmp_dir, options=options, original_pool=pool) print('Removing unnecessary files...') if (tmp_dir / 'logs' / 'map.yml').exists(): print('Removing map units...') for file in [file for file in logged_files if isinstance(file, Path) and \ fnmatch(file.name, '[A-Z]-[0-9]_*.smubin')]: file.unlink() if [file for file in (tmp_dir / 'logs').glob('*texts*')]: print('Removing language bootup packs...') for bootup_lang in (tmp_dir / 'content' / 'Pack').glob('Bootup_*.pack'): bootup_lang.unlink() if (tmp_dir / 'logs' / 'actorinfo.yml').exists() and \ (tmp_dir / 'content' / 'Actor' / 'ActorInfo.product.sbyml').exists(): print('Removing ActorInfo.product.sbyml...') (tmp_dir / 'content' / 'Actor' / 'ActorInfo.product.sbyml').unlink() if (tmp_dir / 'logs' / 'gamedata.yml').exists() or ( tmp_dir / 'logs' / 'savedata.yml').exists(): print('Removing gamedata sarcs...') with (tmp_dir / 'content' / 'Pack' / 'Bootup.pack').open('rb') as b_file: bsarc = sarc.read_file_and_make_sarc(b_file) csarc = sarc.make_writer_from_sarc(bsarc) bsarc_files = list(bsarc.list_files()) if 'GameData/gamedata.ssarc' in bsarc_files: csarc.delete_file('GameData/gamedata.ssarc') if 'GameData/savedataformat.ssarc' in bsarc_files: csarc.delete_file('GameData/savedataformat.ssarc') with (tmp_dir / 'content' / 'Pack' / 'Bootup.pack').open('wb') as b_file: csarc.write(b_file) hashes = util.get_hash_table() print('Creating partial packs...') sarc_files = { file for file in tmp_dir.rglob('**/*') if file.suffix in util.SARC_EXTS } if sarc_files: pool.map(partial(_clean_sarc, hashes=hashes, tmp_dir=tmp_dir), sarc_files) pool.close() pool.join() sarc_files = { file for file in tmp_dir.rglob('**/*') if file.suffix in util.SARC_EXTS } if sarc_files: with (tmp_dir / 'logs' / 'packs.log').open( 'w', encoding='utf-8') as p_file: final_packs = [ file for file in list(tmp_dir.rglob('**/*')) if file.suffix in util.SARC_EXTS ] if final_packs: p_file.write('name,path\n') for file in final_packs: p_file.write( f'{util.get_canon_name(file.relative_to(tmp_dir))},' f'{file.relative_to(tmp_dir)}\n') else: if (tmp_dir / 'logs' / 'packs.log').exists(): (tmp_dir / 'logs' / 'packs.log').unlink() print('Cleaning any junk files...') for file in tmp_dir.rglob('**/*'): if file.parent.stem == 'logs': continue if file.suffix in ['.yml', '.bak', '.tmp', '.old']: file.unlink() print('Removing blank folders...') for folder in reversed(list(tmp_dir.rglob('**/*'))): if folder.is_dir() and not list(folder.glob('*')): shutil.rmtree(folder) print(f'Saving output file to {str(output)}...') x_args = [ str(util.get_exec_dir() / 'helpers' / '7z.exe'), 'a', str(output), f'{str(tmp_dir / "*")}' ] subprocess.run(x_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=util.CREATE_NO_WINDOW) print('Conversion complete.')