Skip to content

Commit

Permalink
Merge pull request #29 from triwinds/dev
Browse files Browse the repository at this point in the history
PR for 0.3.0
  • Loading branch information
triwinds authored Feb 26, 2023
2 parents 256ba2f + 550e724 commit d398b06
Show file tree
Hide file tree
Showing 18 changed files with 188 additions and 101 deletions.
2 changes: 1 addition & 1 deletion api/common_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from api.common_response import success_response, exception_response, error_response
from config import current_version
import logging
from module.common import get_firmware_infos
from module.firmware import get_firmware_infos

logger = logging.getLogger(__name__)

Expand Down
6 changes: 5 additions & 1 deletion api/common_response.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from module.msg_notifier import send_notify
from exception.common_exception import VersionNotFoundException
from exception.common_exception import *


logger = logging.getLogger(__name__)
Expand All @@ -16,6 +16,10 @@ def exception_response(ex):
logger.error(f'{str(ex)}')
send_notify(f'无法获取 {ex.branch} 分支的 [{ex.target_version}] 版本信息')
return error_response(404, str(ex))
elif isinstance(ex, Md5NotMatchException):
logger.error(f'{str(ex)}')
send_notify(f'固件文件 md5 不匹配, 请重新下载')
return error_response(501, str(ex))
logger.error(ex, exc_info=True)
traceback_str = "\n".join(traceback.format_exception(ex))
send_notify(f'出现异常, {traceback_str}')
Expand Down
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Change Log

## 0.3.0
- 在 api 请求发生超时错误时进行重试
- 当 IPv6 启用时 DoH 尝试查询 AAAA 记录
- 安装固件时对下载文件的 md5 进行校验
- 修复某些情况下 aria2 进程没有正常关闭的问题
- 更正检测固件版本时固件文件解密失败的错误文本
- 修复某些代理软件错误配置 localhost 代理导致无法调用 aria2 api 的问题

## 0.2.9
- 添加新 GitHub 下载源 nuaa.cf, 并更新在其它 GitHub 下载源中使用的 UA
- 更正尝试下载一个不存在的 Ryujinx 版本时所展示的文本
Expand Down
3 changes: 2 additions & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sys


current_version = '0.2.9'
current_version = '0.3.0'
user_agent = f'ns-emu-tools/{current_version}'


Expand Down Expand Up @@ -72,6 +72,7 @@ class DownloadSetting:
autoDeleteAfterInstall: Optional[bool] = True
disableAria2Ipv6: Optional[bool] = True
removeOldAria2LogFile: Optional[bool] = True
verifyFirmwareMd5: Optional[bool] = True


@dataclass_json
Expand Down
5 changes: 5 additions & 0 deletions exception/common_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ def __init__(self, target_version, branch, emu_type):
self.emu_type = emu_type
self.msg = f'Fail to get release info of version [{target_version}] on branch [{branch}]'
super().__init__(self.msg)


class Md5NotMatchException(Exception):
def __init__(self):
super().__init__('MD5 not match')
68 changes: 3 additions & 65 deletions module/common.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,11 @@
import logging
import os
import shutil
import subprocess
from functools import lru_cache
from pathlib import Path
from module.msg_notifier import send_notify
from config import config
import bs4
from utils.network import get_finial_url, session
import logging
from module.downloader import download

logger = logging.getLogger(__name__)

from utils.network import get_finial_url

@lru_cache(1)
def get_firmware_infos():
base_url = 'https://archive.org/download/nintendo-switch-global-firmwares/'
resp = session.get(get_finial_url(base_url))
soup = bs4.BeautifulSoup(resp.text, features="html.parser")
a_tags = soup.select('#maincontent > div > div > pre > table > tbody > tr > td > a')
archive_versions = []
for a in a_tags:
name = a.text
if name.startswith('Firmware ') and name.endswith('.zip'):
size = a.parent.next_sibling.next_sibling.next_sibling.next_sibling.text
version = name[9:-4]
version_num = 0
for num in version.split('.'):
version_num *= 100
version_num += int(''.join(ch for ch in num if ch.isdigit()))
archive_versions.append({
'name': name,
'version': version,
'size': size,
'url': base_url + a.attrs['href'],
'version_num': version_num,
})
archive_versions = sorted(archive_versions, key=lambda x: x['version_num'], reverse=True)
return archive_versions
logger = logging.getLogger(__name__)


def check_and_install_msvc():
Expand All @@ -57,36 +25,6 @@ def check_and_install_msvc():
# process.wait()


def install_firmware(firmware_version, target_firmware_path):
send_notify('正在获取固件信息...')
firmware_infos = get_firmware_infos()
target_info = None
if firmware_version:
firmware_map = {fi['version']: fi for fi in firmware_infos}
target_info = firmware_map.get(firmware_version)
if not target_info:
logger.info(f'Target firmware version [{firmware_version}] not found, skip install.')
send_notify(f'Target firmware version [{firmware_version}] not found, skip install.')
return
url = get_finial_url(target_info['url'])
send_notify(f'开始下载固件...')
logger.info(f"downloading firmware of [{firmware_version}] from {url}")
info = download(url)
file = info.files[0]
import zipfile
with zipfile.ZipFile(file.path, 'r') as zf:
firmware_path = target_firmware_path
shutil.rmtree(firmware_path, ignore_errors=True)
firmware_path.mkdir(parents=True, exist_ok=True)
send_notify(f'开始解压安装固件...')
logger.info(f'Unzipping firmware files to {firmware_path}')
zf.extractall(firmware_path)
logger.info(f'Firmware of [{firmware_version}] install successfully.')
if config.setting.download.autoDeleteAfterInstall:
os.remove(file.path)
return firmware_version


if __name__ == '__main__':
# infos = get_firmware_infos()
# for info in infos:
Expand Down
29 changes: 18 additions & 11 deletions module/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ def init_aria2():
send_notify(f'starting aria2 daemon at port {port}')
logger.info(f'starting aria2 daemon at port {port}')
if config.setting.download.removeOldAria2LogFile and os.path.exists('aria2.log'):
logger.info('removing old aria2 logs.')
os.remove('aria2.log')
try:
logger.info('removing old aria2 logs.')
os.remove('aria2.log')
except:
pass
st_inf = subprocess.STARTUPINFO()
st_inf.dwFlags = st_inf.dwFlags | subprocess.STARTF_USESHOWWINDOW
cli = [aria2_path, '--enable-rpc', '--rpc-listen-port', str(port), '--async-dns=true',
'--rpc-secret', '123456', '--log', 'aria2.log', '--log-level=info']
'--rpc-secret', '123456', '--log', 'aria2.log', '--log-level=info', f'--stop-with-process={os.getpid()}']
if config.setting.download.disableAria2Ipv6:
cli.append('--disable-ipv6=true')
cli.append('--async-dns-server=223.5.5.5,119.29.29.29')
Expand All @@ -51,11 +54,21 @@ def init_aria2():
global_options = get_global_options()
logger.info(f'aria2 global options: {global_options}')
aria2.set_global_options(global_options)
import atexit
atexit.register(shutdown_aria2)


def download(url, save_dir=None, options=None, download_in_background=False):
origin_no_proxy = os.environ.get('no_proxy')
os.environ['no_proxy'] = '127.0.0.1,localhost'
try:
return _download(url, save_dir, options, download_in_background)
finally:
if origin_no_proxy is None:
del os.environ['no_proxy']
else:
os.environ['no_proxy'] = origin_no_proxy


def _download(url, save_dir=None, options=None, download_in_background=False):
init_aria2()
tmp = init_download_options_with_proxy(url)
tmp['auto-file-renaming'] = 'false'
Expand Down Expand Up @@ -101,12 +114,6 @@ def download(url, save_dir=None, options=None, download_in_background=False):
return info


def shutdown_aria2():
if aria2_process:
# logger.info('Shutdown aria2...')
aria2_process.kill()


if __name__ == '__main__':
info = download('http://ipv4.download.thinkbroadband.com/200MB.zip')
os.remove(info.files[0].path)
88 changes: 88 additions & 0 deletions module/firmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
from config import config, dump_config
import shutil
from module.msg_notifier import send_notify
import xmltodict
from functools import lru_cache
from config import config
from module.downloader import download
from utils.network import get_finial_url, session

logger = logging.getLogger(__name__)
hactool_path = Path(os.path.realpath(os.path.dirname(__file__))).joinpath('hactool.exe')
Expand Down Expand Up @@ -84,6 +89,10 @@ def extract_version(target_file, key_path):
f'--romfsdir="{str(tmp_path)}"', shell=True,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
process.wait()
if not tmp_path.exists():
logger.info(f'Fail to decrypt file.')
send_notify(f'无法解析固件文件, 可能是当前使用的密钥与固件版本不匹配')
return
if tmp_path.joinpath('file').exists():
with open(tmp_path.joinpath('file'), 'rb') as f:
f.seek(0x68)
Expand All @@ -94,5 +103,84 @@ def extract_version(target_file, key_path):
return version


@lru_cache(1)
def get_firmware_infos():
import urllib.parse
base_url = 'https://archive.org/download/nintendo-switch-global-firmwares/'
url = base_url + 'nintendo-switch-global-firmwares_files.xml'
resp = session.get(get_finial_url(url), timeout=5)
data = xmltodict.parse(resp.text)
files = data['files']['file']
res = []
for info in files:
if 'ZIP' != info['format']:
continue
info['name'] = info['@name']
del info['@name']
info['url'] = base_url + urllib.parse.quote(info['name'])
version = info['name'][9:-4]
info['version'] = version
version_num = 0
for num in version.split('.'):
version_num *= 100
version_num += int(''.join(ch for ch in num if ch.isdigit()))
info['version_num'] = version_num
res.append(info)
res = sorted(res, key=lambda x: x['version_num'], reverse=True)
return res


def check_file_md5(file: Path, target_md5: str):
if not file.exists() or not file.is_file():
return None
import hashlib
logger.debug(f'calculating md5 of file: {file}')
send_notify('开始校验文件 md5...')
hash_md5 = hashlib.md5()
with file.open('rb') as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
file_md5 = hash_md5.hexdigest()
send_notify(f'本地文件 md5: {file_md5}')
send_notify(f'远端文件 md5: {target_md5}')
logger.debug(f'file md5: {file_md5}, target md5: {target_md5}')
return file_md5.lower() == target_md5.lower()


def install_firmware(firmware_version, target_firmware_path):
send_notify('正在获取固件信息...')
firmware_infos = get_firmware_infos()
target_info = None
if firmware_version:
firmware_map = {fi['version']: fi for fi in firmware_infos}
target_info = firmware_map.get(firmware_version)
if not target_info:
logger.info(f'Target firmware version [{firmware_version}] not found, skip install.')
send_notify(f'Target firmware version [{firmware_version}] not found, skip install.')
return
url = get_finial_url(target_info['url'])
send_notify(f'开始下载固件...')
logger.info(f"downloading firmware of [{firmware_version}] from {url}")
info = download(url)
file = info.files[0]
if config.setting.download.verifyFirmwareMd5 and not check_file_md5(file.path, target_info['md5']):
logger.info(f'firmware md5 not match, removing file [{file}]...')
os.remove(file)
from exception.common_exception import Md5NotMatchException
raise Md5NotMatchException()
import zipfile
with zipfile.ZipFile(file.path, 'r') as zf:
firmware_path = target_firmware_path
shutil.rmtree(firmware_path, ignore_errors=True)
firmware_path.mkdir(parents=True, exist_ok=True)
send_notify(f'开始解压安装固件...')
logger.info(f'Unzipping firmware files to {firmware_path}')
zf.extractall(firmware_path)
logger.info(f'Firmware of [{firmware_version}] install successfully.')
if config.setting.download.autoDeleteAfterInstall:
os.remove(file.path)
return firmware_version


if __name__ == '__main__':
detect_firmware_version('yuzu')
2 changes: 1 addition & 1 deletion module/ryujinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def install_firmware_to_ryujinx(firmware_version=None):
shutil.rmtree(firmware_path, ignore_errors=True)
firmware_path.mkdir(parents=True, exist_ok=True)
tmp_dir = firmware_path.joinpath('tmp/')
from module.common import install_firmware
from module.firmware import install_firmware
new_version = install_firmware(firmware_version, tmp_dir)
if new_version:
for path in tmp_dir.glob('*.nca'):
Expand Down
2 changes: 1 addition & 1 deletion module/sentry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

def sampler(sample_data):
if 'wsgi_environ' in sample_data and sample_data['wsgi_environ']['PATH_INFO'] == '/index.html':
return 1
return 0.1
return 0


Expand Down
2 changes: 1 addition & 1 deletion module/yuzu.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def install_firmware_to_yuzu(firmware_version=None):
logger.info(f'Current firmware are same as target version [{firmware_version}], skip install.')
send_notify(f'当前的 固件 就是 [{firmware_version}], 跳过安装.')
return
from module.common import install_firmware
from module.firmware import install_firmware
new_version = install_firmware(firmware_version, get_yuzu_nand_path().joinpath(r'system\Contents\registered'))
if new_version:
config.yuzu.yuzu_firmware = new_version
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ requests-cache
dnspython[doh]
pywebview
sentry-sdk
xmltodict
Loading

0 comments on commit d398b06

Please sign in to comment.