From ab46a223f1c736d5e844b9f8f251411c3089ad97 Mon Sep 17 00:00:00 2001 From: jiqiu2021 Date: Sat, 25 Oct 2025 17:28:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0kpm=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/jiqiu/configapp/ConfigManager.java | 217 +++++++++++++++ .../com/jiqiu/configapp/HideSoAdapter.java | 105 ++++++++ .../com/jiqiu/configapp/KpmHideFragment.java | 249 ++++++++++++++++++ .../com/jiqiu/configapp/MainActivity.java | 11 + .../src/main/res/layout/fragment_kpm_hide.xml | 202 ++++++++++++++ .../src/main/res/layout/item_hide_so.xml | 58 ++++ .../src/main/res/menu/bottom_nav_menu.xml | 5 + configapp/src/main/res/values/strings.xml | 22 ++ module/build.gradle | 4 + module/service.sh | 38 +++ 10 files changed, 911 insertions(+) create mode 100644 configapp/src/main/java/com/jiqiu/configapp/HideSoAdapter.java create mode 100644 configapp/src/main/java/com/jiqiu/configapp/KpmHideFragment.java create mode 100644 configapp/src/main/res/layout/fragment_kpm_hide.xml create mode 100644 configapp/src/main/res/layout/item_hide_so.xml diff --git a/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java b/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java index 8cb5f73..b1c8a48 100644 --- a/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java +++ b/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java @@ -18,6 +18,9 @@ public class ConfigManager { public static final String MODULE_PATH = "/data/adb/modules/zygisk-myinjector"; public static final String CONFIG_FILE = MODULE_PATH + "/config.json"; public static final String SO_STORAGE_DIR = MODULE_PATH + "/so_files"; + public static final String KPM_MODULE_PATH = MODULE_PATH + "/injectHide.kpm"; + public static final String KPM_HIDE_CONFIG = MODULE_PATH + "/kpm_hide_config.txt"; + private static final String KPM_MODULE_NAME = "hideInject"; private final Context context; private final Gson gson; @@ -651,6 +654,220 @@ public class ConfigManager { } } + // ==================== KPM Module Management ==================== + + /** + * 检查 KPM 模块是否已加载 + */ + public boolean isKpmModuleLoaded() { + Shell.Result result = Shell.cmd("lsmod | grep " + KPM_MODULE_NAME).exec(); + return result.isSuccess() && !result.getOut().isEmpty(); + } + + /** + * 加载 KPM 模块 + */ + public boolean loadKpmModule() { + if (!isRootAvailable()) { + Log.e(TAG, "Root access not available!"); + return false; + } + + // Check if module file exists + Shell.Result checkResult = Shell.cmd("test -f \"" + KPM_MODULE_PATH + "\" && echo 'exists'").exec(); + if (!checkResult.isSuccess() || checkResult.getOut().isEmpty()) { + Log.e(TAG, "KPM module file not found: " + KPM_MODULE_PATH); + return false; + } + + // Check if already loaded + if (isKpmModuleLoaded()) { + Log.i(TAG, "KPM module already loaded"); + return true; + } + + // Load module + Shell.Result result = Shell.cmd("insmod \"" + KPM_MODULE_PATH + "\"").exec(); + if (result.isSuccess()) { + Log.i(TAG, "KPM module loaded successfully"); + return true; + } else { + Log.e(TAG, "Failed to load KPM module: " + String.join("\n", result.getErr())); + return false; + } + } + + /** + * 卸载 KPM 模块 + */ + public boolean unloadKpmModule() { + if (!isRootAvailable()) { + Log.e(TAG, "Root access not available!"); + return false; + } + + if (!isKpmModuleLoaded()) { + Log.i(TAG, "KPM module not loaded"); + return true; + } + + Shell.Result result = Shell.cmd("rmmod " + KPM_MODULE_NAME).exec(); + if (result.isSuccess()) { + Log.i(TAG, "KPM module unloaded successfully"); + return true; + } else { + Log.e(TAG, "Failed to unload KPM module: " + String.join("\n", result.getErr())); + return false; + } + } + + /** + * 重新加载 KPM 模块 + */ + public boolean reloadKpmModule() { + Log.i(TAG, "Reloading KPM module..."); + + // Unload first + if (isKpmModuleLoaded()) { + if (!unloadKpmModule()) { + Log.e(TAG, "Failed to unload module before reload"); + return false; + } + // Give kernel time to cleanup + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + // Load again + return loadKpmModule(); + } + + /** + * 更新 KPM 隐藏配置并重载模块 + */ + public boolean updateKpmHideConfig(List soNames) { + if (!isRootAvailable()) { + Log.e(TAG, "Root access not available!"); + return false; + } + + // Build config content + StringBuilder configContent = new StringBuilder(); + for (String soName : soNames) { + configContent.append(soName).append("\n"); + } + + // Write to temp file + String tempFile = context.getCacheDir() + "/kpm_hide_config.txt"; + try { + java.io.FileWriter writer = new java.io.FileWriter(tempFile); + writer.write(configContent.toString()); + writer.close(); + + // Ensure module directory exists + Shell.cmd("mkdir -p \"" + MODULE_PATH + "\"").exec(); + + // Copy to module directory with root + Shell.Result copyResult = Shell.cmd("cp \"" + tempFile + "\" \"" + KPM_HIDE_CONFIG + "\"").exec(); + if (!copyResult.isSuccess()) { + Log.e(TAG, "Failed to copy KPM config: " + String.join("\n", copyResult.getErr())); + return false; + } + + Shell.cmd("chmod 644 \"" + KPM_HIDE_CONFIG + "\"").exec(); + + // Clean up temp file + new File(tempFile).delete(); + + Log.i(TAG, "KPM hide config updated with " + soNames.size() + " entries"); + + // Reload module to apply changes + return reloadKpmModule(); + + } catch (Exception e) { + Log.e(TAG, "Failed to update KPM config", e); + return false; + } + } + + /** + * 获取当前隐藏的 SO 列表 + */ + public List getHiddenSoList() { + List hiddenList = new ArrayList<>(); + + Shell.Result result = Shell.cmd("cat \"" + KPM_HIDE_CONFIG + "\"").exec(); + if (result.isSuccess()) { + for (String line : result.getOut()) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + hiddenList.add(trimmed); + } + } + } + + return hiddenList; + } + + /** + * 添加 SO 到隐藏列表 + */ + public boolean addSoToHideList(String soName) { + List hiddenList = getHiddenSoList(); + if (!hiddenList.contains(soName)) { + hiddenList.add(soName); + return updateKpmHideConfig(hiddenList); + } + return true; + } + + /** + * 从隐藏列表移除 SO + */ + public boolean removeSoFromHideList(String soName) { + List hiddenList = getHiddenSoList(); + if (hiddenList.remove(soName)) { + return updateKpmHideConfig(hiddenList); + } + return true; + } + + /** + * 获取所有可隐藏的 SO 文件列表(从已启用应用中提取) + */ + public List getAvailableSoList() { + List availableSos = new ArrayList<>(); + + // Always include our injector library + availableSos.add("libmyinjector.so"); + + // Add SOs from all enabled apps + for (Map.Entry entry : config.perAppConfig.entrySet()) { + AppConfig appConfig = entry.getValue(); + if (appConfig.enabled && appConfig.soFiles != null) { + for (SoFile soFile : appConfig.soFiles) { + if (!availableSos.contains(soFile.name)) { + availableSos.add(soFile.name); + } + } + } + } + + // Add global SO files + if (config.globalSoFiles != null) { + for (SoFile soFile : config.globalSoFiles) { + if (!availableSos.contains(soFile.name)) { + availableSos.add(soFile.name); + } + } + } + + return availableSos; + } + // Data classes public static class ModuleConfig { public boolean enabled = true; diff --git a/configapp/src/main/java/com/jiqiu/configapp/HideSoAdapter.java b/configapp/src/main/java/com/jiqiu/configapp/HideSoAdapter.java new file mode 100644 index 0000000..f65cb75 --- /dev/null +++ b/configapp/src/main/java/com/jiqiu/configapp/HideSoAdapter.java @@ -0,0 +1,105 @@ +package com.jiqiu.configapp; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +/** + * SO 文件隐藏列表适配器 + */ +public class HideSoAdapter extends RecyclerView.Adapter { + + private final List items; + private final OnItemCheckedChangeListener listener; + + public interface OnItemCheckedChangeListener { + void onItemCheckedChanged(KpmHideFragment.HideSoItem item, boolean isChecked); + } + + public HideSoAdapter(List items, + OnItemCheckedChangeListener listener) { + this.items = items; + this.listener = listener; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_hide_so, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + KpmHideFragment.HideSoItem item = items.get(position); + holder.bind(item); + } + + @Override + public int getItemCount() { + return items.size(); + } + + class ViewHolder extends RecyclerView.ViewHolder { + + private final CheckBox cbHide; + private final TextView tvSoName; + private final TextView tvSoStatus; + private final TextView tvFixedBadge; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + cbHide = itemView.findViewById(R.id.cbHide); + tvSoName = itemView.findViewById(R.id.tvSoName); + tvSoStatus = itemView.findViewById(R.id.tvSoStatus); + tvFixedBadge = itemView.findViewById(R.id.tvFixedBadge); + } + + public void bind(KpmHideFragment.HideSoItem item) { + tvSoName.setText(item.soName); + + // 设置勾选状态 + cbHide.setOnCheckedChangeListener(null); // 先移除监听器避免触发 + cbHide.setChecked(item.isHidden); + + // 显示状态 + if (item.isFixed) { + tvSoStatus.setText("必需项 - 始终隐藏"); + tvSoStatus.setTextColor(itemView.getContext().getResources() + .getColor(android.R.color.holo_green_dark, null)); + tvFixedBadge.setVisibility(View.VISIBLE); + cbHide.setEnabled(false); + cbHide.setChecked(true); // 固定项始终勾选 + } else { + tvSoStatus.setText(item.isHidden ? "已隐藏" : "未隐藏"); + tvSoStatus.setTextColor(itemView.getContext().getResources() + .getColor(android.R.color.darker_gray, null)); + tvFixedBadge.setVisibility(View.GONE); + cbHide.setEnabled(true); + } + + // 设置点击监听器 + cbHide.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (listener != null && !item.isFixed) { + listener.onItemCheckedChanged(item, isChecked); + } + }); + + // 整行点击也触发勾选 + itemView.setOnClickListener(v -> { + if (!item.isFixed) { + cbHide.setChecked(!cbHide.isChecked()); + } + }); + } + } +} + diff --git a/configapp/src/main/java/com/jiqiu/configapp/KpmHideFragment.java b/configapp/src/main/java/com/jiqiu/configapp/KpmHideFragment.java new file mode 100644 index 0000000..92204b3 --- /dev/null +++ b/configapp/src/main/java/com/jiqiu/configapp/KpmHideFragment.java @@ -0,0 +1,249 @@ +package com.jiqiu.configapp; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.card.MaterialCardView; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * KPM 隐藏功能 Fragment + * 管理 KPM 内核模块和 SO 文件隐藏配置 + */ +public class KpmHideFragment extends Fragment { + + private static final String TAG = "KpmHideFragment"; + + private TextView tvModuleStatus; + private TextView tvModuleInfo; + private TextView tvConfigPath; + private Button btnReloadModule; + private RecyclerView rvSoList; + + private ConfigManager configManager; + private HideSoAdapter adapter; + private ExecutorService executor; + private Handler mainHandler; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_kpm_hide, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + initViews(view); + initExecutor(); + initConfigManager(); + setupListeners(); + loadData(); + } + + private void initViews(View view) { + tvModuleStatus = view.findViewById(R.id.tvModuleStatus); + tvModuleInfo = view.findViewById(R.id.tvModuleInfo); + tvConfigPath = view.findViewById(R.id.tvConfigPath); + btnReloadModule = view.findViewById(R.id.btnReloadModule); + rvSoList = view.findViewById(R.id.rvSoList); + + rvSoList.setLayoutManager(new LinearLayoutManager(getContext())); + } + + private void initExecutor() { + executor = Executors.newSingleThreadExecutor(); + mainHandler = new Handler(Looper.getMainLooper()); + } + + private void initConfigManager() { + configManager = new ConfigManager(getContext()); + } + + private void setupListeners() { + btnReloadModule.setOnClickListener(v -> reloadModule()); + } + + private void loadData() { + // 显示配置路径 + tvConfigPath.setText("配置文件: " + ConfigManager.KPM_HIDE_CONFIG); + + // 异步加载模块状态和 SO 列表 + executor.execute(() -> { + try { + final boolean isLoaded = configManager.isKpmModuleLoaded(); + final List availableSos = configManager.getAvailableSoList(); + final List hiddenSos = configManager.getHiddenSoList(); + + mainHandler.post(() -> { + updateModuleStatus(isLoaded); + setupSoList(availableSos, hiddenSos); + }); + } catch (Exception e) { + Log.e(TAG, "Error loading data", e); + mainHandler.post(() -> { + Toast.makeText(getContext(), "加载数据失败: " + e.getMessage(), + Toast.LENGTH_SHORT).show(); + }); + } + }); + } + + private void updateModuleStatus(boolean isLoaded) { + if (isLoaded) { + tvModuleStatus.setText("● 已加载"); + tvModuleStatus.setTextColor(getResources().getColor(android.R.color.holo_green_dark, null)); + tvModuleInfo.setText("KPM 内核模块运行中\n模块名称: hideInject\n版本: 0.0.1"); + } else { + tvModuleStatus.setText("● 未加载"); + tvModuleStatus.setTextColor(getResources().getColor(android.R.color.holo_red_dark, null)); + tvModuleInfo.setText("KPM 内核模块未运行\n请检查模块文件是否存在或手动重载"); + } + } + + private void setupSoList(List availableSos, List hiddenSos) { + List items = new ArrayList<>(); + + for (String soName : availableSos) { + HideSoItem item = new HideSoItem(); + item.soName = soName; + item.isHidden = hiddenSos.contains(soName); + // libmyinjector.so 是固定隐藏的 + item.isFixed = soName.equals("libmyinjector.so"); + items.add(item); + } + + adapter = new HideSoAdapter(items, this::onSoItemCheckedChanged); + rvSoList.setAdapter(adapter); + } + + private void onSoItemCheckedChanged(HideSoItem item, boolean isChecked) { + if (item.isFixed) { + // 固定项不允许取消勾选 + Toast.makeText(getContext(), "libmyinjector.so 是必需的,不能取消隐藏", + Toast.LENGTH_SHORT).show(); + return; + } + + // 异步更新配置 + executor.execute(() -> { + try { + boolean success; + if (isChecked) { + success = configManager.addSoToHideList(item.soName); + } else { + success = configManager.removeSoFromHideList(item.soName); + } + + final boolean finalSuccess = success; + mainHandler.post(() -> { + if (finalSuccess) { + item.isHidden = isChecked; + Toast.makeText(getContext(), + isChecked ? "已添加到隐藏列表" : "已从隐藏列表移除", + Toast.LENGTH_SHORT).show(); + // 更新模块状态 + refreshModuleStatus(); + } else { + Toast.makeText(getContext(), "更新失败,请检查日志", + Toast.LENGTH_SHORT).show(); + // 恢复原来的状态 + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + } + }); + } catch (Exception e) { + Log.e(TAG, "Error updating SO hide status", e); + mainHandler.post(() -> { + Toast.makeText(getContext(), "更新失败: " + e.getMessage(), + Toast.LENGTH_SHORT).show(); + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + }); + } + }); + } + + private void reloadModule() { + btnReloadModule.setEnabled(false); + btnReloadModule.setText("重载中..."); + + executor.execute(() -> { + try { + final boolean success = configManager.reloadKpmModule(); + + mainHandler.post(() -> { + btnReloadModule.setEnabled(true); + btnReloadModule.setText("重载模块"); + + if (success) { + Toast.makeText(getContext(), "模块重载成功", Toast.LENGTH_SHORT).show(); + refreshModuleStatus(); + } else { + Toast.makeText(getContext(), "模块重载失败,请查看日志", + Toast.LENGTH_SHORT).show(); + } + }); + } catch (Exception e) { + Log.e(TAG, "Error reloading module", e); + mainHandler.post(() -> { + btnReloadModule.setEnabled(true); + btnReloadModule.setText("重载模块"); + Toast.makeText(getContext(), "重载失败: " + e.getMessage(), + Toast.LENGTH_SHORT).show(); + }); + } + }); + } + + private void refreshModuleStatus() { + executor.execute(() -> { + try { + final boolean isLoaded = configManager.isKpmModuleLoaded(); + mainHandler.post(() -> updateModuleStatus(isLoaded)); + } catch (Exception e) { + Log.e(TAG, "Error refreshing module status", e); + } + }); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (executor != null && !executor.isShutdown()) { + executor.shutdown(); + } + } + + /** + * SO 隐藏项数据类 + */ + public static class HideSoItem { + public String soName; + public boolean isHidden; + public boolean isFixed; // 是否是固定隐藏项(不可取消) + } +} + diff --git a/configapp/src/main/java/com/jiqiu/configapp/MainActivity.java b/configapp/src/main/java/com/jiqiu/configapp/MainActivity.java index 6311488..ae6f528 100644 --- a/configapp/src/main/java/com/jiqiu/configapp/MainActivity.java +++ b/configapp/src/main/java/com/jiqiu/configapp/MainActivity.java @@ -19,6 +19,7 @@ public class MainActivity extends AppCompatActivity implements SettingsFragment. private AppListFragment appListFragment; private SettingsFragment settingsFragment; private SoManagerFragment soManagerFragment; + private KpmHideFragment kpmHideFragment; @Override protected void onCreate(Bundle savedInstanceState) { @@ -53,6 +54,9 @@ public class MainActivity extends AppCompatActivity implements SettingsFragment. } else if (itemId == R.id.navigation_so_manager) { showSoManagerFragment(); return true; + } else if (itemId == R.id.navigation_kpm_hide) { + showKpmHideFragment(); + return true; } else if (itemId == R.id.navigation_settings) { showSettingsFragment(); return true; @@ -75,6 +79,13 @@ public class MainActivity extends AppCompatActivity implements SettingsFragment. showFragment(soManagerFragment); } + private void showKpmHideFragment() { + if (kpmHideFragment == null) { + kpmHideFragment = new KpmHideFragment(); + } + showFragment(kpmHideFragment); + } + private void showSettingsFragment() { if (settingsFragment == null) { settingsFragment = new SettingsFragment(); diff --git a/configapp/src/main/res/layout/fragment_kpm_hide.xml b/configapp/src/main/res/layout/fragment_kpm_hide.xml new file mode 100644 index 0000000..0344a9f --- /dev/null +++ b/configapp/src/main/res/layout/fragment_kpm_hide.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/configapp/src/main/res/layout/item_hide_so.xml b/configapp/src/main/res/layout/item_hide_so.xml new file mode 100644 index 0000000..01fdf42 --- /dev/null +++ b/configapp/src/main/res/layout/item_hide_so.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/configapp/src/main/res/menu/bottom_nav_menu.xml b/configapp/src/main/res/menu/bottom_nav_menu.xml index 753e065..eec0d4e 100644 --- a/configapp/src/main/res/menu/bottom_nav_menu.xml +++ b/configapp/src/main/res/menu/bottom_nav_menu.xml @@ -9,6 +9,11 @@ android:id="@+id/navigation_so_manager" android:icon="@android:drawable/ic_menu_save" android:title="@string/title_so_manager" /> + + 应用列表 SO库管理 + KPM隐藏 全局设置 @@ -21,4 +22,25 @@ 关于 MyInjector 配置应用,用于管理注入设置 + + + KPM 注入隐藏 + 模块状态 + 已加载 + 未加载 + KPM 内核模块信息 + 重载模块 + 模块重载成功 + 模块重载失败 + 配置文件路径 + 固定隐藏项 + 可选隐藏 SO 列表 + 使用说明 + 固定 + 已隐藏 + 未隐藏 + 已添加到隐藏列表 + 已从隐藏列表移除 + 更新失败 + libmyinjector.so 是必需的,不能取消隐藏 \ No newline at end of file diff --git a/module/build.gradle b/module/build.gradle index 3402c04..5fe2f04 100644 --- a/module/build.gradle +++ b/module/build.gradle @@ -70,6 +70,10 @@ afterEvaluate { from("$projectDir") { include 'service.sh' } + // Copy KPM module + from("$projectDir/kpm") { + include 'injectHide.kpm' + } // Copy ConfigApp APK if it exists def apkFile = file("$rootDir/configapp/build/outputs/apk/debug/configapp-debug.apk") if (apkFile.exists()) { diff --git a/module/service.sh b/module/service.sh index 8abbbff..4fb2844 100755 --- a/module/service.sh +++ b/module/service.sh @@ -94,5 +94,43 @@ chown -R root:root /data/adb/modules/zygisk-myinjector log "ConfigApp 安装脚本执行完成" +# ==================== KPM 模块加载 ==================== + +# KPM 模块路径 +KPM_MODULE="$MODDIR/injectHide.kpm" +KPM_CONFIG="$MODDIR/kpm_hide_config.txt" + +log "开始加载 KPM 内核模块" + +# 检查 KPM 模块文件是否存在 +if [ ! -f "$KPM_MODULE" ]; then + log "KPM 模块文件不存在: $KPM_MODULE" +else + log "找到 KPM 模块文件: $KPM_MODULE" + + # 创建初始配置文件(如果不存在) + if [ ! -f "$KPM_CONFIG" ]; then + log "创建初始 KPM 配置文件" + echo "libmyinjector.so" > "$KPM_CONFIG" + chmod 644 "$KPM_CONFIG" + fi + + # 等待一段时间确保系统稳定 + sleep 3 + + # 加载 KPM 模块 + log "正在加载 KPM 模块..." + insmod "$KPM_MODULE" 2>&1 | while read line; do + log "insmod: $line" + done + + # 检查模块是否加载成功 + if lsmod | grep -q "hideInject"; then + log "KPM 模块加载成功!" + else + log "KPM 模块加载失败,请检查日志" + fi +fi + # 脚本完成 exit 0 \ No newline at end of file