This commit is contained in:
秋城落叶
2025-04-25 09:20:30 +08:00
parent 10a2eb2c10
commit 390a89ab0d
3 changed files with 151 additions and 104 deletions

View File

@@ -885,7 +885,8 @@
"injectFile": "SenPlayer",
"dylibSelect": "CoreInject.dylib",
"supportVersion": [
"5.2.2"
"5.2.2",
"5.5.0"
]
},
{

View File

@@ -14,39 +14,39 @@ from src.utils.color import Color
# 获取工具真实路径的辅助函数
def get_tool_path(tool_name):
"""获取工具的真实路径
Args:
tool_name: 工具名称
Returns:
str: 工具的绝对路径
"""
# 获取项目根目录
root_dir = Path(__file__).resolve().parent.parent.parent
tool_path = os.path.join(root_dir, "tool", tool_name)
# 检查工具是否存在
if not os.path.exists(tool_path):
print(Color.red(f"[错误] 工具 {tool_name} 不存在于路径: {tool_path}"))
return tool_path
# 执行命令并检查结果的辅助函数
def run_command(command, shell=True, check_error=True):
"""运行命令并检查结果,如果出错则显示红色警告
Args:
command: 要执行的命令
shell: 是否使用shell执行
check_error: 是否检查错误
Returns:
bool: 命令执行成功返回True否则返回False
"""
try:
result = subprocess.run(command, shell=shell, capture_output=True, text=True)
# 检查命令是否执行成功
if check_error and result.returncode != 0:
error_msg = result.stderr.strip() or f"命令执行失败: {command}"
@@ -64,7 +64,7 @@ def process_app(app, base_public_config, install_apps, current_dir=None, skip_co
"""处理单个应用的注入逻辑"""
# 获取项目根目录
root_dir = Path(__file__).resolve().parent.parent.parent
package_name = app.get("packageName")
app_base_locate = app.get("appBaseLocate")
bridge_file = app.get("bridgeFile")
@@ -87,10 +87,10 @@ def process_app(app, base_public_config, install_apps, current_dir=None, skip_co
SMExtra = app.get("SMExtra")
keygen = app.get("keygen")
useOptool = app.get("useOptool")
helperNoInject = app.get("helperNoInject")
helperNoInject = app.get("helperNoInject")
forceSignMainExecute = app.get("forceSignMainExecute")
dylibSelect = app.get("dylibSelect") # 选择注入的库
if dylibSelect is None:
dylibSelect = "91QiuChenly.dylib"
@@ -99,11 +99,18 @@ def process_app(app, base_public_config, install_apps, current_dir=None, skip_co
optool_path = get_tool_path("optool")
dylib_path = get_tool_path(dylibSelect)
local_app = [
local_app
for local_app in install_apps
if local_app["CFBundleIdentifier"] == package_name
]
# 查找匹配的应用,同时考虑包名和路径
local_app = []
for app_info in install_apps:
# 检查包名是否匹配
if app_info["CFBundleIdentifier"] == package_name:
# 如果配置中指定了路径,则检查路径是否匹配
if app_base_locate:
if app_info["appBaseLocate"] == app_base_locate:
local_app.append(app_info)
else:
# 如果配置中没有指定路径,则只匹配包名
local_app.append(app_info)
if not local_app and (
app_base_locate is None or not os.path.isdir(app_base_locate)
@@ -153,7 +160,7 @@ def process_app(app, base_public_config, install_apps, current_dir=None, skip_co
success = True
success &= run_command(["sudo", "chmod", "-R", "777", app_base_locate], shell=False)
success &= run_command(["sudo", "xattr", "-cr", app_base_locate], shell=False)
# 尝试终止进程,但忽略可能的错误
run_command(["sudo", "pkill", "-f", getAppMainExecutable(app_base_locate)], shell=False, check_error=False)
@@ -174,11 +181,11 @@ def process_app(app, base_public_config, install_apps, current_dir=None, skip_co
return False
isDevHome = False # os.getenv("InjectLibDev")
# 设置工具权限
if not run_command(f"chmod +x '{insert_dylib_path}'"):
print(Color.red(f"[错误] 无法设置 insert_dylib 为可执行文件,请检查文件是否存在: {insert_dylib_path}"))
if not run_command(f"chmod +x '{optool_path}'"):
print(Color.red(f"[错误] 无法设置 optool 为可执行文件,请检查文件是否存在: {optool_path}"))
@@ -187,7 +194,7 @@ def process_app(app, base_public_config, install_apps, current_dir=None, skip_co
command = f"sudo '{optool_path}' install -p '{dylib_path}' -t '{dest}'"
else:
command = f"sudo '{insert_dylib_path}' '{dylib_path}' '{backup}' '{dest}'"
# 执行注入命令
if not run_command(command):
print(Color.red(f"[错误] 执行注入命令失败: {command}"))
@@ -201,7 +208,7 @@ def process_app(app, base_public_config, install_apps, current_dir=None, skip_co
if not run_command(f"{command} {source_dylib} {destination_dylib}"):
print(Color.red(f"[错误] 复制动态库失败: {source_dylib} -> {destination_dylib}"))
return False
# codesign
if not run_command(f"codesign -fs - --timestamp=none --all-architectures {destination_dylib}"):
print(Color.yellow(f"[警告] 签名动态库失败: {destination_dylib}"))
@@ -227,7 +234,7 @@ def process_app(app, base_public_config, install_apps, current_dir=None, skip_co
if not run_command(command):
print(Color.red(f"[错误] 执行注入命令失败: {command}"))
success = False
sign_prefix = (
"/usr/bin/codesign -f -s - --timestamp=none --all-architectures"
)
@@ -245,7 +252,7 @@ def process_app(app, base_public_config, install_apps, current_dir=None, skip_co
print("开始签名...")
if not run_command(f"{sign_prefix} '{dest}'"):
print(Color.yellow(f"[警告] 签名失败: {dest}"))
if not run_command(f"{sign_prefix} '{app_base_locate}'"):
print(Color.yellow(f"[警告] 签名失败: {app_base_locate}"))
@@ -314,5 +321,5 @@ def process_app(app, base_public_config, install_apps, current_dir=None, skip_co
print(Color.green("App处理完成。"))
else:
print(Color.yellow("App处理完成但存在一些警告或错误。"))
return success
return success

View File

@@ -11,7 +11,7 @@ def search_apps(app_list, install_apps, keyword):
"""根据关键字搜索应用"""
# 将关键字转为小写以便不区分大小写地搜索
keyword = keyword.lower()
# 构建已安装应用的字典,包含包名和应用名称(参考原始实现)
installed_apps = {}
for app in install_apps:
@@ -23,12 +23,12 @@ def search_apps(app_list, install_apps, keyword):
'name': app_name.lower(),
'app_info': app
}
# 辅助函数:判断文本是否匹配关键字
def is_match(text):
if not text:
return False
if isinstance(text, str):
# 检查文本是否包含关键字
return keyword in text.lower()
@@ -36,13 +36,15 @@ def search_apps(app_list, install_apps, keyword):
# 如果是列表,检查任何元素是否匹配
return any(keyword in item.lower() for item in text if isinstance(item, str))
return False
matched_apps = []
# 遍历配置中的所有应用
for app in app_list:
pn_list = app.get("packageName")
# 获取配置中的应用路径
config_app_path = app.get("appBaseLocate")
# 如果包名是列表,处理每个包名
if isinstance(pn_list, list):
matched = []
@@ -53,11 +55,17 @@ def search_apps(app_list, install_apps, keyword):
installed_app_data = installed_apps[pn]
installed_app = installed_app_data['app_info']
app_name = installed_app_data['name']
# 参考原始实现的匹配逻辑:检查包名或者应用名匹配关键字
if is_match(pn) or is_match(app_name):
installed_app_path = installed_app["appBaseLocate"]
# 检查路径是否匹配(如果配置中指定了路径)
path_matches = True
if config_app_path and installed_app_path:
path_matches = config_app_path == installed_app_path
# 参考原始实现的匹配逻辑:检查包名或者应用名匹配关键字,并且路径匹配
if (is_match(pn) or is_match(app_name)) and path_matches:
matched.append({
**app,
**app,
"packageName": pn,
"displayName": installed_app["CFBundleName"],
"version": installed_app["CFBundleShortVersionString"],
@@ -70,59 +78,76 @@ def search_apps(app_list, install_apps, keyword):
installed_app_data = installed_apps[pn_list]
installed_app = installed_app_data['app_info']
app_name = installed_app_data['name']
# 检查包名或者应用名匹配关键字
if is_match(pn_list) or is_match(app_name):
installed_app_path = installed_app["appBaseLocate"]
# 检查路径是否匹配(如果配置中指定了路径)
path_matches = True
if config_app_path and installed_app_path:
path_matches = config_app_path == installed_app_path
# 检查包名或者应用名匹配关键字,并且路径匹配
if (is_match(pn_list) or is_match(app_name)) and path_matches:
matched_apps.append({
**app,
**app,
"packageName": pn_list,
"displayName": installed_app["CFBundleName"],
"version": installed_app["CFBundleShortVersionString"],
"buildVersion": installed_app["CFBundleVersion"],
"appPath": installed_app["appBaseLocate"]
})
return matched_apps
def get_installed_apps_info(app_list, install_apps):
"""获取所有已安装且受支持的应用信息"""
installed_supported_apps = []
# 检查所有app是否在本地已安装提前展开列表
all_config_apps = []
for app in app_list:
if app.get("forQiuChenly") and not os.path.exists("/Users/qiuchenly"):
continue
pn_list = app.get("packageName")
if isinstance(pn_list, list):
for pn in pn_list:
all_config_apps.append({**app, "packageName": pn})
else:
all_config_apps.append(app)
# 检查是否已安装
installed_app_ids = {app["CFBundleIdentifier"] for app in install_apps}
# 匹配已安装的应用
for app in all_config_apps:
package_name = app.get("packageName")
config_app_path = app.get("appBaseLocate")
if package_name in installed_app_ids:
# 找到对应的安装应用信息
for installed_app in install_apps:
if installed_app["CFBundleIdentifier"] == package_name:
# 合并信息
app_info = {
**app,
"displayName": installed_app["CFBundleName"],
"version": installed_app["CFBundleShortVersionString"],
"buildVersion": installed_app["CFBundleVersion"],
"appPath": installed_app["appBaseLocate"]
}
installed_supported_apps.append(app_info)
break
installed_app_path = installed_app["appBaseLocate"]
# 检查路径是否匹配(如果配置中指定了路径)
path_matches = True
if config_app_path and installed_app_path:
path_matches = config_app_path == installed_app_path
# 只有当路径匹配时才添加应用
if path_matches:
# 合并信息
app_info = {
**app,
"displayName": installed_app["CFBundleName"],
"version": installed_app["CFBundleShortVersionString"],
"buildVersion": installed_app["CFBundleVersion"],
"appPath": installed_app["appBaseLocate"]
}
installed_supported_apps.append(app_info)
break
return installed_supported_apps
@@ -130,60 +155,60 @@ def select_apps_by_keyword(app_list, install_apps):
"""通过关键字搜索选择应用"""
while True:
clear_screen()
# 显示提示信息
print(Color.cyan(_("keyword_search", "关键字搜索")) + "\n")
print(_("keyword_search_description", "请输入关键字进行搜索,支持应用名称和包名模糊搜索"))
print(_("keyword_search_example", "示例:输入\"wechat\"可搜索微信相关应用"))
# 获取用户输入
keyword = read_input("\n" + _("enter_keyword", "请输入关键字 (输入0返回上一级): "))
if keyword == '0':
return []
if not keyword:
print(_("empty_keyword", "关键字不能为空"))
wait_for_enter()
continue
# 搜索匹配的应用
matched_apps = search_apps(app_list, install_apps, keyword)
if not matched_apps:
print(_("no_matching_apps", "未找到匹配的应用"))
wait_for_enter()
continue
# 记录选择的应用列表
app_Lst = []
selected = set()
while len(selected) < len(matched_apps):
print("\n" + _("found_matching_apps", "找到以下匹配的应用程序:"))
# 打印表头
print_app_table_header(include_status=True)
for i, app in enumerate(matched_apps, 1):
status = _("selected", "✅ 已选中") if i-1 in selected else ""
app_with_status = {**app, "status": status}
print_app_info(i, app_with_status, include_status=True)
print(f"\n{_('selected_apps_count', '已选择 {0}/{1} 个应用').format(len(selected), len(matched_apps))}")
print(_("return_previous", "0. 返回上一级"))
print(_("confirm_selection", "Enter. 确认当前选择并继续"))
if len(selected) == len(matched_apps):
print(_("all_apps_selected", "所有应用已选中..."))
break
# 显示输入提示但不换行
print(_("enter_app_number", "请输入要选择的应用编号: "), end='', flush=True)
# 获取单个字符输入
choice = getch()
if choice == '0':
print(choice)
if not app_Lst:
@@ -193,7 +218,7 @@ def select_apps_by_keyword(app_list, install_apps):
# 对于数字选择,需要读取完整的数字
full_choice = choice
print(choice, end='', flush=True) # 打印第一个数字
# 继续读取数字直到遇到非数字字符或Enter
while True:
ch = getch()
@@ -206,7 +231,7 @@ def select_apps_by_keyword(app_list, install_apps):
else:
print() # 换行
break
try:
app_idx = int(full_choice)
if 0 < app_idx <= len(matched_apps):
@@ -216,7 +241,7 @@ def select_apps_by_keyword(app_list, install_apps):
selected.add(index)
print() # 换行
print(_("app_selected", "已选择应用: {0}").format(matched_apps[index].get('displayName')))
if len(selected) == len(matched_apps):
if len(selected) == len(matched_apps):
print(_("all_apps_selected", "所有应用已选中..."))
else:
print() # 换行
@@ -241,7 +266,7 @@ def select_apps_by_keyword(app_list, install_apps):
print(choice) # 打印无效字符
print(_("invalid_input", "无效的输入,请重新选择。"))
wait_for_enter()
return app_Lst
@@ -249,26 +274,26 @@ def display_supported_apps(app_list, install_apps, page=0, page_size=20):
"""分页显示已安装且支持的应用程序"""
installed_supported_apps = get_installed_apps_info(app_list, install_apps)
total_apps = len(installed_supported_apps)
if total_apps == 0:
print("\n" + _("no_installed_supported_apps", "未找到已安装且受支持的应用程序"))
return [], 0, 0
total_pages = (total_apps + page_size - 1) // page_size
page = max(0, min(page, total_pages - 1)) # 确保页码有效
start_idx = page * page_size
end_idx = min(start_idx + page_size, total_apps)
page_info = _("installed_supported_apps_list", "已安装且支持的应用列表 (第 {0}/{1} 页, 共 {2} 个应用)").format(page+1, total_pages, total_apps)
print(f"\n{page_info}")
# 打印表头
print_app_table_header()
for i, app in enumerate(installed_supported_apps[start_idx:end_idx], start_idx+1):
print_app_info(i, app)
return installed_supported_apps[start_idx:end_idx], page, total_pages
@@ -280,61 +305,75 @@ def show_all_supported_apps(app_list, install_apps):
# 跳过特定用户专用的应用
if app.get("forQiuChenly") and not os.path.exists("/Users/qiuchenly"):
continue
pn_list = app.get("packageName")
if isinstance(pn_list, list):
for pn in pn_list:
all_apps.append({**app, "packageName": pn})
else:
all_apps.append(app)
# 尝试匹配安装信息
installed_apps_map = {app["CFBundleIdentifier"]: app for app in install_apps}
for app in all_apps:
package_name = app.get("packageName")
config_app_path = app.get("appBaseLocate")
if package_name in installed_apps_map:
installed_app = installed_apps_map[package_name]
app["displayName"] = installed_app["CFBundleName"]
app["version"] = installed_app["CFBundleShortVersionString"]
app["buildVersion"] = installed_app["CFBundleVersion"]
app["appPath"] = installed_app["appBaseLocate"]
app["isInstalled"] = True
installed_app_path = installed_app["appBaseLocate"]
# 检查路径是否匹配(如果配置中指定了路径)
path_matches = True
if config_app_path and installed_app_path:
path_matches = config_app_path == installed_app_path
# 只有当路径匹配时才标记为已安装
if path_matches:
app["displayName"] = installed_app["CFBundleName"]
app["version"] = installed_app["CFBundleShortVersionString"]
app["buildVersion"] = installed_app["CFBundleVersion"]
app["appPath"] = installed_app["appBaseLocate"]
app["isInstalled"] = True
else:
app["displayName"] = package_name
app["isInstalled"] = False
else:
app["displayName"] = package_name
app["isInstalled"] = False
print("\n" + _("total_supported_apps", "支持的应用总数: {0}").format(len(all_apps)))
page_size = 10
page = 0
total_pages = (len(all_apps) + page_size - 1) // page_size
selected_apps = []
while True:
start_idx = page * page_size
end_idx = min(start_idx + page_size, len(all_apps))
print("\n" + _("showing_page", "显示第 {0}/{1} 页 (应用 {2}-{3}/{4})").format(page+1, total_pages, start_idx+1, end_idx, len(all_apps)))
# 打印表头
print_app_table_header(include_status=True)
for i, app in enumerate(all_apps[start_idx:end_idx], start_idx+1):
print_app_info(i, app, include_status=True)
print("\n" + _("operation_options", "操作选项:"))
print(f"{Color.cyan('n')}. {_('next_page', '下一页')} | {Color.cyan('p')}. {_('prev_page', '上一页')} | {Color.cyan(_('number', '数字'))}. {_('select_app', '选择应用')} | {Color.cyan('0')}. {_('confirm_and_return', '确认并返回')}")
# 显示输入提示但不换行
print("\n" + _("select_operation", "请选择操作: "), end='', flush=True)
# 获取单个字符输入
choice = getch()
if choice in ['n', 'p', '0']:
print(choice) # 打印当前字符提供反馈
if choice == 'n':
if page < total_pages - 1:
page += 1
@@ -353,7 +392,7 @@ def show_all_supported_apps(app_list, install_apps):
# 对于数字选择,需要读取完整的数字
full_choice = choice
print(choice, end='', flush=True) # 打印第一个数字
# 继续读取数字直到遇到非数字字符或Enter
while True:
ch = getch()
@@ -366,7 +405,7 @@ def show_all_supported_apps(app_list, install_apps):
else:
print() # 换行
break
try:
app_idx = int(full_choice)
if 1 <= app_idx <= len(all_apps[start_idx:end_idx]):
@@ -392,5 +431,5 @@ def show_all_supported_apps(app_list, install_apps):
print(choice) # 打印无效字符
print(_("invalid_choice", "无效的选择,请重新选择"))
wait_for_enter()
return selected_apps
return selected_apps