diff --git a/api/cheats_api.py b/api/cheats_api.py index b777e6d..5dec79d 100644 --- a/api/cheats_api.py +++ b/api/cheats_api.py @@ -49,3 +49,12 @@ def open_cheat_mod_folder(folder_path: str): return success_response(open_cheat_mod_folder(folder_path)) except Exception as e: return exception_response(e) + + +@eel.expose +def get_game_data(): + from module.cheats import get_game_data + try: + return success_response(get_game_data()) + except Exception as e: + return exception_response(e) diff --git a/api/common_response.py b/api/common_response.py index dbd8e6f..ab7c1d9 100644 --- a/api/common_response.py +++ b/api/common_response.py @@ -2,6 +2,7 @@ from module.msg_notifier import send_notify from exception.common_exception import * from exception.download_exception import * +from exception.install_exception import * logger = logging.getLogger(__name__) @@ -51,12 +52,20 @@ def download_not_completed_handler(ex: DownloadNotCompleted): return error_response(603, str(ex)) +def fail_to_copy_files_handler(ex: FailToCopyFiles): + logger.exception(ex.raw_exception) + send_notify(f'{ex.msg}, 这可能是由于相关文件被占用或者没有相关目录的写入权限造成的') + send_notify(f'请检查相关程序是否已经关闭, 或者重启一下系统试试') + return error_response(701, str(ex)) + + exception_handler_map = { VersionNotFoundException: version_not_found_handler, Md5NotMatchException: md5_not_found_handler, DownloadInterrupted: download_interrupted_handler, DownloadPaused: download_paused_handler, DownloadNotCompleted: download_not_completed_handler, + FailToCopyFiles: fail_to_copy_files_handler, } diff --git a/changelog.md b/changelog.md index 313a3b4..b324450 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,10 @@ # Change Log +## 0.3.5 + - 打开金手指管理界面时异步加载游戏信息 + - DoH 查询时复用已建立的连接 + - 一些 bug 修复及错误信息调整 + ## 0.3.4 - 调整 Edge 浏览器的检测逻辑 - 调整 requests cache 的缓存后端 diff --git a/config.py b/config.py index 1111e10..c73623f 100644 --- a/config.py +++ b/config.py @@ -9,7 +9,7 @@ import sys -current_version = '0.3.4' +current_version = '0.3.5' user_agent = f'ns-emu-tools/{current_version}' diff --git a/exception/install_exception.py b/exception/install_exception.py new file mode 100644 index 0000000..d9305ab --- /dev/null +++ b/exception/install_exception.py @@ -0,0 +1,8 @@ +class FailToCopyFiles(Exception): + raw_exception: Exception + msg: str + + def __init__(self, raw_exception: Exception, msg): + self.raw_exception = raw_exception + self.msg = msg + super().__init__(msg) diff --git a/module/cheats.py b/module/cheats.py index 93147ce..aa89213 100644 --- a/module/cheats.py +++ b/module/cheats.py @@ -17,11 +17,11 @@ game_id_re = re.compile(r'^[\dA-Za-z]{16}$') -@lru_cache(1) +# @lru_cache(1) def get_game_data(): res = {} try: - resp = session.get('https://cdn.jsdelivr.net/gh/triwinds/ns-emu-tools@main/game_data.json') + resp = session.get('https://cdn.jsdelivr.net/gh/triwinds/ns-emu-tools@main/game_data.json', timeout=5) return resp.json() except Exception as e: logger.warning(f'fail to load game data, ex: {e}') @@ -32,7 +32,7 @@ def scan_all_cheats_folder(mod_path) -> List[Dict[str, str]]: root = Path(mod_path) logger.info(f'scanning cheats under path: {root}') cheats_folders = root.glob('**/cheats') - game_data = get_game_data() + # game_data = get_game_data() res = [] for folder in cheats_folders: game_id = folder.parent.parent.name @@ -47,7 +47,7 @@ def scan_all_cheats_folder(mod_path) -> List[Dict[str, str]]: res.append({ 'game_id': game_id, 'cheats_path': str(folder.absolute()), - 'game_name': game_data.get(game_id) + # 'game_name': game_data.get(game_id) }) return res diff --git a/module/downloader.py b/module/downloader.py index e993be4..67f8f4e 100644 --- a/module/downloader.py +++ b/module/downloader.py @@ -84,6 +84,7 @@ def download(url, save_dir=None, options=None, download_in_background=False): def _download(url, save_dir=None, options=None, download_in_background=False): init_aria2() send_notify('如果遇到下载失败或卡住的问题, 可以尝试在设置中换个下载源, 如果还是不行就挂个梯子') + send_notify('如果你的网络支持 IPv6, 也可以尝试在设置中允许 aria2 使用 IPv6, 看看能不能解决问题') tmp = init_download_options_with_proxy(url) tmp['auto-file-renaming'] = 'false' tmp['allow-overwrite'] = 'false' @@ -125,12 +126,11 @@ def _download(url, save_dir=None, options=None, download_in_background=False): if not info.is_complete: logger.info(f'remove downloading files due to download interrupted.') for file in info.files: - if file.path.exists(): + if file.path.exists() and file.path.is_file(): logger.debug(f'remove file: {file.path}') os.remove(file.path) raise DownloadInterrupted() else: - print(info) logger.error(f'info.error_code: {info.error_code}, error message: {info.error_message}') raise RuntimeError(f'下载出错, error_code: {info.error_code}, error message: {info.error_message}') else: diff --git a/module/ryujinx.py b/module/ryujinx.py index 6156bcb..f7009d6 100644 --- a/module/ryujinx.py +++ b/module/ryujinx.py @@ -61,6 +61,7 @@ def install_ryujinx_by_version(target_version: str, branch: str): file = info.files[0] ryujinx_path = Path(config.ryujinx.path) ryujinx_path.mkdir(parents=True, exist_ok=True) + kill_all_ryujinx_instance(ryujinx_path) clear_ryujinx_folder(ryujinx_path) import zipfile with zipfile.ZipFile(file.path, 'r') as zf: @@ -72,8 +73,11 @@ def install_ryujinx_by_version(target_version: str, branch: str): ryujinx_tmp_dir = tmp_dir.joinpath('publish') logger.info(f'Copy back ryujinx files...') send_notify('安装 ryujinx 文件至目录...') - kill_all_ryujinx_instance() - shutil.copytree(ryujinx_tmp_dir, ryujinx_path, dirs_exist_ok=True) + try: + shutil.copytree(ryujinx_tmp_dir, ryujinx_path, dirs_exist_ok=True) + except Exception as e: + from exception.install_exception import FailToCopyFiles + raise FailToCopyFiles(e, 'Ryujinx 文件复制失败') shutil.rmtree(tmp_dir) config.ryujinx.version = target_version config.ryujinx.branch = branch @@ -116,11 +120,15 @@ def clear_ryujinx_folder(ryujinx_path: Path): os.remove(path) -def kill_all_ryujinx_instance(): +def kill_all_ryujinx_instance(ryujinx_path: Path = None): import psutil kill_flag = False for p in psutil.process_iter(): if p.name().startswith('Ryujinx.'): + if ryujinx_path is not None: + process_path = Path(p.exe()).parent.absolute() + if ryujinx_path.absolute() != process_path: + continue send_notify(f'关闭 Ryujinx 进程 [{p.pid}]') logger.info(f'kill Ryujinx process [{p.pid}]') p.kill() @@ -160,8 +168,8 @@ def start_ryujinx(): logger.info(f'starting Ryujinx from: {rj_path}') subprocess.Popen([rj_path]) else: - logger.error(f'Ryujinx not exist in [{rj_path}]') - raise RuntimeError(f'Ryujinx not exist in [{rj_path}]') + logger.error(f'Ryujinx exe not exist in [{config.ryujinx.path}]') + raise RuntimeError(f'Ryujinx exe not exist in [{config.ryujinx.path}]') def detect_current_branch(): @@ -182,6 +190,7 @@ def detect_ryujinx_version(): config.ryujinx.version = None dump_config() return None + kill_all_ryujinx_instance() config.ryujinx.branch = detect_current_branch() st_inf = subprocess.STARTUPINFO() st_inf.dwFlags = st_inf.dwFlags | subprocess.STARTF_USESHOWWINDOW @@ -232,5 +241,6 @@ def update_ryujinx_path(new_ryujinx_path: str): # clear_ryujinx_folder(Path(config.ryujinx.path)) # install_firmware_to_ryujinx('15.0.0') # open_ryujinx_keys_folder() - detect_ryujinx_version() + # detect_ryujinx_version() # install_ryujinx_by_version('3.0.1', 'ldn') + kill_all_ryujinx_instance(Path(config.ryujinx.path)) diff --git a/module/yuzu.py b/module/yuzu.py index f9b8abb..7924dd5 100644 --- a/module/yuzu.py +++ b/module/yuzu.py @@ -90,8 +90,13 @@ def copy_back_yuzu_files(tmp_dir: Path, yuzu_path: Path, ): os.remove(useless_file) logger.info(f'Copy back yuzu files...') send_notify('安装 yuzu 文件至目录...') - kill_all_yuzu_instance() - shutil.copytree(tmp_dir, yuzu_path, dirs_exist_ok=True) + kill_all_yuzu_instance(yuzu_path) + try: + shutil.copytree(tmp_dir, yuzu_path, dirs_exist_ok=True) + time.sleep(0.5) + except Exception as e: + from exception.install_exception import FailToCopyFiles + raise FailToCopyFiles(e, 'Yuzu 文件复制失败') shutil.rmtree(tmp_dir) @@ -166,11 +171,15 @@ def detect_yuzu_version(): return version -def kill_all_yuzu_instance(): +def kill_all_yuzu_instance(yuzu_path: Path = None): import psutil kill_flag = False for p in psutil.process_iter(): if p.name() == 'yuzu.exe': + if yuzu_path is not None: + process_path = Path(p.exe()).parent.absolute() + if yuzu_path.absolute() != process_path: + continue send_notify(f'关闭 yuzu 进程 [{p.pid}]') logger.info(f'kill yuzu.exe [{p.pid}]') p.kill() diff --git a/utils/doh.py b/utils/doh.py index c880aba..c1e2541 100644 --- a/utils/doh.py +++ b/utils/doh.py @@ -21,6 +21,7 @@ # DOH_SERVER = '120.53.53.53' # aliyun https://dns.alidns.com DOH_SERVER = '223.5.5.5' +session = requests.Session() resolver = dns.resolver.Resolver(configure=False) resolver.nameservers = ["223.5.5.5", '119.29.29.29'] @@ -99,20 +100,19 @@ def query_address(name, record_type='A', server=DOH_SERVER, path="/dns-query", f return retval try: - with requests.sessions.Session() as session: - q = dns.message.make_query(name, dns.rdatatype.from_text(record_type)) - resp = dns.query.https(q, server, session=session) - # print(f'[{name}] doh answer: {resp.answer}') - logger.debug(f'doh answer of [{name} in {record_type}]: {resp.answer}') - if not resp.answer: - return [] - retval = [] - for answer in resp.answer: - if answer.rdtype not in {dns.rdatatype.AAAA, dns.rdatatype.A}: - continue - update_dns_cache(name, answer) - for item in answer: - retval.append(item.address) + q = dns.message.make_query(name, dns.rdatatype.from_text(record_type)) + resp = dns.query.https(q, server, session=session) + # print(f'[{name}] doh answer: {resp.answer}') + logger.debug(f'doh answer of [{name} in {record_type}]: {resp.answer}') + if not resp.answer: + return [] + retval = [] + for answer in resp.answer: + if answer.rdtype not in {dns.rdatatype.AAAA, dns.rdatatype.A}: + continue + update_dns_cache(name, answer) + for item in answer: + retval.append(item.address) except Exception as ex: if verbose: logger.debug("Exception occurred: '%s'" % ex) diff --git a/utils/network.py b/utils/network.py index a5896d1..b54a03e 100644 --- a/utils/network.py +++ b/utils/network.py @@ -30,6 +30,7 @@ session.mount('https://cfrp.e6ex.com', HTTPAdapter(max_retries=5)) session.mount('https://nsarchive.e6ex.com', HTTPAdapter(max_retries=5)) session.mount('https://api.github.com', HTTPAdapter(max_retries=5)) +session.mount('https://cdn.jsdelivr.net', HTTPAdapter(max_retries=5)) options_on_proxy = { 'split': '16', diff --git a/vue/src/pages/YuzuCheatsManagement.vue b/vue/src/pages/YuzuCheatsManagement.vue index 9d33fd4..e456f76 100644 --- a/vue/src/pages/YuzuCheatsManagement.vue +++ b/vue/src/pages/YuzuCheatsManagement.vue @@ -15,6 +15,7 @@ item-value="cheats_path" label="选择游戏 mod 目录" hide-details + :disabled="!cheatsInited" > @@ -89,14 +90,15 @@ export default { components: {SimplePage}, data() { return { - selected: false, + cheatsInited: false, cheatsFolders: [], selectedFolder: '', cheatFiles: [], selectedCheatFile: '', cheatItems: [], - cheatItemBoxHeight: 340, - descriptionHtml: '' + cheatItemBoxHeight: 410, + descriptionHtml: '', + gameDataInited: false, } }, async created() { @@ -117,15 +119,28 @@ export default { let resp = await window.eel.scan_all_cheats_folder()() if (resp.code === 0 && resp.data) { this.cheatsFolders = resp.data + window.eel.get_game_data()((resp) => { + this.gameDataInited = true + if (resp.code === 0) { + let nl = [] + for (let item of this.cheatsFolders) { + item.game_name = resp.data[item.game_id] + nl.push(item) + } + this.cheatsFolders = nl; + } + }); + this.cheatsInited = true return this.cheatsFolders } return [] }, updateCheatItemBoxHeight() { - this.cheatItemBoxHeight = window.innerHeight - 510 + this.cheatItemBoxHeight = window.innerHeight - 440 }, concatFolderItemName(item) { - return `[${item.game_id}] ${item.game_name ? item.game_name : '未能识别的游戏'}` + let gameName = item.game_name ? item.game_name : this.gameDataInited ? '未知游戏' : '游戏信息加载中...' + return `[${item.game_id}] ${gameName}` }, listAllCheatFilesFromFolder(selectedFolder) { window.eel.list_all_cheat_files_from_folder(selectedFolder)((resp) => {