diff --git a/bot_manager.py b/bot_manager.py index b1c6ee5..d22ca96 100644 --- a/bot_manager.py +++ b/bot_manager.py @@ -125,7 +125,10 @@ def get_game_client_type(self) -> utils.GameClientType: def start_browser(self): """ Start the browser thread, open browser window """ ms_url = self.st.ms_url - proxy = self.mitm_server.proxy_str + if self.st.majsoulmax_proxy: + proxy = self.st.majsoulmax_proxy + else: + proxy = self.mitm_server.proxy_str self.browser.start(ms_url, proxy, self.st.browser_width, self.st.browser_height, self.st.enable_chrome_ext) def is_browser_zoom_off(self): diff --git a/common/settings.py b/common/settings.py index 397a277..b948e2f 100644 --- a/common/settings.py +++ b/common/settings.py @@ -30,6 +30,7 @@ def __init__(self, json_file:str=DEFAULT_SETTING_FILE) -> None: self.inject_process_name:str = self._get_value("inject_process_name", "jantama_mahjongsoul") self.language:str = self._get_value("language", list(LAN_OPTIONS.keys())[-1], self.valid_language) # language code self.enable_overlay:bool = self._get_value("enable_overlay", True, self.valid_bool) # not shown + self.majsoulmax_proxy:str = self._get_value("majsoulmax_proxy", "") # AI Model settings self.model_type:str = self._get_value("model_type", "Local") diff --git a/common/utils.py b/common/utils.py index 6a6f71b..74feaae 100644 --- a/common/utils.py +++ b/common/utils.py @@ -12,6 +12,7 @@ import random import string from cryptography import x509 +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend import requests @@ -134,19 +135,21 @@ def wait_for_file(file:str, timeout:int=5) -> bool: def sub_run_args() -> dict: - """ return **args for subprocess.run""" - startup_info = subprocess.STARTUPINFO() - startup_info.dwFlags |= subprocess.STARTF_USESHOWWINDOW - startup_info.wShowWindow = subprocess.SW_HIDE + """return **args for subprocess.run""" args = { - 'capture_output':True, - 'text': True, - 'check': False, - 'shell': True, - 'startupinfo': startup_info} + "capture_output": True, + "text": True, + "check": False, + } + if sys.platform == "win32": + startup_info = subprocess.STARTUPINFO() + startup_info.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startup_info.wShowWindow = subprocess.SW_HIDE + args.update({"shell": True, "startupinfo": startup_info}) + else: + args["shell"] = False # macOS / Linux return args - def get_cert_serial_number(cert_file:str) ->str: """Extract the serial number as a hexadecimal string from a certificate.""" with open(cert_file, 'rb') as file: @@ -170,11 +173,35 @@ def is_certificate_installed(cert_file:str) -> tuple[bool, str]: cmd = ['certutil', '-store', 'Root', serial_number] store_found_phrase = serial_number elif sys.platform == "darwin": - # TODO test on MacOS - # Use security to find the certificate by its serial number in the System keychain - cmd = ['security', 'find-certificate', '-c', serial_number, '/Library/Keychains/System.keychain'] - store_found_phrase = 'attributes:' - else: # unsupported platform + # macOS: 通过比较 SHA-1 指纹确保本地证书已被系统信任。 + try: + with open(cert_file, "rb") as f: + cert_data = f.read() + cert = x509.load_pem_x509_certificate(cert_data, default_backend()) + sha1_fingerprint = cert.fingerprint(hashes.SHA1()).hex().upper() + except Exception as e: + return False, str(e) + + # 列出系统钥匙串中所有 mitmproxy 证书并输出指纹。 + cmd = [ + "security", + "find-certificate", + "-a", + "-Z", + "-c", + "mitmproxy", + "/Library/Keychains/System.keychain", + ] + result = subprocess.run(cmd, capture_output=True, text=True, check=False) + + installed = sha1_fingerprint in result.stdout.upper() + + # 仅保留前两行 SHA 信息,避免打印过多属性 + lines = result.stdout.splitlines() + sha_lines = [l for l in lines if l.startswith("SHA-")][:2] + short_output = "\n".join(sha_lines) + return installed, short_output + else: # unsupported platform return False args = sub_run_args() result = subprocess.run(cmd, **args) #pylint:disable=subprocess-run-check diff --git a/readme.md b/readme.md index ecf6828..b8f350d 100644 --- a/readme.md +++ b/readme.md @@ -65,6 +65,23 @@ python main.py ### Model Configuration This program supports different types of AI models. The 'Local' Model type uses Mortal models compatible with Akagi. To acquire Akagi's models, please refer to Akagi Github . +### 与 MajsoulMax 搭配使用 + +如果你想要和 [MajsoulMax](https://github.com/Avenshy/MajsoulMax) 搭配使用,请在 `settings.json` 中设置 `"majsoulmax_proxy": "http://127.0.0.1:23410"` 。 + +随后,请以如下方式启动 MajsoulMax: + +```bash +mitmdump -p 23410 --mode upstream:http://127.0.0.1:10999 -s addons.py --ssl-insecure +``` + +请注意,如果你修改了 MajsoulMax 或者 MahjangCopilot 的代理端口,请相应修改对应端口,并且确保 MajsoulMax 和 MahjangCopilot 的自签名证书均正确安装(这两者是不同的,前者默认使用 `~/.mitmproxy/` 下的证书,而后者使用 `./mitm_config/` 下的证书)。 + +最终代理链为: + +``` +Program -> MajsoulMax(23410) -> MahjongCopilot(10999) -> Server +``` ## 截图 / Screenshots diff --git a/requirements.txt b/requirements.txt index adb3d1d..1655f58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ pillow riichi>=0.1.1 tkhtmlview pyinstaller - +cryptography