Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bot_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions common/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
57 changes: 42 additions & 15 deletions common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
17 changes: 17 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="https://github.com/shinkuan/Akagi" target="_blank"> Akagi Github </a>.

### 与 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

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ pillow
riichi>=0.1.1
tkhtmlview
pyinstaller

cryptography