diff --git a/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseModule.java b/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseModule.java index 36d850b68..8ca0b17c9 100644 --- a/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseModule.java +++ b/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseModule.java @@ -69,13 +69,15 @@ public void init(LoadPackageParam lpparam) { } mLoadPackageParam = lpparam; - DexKit dexKit = new DexKit(lpparam, TAG); - initZygote(); - handleLoadPackage(); - - if (dexKit.isInit) { - dexKit.close(); + DexKit.ready(lpparam, TAG); + try { + initZygote(); + handleLoadPackage(); + } catch (Throwable e) { + DexKit.close(); + throw new RuntimeException(e); } + DexKit.close(); } /*public void initHook(BaseHook baseHook) { diff --git a/app/src/main/java/com/sevtinge/hyperceiler/module/base/dexkit/DexKit.java b/app/src/main/java/com/sevtinge/hyperceiler/module/base/dexkit/DexKit.java index 9e72ce9d5..cec899c2b 100644 --- a/app/src/main/java/com/sevtinge/hyperceiler/module/base/dexkit/DexKit.java +++ b/app/src/main/java/com/sevtinge/hyperceiler/module/base/dexkit/DexKit.java @@ -23,9 +23,11 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; import com.sevtinge.hyperceiler.R; import com.tencent.mmkv.MMKV; @@ -45,44 +47,61 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import de.robv.android.xposed.callbacks.XC_LoadPackage; /** - * 使用 MMKV 工具缓存 Dexkit 结果的工具类。 - * 缓存路径为 /data/data//files/mmkv - * * @author 焕晨HChen * @co-author Ling Qiqi */ public class DexKit { - private static final int mVersion = 7; - public boolean isInit = false; - private static final String MMKV_PATH = "/files/mmkv"; + private static String TAG = "DexKit"; + private static boolean isInit = false; + private static final int mVersion = 1; // 新时节,新气象 + private static final String MMKV_PATH = "/files/hyperceiler/mmkv"; private static XC_LoadPackage.LoadPackageParam mParam; private static final String TYPE_METHOD = "METHOD"; private static final String TYPE_CLASS = "CLASS"; private static final String TYPE_FIELD = "FIELD"; private static MMKV mMMKV = null; - private static DexKit mThis; + private static Gson mGson = null; private static DexKitBridge mDexKitBridge; - private final String TAG; - public DexKit(XC_LoadPackage.LoadPackageParam param, String tag) { + public static void ready(XC_LoadPackage.LoadPackageParam param, String tag) { mParam = param; TAG = tag; - mThis = this; + isInit = false; } @NotNull public static DexKitBridge initDexkitBridge() { - if (mDexKitBridge == null) { - if (mThis == null) { - throw new RuntimeException("InitDexKit is null!!"); - } else { - mThis.init(); - } + if (mDexKitBridge != null) + return mDexKitBridge; + if (isInit) + throw new RuntimeException(TAG + ": mDexKitBridge is null!"); + if (mParam == null) + throw new RuntimeException(TAG + ": lpparam is null!"); + + String hostDir = mParam.appInfo.sourceDir, + mmkvPath = mParam.appInfo.dataDir + MMKV_PATH; + mGson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + + // 启动 MMKV + MMKV.initialize(mmkvPath, System::loadLibrary); + mMMKV = MMKV.defaultMMKV(); + int version = mMMKV.getInt("version", 0); + if (version != 0 && version != mVersion) { + mMMKV.clearAll(); } + mMMKV.putInt("version", mVersion); + + // 启动 DexKit + System.loadLibrary("dexkit"); + mDexKitBridge = DexKitBridge.create(hostDir); + isInit = true; + return mDexKitBridge; } @@ -95,40 +114,36 @@ public static T findMember(@NonNull String key, IDexKit iDexKit) { public static T findMember(@NonNull String key, ClassLoader classLoader, IDexKit iDexKit) { DexKitBridge dexKitBridge = initDexkitBridge(); - String json = mMMKV.getString(key, ""); - if (json.isEmpty()) { + String descriptor = mMMKV.getString(key, ""); + if (descriptor.isEmpty()) { try { BaseData baseData = iDexKit.dexkit(dexKitBridge); if (baseData instanceof FieldData fieldData) { - saveToMMKV(key, TYPE_FIELD, List.of(fieldData.toDexField().serialize())); + mMMKV.putString(key, mGson.toJson(new MemberData(TYPE_FIELD, fieldData.toDexField().serialize()))); return (T) fieldData.getFieldInstance(classLoader); } else if (baseData instanceof MethodData methodData) { - saveToMMKV(key, TYPE_METHOD, List.of(methodData.toDexMethod().serialize())); + mMMKV.putString(key, mGson.toJson(new MemberData(TYPE_METHOD, methodData.toDexMethod().serialize()))); return (T) methodData.getMethodInstance(classLoader); } else if (baseData instanceof ClassData classData) { - saveToMMKV(key, TYPE_CLASS, List.of(classData.toDexType().serialize())); + mMMKV.putString(key, mGson.toJson(new MemberData(TYPE_CLASS, classData.toDexType().serialize()))); return (T) classData.getInstance(classLoader); } } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } else { + MemberData data = mGson.fromJson(descriptor, new TypeToken() { + }.getType()); try { - JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); - for (String jsonKey : jsonObject.keySet()) { - JsonObject item = jsonObject.getAsJsonObject(jsonKey); - String type = item.get("type").getAsString(); - String serializedItem = item.get("name").getAsString(); - switch (type) { - case TYPE_METHOD -> { - return (T) new DexMethod(serializedItem).getMethodInstance(classLoader); - } - case TYPE_FIELD -> { - return (T) new DexField(serializedItem).getFieldInstance(classLoader); - } - case TYPE_CLASS -> { - return (T) new DexClass(serializedItem).getInstance(classLoader); - } + switch (data.type) { + case TYPE_METHOD -> { + return (T) new DexMethod(data.serialize).getMethodInstance(classLoader); + } + case TYPE_FIELD -> { + return (T) new DexField(data.serialize).getFieldInstance(classLoader); + } + case TYPE_CLASS -> { + return (T) new DexClass(data.serialize).getInstance(classLoader); } } } catch (NoSuchMethodException | NoSuchFieldException | ClassNotFoundException e) { @@ -144,8 +159,8 @@ public static List findMemberList(@NonNull String key, IDexKitList iDexKi public static List findMemberList(@NonNull String key, ClassLoader classLoader, IDexKitList iDexKitList) { DexKitBridge dexKitBridge = initDexkitBridge(); - String json = mMMKV.getString(key, ""); - if (json.isEmpty()) { + String descriptor = mMMKV.getString(key, ""); + if (descriptor.isEmpty()) { try { BaseDataList baseDataList = iDexKitList.dexkit(dexKitBridge); ArrayList serializeList = new ArrayList<>(); @@ -155,41 +170,51 @@ public static List findMemberList(@NonNull String key, ClassLoader classL serializeList.add(f.toDexField().serialize()); instanceList.add((T) f.getFieldInstance(classLoader)); } - saveToMMKV(key, TYPE_FIELD, serializeList); + mMMKV.putString(key, mGson.toJson(new MemberData(TYPE_FIELD, serializeList))); return instanceList; } else if (baseDataList instanceof MethodDataList methodDataList) { for (MethodData m : methodDataList) { serializeList.add(m.toDexMethod().serialize()); instanceList.add((T) m.getMethodInstance(classLoader)); } - saveToMMKV(key, TYPE_METHOD, serializeList); + mMMKV.putString(key, mGson.toJson(new MemberData(TYPE_METHOD, serializeList))); return instanceList; } else if (baseDataList instanceof ClassDataList classDataList) { for (ClassData c : classDataList) { serializeList.add(c.toDexType().serialize()); instanceList.add((T) c.getInstance(classLoader)); } - saveToMMKV(key, TYPE_CLASS, serializeList); + mMMKV.putString(key, mGson.toJson(new MemberData(TYPE_CLASS, serializeList))); return instanceList; } } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } else { + MemberData data = mGson.fromJson(descriptor, new TypeToken() { + }.getType()); ArrayList instanceList = new ArrayList<>(); try { - JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); - for (String jsonKey : jsonObject.keySet()) { - JsonObject item = jsonObject.getAsJsonObject(jsonKey); - String type = item.get("type").getAsString(); - String serializedItem = item.get("name").getAsString(); - switch (type) { - case TYPE_METHOD -> instanceList.add((T) new DexMethod(serializedItem).getMethodInstance(classLoader)); - case TYPE_FIELD -> instanceList.add((T) new DexField(serializedItem).getFieldInstance(classLoader)); - case TYPE_CLASS -> instanceList.add((T) new DexClass(serializedItem).getInstance(classLoader)); + switch (data.type) { + case TYPE_METHOD -> { + for (String s : data.serializeList) { + instanceList.add((T) new DexMethod(s).getMethodInstance(classLoader)); + } + return instanceList; + } + case TYPE_FIELD -> { + for (String s : data.serializeList) { + instanceList.add((T) new DexField(s).getFieldInstance(classLoader)); + } + return instanceList; + } + case TYPE_CLASS -> { + for (String s : data.serializeList) { + instanceList.add((T) new DexClass(s).getInstance(classLoader)); + } + return instanceList; } } - return instanceList; } catch (NoSuchMethodException | NoSuchFieldException | ClassNotFoundException e) { throw new RuntimeException(e); } @@ -197,48 +222,23 @@ public static List findMemberList(@NonNull String key, ClassLoader classL return new ArrayList<>(); } - private static void saveToMMKV(String key, String type, List serializeList) { - JsonObject jsonObject = new JsonObject(); - for (int i = 0; i < serializeList.size(); i++) { - JsonObject item = new JsonObject(); - item.addProperty("type", type); - item.addProperty("name", serializeList.get(i)); - jsonObject.add(String.valueOf(i), item); - } - mMMKV.putString(key, jsonObject + "\n\n"); - } - - private void init() { - if (mDexKitBridge != null) { - return; - } - - if (mParam == null) { - throw new RuntimeException(TAG != null ? TAG : "InitDexKit" + ": lpparam is null"); - } - - String hostDir = mParam.appInfo.sourceDir; - String mmkvPath = mParam.appInfo.dataDir + MMKV_PATH; - - // 启动 MMKV - MMKV.initialize(mmkvPath, System::loadLibrary); - mMMKV = MMKV.defaultMMKV(); - int version = mMMKV.getInt("version", 0); - if (version != 0 && version != mVersion) { - mMMKV.clearAll(); - } - mMMKV.putInt("version", mVersion); - - // 启动 DexKit - System.loadLibrary("dexkit"); - mDexKitBridge = DexKitBridge.create(hostDir); - isInit = true; + public static void deleteAllCache(Context context) { + String[] folderNames = context.getResources().getStringArray(R.array.xposed_scope); + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.submit(() -> { + for (String folderName : folderNames) { + String folderPath = "/data/data/" + folderName + MMKV_PATH; + safeExecCommandWithRoot("rm -rf " + folderPath); + } + }); } /** * 请勿手动调用。 */ - public void close() { + public static void close() { + if (!isInit) return; + if (mDexKitBridge != null) { mDexKitBridge.close(); mDexKitBridge = null; @@ -248,17 +248,38 @@ public void close() { mMMKV = null; } mParam = null; - mThis = null; + mGson = null; isInit = false; } - public static void deleteAllCache(Context context) { - String[] folderNames = context.getResources().getStringArray(R.array.xposed_scope); - for (String folderName : folderNames) { - String folderPath = "/data/data/" + folderName + MMKV_PATH; - if (safeExecCommandWithRoot("ls " + folderPath).contains("mmkv")) { - safeExecCommandWithRoot("rm -rf " + folderPath); - } + private static final class MemberData { + public String type = ""; + public String serialize = ""; + + public ArrayList serializeList = new ArrayList<>(); + + public MemberData(String type, String serialize) { + this.type = type; + this.serialize = serialize; + } + + public MemberData(String type, ArrayList serializeList) { + this.type = type; + this.serializeList = serializeList; + } + + @NonNull + @Override + public String toString() { + return "Type: " + type + ", Serialize: " + serialize + ", SerializeList: " + serializeList; + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof MemberData memberData + && memberData.type.equals(type) + && memberData.serialize.equals(serialize) + && memberData.serializeList.equals(serializeList); } } } diff --git a/app/src/main/java/com/sevtinge/hyperceiler/ui/fragment/main/settings/development/DevelopmentFragment.java b/app/src/main/java/com/sevtinge/hyperceiler/ui/fragment/main/settings/development/DevelopmentFragment.java index 45fcb7114..6b9e29e1c 100644 --- a/app/src/main/java/com/sevtinge/hyperceiler/ui/fragment/main/settings/development/DevelopmentFragment.java +++ b/app/src/main/java/com/sevtinge/hyperceiler/ui/fragment/main/settings/development/DevelopmentFragment.java @@ -1,21 +1,21 @@ /* - * This file is part of HyperCeiler. + * This file is part of HyperCeiler. - * HyperCeiler is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License. + * HyperCeiler is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . - * Copyright (C) 2023-2024 HyperCeiler Contributions -*/ + * Copyright (C) 2023-2024 HyperCeiler Contributions + */ package com.sevtinge.hyperceiler.ui.fragment.main.settings.development; import static com.sevtinge.hyperceiler.utils.log.LogManager.fixLsposedLogService; @@ -27,6 +27,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.preference.Preference; import com.sevtinge.hyperceiler.R; import com.sevtinge.hyperceiler.module.base.dexkit.DexKit; @@ -34,7 +35,6 @@ import com.sevtinge.hyperceiler.utils.DialogHelper; import fan.appcompat.app.AlertDialog; -import androidx.preference.Preference; public class DevelopmentFragment extends SettingsPreferenceFragment implements Preference.OnPreferenceClickListener { @@ -64,12 +64,13 @@ public void initPrefs() { @Override public boolean onPreferenceClick(@NonNull Preference preference) { switch (preference.getKey()) { - case "prefs_key_development_cmd_r" -> showInDialog(new DevelopmentKillFragment.EditDialogCallback() { - @Override - public void onInputReceived(String command) { - showOutDialog(safeExecCommandWithRoot(command)); - } - }); + case "prefs_key_development_cmd_r" -> + showInDialog(new DevelopmentKillFragment.EditDialogCallback() { + @Override + public void onInputReceived(String command) { + showOutDialog(safeExecCommandWithRoot(command)); + } + }); case "prefs_key_development_delete_all_dexkit_cache" -> DialogHelper.showDialog(getActivity(), R.string.warn, R.string.delete_all_dexkit_cache_desc, (dialog, which) -> { DexKit.deleteAllCache(getActivity());