├── .gitignore ├── LICENSE ├── README.md ├── app ├── build.gradle ├── libs │ └── XposedBridgeApi-82.jar ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── xposed_init │ ├── ic_launcher-web.png │ ├── java │ └── me │ │ └── firesun │ │ └── wechat │ │ └── enhancement │ │ ├── HookUtil.java │ │ ├── Main.java │ │ ├── PreferencesUtils.java │ │ ├── RangePreference.java │ │ ├── SettingsActivity.java │ │ ├── plugin │ │ ├── ADBlock.java │ │ ├── AntiRevoke.java │ │ ├── AntiSnsDelete.java │ │ ├── AutoLogin.java │ │ ├── HideModule.java │ │ ├── IPlugin.java │ │ ├── Limits.java │ │ └── LuckMoney.java │ │ └── util │ │ ├── ConfigReceiver.java │ │ ├── HookParams.java │ │ ├── ReflectionUtil.java │ │ ├── SearchClasses.java │ │ ├── Tag.java │ │ └── XmlToJson.java │ └── res │ ├── layout │ ├── activity_settings.xml │ └── preference_range.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values │ ├── colors.xml │ ├── strings.xml │ └── style.xml │ └── xml │ └── pref_setting.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hotxposed ├── .gitignore ├── build.gradle ├── libs │ └── lite-orm-1.9.2.jar ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── androidwing │ │ └── hotxposed │ │ ├── CommonUtils.java │ │ ├── HotXposed.java │ │ ├── IHookerDispatcher.java │ │ ├── PreferencesUtils.java │ │ ├── ShellUtil.java │ │ ├── XposedUtil.java │ │ ├── database │ │ ├── Lite.java │ │ ├── SqliteHelper.java │ │ └── Student.java │ │ ├── debug │ │ ├── DebugListner.java │ │ └── Trace.java │ │ ├── hot │ │ ├── BaseHook.java │ │ ├── HotXPosed.java │ │ └── IHookerDispatcher.java │ │ ├── log │ │ └── Logs.java │ │ ├── shell │ │ ├── CommandResult.java │ │ ├── Shell.java │ │ ├── ShellExitCode.java │ │ ├── ShellNotFoundException.java │ │ └── StreamGobbler.java │ │ └── thread │ │ └── ThreadPool.java │ └── res │ └── values │ └── strings.xml ├── image ├── screenshot1.jpg ├── screenshot2.jpg └── screenshot3.jpg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | .idea/ 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # Intellij 37 | *.iml 38 | .idea/workspace.xml 39 | .idea/tasks.xml 40 | .idea/gradle.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | 44 | # Keystore files 45 | *.jks 46 | 47 | # External native build folder generated in Android Studio 2.2 and later 48 | .externalNativeBuild 49 | 50 | # Google Services (e.g. APIs or Firebase) 51 | google-services.json 52 | 53 | # Freeline 54 | freeline.py 55 | freeline/ 56 | freeline_project_description.json 57 | 58 | app/keystore/* 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信增强插件 WechatEnhancement 2 | 本项目需要Xposed,同时支持太极与[VirtualXposed](https://github.com/android-hacker/VirtualXposed) 3 | 4 | ## 支持的微信版本 5 | - 理论上支持微信全版本,自适应方式采用微信巫师的自动搜索方式,所以理论上即使微信升级也可继续支持 6 | - 已测试通过的微信版本:6.6.0,6.6.3,6.6.5,6.6.6,6.6.7,6.7.3,7.0.0,7.0.3,7.0.4,7.0.6,7.0.7 7 | 8 | ## 支持功能 9 | - 抢红包 10 | - 自动接收转账 **(恢复聊天记录时务必关闭此功能)** 11 | - 消息防撤回 12 | - 朋友圈防删除 13 | - 朋友圈去广告 14 | - 电脑端微信自动登录 15 | - 突破发送图片9张限制 16 | 17 | ## 效果预览 18 | 19 | 20 | 21 | ## 致谢 22 | 本项目为以下三个项目的融合,使用Java重(chao)写(xi)了微信巫师的自动搜索hook类的功能,并应用在抢红包和自动接收转账上,使得以上功能都能自动适配微信,在此十分感谢veryyoung,Gh0u1L5,wuxiaosu 23 | 24 | [WechatLuckyMoney](https://github.com/veryyoung/WechatLuckyMoney) 25 | 26 | [WechatMagician](https://github.com/Gh0u1L5/WechatMagician) 27 | 28 | [XposedWechatHelper](https://github.com/wuxiaosu/XposedWechatHelper) 29 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | 5 | // signingConfigs { 6 | // debug { 7 | // storeFile file("keystore/debug.keystore") 8 | // storePassword "firesun" 9 | // keyAlias "firesun" 10 | // keyPassword "firesun" 11 | // } 12 | // release { 13 | // storeFile file("keystore/release.keystore") 14 | // storePassword "firesun" 15 | // keyAlias "firesun" 16 | // keyPassword "firesun" 17 | // } 18 | // } 19 | 20 | compileSdkVersion 23 21 | buildToolsVersion "27.0.3" 22 | defaultConfig { 23 | applicationId "me.firesun.wechat.enhancement" 24 | minSdkVersion 17 25 | targetSdkVersion 23 26 | versionCode 46 27 | versionName "1.9.1" 28 | } 29 | 30 | buildTypes { 31 | release { 32 | // signingConfig signingConfigs.release 33 | minifyEnabled true 34 | shrinkResources false 35 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 36 | } 37 | } 38 | 39 | } 40 | 41 | dependencies { 42 | compileOnly files('libs/XposedBridgeApi-82.jar') 43 | implementation 'com.android.support:appcompat-v7:23.1.1' 44 | implementation 'net.dongliu:apk-parser:2.4.2' 45 | implementation 'com.google.code.gson:gson:2.8.2' 46 | api project(path: ':hotxposed') 47 | } 48 | 49 | -------------------------------------------------------------------------------- /app/libs/XposedBridgeApi-82.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/app/libs/XposedBridgeApi-82.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\Bin\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | -keep class me.firesun.wechat.enhancement.* { *; } 20 | -dontwarn okio.** 21 | -dontwarn net.dongliu.apk.parser.** 22 | -dontwarn org.bouncycastle.** 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | me.firesun.wechat.enhancement.Main -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/HookUtil.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement; 2 | 3 | import net.androidwing.hotxposed.HotXposed; 4 | 5 | import de.robv.android.xposed.IXposedHookLoadPackage; 6 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 7 | 8 | /** 9 | * Created by wing on 4/18/17. 10 | */ 11 | 12 | public class HookUtil implements IXposedHookLoadPackage { 13 | @Override 14 | public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) 15 | throws Throwable { 16 | 17 | HotXposed.hook(Main.class, loadPackageParam); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/Main.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.content.ContextWrapper; 7 | import android.content.pm.PackageInfo; 8 | import android.content.pm.PackageManager; 9 | import android.os.Bundle; 10 | import android.widget.Toast; 11 | 12 | import net.androidwing.hotxposed.debug.DebugListner; 13 | import net.androidwing.hotxposed.debug.Trace; 14 | import net.androidwing.hotxposed.hot.BaseHook; 15 | import net.androidwing.hotxposed.log.Logs; 16 | 17 | import de.robv.android.xposed.XC_MethodHook; 18 | import de.robv.android.xposed.XposedBridge; 19 | import de.robv.android.xposed.XposedHelpers; 20 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 21 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; 22 | import me.firesun.wechat.enhancement.plugin.ADBlock; 23 | import me.firesun.wechat.enhancement.plugin.AntiRevoke; 24 | import me.firesun.wechat.enhancement.plugin.AntiSnsDelete; 25 | import me.firesun.wechat.enhancement.plugin.AutoLogin; 26 | import me.firesun.wechat.enhancement.plugin.HideModule; 27 | import me.firesun.wechat.enhancement.plugin.IPlugin; 28 | import me.firesun.wechat.enhancement.plugin.Limits; 29 | import me.firesun.wechat.enhancement.plugin.LuckMoney; 30 | import me.firesun.wechat.enhancement.util.HookParams; 31 | import me.firesun.wechat.enhancement.util.SearchClasses; 32 | 33 | import static de.robv.android.xposed.XposedBridge.log; 34 | 35 | 36 | public class Main extends BaseHook { 37 | 38 | private static IPlugin[] plugins = { 39 | new ADBlock(), 40 | new AntiRevoke(), 41 | new AntiSnsDelete(), 42 | new AutoLogin(), 43 | new HideModule(), 44 | new LuckMoney(), 45 | new Limits(), 46 | }; 47 | 48 | private static final String LOCALE_PACKAGE = BuildConfig.APPLICATION_ID; 49 | public static final String[] TARGET_PACKAGES = {"com.wingsofts.zoomimageheader", "com.tencent.mm", "com.tencent.mm:tools"}; 50 | 51 | @Override 52 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { 53 | startHotXPosed(Main.class, loadPackageParam, LOCALE_PACKAGE, TARGET_PACKAGES); 54 | } 55 | 56 | @Override 57 | public void dispatch(final LoadPackageParam lpparam) { 58 | 59 | XposedBridge.log("Main hook dispatch ->" + lpparam.packageName + "-" + LOCALE_PACKAGE + "-" + lpparam.packageName); 60 | Logs.init("z.houbin.lib.test"); 61 | XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() { 62 | @Override 63 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 64 | super.afterHookedMethod(param); 65 | 66 | Logs.printMethodParam("attach", param); 67 | 68 | Application context = (Application) param.thisObject; 69 | 70 | dispatchAttach(context); 71 | 72 | } 73 | }); 74 | 75 | try { 76 | XposedHelpers.findAndHookMethod(ContextWrapper.class, "attachBaseContext", Context.class, new XC_MethodHook() { 77 | @Override 78 | protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable { 79 | super.afterHookedMethod(param); 80 | Context context = (Context) param.args[0]; 81 | String processName = lpparam.processName; 82 | //Only hook important process 83 | 84 | String versionName = getVersionName(context, HookParams.WECHAT_PACKAGE_NAME); 85 | String text = "wechat version:" + versionName; 86 | log(text); 87 | 88 | Toast.makeText(context, text, Toast.LENGTH_SHORT).show(); 89 | 90 | if (!HookParams.hasInstance()) { 91 | SearchClasses.init(context, lpparam, versionName); 92 | loadPlugins(lpparam); 93 | } 94 | } 95 | }); 96 | } catch (Error | Exception e) { 97 | } 98 | } 99 | 100 | 101 | @Override 102 | public void onActivityCreated(Activity activity, Bundle bundle) { 103 | super.onActivityCreated(activity, bundle); 104 | 105 | Toast.makeText(activity, "hello", Toast.LENGTH_SHORT).show(); 106 | Trace.traceClass(activity.getClass(), new DebugListner()); 107 | 108 | } 109 | 110 | private String getVersionName(Context context, String packageName) { 111 | try { 112 | PackageManager packageManager = context.getPackageManager(); 113 | PackageInfo packInfo = packageManager.getPackageInfo(packageName, 0); 114 | return packInfo.versionName; 115 | } catch (PackageManager.NameNotFoundException e) { 116 | } 117 | return ""; 118 | } 119 | 120 | 121 | private void loadPlugins(LoadPackageParam lpparam) { 122 | for (IPlugin plugin : plugins) { 123 | try { 124 | plugin.hook(lpparam); 125 | } catch (Error | Exception e) { 126 | log("loadPlugins error" + e); 127 | } 128 | } 129 | } 130 | 131 | 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/PreferencesUtils.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement; 2 | 3 | 4 | import de.robv.android.xposed.XSharedPreferences; 5 | 6 | public class PreferencesUtils { 7 | 8 | private static XSharedPreferences instance = null; 9 | 10 | private static XSharedPreferences getInstance() { 11 | if (instance == null) { 12 | instance = new XSharedPreferences(PreferencesUtils.class.getPackage().getName()); 13 | instance.makeWorldReadable(); 14 | } else { 15 | instance.reload(); 16 | } 17 | return instance; 18 | } 19 | 20 | public static boolean open() { 21 | return getInstance().getBoolean("open", false); 22 | } 23 | 24 | public static boolean notSelf() { 25 | return getInstance().getBoolean("not_self", false); 26 | } 27 | 28 | public static boolean notWhisper() { 29 | return getInstance().getBoolean("not_whisper", false); 30 | } 31 | 32 | public static String notContains() { 33 | return getInstance().getString("not_contains", "").replace(",", ","); 34 | } 35 | 36 | public static boolean delay() { 37 | return getInstance().getBoolean("delay", false); 38 | } 39 | 40 | public static int delayMin() { 41 | return getInstance().getInt("delay_min", 0); 42 | } 43 | 44 | public static int delayMax() { 45 | return getInstance().getInt("delay_max", 0); 46 | } 47 | 48 | public static boolean receiveTransfer() { 49 | return getInstance().getBoolean("receive_transfer", true); 50 | } 51 | 52 | public static boolean quickOpen() { 53 | return getInstance().getBoolean("quick_open", true); 54 | } 55 | 56 | public static boolean showWechatId() { 57 | return getInstance().getBoolean("show_wechat_id", false); 58 | } 59 | 60 | public static String blackList() { 61 | return getInstance().getString("black_list", "").replace(",", ","); 62 | } 63 | 64 | public static boolean isAntiRevoke() { 65 | return getInstance().getBoolean("is_anti_revoke", false); 66 | } 67 | 68 | public static boolean isAntiSnsDelete() { 69 | return getInstance().getBoolean("is_anti_sns_delete", false); 70 | } 71 | 72 | public static boolean isADBlock() { 73 | return getInstance().getBoolean("is_ad_block", false); 74 | } 75 | 76 | public static boolean isAutoLogin() { 77 | return getInstance().getBoolean("is_auto_login", false); 78 | } 79 | 80 | public static boolean isBreakLimit() { 81 | return getInstance().getBoolean("is_break_limit", false); 82 | } 83 | 84 | } 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/RangePreference.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.preference.DialogPreference; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | 11 | public class RangePreference extends DialogPreference { 12 | 13 | private TextView startEditText, endEditText; 14 | 15 | private String startKey, endKey; 16 | 17 | public RangePreference(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | setDialogLayoutResource(R.layout.preference_range); 20 | 21 | 22 | for (int i = 0; i < attrs.getAttributeCount(); i++) { 23 | String attr = attrs.getAttributeName(i); 24 | if (attr.equalsIgnoreCase("start")) { 25 | startKey = attrs.getAttributeValue(i); 26 | } else if (attr.equalsIgnoreCase("end")) { 27 | endKey = attrs.getAttributeValue(i); 28 | } 29 | } 30 | } 31 | 32 | @Override 33 | protected void onBindDialogView(View view) { 34 | super.onBindDialogView(view); 35 | 36 | startEditText = (TextView) view.findViewById(R.id.min_value); 37 | endEditText = (TextView) view.findViewById(R.id.max_value); 38 | 39 | SharedPreferences pref = getSharedPreferences(); 40 | int startValue = pref.getInt(startKey, 0); 41 | int endValue = pref.getInt(endKey, 0); 42 | startEditText.setText(String.valueOf(startValue)); 43 | endEditText.setText(String.valueOf(endValue)); 44 | } 45 | 46 | @Override 47 | protected void onDialogClosed(boolean positiveResult) { 48 | if (positiveResult) { 49 | SharedPreferences.Editor editor = getEditor(); 50 | editor.putInt(startKey, Integer.parseInt(startEditText.getText().toString())); 51 | editor.putInt(endKey, Integer.parseInt(endEditText.getText().toString())); 52 | editor.commit(); 53 | } 54 | super.onDialogClosed(positiveResult); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement; 2 | 3 | 4 | import android.annotation.TargetApi; 5 | import android.app.Application; 6 | import android.app.FragmentManager; 7 | import android.app.ProgressDialog; 8 | import android.content.ComponentName; 9 | import android.content.Context; 10 | import android.content.Intent; 11 | import android.content.SharedPreferences; 12 | import android.content.pm.PackageInfo; 13 | import android.content.pm.PackageManager; 14 | import android.net.Uri; 15 | import android.os.Build; 16 | import android.os.Bundle; 17 | import android.os.Handler; 18 | import android.os.Looper; 19 | import android.preference.Preference; 20 | import android.preference.Preference.OnPreferenceClickListener; 21 | import android.preference.PreferenceFragment; 22 | import android.support.v7.app.AppCompatActivity; 23 | import android.widget.Toast; 24 | 25 | import com.google.gson.Gson; 26 | 27 | import net.androidwing.hotxposed.CommonUtils; 28 | 29 | import java.lang.reflect.Method; 30 | 31 | import dalvik.system.PathClassLoader; 32 | import me.firesun.wechat.enhancement.util.HookParams; 33 | import me.firesun.wechat.enhancement.util.SearchClasses; 34 | 35 | import static net.androidwing.hotxposed.CommonUtils.restartTargetApp; 36 | 37 | 38 | public class SettingsActivity extends AppCompatActivity { 39 | 40 | private SettingsFragment mSettingsFragment; 41 | 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_settings); 46 | if (savedInstanceState == null) { 47 | mSettingsFragment = new SettingsFragment(); 48 | replaceFragment(R.id.settings_container, mSettingsFragment); 49 | } 50 | 51 | } 52 | 53 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 54 | public void replaceFragment(int viewId, android.app.Fragment fragment) { 55 | FragmentManager fragmentManager = getFragmentManager(); 56 | fragmentManager.beginTransaction().replace(viewId, fragment).commit(); 57 | } 58 | 59 | /** 60 | * A placeholder fragment containing a settings view. 61 | */ 62 | public static class SettingsFragment extends PreferenceFragment { 63 | @Override 64 | public void onCreate(final Bundle savedInstanceState) { 65 | super.onCreate(savedInstanceState); 66 | 67 | getPreferenceManager().setSharedPreferencesMode(MODE_WORLD_READABLE); 68 | addPreferencesFromResource(R.xml.pref_setting); 69 | 70 | 71 | new CommonUtils().with(getActivity()).initAppPath(); 72 | 73 | restartTargetApp(HookParams.WECHAT_PACKAGE_NAME, "ui.LauncherUI"); 74 | 75 | Preference reset = findPreference("author"); 76 | reset.setOnPreferenceClickListener(new OnPreferenceClickListener() { 77 | @Override 78 | public boolean onPreferenceClick(Preference pref) { 79 | Intent intent = new Intent(); 80 | intent.setAction("android.intent.action.VIEW"); 81 | intent.setData(Uri.parse("https://github.com/firesunCN")); 82 | startActivity(intent); 83 | return true; 84 | } 85 | }); 86 | 87 | Preference show_icon = findPreference("show_icon"); 88 | show_icon.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { 89 | @Override 90 | public boolean onPreferenceChange(Preference preference, Object isChecked) { 91 | PackageManager packageManager = getActivity().getPackageManager(); 92 | int status = (boolean) isChecked ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 93 | packageManager.setComponentEnabledSetting(new ComponentName(getActivity(), "me.firesun.wechat.enhancement.SettingsActivity_Alias"), status, PackageManager.DONT_KILL_APP); 94 | return true; 95 | } 96 | }); 97 | 98 | Preference repair = findPreference("repair"); 99 | repair.setOnPreferenceClickListener(new OnPreferenceClickListener() { 100 | @Override 101 | public boolean onPreferenceClick(Preference pref) { 102 | Context context = getApplication(); 103 | if (context != null) { 104 | SharedPreferences.Editor editor = context.getSharedPreferences(HookParams.WECHAT_ENHANCEMENT_CONFIG_NAME, Context.MODE_WORLD_READABLE).edit(); 105 | editor.clear(); 106 | editor.commit(); 107 | Toast toast = Toast.makeText(context, getString(R.string.repair_done), Toast.LENGTH_SHORT); 108 | toast.show(); 109 | } 110 | return true; 111 | } 112 | }); 113 | 114 | Preference generate = findPreference("generate"); 115 | generate.setOnPreferenceClickListener(new OnPreferenceClickListener() { 116 | @Override 117 | public boolean onPreferenceClick(Preference pref) { 118 | final Context context = getApplication(); 119 | if (context == null) { 120 | return false; 121 | } 122 | final PackageManager packageManager = context.getPackageManager(); 123 | if (packageManager == null) { 124 | return false; 125 | } 126 | 127 | final ProgressDialog dialog = new ProgressDialog(getActivity()); 128 | dialog.setCancelable(false); 129 | dialog.setMessage(getResources().getString(R.string.generating)); 130 | dialog.show(); 131 | 132 | new Thread(new Runnable() { 133 | @Override 134 | public void run() { 135 | boolean success = false; 136 | try { 137 | PackageInfo packageInfo = packageManager.getPackageInfo(HookParams.WECHAT_PACKAGE_NAME, 0); 138 | String wechatApk = packageInfo.applicationInfo.sourceDir; 139 | PathClassLoader wxClassLoader = new PathClassLoader(wechatApk, ClassLoader.getSystemClassLoader()); 140 | SearchClasses.generateConfig(wechatApk, wxClassLoader, packageInfo.versionName); 141 | 142 | String config = new Gson().toJson(HookParams.getInstance()); 143 | SharedPreferences.Editor editor = context.getSharedPreferences(HookParams.WECHAT_ENHANCEMENT_CONFIG_NAME, Context.MODE_WORLD_READABLE).edit(); 144 | editor.clear(); 145 | editor.putString("params", config); 146 | editor.commit(); 147 | 148 | success = true; 149 | 150 | } catch (Throwable e) { 151 | e.printStackTrace(); 152 | } 153 | 154 | final String msg = getResources().getString(success ? R.string.generate_success : R.string.generate_failed); 155 | new Handler(Looper.getMainLooper()).post(new Runnable() { 156 | @Override 157 | public void run() { 158 | dialog.dismiss(); 159 | Toast.makeText(getApplication(), msg, Toast.LENGTH_SHORT).show(); 160 | } 161 | }); 162 | } 163 | }, "generate-config").start(); 164 | 165 | return true; 166 | } 167 | }); 168 | } 169 | 170 | private Application getApplication() { 171 | try { 172 | final Class activityThreadClass = 173 | Class.forName("android.app.ActivityThread"); 174 | final Method method = activityThreadClass.getMethod("currentApplication"); 175 | return (Application) method.invoke(null, (Object[]) null); 176 | } catch (Exception e) { 177 | } 178 | return null; 179 | } 180 | 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/plugin/ADBlock.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement.plugin; 2 | 3 | 4 | import de.robv.android.xposed.XC_MethodHook; 5 | import de.robv.android.xposed.XposedHelpers; 6 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 7 | import me.firesun.wechat.enhancement.PreferencesUtils; 8 | import me.firesun.wechat.enhancement.util.HookParams; 9 | 10 | 11 | public class ADBlock implements IPlugin { 12 | @Override 13 | public void hook(XC_LoadPackage.LoadPackageParam lpparam) { 14 | XposedHelpers.findAndHookMethod(HookParams.getInstance().XMLParserClassName, lpparam.classLoader, HookParams.getInstance().XMLParserMethod, String.class, String.class, new XC_MethodHook() { 15 | @Override 16 | protected void beforeHookedMethod(MethodHookParam param) { 17 | try { 18 | if (!PreferencesUtils.isADBlock()) 19 | return; 20 | 21 | if (param.args[1].equals("ADInfo")) 22 | param.setResult(null); 23 | } catch (Error | Exception e) { 24 | } 25 | 26 | } 27 | }); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/plugin/AntiRevoke.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement.plugin; 2 | 3 | import android.content.ContentValues; 4 | 5 | 6 | import java.io.File; 7 | import java.util.Arrays; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import de.robv.android.xposed.XC_MethodHook; 12 | import de.robv.android.xposed.XposedHelpers; 13 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 14 | import me.firesun.wechat.enhancement.PreferencesUtils; 15 | import me.firesun.wechat.enhancement.util.HookParams; 16 | 17 | 18 | public class AntiRevoke implements IPlugin { 19 | private static Map msgCacheMap = new HashMap<>(); 20 | private static Object storageInsertClazz; 21 | 22 | @Override 23 | public void hook(XC_LoadPackage.LoadPackageParam lpparam) { 24 | XposedHelpers.findAndHookMethod(HookParams.getInstance().SQLiteDatabaseClassName, lpparam.classLoader, HookParams.getInstance().SQLiteDatabaseUpdateMethod, String.class, ContentValues.class, String.class, String[].class, int.class, new XC_MethodHook() { 25 | @Override 26 | protected void beforeHookedMethod(MethodHookParam param) { 27 | try { 28 | if (!PreferencesUtils.isAntiRevoke()) { 29 | return; 30 | } 31 | 32 | if (param.args[0].equals("message")) { 33 | ContentValues contentValues = ((ContentValues) param.args[1]); 34 | 35 | if (contentValues.getAsInteger("type") == 10000 && 36 | !contentValues.getAsString("content").equals("你撤回了一条消息") && 37 | !contentValues.getAsString("content").equals("You've recalled a message") && 38 | !contentValues.getAsString("content").startsWith("> 7) > 0) { 134 | byte[] res = {(byte) (msgSize & 0x7F | 0x80), (byte) (msgSize >> 7)}; 135 | return res; 136 | } else { 137 | byte[] res = {(byte) msgSize}; 138 | return res; 139 | } 140 | } 141 | 142 | private byte[] concatAll(byte[] first, byte[]... rest) { 143 | int totalLength = first.length; 144 | for (byte[] array : rest) { 145 | totalLength += array.length; 146 | } 147 | byte[] result = Arrays.copyOf(first, totalLength); 148 | int offset = first.length; 149 | for (byte[] array : rest) { 150 | System.arraycopy(array, 0, result, offset, array.length); 151 | offset += array.length; 152 | } 153 | return result; 154 | } 155 | 156 | 157 | } 158 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/plugin/AutoLogin.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement.plugin; 2 | 3 | 4 | import android.app.Activity; 5 | import android.widget.Button; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | import de.robv.android.xposed.XC_MethodHook; 10 | import de.robv.android.xposed.XposedHelpers; 11 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 12 | import me.firesun.wechat.enhancement.PreferencesUtils; 13 | import me.firesun.wechat.enhancement.util.HookParams; 14 | 15 | 16 | public class AutoLogin implements IPlugin { 17 | @Override 18 | public void hook(XC_LoadPackage.LoadPackageParam lpparam) { 19 | XposedHelpers.findAndHookMethod(android.app.Activity.class, "onStart", new XC_MethodHook() { 20 | @Override 21 | protected void beforeHookedMethod(MethodHookParam param) { 22 | try { 23 | if (!PreferencesUtils.isAutoLogin()) 24 | return; 25 | if (!(param.thisObject instanceof Activity)) { 26 | return; 27 | } 28 | Activity activity = (Activity) param.thisObject; 29 | if (activity.getClass().getName().equals(HookParams.getInstance().WebWXLoginUIClassName)) { 30 | Class clazz = activity.getClass(); 31 | Field field = XposedHelpers.findFirstFieldByExactType(clazz, Button.class); 32 | Button button = (Button) field.get(activity); 33 | if (button != null) { 34 | button.performClick(); 35 | } 36 | } 37 | 38 | } catch (Error | Exception e) { 39 | } 40 | } 41 | }); 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/plugin/HideModule.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement.plugin; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.pm.PackageInfo; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import de.robv.android.xposed.XC_MethodHook; 11 | import de.robv.android.xposed.XposedHelpers; 12 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 13 | import me.firesun.wechat.enhancement.util.HookParams; 14 | 15 | 16 | public class HideModule implements IPlugin { 17 | @Override 18 | public void hook(XC_LoadPackage.LoadPackageParam lpparam) { 19 | XposedHelpers.findAndHookMethod("android.app.ApplicationPackageManager", lpparam.classLoader, "getInstalledApplications", int.class, new XC_MethodHook() { 20 | @Override 21 | protected void afterHookedMethod(MethodHookParam param) { 22 | try { 23 | List applicationList = (List) param.getResult(); 24 | List resultApplicationList = new ArrayList<>(); 25 | for (ApplicationInfo applicationInfo : applicationList) { 26 | String packageName = applicationInfo.packageName; 27 | if (!(packageName.contains("firesun") || packageName.contains("xposed"))) { 28 | 29 | resultApplicationList.add(applicationInfo); 30 | } 31 | } 32 | param.setResult(resultApplicationList); 33 | } catch (Error | Exception e) { 34 | } 35 | 36 | } 37 | }); 38 | 39 | XposedHelpers.findAndHookMethod("android.app.ApplicationPackageManager", lpparam.classLoader, "getInstalledPackages", int.class, new XC_MethodHook() { 40 | @Override 41 | protected void afterHookedMethod(MethodHookParam param) { 42 | try { 43 | List packageInfoList = (List) param.getResult(); 44 | List resultpackageInfoList = new ArrayList<>(); 45 | 46 | for (PackageInfo packageInfo : packageInfoList) { 47 | String packageName = packageInfo.packageName; 48 | if (!(packageName.contains("firesun") || packageName.contains("xposed"))) { 49 | resultpackageInfoList.add(packageInfo); 50 | } 51 | } 52 | param.setResult(resultpackageInfoList); 53 | } catch (Error | Exception e) { 54 | } 55 | } 56 | }); 57 | 58 | XposedHelpers.findAndHookMethod("android.app.ApplicationPackageManager", lpparam.classLoader, "getPackageInfo", String.class, int.class, new XC_MethodHook() { 59 | @Override 60 | protected void beforeHookedMethod(MethodHookParam param) { 61 | try { 62 | String packageName = (String) param.args[0]; 63 | if (packageName.contains("firesun") || packageName.contains("xposed")) { 64 | param.args[0] = HookParams.WECHAT_PACKAGE_NAME; 65 | } 66 | } catch (Error | Exception e) { 67 | } 68 | } 69 | }); 70 | 71 | XposedHelpers.findAndHookMethod("android.app.ApplicationPackageManager", lpparam.classLoader, "getApplicationInfo", String.class, int.class, new XC_MethodHook() { 72 | @Override 73 | protected void beforeHookedMethod(MethodHookParam param) { 74 | try { 75 | String packageName = (String) param.args[0]; 76 | if (packageName.contains("firesun") || packageName.contains("xposed")) { 77 | param.args[0] = HookParams.WECHAT_PACKAGE_NAME; 78 | } 79 | } catch (Error | Exception e) { 80 | } 81 | } 82 | }); 83 | 84 | XposedHelpers.findAndHookMethod("android.app.ActivityManager", lpparam.classLoader, "getRunningServices", int.class, new XC_MethodHook() { 85 | @Override 86 | protected void afterHookedMethod(MethodHookParam param) { 87 | try { 88 | List serviceInfoList = (List) param.getResult(); 89 | List resultList = new ArrayList<>(); 90 | 91 | for (ActivityManager.RunningServiceInfo runningServiceInfo : serviceInfoList) { 92 | String serviceName = runningServiceInfo.process; 93 | if (!(serviceName.contains("firesun") || serviceName.contains("xposed"))) { 94 | resultList.add(runningServiceInfo); 95 | } 96 | } 97 | param.setResult(resultList); 98 | } catch (Error | Exception e) { 99 | } 100 | } 101 | }); 102 | 103 | XposedHelpers.findAndHookMethod("android.app.ActivityManager", lpparam.classLoader, "getRunningTasks", int.class, new XC_MethodHook() { 104 | @Override 105 | protected void afterHookedMethod(MethodHookParam param) { 106 | try { 107 | List serviceInfoList = (List) param.getResult(); 108 | List resultList = new ArrayList<>(); 109 | 110 | for (ActivityManager.RunningTaskInfo runningTaskInfo : serviceInfoList) { 111 | String taskName = runningTaskInfo.baseActivity.flattenToString(); 112 | if (!(taskName.contains("firesun") || taskName.contains("xposed"))) { 113 | resultList.add(runningTaskInfo); 114 | } 115 | } 116 | param.setResult(resultList); 117 | } catch (Error | Exception e) { 118 | } 119 | } 120 | }); 121 | 122 | XposedHelpers.findAndHookMethod("android.app.ActivityManager", lpparam.classLoader, "getRunningAppProcesses", new XC_MethodHook() { 123 | @Override 124 | protected void afterHookedMethod(MethodHookParam param) { 125 | try { 126 | List runningAppProcessInfos = (List) param.getResult(); 127 | List resultList = new ArrayList<>(); 128 | 129 | for (ActivityManager.RunningAppProcessInfo runningAppProcessInfo : runningAppProcessInfos) { 130 | String processName = runningAppProcessInfo.processName; 131 | if (!(processName.contains("firesun") || processName.contains("xposed"))) { 132 | resultList.add(runningAppProcessInfo); 133 | } 134 | } 135 | param.setResult(resultList); 136 | } catch (Error | Exception e) { 137 | } 138 | } 139 | }); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/plugin/IPlugin.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement.plugin; 2 | 3 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 4 | 5 | public interface IPlugin { 6 | public void hook(XC_LoadPackage.LoadPackageParam lpparam); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/plugin/Limits.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement.plugin; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.graphics.Color; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import android.widget.CheckBox; 10 | import android.widget.CompoundButton; 11 | import android.widget.HeaderViewListAdapter; 12 | import android.widget.LinearLayout; 13 | import android.widget.LinearLayout.LayoutParams; 14 | import android.widget.ListAdapter; 15 | import android.widget.ListView; 16 | import android.widget.TextView; 17 | 18 | import java.lang.reflect.Field; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import de.robv.android.xposed.XC_MethodHook; 23 | import de.robv.android.xposed.XposedHelpers; 24 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 25 | import me.firesun.wechat.enhancement.PreferencesUtils; 26 | import me.firesun.wechat.enhancement.util.HookParams; 27 | 28 | import static de.robv.android.xposed.XposedBridge.log; 29 | 30 | 31 | public class Limits implements IPlugin { 32 | @Override 33 | public void hook(XC_LoadPackage.LoadPackageParam lpparam) { 34 | 35 | XposedHelpers.findAndHookMethod(android.app.Activity.class, "onCreate", android.os.Bundle.class, new XC_MethodHook() { 36 | @Override 37 | protected void beforeHookedMethod(MethodHookParam param) { 38 | try { 39 | if (!PreferencesUtils.isBreakLimit()) 40 | return; 41 | Activity activity = (Activity) param.thisObject; 42 | String className = activity.getClass().getName(); 43 | if (className.equals(HookParams.getInstance().AlbumPreviewUIClassName)) { 44 | Intent intent = activity.getIntent(); 45 | if (intent == null) { 46 | return; 47 | } 48 | int oldLimit = intent.getIntExtra("max_select_count", 9); 49 | int newLimit = 1000; 50 | if (oldLimit <= 9) { 51 | intent.putExtra("max_select_count", oldLimit + newLimit - 9); 52 | } 53 | } 54 | 55 | if (className.equals(HookParams.getInstance().SelectContactUIClassName)) { 56 | Intent intent = activity.getIntent(); 57 | if (intent == null) { 58 | return; 59 | } 60 | 61 | if (intent.getIntExtra("max_limit_num", -1) == 9) { 62 | intent.putExtra("max_limit_num", Integer.MAX_VALUE); 63 | } 64 | } 65 | 66 | } catch (Error | Exception e) { 67 | log("error:" + e); 68 | } 69 | } 70 | }); 71 | 72 | XposedHelpers.findAndHookMethod(HookParams.getInstance().MMActivityClassName, lpparam.classLoader, "onCreateOptionsMenu", Menu.class, new XC_MethodHook() { 73 | @Override 74 | protected void afterHookedMethod(MethodHookParam param) { 75 | 76 | try { 77 | if (!PreferencesUtils.isBreakLimit()) 78 | return; 79 | 80 | 81 | final Activity activity = (Activity) param.thisObject; 82 | Menu menu = (Menu) param.args[0]; 83 | if (!activity.getClass().getName().equals(HookParams.getInstance().SelectContactUIClassName)) { 84 | return; 85 | } 86 | 87 | Intent intent = activity.getIntent(); 88 | if (intent == null) 89 | return; 90 | boolean checked = intent.getBooleanExtra("select_all_checked", false); 91 | 92 | MenuItem selectAll = menu.add(0, 2, 0, ""); 93 | selectAll.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 94 | TextView btnText = new TextView(activity); 95 | btnText.setTextColor(Color.WHITE); 96 | btnText.setText("全选"); 97 | 98 | LayoutParams layoutParams = new LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 99 | LinearLayout.LayoutParams.WRAP_CONTENT); 100 | Context context = btnText.getContext(); 101 | float scale = context.getResources().getDisplayMetrics().density; 102 | layoutParams.setMarginEnd((int) ((float) 4 * scale + 0.5F)); 103 | btnText.setLayoutParams(layoutParams); 104 | 105 | CheckBox btnCheckbox = new CheckBox(activity); 106 | btnCheckbox.setChecked(checked); 107 | btnCheckbox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() { 108 | @Override 109 | public void onCheckedChanged(CompoundButton buttonView, 110 | boolean isChecked) { 111 | onSelectContactUISelectAll(activity, isChecked); 112 | } 113 | 114 | }); 115 | 116 | layoutParams = new LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 117 | LinearLayout.LayoutParams.WRAP_CONTENT); 118 | 119 | context = btnCheckbox.getContext(); 120 | scale = context.getResources().getDisplayMetrics().density; 121 | layoutParams.setMarginEnd((int) ((float) 6 * scale + 0.5F)); 122 | btnCheckbox.setLayoutParams(layoutParams); 123 | 124 | 125 | LinearLayout actionView = new LinearLayout(activity); 126 | actionView.addView(btnText); 127 | actionView.addView(btnCheckbox); 128 | actionView.setOrientation(LinearLayout.HORIZONTAL); 129 | selectAll.setActionView(actionView); 130 | } catch (Error | Exception e) { 131 | } 132 | } 133 | }); 134 | 135 | XposedHelpers.findAndHookMethod(HookParams.getInstance().SelectContactUIClassName, lpparam.classLoader, "onActivityResult", int.class, int.class, Intent.class, new XC_MethodHook() { 136 | @Override 137 | protected void beforeHookedMethod(MethodHookParam param) { 138 | try { 139 | if (!PreferencesUtils.isBreakLimit()) 140 | return; 141 | int requestCode = (int) param.args[0]; 142 | int resultCode = (int) param.args[1]; 143 | Intent data = (Intent) param.args[2]; 144 | 145 | if (requestCode == 5) { 146 | Activity activity = (Activity) param.thisObject; 147 | activity.setResult(resultCode, data); 148 | activity.finish(); 149 | param.setResult(null); 150 | } 151 | } catch (Error | Exception e) { 152 | } 153 | } 154 | }); 155 | 156 | XposedHelpers.findAndHookMethod(HookParams.getInstance().SelectConversationUIClassName, lpparam.classLoader, HookParams.getInstance().SelectConversationUICheckLimitMethod, boolean.class, new XC_MethodHook() { 157 | @Override 158 | protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) { 159 | try { 160 | if (!PreferencesUtils.isBreakLimit()) 161 | return; 162 | param.setResult(false); 163 | } catch (Error | Exception e) { 164 | 165 | } 166 | 167 | 168 | } 169 | }); 170 | } 171 | 172 | private final void onSelectContactUISelectAll(Activity activity, boolean isChecked) { 173 | try { 174 | Intent intent = activity.getIntent(); 175 | if (intent == null) { 176 | return; 177 | } 178 | intent.putExtra("select_all_checked", isChecked); 179 | intent.putExtra("already_select_contact", ""); 180 | if (isChecked) { 181 | ListView listView = (ListView) XposedHelpers.findFirstFieldByExactType(activity.getClass(), ListView.class).get(activity); 182 | if (listView == null) { 183 | return; 184 | } 185 | 186 | ListAdapter adapter = listView.getAdapter(); 187 | if (adapter == null) { 188 | return; 189 | } 190 | 191 | adapter = ((HeaderViewListAdapter) adapter).getWrappedAdapter(); 192 | 193 | Field contactField = null; 194 | Field usernameField = null; 195 | List userList = new ArrayList(); 196 | 197 | for (int i = 0; i < adapter.getCount(); ++i) { 198 | Object item = adapter.getItem(i); 199 | if (contactField == null) { 200 | Field[] fileds = item.getClass().getFields(); 201 | 202 | for (int j = 0; j < fileds.length; j++) { 203 | Field field = fileds[j]; 204 | field.getType().getName(); 205 | if (field.getType().getName().equals(HookParams.getInstance().ContactInfoClassName)) { 206 | contactField = field; 207 | break; 208 | } 209 | } 210 | 211 | if (contactField == null) { 212 | continue; 213 | } 214 | } 215 | 216 | Object contact = contactField.get(item); 217 | if (contact != null) { 218 | if (usernameField == null) { 219 | Field[] fields = contact.getClass().getFields(); 220 | 221 | for (int j = 0; j < fields.length; j++) { 222 | Field field = fields[j]; 223 | if (field.getName().equals("field_username")) { 224 | usernameField = field; 225 | } 226 | } 227 | 228 | if (usernameField == null) { 229 | continue; 230 | } 231 | } 232 | 233 | Object username = usernameField.get(contact); 234 | if (username != null) { 235 | userList.add((String) username); 236 | } 237 | } 238 | } 239 | intent.putExtra("already_select_contact", stringJoin(",", userList)); 240 | } 241 | activity.startActivityForResult(intent, 5); 242 | } catch (Error | Exception e) { 243 | } 244 | 245 | } 246 | 247 | private static String stringJoin(String join, List strAry) { 248 | StringBuffer sb = new StringBuffer(); 249 | for (int i = 0; i < strAry.size(); i++) { 250 | if (i == (strAry.size() - 1)) { 251 | sb.append(strAry.get(i)); 252 | } else { 253 | sb.append(strAry.get(i)).append(join); 254 | } 255 | } 256 | 257 | return new String(sb); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/plugin/LuckMoney.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement.plugin; 2 | 3 | import android.app.Activity; 4 | import android.content.ClipboardManager; 5 | import android.content.ContentValues; 6 | import android.content.Context; 7 | import android.content.pm.PackageManager; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.text.TextUtils; 11 | import android.widget.Button; 12 | import android.widget.Toast; 13 | 14 | import org.json.JSONException; 15 | import org.json.JSONObject; 16 | import org.xmlpull.v1.XmlPullParserException; 17 | 18 | import java.io.IOException; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import de.robv.android.xposed.XC_MethodHook; 23 | import de.robv.android.xposed.XposedHelpers; 24 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 25 | import me.firesun.wechat.enhancement.PreferencesUtils; 26 | import me.firesun.wechat.enhancement.util.HookParams; 27 | import me.firesun.wechat.enhancement.util.XmlToJson; 28 | 29 | import static android.text.TextUtils.isEmpty; 30 | import static android.widget.Toast.LENGTH_LONG; 31 | import static de.robv.android.xposed.XposedHelpers.callMethod; 32 | import static de.robv.android.xposed.XposedHelpers.callStaticMethod; 33 | import static de.robv.android.xposed.XposedHelpers.findFirstFieldByExactType; 34 | import static de.robv.android.xposed.XposedHelpers.newInstance; 35 | 36 | 37 | public class LuckMoney implements IPlugin { 38 | private static Object requestCaller; 39 | private static List luckyMoneyMessages = new ArrayList<>(); 40 | 41 | @Override 42 | public void hook(final XC_LoadPackage.LoadPackageParam lpparam) { 43 | XposedHelpers.findAndHookMethod(HookParams.getInstance().SQLiteDatabaseClassName, lpparam.classLoader, HookParams.getInstance().SQLiteDatabaseInsertMethod, String.class, String.class, ContentValues.class, new XC_MethodHook() { 44 | @Override 45 | protected void afterHookedMethod(MethodHookParam param) { 46 | try { 47 | ContentValues contentValues = (ContentValues) param.args[2]; 48 | String tableName = (String) param.args[0]; 49 | if (TextUtils.isEmpty(tableName) || !tableName.equals("message")) { 50 | return; 51 | } 52 | Integer type = contentValues.getAsInteger("type"); 53 | if (null == type) { 54 | return; 55 | } 56 | if (type == 436207665 || type == 469762097) { 57 | handleLuckyMoney(contentValues, lpparam); 58 | } else if (type == 419430449) { 59 | handleTransfer(contentValues, lpparam); 60 | } 61 | } catch (Error | Exception e) { 62 | } 63 | } 64 | }); 65 | 66 | XposedHelpers.findAndHookMethod(HookParams.getInstance().ReceiveLuckyMoneyRequestClassName, lpparam.classLoader, HookParams.getInstance().ReceiveLuckyMoneyRequestMethod, int.class, String.class, JSONObject.class, new XC_MethodHook() { 67 | @Override 68 | protected void beforeHookedMethod(MethodHookParam param) { 69 | try { 70 | if (!HookParams.getInstance().hasTimingIdentifier) { 71 | return; 72 | } 73 | 74 | if (luckyMoneyMessages.size() <= 0) { 75 | return; 76 | } 77 | 78 | String timingIdentifier = ((JSONObject) (param.args[2])).getString("timingIdentifier"); 79 | if (isEmpty(timingIdentifier)) { 80 | return; 81 | } 82 | LuckyMoneyMessage luckyMoneyMessage = luckyMoneyMessages.get(0); 83 | 84 | Class luckyMoneyRequestClass = XposedHelpers.findClass(HookParams.getInstance().LuckyMoneyRequestClassName, lpparam.classLoader); 85 | Object luckyMoneyRequest = newInstance(luckyMoneyRequestClass, 86 | luckyMoneyMessage.getMsgType(), luckyMoneyMessage.getChannelId(), luckyMoneyMessage.getSendId(), luckyMoneyMessage.getNativeUrlString(), "", "", luckyMoneyMessage.getTalker(), "v1.0", timingIdentifier); 87 | callMethod(requestCaller, HookParams.getInstance().RequestCallerMethod, luckyMoneyRequest, getDelayTime()); 88 | luckyMoneyMessages.remove(0); 89 | 90 | } catch (Error | Exception e) { 91 | } 92 | } 93 | }); 94 | 95 | Class receiveUIParamNameClass = XposedHelpers.findClass(HookParams.getInstance().ReceiveUIParamNameClassName, lpparam.classLoader); 96 | XposedHelpers.findAndHookMethod(HookParams.getInstance().LuckyMoneyReceiveUIClassName, lpparam.classLoader, HookParams.getInstance().ReceiveUIMethod, int.class, int.class, String.class, receiveUIParamNameClass, new XC_MethodHook() { 97 | @Override 98 | protected void afterHookedMethod(MethodHookParam param) { 99 | try { 100 | if (PreferencesUtils.quickOpen()) { 101 | Button button = (Button) findFirstFieldByExactType(param.thisObject.getClass(), Button.class).get(param.thisObject); 102 | if (button.isShown() && button.isClickable()) { 103 | button.performClick(); 104 | } 105 | } 106 | } catch (Error | Exception e) { 107 | } 108 | } 109 | }); 110 | 111 | XposedHelpers.findAndHookMethod(HookParams.getInstance().ContactInfoUIClassName, lpparam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() { 112 | @Override 113 | protected void afterHookedMethod(MethodHookParam param) { 114 | try { 115 | if (PreferencesUtils.showWechatId()) { 116 | Activity activity = (Activity) param.thisObject; 117 | ClipboardManager cmb = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); 118 | String wechatId = activity.getIntent().getStringExtra("Contact_User"); 119 | cmb.setText(wechatId); 120 | Toast.makeText(activity, "微信ID:" + wechatId + "已复制到剪切板", LENGTH_LONG).show(); 121 | } 122 | } catch (Error | Exception e) { 123 | } 124 | } 125 | }); 126 | 127 | XposedHelpers.findAndHookMethod(HookParams.getInstance().ChatroomInfoUIClassName, lpparam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() { 128 | @Override 129 | protected void afterHookedMethod(MethodHookParam param) { 130 | try { 131 | if (PreferencesUtils.showWechatId()) { 132 | Activity activity = (Activity) param.thisObject; 133 | String wechatId = activity.getIntent().getStringExtra("RoomInfo_Id"); 134 | ClipboardManager cmb = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); 135 | cmb.setText(wechatId); 136 | Toast.makeText(activity, "微信ID:" + wechatId + "已复制到剪切板", LENGTH_LONG).show(); 137 | } 138 | } catch (Error | Exception e) { 139 | } 140 | } 141 | }); 142 | 143 | } 144 | 145 | private void handleLuckyMoney(ContentValues contentValues, XC_LoadPackage.LoadPackageParam lpparam) throws XmlPullParserException, IOException, JSONException { 146 | if (!PreferencesUtils.open()) { 147 | return; 148 | } 149 | 150 | int status = contentValues.getAsInteger("status"); 151 | if (status == 4) { 152 | return; 153 | } 154 | 155 | String talker = contentValues.getAsString("talker"); 156 | 157 | String blackList = PreferencesUtils.blackList(); 158 | if (!isEmpty(blackList)) { 159 | for (String wechatId : blackList.split(",")) { 160 | if (talker.equals(wechatId.trim())) { 161 | return; 162 | } 163 | } 164 | } 165 | 166 | int isSend = contentValues.getAsInteger("isSend"); 167 | if (PreferencesUtils.notSelf() && isSend != 0) { 168 | return; 169 | } 170 | 171 | if (PreferencesUtils.notWhisper() && !isGroupTalk(talker)) { 172 | return; 173 | } 174 | 175 | if (!isGroupTalk(talker) && isSend != 0) { 176 | return; 177 | } 178 | 179 | String content = contentValues.getAsString("content"); 180 | if (!content.startsWith(" list = Arrays.asList(XposedHelpers.findMethodsByExactParameters(clazz, returnType, (Class[]) Arrays.copyOf(parameterTypes, parameterTypes.length))); 52 | List list = Arrays.asList(findMethodsByExactParametersInner(clazz, returnType, (Class[]) Arrays.copyOf(parameterTypes, parameterTypes.length))); 53 | if (list.isEmpty()) 54 | return null; 55 | else if (list.size() > 1) { 56 | log("find too many methods"); 57 | for (int i = 0; i < list.size(); i++) { 58 | log("methods" + i + ": " + list.get(i)); 59 | } 60 | } 61 | 62 | return list.get(0); 63 | } 64 | 65 | public static final String getClassName(DexClass clazz) { 66 | String str = clazz.getClassType().replace('/', '.'); 67 | return str.substring(1, str.length() - 1); 68 | } 69 | 70 | public static Classes findClassesFromPackage(ClassLoader loader, List classes, String packageName, int depth) { 71 | if (classesCache.containsKey(packageName + depth)) { 72 | return (Classes) classesCache.get(packageName + depth); 73 | } 74 | 75 | List classNameList = new ArrayList(); 76 | for (int i = 0; i < classes.size(); i++) { 77 | String clazz = classes.get(i); 78 | String currentPackage = clazz.substring(0, clazz.lastIndexOf(".")); 79 | for (int j = 0; j < depth; j++) { 80 | int pos = currentPackage.lastIndexOf("."); 81 | if (pos < 0) 82 | break; 83 | currentPackage = currentPackage.substring(0, currentPackage.lastIndexOf(".")); 84 | } 85 | if (currentPackage.equals(packageName)) { 86 | classNameList.add(clazz); 87 | } 88 | } 89 | List classList = new ArrayList(); 90 | for (int i = 0; i < classNameList.size(); i++) { 91 | String className = classNameList.get(i); 92 | Class c = findClassIfExists(className, loader); 93 | if (c != null) { 94 | classList.add(c); 95 | } 96 | } 97 | Classes cs = new Classes(classList); 98 | classesCache.put(packageName + depth, cs); 99 | return cs; 100 | } 101 | 102 | private static Map methodCache = new HashMap<>(); 103 | 104 | private static String getParametersString(Class... clazzes) { 105 | StringBuilder sb = new StringBuilder("("); 106 | boolean first = true; 107 | for (Class clazz : clazzes) { 108 | if (first) 109 | first = false; 110 | else 111 | sb.append(","); 112 | 113 | if (clazz != null) 114 | sb.append(clazz.getCanonicalName()); 115 | else 116 | sb.append("null"); 117 | } 118 | sb.append(")"); 119 | return sb.toString(); 120 | } 121 | 122 | public static Method findMethodExact(Class clazz, String methodName, Class... parameterTypes) { 123 | String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#exact"; 124 | 125 | if (methodCache.containsKey(fullMethodName)) { 126 | Method method = methodCache.get(fullMethodName); 127 | if (method == null) 128 | throw new NoSuchMethodError(fullMethodName); 129 | return method; 130 | } 131 | 132 | try { 133 | Method method = clazz.getDeclaredMethod(methodName, parameterTypes); 134 | method.setAccessible(true); 135 | methodCache.put(fullMethodName, method); 136 | return method; 137 | } catch (NoSuchMethodException e) { 138 | methodCache.put(fullMethodName, null); 139 | throw new NoSuchMethodError(fullMethodName); 140 | } 141 | } 142 | 143 | public static Method[] findMethodsByExactParametersInner(Class clazz, Class returnType, Class... parameterTypes) { 144 | List result = new LinkedList<>(); 145 | for (Method method : clazz.getDeclaredMethods()) { 146 | if (returnType != null && returnType != method.getReturnType()) 147 | continue; 148 | 149 | Class[] methodParameterTypes = method.getParameterTypes(); 150 | if (parameterTypes.length != methodParameterTypes.length) 151 | continue; 152 | 153 | boolean match = true; 154 | for (int i = 0; i < parameterTypes.length; i++) { 155 | if (parameterTypes[i] != methodParameterTypes[i]) { 156 | match = false; 157 | break; 158 | } 159 | } 160 | 161 | if (!match) 162 | continue; 163 | 164 | method.setAccessible(true); 165 | result.add(method); 166 | } 167 | return result.toArray(new Method[result.size()]); 168 | } 169 | 170 | public static final Method findMethodExactIfExists(Class clazz, String methodName, Class... parameterTypes) { 171 | Method method = null; 172 | try { 173 | method = findMethodExact(clazz, methodName, (Class[]) Arrays.copyOf(parameterTypes, parameterTypes.length)); 174 | } catch (Error | Exception e) { 175 | } 176 | return method; 177 | } 178 | 179 | public static final Class findClassIfExists(String className, ClassLoader classLoader) { 180 | Class c = null; 181 | try { 182 | c = Class.forName(className, false, classLoader); 183 | } catch (Error | Exception e) { 184 | } 185 | return c; 186 | } 187 | 188 | public static final Field findFieldIfExists(Class clazz, String fieldName) { 189 | if (clazz == null) { 190 | return null; 191 | } 192 | Field field = null; 193 | try { 194 | field = clazz.getField(fieldName); 195 | } catch (Error | Exception e) { 196 | } 197 | return field; 198 | } 199 | 200 | public static final List findFieldsWithType(Class clazz, String typeName) { 201 | List list = new ArrayList(); 202 | if (clazz == null) { 203 | return list; 204 | } 205 | Field[] fields = clazz.getDeclaredFields(); 206 | if (fields != null) { 207 | for (int i = 0; i < fields.length; i++) { 208 | Field field = fields[i]; 209 | Class fieldType = field.getType(); 210 | if (fieldType.getName().equals(typeName)) { 211 | list.add(field); 212 | } 213 | } 214 | } 215 | return list; 216 | } 217 | 218 | public static final class Classes { 219 | private final List classes; 220 | 221 | public Classes(List list) { 222 | this.classes = (List) list; 223 | } 224 | 225 | public final Classes filterByNoMethod(Class cls, Class... clsArr) { 226 | List arrayList = new ArrayList(); 227 | for (Object next : this.classes) { 228 | if (ReflectionUtil.findMethodsByExactParameters((Class) next, cls, (Class[]) Arrays.copyOf(clsArr, clsArr.length)) == null) { 229 | arrayList.add(next); 230 | } 231 | } 232 | return new Classes(arrayList); 233 | } 234 | 235 | public final Classes filterByMethod(Class cls, Class... clsArr) { 236 | List arrayList = new ArrayList(); 237 | for (Object next : this.classes) { 238 | if (ReflectionUtil.findMethodsByExactParameters((Class) next, cls, (Class[]) Arrays.copyOf(clsArr, clsArr.length)) != null) { 239 | arrayList.add(next); 240 | } 241 | } 242 | 243 | return new Classes(arrayList); 244 | } 245 | 246 | public final Classes filterByNoField(String fieldType) { 247 | List arrayList = new ArrayList(); 248 | for (Object next : this.classes) { 249 | if (ReflectionUtil.findFieldsWithType((Class) next, fieldType).isEmpty()) { 250 | arrayList.add(next); 251 | } 252 | } 253 | 254 | return new Classes(arrayList); 255 | } 256 | 257 | public final Classes filterByField(String fieldType) { 258 | List arrayList = new ArrayList(); 259 | for (Object next : this.classes) { 260 | if (!ReflectionUtil.findFieldsWithType((Class) next, fieldType).isEmpty()) { 261 | 262 | arrayList.add(next); 263 | } 264 | } 265 | 266 | return new Classes(arrayList); 267 | } 268 | 269 | public final Classes filterByField(String fieldName, String fieldType) { 270 | List arrayList = new ArrayList(); 271 | for (Object next : this.classes) { 272 | Field field = ReflectionUtil.findFieldIfExists((Class) next, fieldName); 273 | 274 | if (field != null && field.getType().getCanonicalName().equals(fieldType)) { 275 | arrayList.add(next); 276 | } 277 | } 278 | 279 | 280 | return new Classes(arrayList); 281 | } 282 | 283 | public final Classes filterByMethod(Class returnType, String methodName, Class... parameterTypes) { 284 | List arrayList = new ArrayList(); 285 | for (Object next : this.classes) { 286 | Method method = ReflectionUtil.findMethodExactIfExists((Class) next, methodName, (Class[]) Arrays.copyOf(parameterTypes, parameterTypes.length)); 287 | if (method != null && method.getReturnType().getName().equals(returnType.getName())) { 288 | arrayList.add(next); 289 | } 290 | } 291 | 292 | return new Classes(arrayList); 293 | } 294 | 295 | public final Class firstOrNull() { 296 | if (this.classes.isEmpty()) 297 | return null; 298 | else if (this.classes.size() > 1) { 299 | log("find too many classes"); 300 | for (int i = 0; i < this.classes.size(); i++) { 301 | log("class" + i + ": " + this.classes.get(i)); 302 | } 303 | } 304 | return this.classes.get(0); 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/util/SearchClasses.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement.util; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | 7 | import com.google.gson.Gson; 8 | 9 | import net.dongliu.apk.parser.ApkFile; 10 | import net.dongliu.apk.parser.bean.DexClass; 11 | 12 | import org.json.JSONObject; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | import de.robv.android.xposed.XSharedPreferences; 19 | import de.robv.android.xposed.XposedHelpers; 20 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 21 | import me.firesun.wechat.enhancement.Main; 22 | 23 | import static me.firesun.wechat.enhancement.util.ReflectionUtil.log; 24 | 25 | public class SearchClasses { 26 | private static List wxClasses = new ArrayList<>(); 27 | private static XSharedPreferences preferencesInstance = null; 28 | 29 | public static void init(Context context, XC_LoadPackage.LoadPackageParam lparam, String versionName) { 30 | 31 | if (loadConfig(lparam, versionName)) 32 | return; 33 | 34 | log("failed to load config, start finding..."); 35 | 36 | generateConfig(lparam.appInfo.sourceDir, lparam.classLoader, versionName); 37 | 38 | saveConfig(context); 39 | } 40 | 41 | public static void generateConfig(String wechatApk, ClassLoader classLoader, String versionName) { 42 | 43 | HookParams hp = HookParams.getInstance(); 44 | hp.versionName = versionName; 45 | hp.versionCode = HookParams.VERSION_CODE; 46 | int versionNum = getVersionNum(versionName); 47 | if (versionNum >= getVersionNum("6.5.6") && versionNum <= getVersionNum("6.5.23")) 48 | hp.LuckyMoneyReceiveUIClassName = "com.tencent.mm.plugin.luckymoney.ui.En_fba4b94f"; 49 | if (versionNum < getVersionNum("6.5.8")) 50 | hp.SQLiteDatabaseClassName = "com.tencent.mmdb.database.SQLiteDatabase"; 51 | if (versionNum < getVersionNum("6.5.4")) 52 | hp.hasTimingIdentifier = false; 53 | if (versionNum >= getVersionNum("7.0.0")) 54 | hp.LuckyMoneyReceiveUIClassName = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI"; 55 | if (versionNum >= getVersionNum("7.0.0")) 56 | hp.ChatroomInfoUIClassName = "com.tencent.mm.chatroom.ui.ChatroomInfoUI"; 57 | 58 | ApkFile apkFile = null; 59 | try { 60 | apkFile = new ApkFile(wechatApk); 61 | DexClass[] dexClasses = apkFile.getDexClasses(); 62 | 63 | wxClasses.clear(); 64 | 65 | for (int i = 0; i < dexClasses.length; i++) { 66 | wxClasses.add(ReflectionUtil.getClassName(dexClasses[i])); 67 | } 68 | } catch (Error | Exception e) { 69 | log("Open ApkFile Failed!"); 70 | } finally { 71 | try { 72 | apkFile.close(); 73 | } catch (Exception e) { 74 | log("Close ApkFile Failed!"); 75 | } 76 | } 77 | 78 | //LuckMoney 79 | try { 80 | Class ReceiveUIParamNameClass = ReflectionUtil.findClassesFromPackage(classLoader, wxClasses, "com.tencent.mm", 1) 81 | .filterByMethod(String.class, "getInfo") 82 | .filterByMethod(int.class, "getType") 83 | .filterByMethod(void.class, "reset") 84 | .firstOrNull(); 85 | hp.ReceiveUIParamNameClassName = ReceiveUIParamNameClass.getName(); 86 | 87 | Class RequestCallerClass = ReflectionUtil.findClassesFromPackage(classLoader, wxClasses, "com.tencent.mm", 1) 88 | .filterByField("foreground", "boolean") 89 | .filterByMethod(void.class, int.class, String.class, int.class, boolean.class) 90 | .filterByMethod(void.class, "cancel", int.class) 91 | .filterByMethod(void.class, "reset") 92 | .firstOrNull(); 93 | hp.RequestCallerClassName = RequestCallerClass.getName(); 94 | 95 | hp.RequestCallerMethod = ReflectionUtil.findMethodsByExactParameters(RequestCallerClass, 96 | void.class, RequestCallerClass, int.class) 97 | .getName(); 98 | 99 | Class NetworkRequestClass = ReflectionUtil.findClassesFromPackage(classLoader, wxClasses, "com.tencent.mm", 1) 100 | .filterByMethod(void.class, "unhold") 101 | .filterByMethod(RequestCallerClass) 102 | .firstOrNull(); 103 | hp.NetworkRequestClassName = NetworkRequestClass.getName(); 104 | 105 | hp.GetNetworkByModelMethod = ReflectionUtil.findMethodsByExactParameters(NetworkRequestClass, 106 | RequestCallerClass) 107 | .getName(); 108 | 109 | Class ReceiveLuckyMoneyRequestClass = ReflectionUtil.findClassesFromPackage(classLoader, wxClasses, "com.tencent.mm.plugin.luckymoney", 1) 110 | .filterByField("msgType", "int") 111 | .filterByMethod(void.class, int.class, String.class, JSONObject.class) 112 | .firstOrNull(); 113 | hp.ReceiveLuckyMoneyRequestClassName = ReceiveLuckyMoneyRequestClass.getName(); 114 | 115 | hp.ReceiveLuckyMoneyRequestMethod = ReflectionUtil.findMethodsByExactParameters(ReceiveLuckyMoneyRequestClass, 116 | void.class, int.class, String.class, JSONObject.class) 117 | .getName(); 118 | 119 | hp.LuckyMoneyRequestClassName = ReflectionUtil.findClassesFromPackage(classLoader, wxClasses, "com.tencent.mm.plugin.luckymoney", 1) 120 | .filterByField("talker", "java.lang.String") 121 | .filterByMethod(void.class, int.class, String.class, JSONObject.class) 122 | .filterByMethod(int.class, "getType") 123 | .filterByNoMethod(boolean.class) 124 | .firstOrNull() 125 | .getName(); 126 | 127 | hp.GetTransferRequestClassName = ReflectionUtil.findClassesFromPackage(classLoader, wxClasses, "com.tencent.mm.plugin.remittance", 1) 128 | .filterByField("java.lang.String") 129 | .filterByNoField("int") 130 | 131 | .filterByMethod(void.class, int.class, String.class, JSONObject.class) 132 | .filterByMethod(String.class, "getUri") 133 | .firstOrNull() 134 | .getName(); 135 | 136 | Class LuckyMoneyReceiveUIClass = ReflectionUtil.findClassIfExists(hp.LuckyMoneyReceiveUIClassName, classLoader); 137 | hp.ReceiveUIMethod = ReflectionUtil.findMethodsByExactParameters(LuckyMoneyReceiveUIClass, 138 | boolean.class, int.class, int.class, String.class, ReceiveUIParamNameClass) 139 | .getName(); 140 | 141 | } catch (Error | Exception e) { 142 | log("Search LuckMoney Classes Failed!"); 143 | throw e; 144 | } 145 | 146 | //ADBlock 147 | try { 148 | Class XMLParserClass = ReflectionUtil.findClassesFromPackage(classLoader, wxClasses, "com.tencent.mm.sdk.platformtools", 0) 149 | .filterByMethod(Map.class, String.class, String.class) 150 | .firstOrNull(); 151 | hp.XMLParserClassName = XMLParserClass.getName(); 152 | 153 | hp.XMLParserMethod = ReflectionUtil.findMethodsByExactParameters(XMLParserClass, Map.class, String.class, String.class) 154 | .getName(); 155 | } catch (Error | Exception e) { 156 | log("Search LuckMoney Classes Failed!"); 157 | } 158 | 159 | 160 | //AntiRevoke 161 | try { 162 | ReflectionUtil.Classes storageClasses = ReflectionUtil.findClassesFromPackage(classLoader, wxClasses, "com.tencent.mm.storage", 0); 163 | Class MsgInfoClass = storageClasses 164 | .filterByMethod(boolean.class, "isSystem") 165 | .firstOrNull(); 166 | hp.MsgInfoClassName = MsgInfoClass.getName(); 167 | if (versionNum < getVersionNum("6.5.8")) { 168 | Class MsgInfoStorageClass = storageClasses 169 | .filterByMethod(long.class, MsgInfoClass) 170 | .firstOrNull(); 171 | hp.MsgInfoStorageClassName = MsgInfoStorageClass.getName(); 172 | hp.MsgInfoStorageInsertMethod = ReflectionUtil.findMethodsByExactParameters(MsgInfoStorageClass, long.class, MsgInfoClass) 173 | .getName(); 174 | } else { 175 | Class MsgInfoStorageClass = storageClasses 176 | .filterByMethod(long.class, MsgInfoClass, boolean.class) 177 | .firstOrNull(); 178 | hp.MsgInfoStorageClassName = MsgInfoStorageClass.getName(); 179 | hp.MsgInfoStorageInsertMethod = ReflectionUtil.findMethodsByExactParameters(MsgInfoStorageClass, long.class, MsgInfoClass, boolean.class) 180 | .getName(); 181 | } 182 | } catch (Error | Exception e) { 183 | log("Search AntiRevoke Classes Failed!"); 184 | } 185 | 186 | 187 | //Photo Limits 188 | try { 189 | Class SelectConversationUIClass = XposedHelpers.findClass(hp.SelectConversationUIClassName, classLoader); 190 | hp.SelectConversationUICheckLimitMethod = ReflectionUtil.findMethodsByExactParameters(SelectConversationUIClass, 191 | boolean.class, boolean.class) 192 | .getName(); 193 | 194 | hp.ContactInfoClassName = ReflectionUtil.findClassesFromPackage(classLoader, wxClasses, "com.tencent.mm.storage", 0) 195 | .filterByMethod(String.class, "getCityCode") 196 | .filterByMethod(String.class, "getCountryCode") 197 | .firstOrNull() 198 | .getName(); 199 | 200 | } catch (Error | Exception e) { 201 | log("Search Photo Limits Classes Failed!"); 202 | } 203 | } 204 | 205 | private static int getVersionNum(String version) { 206 | String[] v = version.split("\\."); 207 | if (v.length == 3) 208 | return Integer.valueOf(v[0]) * 100 * 100 + Integer.valueOf(v[1]) * 100 + Integer.valueOf(v[2]); 209 | else 210 | return 0; 211 | } 212 | 213 | private static boolean loadConfig(XC_LoadPackage.LoadPackageParam lpparam, String curVersionName) { 214 | try { 215 | SharedPreferences pref = getPreferencesInstance(); 216 | HookParams hp = new Gson().fromJson(pref.getString("params", ""), HookParams.class); 217 | 218 | if (hp == null 219 | || !hp.versionName.equals(curVersionName) 220 | || hp.versionCode != HookParams.VERSION_CODE) { 221 | return false; 222 | } 223 | 224 | HookParams.setInstance(hp); 225 | log("load config successful"); 226 | return true; 227 | } catch (Error | Exception e) { 228 | log("load config failed!"); 229 | } 230 | return false; 231 | } 232 | 233 | private static void saveConfig(Context context) { 234 | try { 235 | Intent saveConfigIntent = new Intent(); 236 | saveConfigIntent.setAction(HookParams.SAVE_WECHAT_ENHANCEMENT_CONFIG); 237 | saveConfigIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); 238 | saveConfigIntent.putExtra("params", new Gson().toJson(HookParams.getInstance())); 239 | context.sendBroadcast(saveConfigIntent); 240 | log("saving config..."); 241 | } catch (Error | Exception e) { 242 | log("saving config failed!"); 243 | } 244 | } 245 | 246 | private static XSharedPreferences getPreferencesInstance() { 247 | if (preferencesInstance == null) { 248 | preferencesInstance = new XSharedPreferences(Main.class.getPackage().getName(), HookParams.WECHAT_ENHANCEMENT_CONFIG_NAME); 249 | preferencesInstance.makeWorldReadable(); 250 | } else { 251 | preferencesInstance.reload(); 252 | } 253 | return preferencesInstance; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/util/Tag.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | 6 | public class Tag { 7 | 8 | private String mPath; 9 | private String mName; 10 | private ArrayList mChildren = new ArrayList<>(); 11 | private String mContent; 12 | 13 | Tag(String path, String name) { 14 | mPath = path; 15 | mName = name; 16 | } 17 | 18 | void addChild(Tag tag) { 19 | mChildren.add(tag); 20 | } 21 | 22 | String getName() { 23 | return mName; 24 | } 25 | 26 | String getContent() { 27 | return mContent; 28 | } 29 | 30 | void setContent(String content) { 31 | boolean hasContent = false; 32 | if (content != null) { 33 | for (int i = 0; i < content.length(); ++i) { 34 | char c = content.charAt(i); 35 | if ((c != ' ') && (c != '\n')) { 36 | hasContent = true; 37 | break; 38 | } 39 | } 40 | } 41 | if (hasContent) { 42 | mContent = content; 43 | } 44 | } 45 | 46 | ArrayList getChildren() { 47 | return mChildren; 48 | } 49 | 50 | boolean hasChildren() { 51 | return (mChildren.size() > 0); 52 | } 53 | 54 | int getChildrenCount() { 55 | return mChildren.size(); 56 | } 57 | 58 | Tag getChild(int index) { 59 | if ((index >= 0) && (index < mChildren.size())) { 60 | return mChildren.get(index); 61 | } 62 | return null; 63 | } 64 | 65 | HashMap> getGroupedElements() { 66 | HashMap> groups = new HashMap<>(); 67 | for (Tag child : mChildren) { 68 | String key = child.getName(); 69 | ArrayList group = groups.get(key); 70 | if (group == null) { 71 | group = new ArrayList<>(); 72 | groups.put(key, group); 73 | } 74 | group.add(child); 75 | } 76 | return groups; 77 | } 78 | 79 | String getPath() { 80 | return mPath; 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return "Tag: " + mName + ", " + mChildren.size() + " children, Content: " + mContent; 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/me/firesun/wechat/enhancement/util/XmlToJson.java: -------------------------------------------------------------------------------- 1 | package me.firesun.wechat.enhancement.util; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.util.Log; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | import org.xmlpull.v1.XmlPullParser; 11 | import org.xmlpull.v1.XmlPullParserException; 12 | import org.xmlpull.v1.XmlPullParserFactory; 13 | 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.StringReader; 17 | import java.util.ArrayList; 18 | import java.util.HashMap; 19 | import java.util.HashSet; 20 | import java.util.Iterator; 21 | import java.util.regex.Matcher; 22 | 23 | /** 24 | * Converts XML to JSON 25 | */ 26 | 27 | public class XmlToJson { 28 | 29 | private static final String TAG = "XmlToJson"; 30 | private static final String DEFAULT_CONTENT_NAME = "content"; 31 | private static final String DEFAULT_ENCODING = "utf-8"; 32 | private static final String DEFAULT_INDENTATION = " "; 33 | // default values when a Tag is empty 34 | private static final String DEFAULT_EMPTY_STRING = ""; 35 | private static final int DEFAULT_EMPTY_INTEGER = 0; 36 | private static final long DEFAULT_EMPTY_LONG = 0; 37 | private static final double DEFAULT_EMPTY_DOUBLE = 0; 38 | private static final boolean DEFAULT_EMPTY_BOOLEAN = false; 39 | private String mIndentationPattern = DEFAULT_INDENTATION; 40 | private StringReader mStringSource; 41 | private InputStream mInputStreamSource; 42 | private String mInputEncoding; 43 | private HashSet mForceListPaths; 44 | private HashMap mAttributeNameReplacements; 45 | private HashMap mContentNameReplacements; 46 | private HashMap mForceClassForPath; 47 | private HashSet mSkippedAttributes = new HashSet<>(); 48 | private HashSet mSkippedTags = new HashSet<>(); 49 | private JSONObject mJsonObject; // Used for caching the result 50 | private XmlToJson(Builder builder) { 51 | mStringSource = builder.mStringSource; 52 | mInputEncoding = builder.mInputEncoding; 53 | mForceListPaths = builder.mForceListPaths; 54 | mAttributeNameReplacements = builder.mAttributeNameReplacements; 55 | mContentNameReplacements = builder.mContentNameReplacements; 56 | mForceClassForPath = builder.mForceClassForPath; 57 | mSkippedAttributes = builder.mSkippedAttributes; 58 | mSkippedTags = builder.mSkippedTags; 59 | 60 | mJsonObject = convertToJSONObject(); // Build now so that the InputStream can be closed just after 61 | } 62 | 63 | private 64 | @Nullable 65 | JSONObject convertToJSONObject() { 66 | try { 67 | Tag parentTag = new Tag("", "xml"); 68 | 69 | XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 70 | factory.setNamespaceAware(false); // tags with namespace are taken as-is ("namespace:tagname") 71 | XmlPullParser xpp = factory.newPullParser(); 72 | 73 | setInput(xpp); 74 | 75 | int eventType = xpp.getEventType(); 76 | while (eventType != XmlPullParser.START_DOCUMENT) { 77 | eventType = xpp.next(); 78 | } 79 | readTags(parentTag, xpp); 80 | 81 | unsetInput(); 82 | 83 | return convertTagToJson(parentTag, false); 84 | } catch (XmlPullParserException | IOException e) { 85 | e.printStackTrace(); 86 | return null; 87 | } 88 | } 89 | 90 | private void setInput(XmlPullParser xpp) { 91 | if (mStringSource != null) { 92 | try { 93 | xpp.setInput(mStringSource); 94 | } catch (XmlPullParserException e) { 95 | e.printStackTrace(); 96 | } 97 | } else { 98 | try { 99 | xpp.setInput(mInputStreamSource, mInputEncoding); 100 | } catch (XmlPullParserException e) { 101 | e.printStackTrace(); 102 | } 103 | } 104 | } 105 | 106 | private void unsetInput() { 107 | if (mStringSource != null) { 108 | mStringSource.close(); 109 | } 110 | // else the InputStream has been given by the user, it is not our role to close it 111 | } 112 | 113 | private void readTags(Tag parent, XmlPullParser xpp) { 114 | try { 115 | int eventType; 116 | do { 117 | eventType = xpp.next(); 118 | if (eventType == XmlPullParser.START_TAG) { 119 | String tagName = xpp.getName(); 120 | String path = parent.getPath() + "/" + tagName; 121 | 122 | boolean skipTag = mSkippedTags.contains(path); 123 | 124 | Tag child = new Tag(path, tagName); 125 | if (!skipTag) { 126 | parent.addChild(child); 127 | } 128 | 129 | // Attributes are taken into account as key/values in the child 130 | int attrCount = xpp.getAttributeCount(); 131 | for (int i = 0; i < attrCount; ++i) { 132 | String attrName = xpp.getAttributeName(i); 133 | String attrValue = xpp.getAttributeValue(i); 134 | String attrPath = parent.getPath() + "/" + child.getName() + "/" + attrName; 135 | 136 | // Skip Attributes 137 | if (mSkippedAttributes.contains(attrPath)) { 138 | continue; 139 | } 140 | 141 | attrName = getAttributeNameReplacement(attrPath, attrName); 142 | Tag attribute = new Tag(attrPath, attrName); 143 | attribute.setContent(attrValue); 144 | child.addChild(attribute); 145 | } 146 | 147 | readTags(child, xpp); 148 | } else if (eventType == XmlPullParser.TEXT) { 149 | String text = xpp.getText(); 150 | parent.setContent(text); 151 | } else if (eventType == XmlPullParser.END_TAG) { 152 | return; 153 | } else { 154 | Log.i(TAG, "unknown xml eventType " + eventType); 155 | } 156 | } while (eventType != XmlPullParser.END_DOCUMENT); 157 | } catch (XmlPullParserException | IOException | NullPointerException e) { 158 | e.printStackTrace(); 159 | } 160 | } 161 | 162 | private JSONObject convertTagToJson(Tag tag, boolean isListElement) { 163 | JSONObject json = new JSONObject(); 164 | 165 | // Content is injected as a key/value 166 | if (tag.getContent() != null) { 167 | String path = tag.getPath(); 168 | String name = getContentNameReplacement(path, DEFAULT_CONTENT_NAME); 169 | putContent(path, json, name, tag.getContent()); 170 | } 171 | 172 | try { 173 | 174 | HashMap> groups = tag.getGroupedElements(); // groups by tag names so that we can detect lists or single elements 175 | for (ArrayList group : groups.values()) { 176 | 177 | if (group.size() == 1) { // element, or list of 1 178 | Tag child = group.get(0); 179 | if (isForcedList(child)) { // list of 1 180 | JSONArray list = new JSONArray(); 181 | list.put(convertTagToJson(child, true)); 182 | String childrenNames = child.getName(); 183 | json.put(childrenNames, list); 184 | } else { // stand alone element 185 | if (child.hasChildren()) { 186 | JSONObject jsonChild = convertTagToJson(child, false); 187 | json.put(child.getName(), jsonChild); 188 | } else { 189 | String path = child.getPath(); 190 | putContent(path, json, child.getName(), child.getContent()); 191 | } 192 | } 193 | } else { // list 194 | JSONArray list = new JSONArray(); 195 | for (Tag child : group) { 196 | list.put(convertTagToJson(child, true)); 197 | } 198 | String childrenNames = group.get(0).getName(); 199 | json.put(childrenNames, list); 200 | } 201 | } 202 | return json; 203 | 204 | } catch (JSONException e) { 205 | e.printStackTrace(); 206 | } 207 | return null; 208 | } 209 | 210 | private void putContent(String path, JSONObject json, String tag, String content) { 211 | try { 212 | // checks if the user wants to force a class (Int, Double... for a given path) 213 | Class forcedClass = mForceClassForPath.get(path); 214 | if (forcedClass == null) { // default behaviour, put it as a String 215 | if (content == null) { 216 | content = DEFAULT_EMPTY_STRING; 217 | } 218 | json.put(tag, content); 219 | } else { 220 | if (forcedClass == Integer.class) { 221 | try { 222 | Integer number = Integer.parseInt(content); 223 | json.put(tag, number); 224 | } catch (NumberFormatException exception) { 225 | json.put(tag, DEFAULT_EMPTY_INTEGER); 226 | } 227 | } else if (forcedClass == Long.class) { 228 | try { 229 | Long number = Long.parseLong(content); 230 | json.put(tag, number); 231 | } catch (NumberFormatException exception) { 232 | json.put(tag, DEFAULT_EMPTY_LONG); 233 | } 234 | } else if (forcedClass == Double.class) { 235 | try { 236 | Double number = Double.parseDouble(content); 237 | json.put(tag, number); 238 | } catch (NumberFormatException exception) { 239 | json.put(tag, DEFAULT_EMPTY_DOUBLE); 240 | } 241 | } else if (forcedClass == Boolean.class) { 242 | if (content == null) { 243 | json.put(tag, DEFAULT_EMPTY_BOOLEAN); 244 | } else if (content.equalsIgnoreCase("true")) { 245 | json.put(tag, true); 246 | } else if (content.equalsIgnoreCase("false")) { 247 | json.put(tag, false); 248 | } else { 249 | json.put(tag, DEFAULT_EMPTY_BOOLEAN); 250 | } 251 | } 252 | } 253 | 254 | } catch (JSONException exception) { 255 | // keep continue in case of error 256 | } 257 | } 258 | 259 | private boolean isForcedList(Tag tag) { 260 | String path = tag.getPath(); 261 | return mForceListPaths.contains(path); 262 | } 263 | 264 | private String getAttributeNameReplacement(String path, String defaultValue) { 265 | String result = mAttributeNameReplacements.get(path); 266 | if (result != null) { 267 | return result; 268 | } 269 | return defaultValue; 270 | } 271 | 272 | private String getContentNameReplacement(String path, String defaultValue) { 273 | String result = mContentNameReplacements.get(path); 274 | if (result != null) { 275 | return result; 276 | } 277 | return defaultValue; 278 | } 279 | 280 | @Override 281 | public String toString() { 282 | if (mJsonObject != null) { 283 | return mJsonObject.toString(); 284 | } 285 | return null; 286 | } 287 | 288 | /** 289 | * Format the Json with indentation and line breaks. 290 | * Uses the last intendation pattern used, or the default one (3 spaces) 291 | * 292 | * @return the Builder 293 | */ 294 | public String toFormattedString() { 295 | if (mJsonObject != null) { 296 | String indent = ""; 297 | StringBuilder builder = new StringBuilder(); 298 | builder.append("{\n"); 299 | format(mJsonObject, builder, indent); 300 | builder.append("}\n"); 301 | return builder.toString(); 302 | } 303 | return null; 304 | } 305 | 306 | private void format(JSONObject jsonObject, StringBuilder builder, String indent) { 307 | Iterator keys = jsonObject.keys(); 308 | while (keys.hasNext()) { 309 | String key = keys.next(); 310 | builder.append(indent); 311 | builder.append(mIndentationPattern); 312 | builder.append("\""); 313 | builder.append(key); 314 | builder.append("\": "); 315 | Object value = jsonObject.opt(key); 316 | if (value instanceof JSONObject) { 317 | JSONObject child = (JSONObject) value; 318 | builder.append(indent); 319 | builder.append("{\n"); 320 | format(child, builder, indent + mIndentationPattern); 321 | builder.append(indent); 322 | builder.append(mIndentationPattern); 323 | builder.append("}"); 324 | } else if (value instanceof JSONArray) { 325 | JSONArray array = (JSONArray) value; 326 | formatArray(array, builder, indent + mIndentationPattern); 327 | } else { 328 | formatValue(value, builder); 329 | } 330 | if (keys.hasNext()) { 331 | builder.append(",\n"); 332 | } else { 333 | builder.append("\n"); 334 | } 335 | } 336 | } 337 | 338 | private void formatArray(JSONArray array, StringBuilder builder, String indent) { 339 | builder.append("[\n"); 340 | 341 | for (int i = 0; i < array.length(); ++i) { 342 | Object element = array.opt(i); 343 | if (element instanceof JSONObject) { 344 | JSONObject child = (JSONObject) element; 345 | builder.append(indent); 346 | builder.append(mIndentationPattern); 347 | builder.append("{\n"); 348 | format(child, builder, indent + mIndentationPattern); 349 | builder.append(indent); 350 | builder.append(mIndentationPattern); 351 | builder.append("}"); 352 | } else if (element instanceof JSONArray) { 353 | JSONArray child = (JSONArray) element; 354 | formatArray(child, builder, indent + mIndentationPattern); 355 | } else { 356 | formatValue(element, builder); 357 | } 358 | if (i < array.length() - 1) { 359 | builder.append(","); 360 | } 361 | builder.append("\n"); 362 | } 363 | builder.append(indent); 364 | builder.append("]"); 365 | } 366 | 367 | private void formatValue(Object value, StringBuilder builder) { 368 | if (value instanceof String) { 369 | String string = (String) value; 370 | 371 | // Escape special characters 372 | string = string.replaceAll("\\\\", "\\\\\\\\"); // escape backslash 373 | string = string.replaceAll("\"", Matcher.quoteReplacement("\\\"")); // escape double quotes 374 | string = string.replaceAll("/", "\\\\/"); // escape slash 375 | string = string.replaceAll("\n", "\\\\n").replaceAll("\t", "\\\\t"); // escape \n and \t 376 | 377 | builder.append("\""); 378 | builder.append(string); 379 | builder.append("\""); 380 | } else if (value instanceof Long) { 381 | Long longValue = (Long) value; 382 | builder.append(longValue); 383 | } else if (value instanceof Integer) { 384 | Integer intValue = (Integer) value; 385 | builder.append(intValue); 386 | } else if (value instanceof Boolean) { 387 | Boolean bool = (Boolean) value; 388 | builder.append(bool); 389 | } else if (value instanceof Double) { 390 | Double db = (Double) value; 391 | builder.append(db); 392 | } else { 393 | builder.append(value.toString()); 394 | } 395 | } 396 | 397 | public static class Builder { 398 | 399 | private StringReader mStringSource; 400 | private String mInputEncoding = DEFAULT_ENCODING; 401 | private HashSet mForceListPaths = new HashSet<>(); 402 | private HashMap mAttributeNameReplacements = new HashMap<>(); 403 | private HashMap mContentNameReplacements = new HashMap<>(); 404 | private HashMap mForceClassForPath = new HashMap<>(); // Integer, Long, Double, Boolean 405 | private HashSet mSkippedAttributes = new HashSet<>(); 406 | private HashSet mSkippedTags = new HashSet<>(); 407 | 408 | /** 409 | * Constructor 410 | * 411 | * @param xmlSource XML source 412 | */ 413 | public Builder(@NonNull String xmlSource) { 414 | mStringSource = new StringReader(xmlSource); 415 | } 416 | 417 | /** 418 | * Creates the XmlToJson object 419 | */ 420 | public JSONObject build() { 421 | try { 422 | return new JSONObject(new XmlToJson(this).toString()); 423 | } catch (JSONException e) { 424 | e.printStackTrace(); 425 | return null; 426 | } 427 | } 428 | } 429 | 430 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preference_range.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 20 | 21 | 28 | 29 | 30 | 41 | 42 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #009df1 4 | #0079f1 5 | #0059b0 6 | #238cf4 7 | #FFF 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 微信增强 3 | 抢红包,防撤回,去广告 4 | 抢红包 5 | 自己发的不抢 6 | 延时抢红包 7 | 延时时长 8 | 私聊不抢 9 | 快速拆包 10 | 自动接收转账 11 | 显示微信ID 12 | 延迟0毫秒拆开红包 13 | 设置 14 | 红包设置 15 | 设置 16 | 防撤回设置 17 | 增强功能 18 | 过滤朋友圈广告 19 | 电脑微信自动登录 20 | 突破9张图片限制 21 | 消息防撤回 22 | 朋友圈防删除 23 | 显示图标 24 | 尝试修复插件 25 | 生成本机配置 26 | 修复完成,请重启微信 27 | 关于 28 | 作者 29 | 致谢 30 | 微信捐赠 31 | 支付宝捐赠 32 | 包含关键字不抢 33 | 请输入关键字, 以,分隔 34 | 包含微信ID不抢 35 | 请输入微信ID, 以,分隔 36 | 生成配置成功 37 | 生成配置失败 38 | 正在生成配置… 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/values/style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/xml/pref_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 18 | 22 | 23 | 28 | 29 | 34 | 35 | 40 | 41 | 46 | 47 | 53 | 54 | 59 | 60 | 65 | 66 | 67 | 71 | 72 | 76 | 77 | 78 | 79 | 83 | 87 | 88 | 89 | 90 | 94 | 98 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 112 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter { url "https://jcenter.bintray.com/" } 6 | maven { url 'https://maven.aliyun.com/mvn/repository/' } 7 | google() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.1.0' 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | google() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## Project-wide Gradle settings. 2 | # 3 | # For more details on how to configure your build environment visit 4 | # http://www.gradle.org/docs/current/userguide/build_environment.html 5 | # 6 | # Specifies the JVM arguments used for the daemon process. 7 | # The setting is particularly useful for tweaking memory settings. 8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | #Tue Jul 04 22:35:43 CST 2017 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Apr 01 20:09:28 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /hotxposed/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /hotxposed/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | defaultConfig { 6 | minSdkVersion 15 7 | targetSdkVersion 23 8 | versionCode 1 9 | versionName "1.0" 10 | 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | compileOnly 'de.robv.android.xposed:api:82' 25 | implementation 'com.android.support:support-annotations:28.0.0' 26 | 27 | } 28 | -------------------------------------------------------------------------------- /hotxposed/libs/lite-orm-1.9.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/hotxposed/libs/lite-orm-1.9.2.jar -------------------------------------------------------------------------------- /hotxposed/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /hotxposed/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.util.Log; 6 | 7 | import static android.content.Context.MODE_WORLD_READABLE; 8 | 9 | public class CommonUtils { 10 | private Context activity; 11 | private static CommonUtils commonUtils; 12 | 13 | public CommonUtils with(Context activity) { 14 | this.activity = activity; 15 | return this; 16 | } 17 | 18 | public void initAppPath() { 19 | String preferenceName = activity.getPackageName() + "_preferences"; 20 | 21 | activity.getSharedPreferences(preferenceName, MODE_WORLD_READABLE) 22 | .edit() 23 | .putString("not_contains", activity.getApplicationContext().getPackageResourcePath()) 24 | .apply(); 25 | } 26 | 27 | 28 | public static void restartTargetApp(String packageName, String startActivity) { 29 | ShellUtil.execCommand("am force-stop " + packageName, true); 30 | String startCommand = String.format("am start -n \"%s/%s.%s\"", packageName, packageName, startActivity); 31 | Log.d(packageName, startCommand); 32 | ShellUtil.execCommand(startCommand, true); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/HotXposed.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed; 2 | 3 | import dalvik.system.PathClassLoader; 4 | import de.robv.android.xposed.XC_MethodHook; 5 | import de.robv.android.xposed.XposedBridge; 6 | import de.robv.android.xposed.XposedHelpers; 7 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 8 | 9 | import java.io.File; 10 | import java.io.FileInputStream; 11 | import java.io.FileNotFoundException; 12 | import java.io.IOException; 13 | 14 | /** 15 | * Created on 2018/3/30. 16 | */ 17 | public class HotXposed { 18 | 19 | public static void hook(Class clazz, XC_LoadPackage.LoadPackageParam lpparam) { 20 | String packageName = clazz.getName().replace("." + clazz.getSimpleName(), ""); 21 | 22 | String appPath = getApkPath(packageName); 23 | XposedBridge.log("ysnows: " + lpparam.packageName + ": " + appPath); 24 | 25 | if (appPath != null) { 26 | 27 | PathClassLoader classLoader = 28 | new PathClassLoader(appPath, lpparam.getClass().getClassLoader()); 29 | 30 | try { 31 | XposedHelpers.callMethod(classLoader.loadClass(clazz.getName()).newInstance(), "dispatch", lpparam); 32 | } catch (InstantiationException e) { 33 | e.printStackTrace(); 34 | } catch (IllegalAccessException e) { 35 | e.printStackTrace(); 36 | } catch (ClassNotFoundException e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | } 41 | 42 | 43 | private static String getApkPath(final String packageName) { 44 | // ShellUtil.CommandResult commandResult = ShellUtil.execCommand("pm path " + BuildConfig.APPLICATION_ID, false); 45 | // String tos = commandResult.successMsg.substring(8, commandResult.successMsg.length() - 1); 46 | 47 | String notContains = PreferencesUtils.notContains(packageName); 48 | return notContains; 49 | } 50 | 51 | 52 | private static File[] getApkFile(final String packageName) { 53 | final File file = new File("/sdcard/apppath.txt"); 54 | 55 | // File[] list = file.listFiles(new FilenameFilter() { 56 | // @Override 57 | // public boolean accept(File dir, String filename) { 58 | //// XposedBridge.log("list:" + filename); 59 | // 60 | //// XposedBridge.log("packageName:" + packageName); 61 | // 62 | // boolean contains = filename.contains(packageName); 63 | //// XposedBridge.log("contains:" + contains); 64 | // 65 | // XposedBridge.log("list: " + filename + " packageName: " + packageName + " contains: " + contains); 66 | // 67 | // return contains; 68 | // } 69 | // }); 70 | // XposedBridge.log("reallist: " + list.toString() + list.length); 71 | 72 | 73 | File[] list = new File[]{}; 74 | try { 75 | FileInputStream fileInputStream = new FileInputStream(file); 76 | byte[] bytes = new byte[100000]; 77 | int read = fileInputStream.read(bytes); 78 | String toString = bytes.toString(); 79 | XposedBridge.log(toString); 80 | 81 | } catch (FileNotFoundException e) { 82 | e.printStackTrace(); 83 | } catch (IOException e) { 84 | e.printStackTrace(); 85 | } 86 | 87 | return list; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/IHookerDispatcher.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed; 2 | 3 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 4 | 5 | /** 6 | * Created on 2018/3/30. 7 | */ 8 | public interface IHookerDispatcher { 9 | void dispatch(XC_LoadPackage.LoadPackageParam loadPackageParam); 10 | } 11 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/PreferencesUtils.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed; 2 | 3 | 4 | import de.robv.android.xposed.XSharedPreferences; 5 | import de.robv.android.xposed.XposedBridge; 6 | 7 | public class PreferencesUtils { 8 | 9 | private static XSharedPreferences instance = null; 10 | 11 | // private static XSharedPreferences getInstance() { 12 | // if (instance == null) { 13 | // String packageName = PreferencesUtils.class.getPackage().getName(); 14 | // XposedBridge.log("preference: " + packageName); 15 | // instance = new XSharedPreferences(packageName); 16 | // instance.makeWorldReadable(); 17 | // } else { 18 | // instance.reload(); 19 | // } 20 | // return instance; 21 | // } 22 | 23 | private static XSharedPreferences getInstance(String packageName) { 24 | if (instance == null) { 25 | XposedBridge.log("preference: " + packageName); 26 | instance = new XSharedPreferences(packageName); 27 | instance.makeWorldReadable(); 28 | } else { 29 | instance.reload(); 30 | } 31 | return instance; 32 | } 33 | 34 | // public static boolean open() { 35 | // return getInstance().getBoolean("open", false); 36 | // } 37 | // 38 | // public static boolean notSelf() { 39 | // return getInstance().getBoolean("not_self", false); 40 | // } 41 | // 42 | // public static boolean notWhisper() { 43 | // return getInstance().getBoolean("not_whisper", false); 44 | // } 45 | 46 | // public static String notContains() { 47 | // return getInstance().getString("not_contains", "").replace(",", ","); 48 | // } 49 | 50 | public static String notContains(String packageName) { 51 | return getInstance(packageName).getString("not_contains", "").replace(",", ","); 52 | } 53 | 54 | // public static boolean delay() { 55 | // return getInstance().getBoolean("delay", false); 56 | // } 57 | // 58 | // public static int delayMin() { 59 | // return getInstance().getInt("delay_min", 0); 60 | // } 61 | // 62 | // public static int delayMax() { 63 | // return getInstance().getInt("delay_max", 0); 64 | // } 65 | // 66 | // public static boolean receiveTransfer() { 67 | // return getInstance().getBoolean("receive_transfer", true); 68 | // } 69 | // 70 | // public static boolean quickOpen() { 71 | // return getInstance().getBoolean("quick_open", true); 72 | // } 73 | // 74 | // public static boolean showWechatId() { 75 | // return getInstance().getBoolean("show_wechat_id", false); 76 | // } 77 | // 78 | // public static String blackList() { 79 | // return getInstance().getString("black_list", "").replace(",", ","); 80 | // } 81 | // 82 | // public static boolean isAntiRevoke() { 83 | // return getInstance().getBoolean("is_anti_revoke", false); 84 | // } 85 | // 86 | // public static boolean isAntiSnsDelete() { 87 | // return getInstance().getBoolean("is_anti_sns_delete", false); 88 | // } 89 | // 90 | // public static boolean isADBlock() { 91 | // return getInstance().getBoolean("is_ad_block", false); 92 | // } 93 | // 94 | // public static boolean isAutoLogin() { 95 | // return getInstance().getBoolean("is_auto_login", false); 96 | // } 97 | // 98 | // public static boolean isBreakLimit() { 99 | // return getInstance().getBoolean("is_break_limit", false); 100 | // } 101 | 102 | } 103 | 104 | 105 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/ShellUtil.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed; 2 | import java.io.BufferedReader; 3 | import java.io.DataOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.util.List; 7 | 8 | /** 9 | * ShellUtil 10 | *
    11 | * Check root 12 | *
  • {@link ShellUtil#checkRootPermission()}
  • 13 | *
14 | *
    15 | * Execte command 16 | *
  • {@link ShellUtil#execCommand(String, boolean)}
  • 17 | *
  • {@link ShellUtil#execCommand(String, boolean, boolean)}
  • 18 | *
  • {@link ShellUtil#execCommand(List, boolean)}
  • 19 | *
  • {@link ShellUtil#execCommand(List, boolean, boolean)}
  • 20 | *
  • {@link ShellUtil#execCommand(String[], boolean)}
  • 21 | *
  • {@link ShellUtil#execCommand(String[], boolean, boolean)}
  • 22 | *
23 | * 24 | * @author Trinea 2013-5-16 25 | */ 26 | public class ShellUtil { 27 | 28 | public static final String COMMAND_SU = "su"; 29 | public static final String COMMAND_SH = "sh"; 30 | public static final String COMMAND_EXIT = "exit\n"; 31 | public static final String COMMAND_LINE_END = "\n"; 32 | 33 | private ShellUtil() { 34 | throw new AssertionError(); 35 | } 36 | 37 | /** 38 | * check whether has root permission 39 | */ 40 | public static boolean checkRootPermission() { 41 | return execCommand("echo root", true, false).result == 0; 42 | } 43 | 44 | /** 45 | * execute shell command, default return result msg 46 | * 47 | * @param command command 48 | * @param isRoot whether need to run with root 49 | * @see ShellUtil#execCommand(String[], boolean, boolean) 50 | */ 51 | public static CommandResult execCommand(String command, boolean isRoot) { 52 | return execCommand(new String[] { command }, isRoot, true); 53 | } 54 | 55 | /** 56 | * execute shell commands, default return result msg 57 | * 58 | * @param commands command list 59 | * @param isRoot whether need to run with root 60 | * @see ShellUtil#execCommand(String[], boolean, boolean) 61 | */ 62 | public static CommandResult execCommand(List commands, boolean isRoot) { 63 | return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, true); 64 | } 65 | 66 | /** 67 | * execute shell commands, default return result msg 68 | * 69 | * @param commands command array 70 | * @param isRoot whether need to run with root 71 | * @see ShellUtil#execCommand(String[], boolean, boolean) 72 | */ 73 | public static CommandResult execCommand(String[] commands, boolean isRoot) { 74 | return execCommand(commands, isRoot, true); 75 | } 76 | 77 | /** 78 | * execute shell command 79 | * 80 | * @param command command 81 | * @param isRoot whether need to run with root 82 | * @param isNeedResultMsg whether need result msg 83 | * @see ShellUtil#execCommand(String[], boolean, boolean) 84 | */ 85 | public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) { 86 | return execCommand(new String[] { command }, isRoot, isNeedResultMsg); 87 | } 88 | 89 | /** 90 | * execute shell commands 91 | * 92 | * @param commands command list 93 | * @param isRoot whether need to run with root 94 | * @param isNeedResultMsg whether need result msg 95 | * @see ShellUtil#execCommand(String[], boolean, boolean) 96 | */ 97 | public static CommandResult execCommand(List commands, boolean isRoot, 98 | boolean isNeedResultMsg) { 99 | return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, 100 | isNeedResultMsg); 101 | } 102 | 103 | /** 104 | * execute shell commands 105 | * 106 | * @param commands command array 107 | * @param isRoot whether need to run with root 108 | * @param isNeedResultMsg whether need result msg 109 | * @return
    110 | *
  • if isNeedResultMsg is false, {@link CommandResult#successMsg} is null and 111 | * {@link CommandResult#errorMsg} is null.
  • 112 | *
  • if {@link CommandResult#result} is -1, there maybe some excepiton.
  • 113 | *
114 | */ 115 | public static CommandResult execCommand(String[] commands, boolean isRoot, 116 | boolean isNeedResultMsg) { 117 | int result = -1; 118 | if (commands == null || commands.length == 0) { 119 | return new CommandResult(result, null, null); 120 | } 121 | 122 | Process process = null; 123 | BufferedReader successResult = null; 124 | BufferedReader errorResult = null; 125 | StringBuilder successMsg = null; 126 | StringBuilder errorMsg = null; 127 | 128 | DataOutputStream os = null; 129 | try { 130 | process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH); 131 | os = new DataOutputStream(process.getOutputStream()); 132 | for (String command : commands) { 133 | if (command == null) { 134 | continue; 135 | } 136 | 137 | // don't use os.writeBytes(commmand), avoid chinese charset error 138 | os.write(command.getBytes()); 139 | os.writeBytes(COMMAND_LINE_END); 140 | os.flush(); 141 | } 142 | os.writeBytes(COMMAND_EXIT); 143 | os.flush(); 144 | 145 | result = process.waitFor(); 146 | // get command result 147 | if (isNeedResultMsg) { 148 | successMsg = new StringBuilder(); 149 | errorMsg = new StringBuilder(); 150 | successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); 151 | errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); 152 | String s; 153 | while ((s = successResult.readLine()) != null) { 154 | successMsg.append(s); 155 | } 156 | while ((s = errorResult.readLine()) != null) { 157 | errorMsg.append(s); 158 | } 159 | } 160 | } catch (Exception e) { 161 | e.printStackTrace(); 162 | } finally { 163 | try { 164 | if (os != null) { 165 | os.close(); 166 | } 167 | 168 | if (successResult != null) { 169 | successResult.close(); 170 | } 171 | 172 | if (errorResult != null) { 173 | errorResult.close(); 174 | } 175 | } catch (IOException e) { 176 | e.printStackTrace(); 177 | } 178 | 179 | if (process != null) { 180 | process.destroy(); 181 | } 182 | } 183 | 184 | return new CommandResult(result, successMsg == null ? null : successMsg.toString(), 185 | errorMsg == null ? null : errorMsg.toString()); 186 | } 187 | 188 | /** 189 | * result of command 190 | *
    191 | *
  • {@link CommandResult#result} means result of command, 0 means normal, else means error, 192 | * same to excute in 193 | * linux shell
  • 194 | *
  • {@link CommandResult#successMsg} means success message of command result
  • 195 | *
  • {@link CommandResult#errorMsg} means error message of command result
  • 196 | *
197 | * 198 | * @author Trinea 2013-5-16 199 | */ 200 | public static class CommandResult { 201 | 202 | /** result of command **/ 203 | public int result; 204 | /** success message of command result **/ 205 | public String successMsg; 206 | /** error message of command result **/ 207 | public String errorMsg; 208 | 209 | public CommandResult(int result) { 210 | this.result = result; 211 | } 212 | 213 | public CommandResult(int result, String successMsg, String errorMsg) { 214 | this.result = result; 215 | this.successMsg = successMsg; 216 | this.errorMsg = errorMsg; 217 | } 218 | 219 | @Override public String toString() { 220 | return "CommandResult{" 221 | + "result=" 222 | + result 223 | + ", successMsg='" 224 | + successMsg 225 | + '\'' 226 | + ", errorMsg='" 227 | + errorMsg 228 | + '\'' 229 | + '}'; 230 | } 231 | } 232 | } -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/XposedUtil.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed; 2 | 3 | import android.app.Activity; 4 | import android.app.ActivityManager; 5 | import android.app.Application; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.pm.ApplicationInfo; 9 | import android.content.pm.PackageInfo; 10 | import android.content.pm.PackageManager; 11 | import android.os.Build; 12 | import android.os.Bundle; 13 | import android.os.Looper; 14 | import android.os.Process; 15 | import android.support.annotation.RequiresApi; 16 | import android.util.ArrayMap; 17 | import android.widget.Toast; 18 | 19 | import net.androidwing.hotxposed.log.Logs; 20 | import net.androidwing.hotxposed.shell.Shell; 21 | import net.androidwing.hotxposed.thread.ThreadPool; 22 | 23 | import java.io.Closeable; 24 | import java.io.IOException; 25 | import java.lang.reflect.Field; 26 | import java.lang.reflect.InvocationTargetException; 27 | import java.lang.reflect.Method; 28 | import java.security.MessageDigest; 29 | import java.util.HashMap; 30 | import java.util.List; 31 | import java.util.Locale; 32 | import java.util.Map; 33 | 34 | import de.robv.android.xposed.XposedHelpers; 35 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 36 | 37 | public class XposedUtil { 38 | 39 | /** 40 | * md5加密 41 | * 42 | * @param s 要加密的字符串 43 | * @return 加密结果 44 | */ 45 | public static String md5(String s) { 46 | char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 47 | try { 48 | MessageDigest mdInst = MessageDigest.getInstance("MD5"); 49 | byte[] md = mdInst.digest(s.getBytes()); 50 | int j = md.length; 51 | char[] str = new char[j * 2]; 52 | int k = 0; 53 | for (byte b : md) { 54 | str[k++] = hexDigits[b >>> 4 & 0xf]; 55 | str[k++] = hexDigits[b & 0xf]; 56 | } 57 | return new String(str).toLowerCase(); 58 | } catch (Exception e) { 59 | Logs.e(e); 60 | return ""; 61 | } 62 | } 63 | 64 | /** 65 | * 判断是否主进程 66 | * 67 | * @param context 上下文 68 | * @return true 主进程 69 | */ 70 | public static boolean isMainProcess(Context context) { 71 | try { 72 | int pid = Process.myPid(); 73 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 74 | if (activityManager == null) { 75 | return true; 76 | } 77 | List list = activityManager.getRunningAppProcesses(); 78 | for (Object o : list) { 79 | ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (o); 80 | try { 81 | if (info.pid == pid) { 82 | // 根据进程的信息获取当前进程的名字 83 | if (info.processName.contains(":")) { 84 | return false; 85 | } 86 | } 87 | } catch (Exception e) { 88 | e.printStackTrace(); 89 | } 90 | } 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | } 94 | return true; 95 | } 96 | 97 | /** 98 | * 获取版本名 99 | * 100 | * @param context 上下文 101 | * @return 版本名 102 | */ 103 | public static String getVersionName(Context context) { 104 | String verName = ""; 105 | try { 106 | verName = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; 107 | } catch (PackageManager.NameNotFoundException e) { 108 | e.printStackTrace(); 109 | } 110 | return verName; 111 | } 112 | 113 | /** 114 | * 重启程序 115 | * 116 | * @param context 上下文 117 | * @param packageName 包名 118 | */ 119 | public static void restartPackage(final Context context, final String packageName) { 120 | if (context == null) { 121 | return; 122 | } 123 | ThreadPool.post(new Runnable() { 124 | @Override 125 | public void run() { 126 | Shell.SU.run("am force-stop " + packageName); 127 | 128 | Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName); 129 | if (intent != null) { 130 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 131 | context.startActivity(intent); 132 | } 133 | } 134 | }); 135 | } 136 | 137 | /** 138 | * 重启程序 139 | * 140 | * @param context 上下文 141 | * @param packageName 包名 142 | */ 143 | public static void restartPackage(final Context context, final String user, final String packageName) { 144 | if (context == null) { 145 | return; 146 | } 147 | ThreadPool.post(new Runnable() { 148 | @Override 149 | public void run() { 150 | Shell.SU.run("am force-stop --user " + user + " com.tencent.mm"); 151 | 152 | Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName); 153 | if (intent != null) { 154 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 155 | context.startActivity(intent); 156 | } 157 | } 158 | }); 159 | } 160 | 161 | /** 162 | * 判断是否在当前主线程 163 | */ 164 | public static boolean isMainThread() { 165 | return Thread.currentThread() == Looper.getMainLooper().getThread(); 166 | } 167 | 168 | /** 169 | * 获取对象指定顺序的成员变量 170 | * 171 | * @param object 对象 172 | * @param index 位置 173 | */ 174 | public static Object getField(Object object, int index) { 175 | Field[] fields = object.getClass().getDeclaredFields(); 176 | if (index <= fields.length && index > 0) { 177 | try { 178 | Field f = fields[index]; 179 | f.setAccessible(true); 180 | return f.get(object); 181 | } catch (IllegalAccessException e) { 182 | Logs.e(e); 183 | } 184 | } 185 | return null; 186 | } 187 | 188 | /** 189 | * 根据类型名称获取成员变量 190 | * 191 | * @param object 对象 192 | * @param typeName 类型 193 | * @param name 名称 194 | */ 195 | public static Object getField(Object object, String typeName, String name) { 196 | Field[] fields = object.getClass().getDeclaredFields(); 197 | for (Field field : fields) { 198 | if ((field.getType().getName().equals(typeName)) && (field.getName().equals(name))) { 199 | try { 200 | return field.get(object); 201 | } catch (IllegalAccessException e) { 202 | Logs.e(e); 203 | } 204 | } 205 | } 206 | return null; 207 | } 208 | 209 | /** 210 | * 根据方法名,参数类型,返回类型 查找方法 211 | * 212 | * @param object 对象 213 | * @param methodName 反法名 214 | * @param returnType 返回类型 215 | * @param params 参数 216 | */ 217 | public static Method getMethod(Object object, String methodName, String returnType, Class... params) { 218 | for (Method method : object.getClass().getDeclaredMethods()) { 219 | if (method.getReturnType().getName().equals(returnType) && method.getName().equals(methodName)) { 220 | Class[] parameterTypes = method.getParameterTypes(); 221 | if (params.length != parameterTypes.length) { 222 | continue; 223 | } 224 | for (int i = 0; i < params.length; i++) { 225 | if (params[i] != parameterTypes[i]) { 226 | break; 227 | } 228 | } 229 | method.setAccessible(true); 230 | return method; 231 | } 232 | } 233 | return null; 234 | } 235 | 236 | /** 237 | * 调用方法 238 | * 239 | * @param object 对象 240 | * @param method 方法 241 | * @param params 参数 242 | */ 243 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 244 | public static Object callMethod(Object object, Method method, Object... params) { 245 | if (method != null) { 246 | try { 247 | return method.invoke(object, params); 248 | } catch (IllegalAccessException | InvocationTargetException e) { 249 | Logs.e(e); 250 | } 251 | } 252 | return null; 253 | } 254 | 255 | /** 256 | * 直接获取字符串变量 257 | * 258 | * @param object 对象 259 | * @param field 变量名 260 | */ 261 | public static String getString(Object object, String field) { 262 | return "" + XposedHelpers.getObjectField(object, field); 263 | } 264 | 265 | /** 266 | * 设置成员变量 267 | * 268 | * @param object 对象 269 | * @param typeClass 类型 270 | * @param name 名称 271 | * @param value 值 272 | */ 273 | public static void setField(Object object, Class typeClass, String name, Object value) { 274 | Field[] fields = object.getClass().getDeclaredFields(); 275 | for (Field field : fields) { 276 | if ((field.getType() == typeClass) && (field.getName().equals(name))) { 277 | field.setAccessible(true); 278 | try { 279 | if (value instanceof Integer) { 280 | field.setInt(object, (Integer) value); 281 | } else if (value instanceof Long) { 282 | field.setLong(object, (Long) value); 283 | } else if (value instanceof Boolean) { 284 | field.setBoolean(object, (Boolean) value); 285 | } else { 286 | field.set(object, value); 287 | } 288 | } catch (IllegalAccessException e) { 289 | Logs.e(e); 290 | } 291 | break; 292 | } 293 | } 294 | } 295 | 296 | /** 297 | * 设置成员变量 298 | * 299 | * @param object 对象 300 | * @param index 顺序 301 | * @param value 值 302 | */ 303 | public static void setField(Object object, int index, Object value) { 304 | Field[] fields = object.getClass().getDeclaredFields(); 305 | Field f = fields[index]; 306 | f.setAccessible(true); 307 | try { 308 | if (value instanceof Integer) { 309 | f.setInt(object, (Integer) value); 310 | } else if (value instanceof Long) { 311 | f.setLong(object, (Long) value); 312 | } else if (value instanceof Boolean) { 313 | f.setBoolean(object, (Boolean) value); 314 | } else { 315 | f.set(object, value); 316 | } 317 | } catch (IllegalAccessException e) { 318 | Logs.e(e); 319 | } 320 | } 321 | 322 | /** 323 | * 判断是否VXP 324 | */ 325 | public static boolean isVirtualXposed(XC_LoadPackage.LoadPackageParam loadPackageParam) { 326 | String dir = loadPackageParam.appInfo.dataDir + ""; 327 | return dir.contains("io.va.exposed"); 328 | } 329 | 330 | /** 331 | * 判断是否Hook 模块成功 332 | */ 333 | public static boolean isHook() { 334 | return false; 335 | } 336 | 337 | public static void showVersion(Context activity) { 338 | try { 339 | PackageInfo packageInfo = activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0); 340 | String appName = activity.getResources().getString(packageInfo.applicationInfo.labelRes); 341 | Toast.makeText(activity, String.format(Locale.CHINA, "%s 启动(%s)", appName, packageInfo.versionName), Toast.LENGTH_LONG).show(); 342 | } catch (PackageManager.NameNotFoundException e) { 343 | e.printStackTrace(); 344 | } 345 | } 346 | 347 | public static boolean isSystemApp(Context context, String packageName) { 348 | PackageManager pm = context.getPackageManager(); 349 | if (packageName != null) { 350 | try { 351 | PackageInfo info = pm.getPackageInfo(packageName, 0); 352 | return (info != null) && (info.applicationInfo != null) && 353 | ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); 354 | } catch (PackageManager.NameNotFoundException e) { 355 | return false; 356 | } 357 | } else { 358 | return false; 359 | } 360 | } 361 | 362 | /** 363 | * 获取当前运行的activity 364 | */ 365 | public static Activity getTopActivity(Context context) { 366 | WindowLifecycle windowLifecycle = new WindowLifecycle(context); 367 | Activity currentActivity = windowLifecycle.getCurrentActivity(); 368 | if (currentActivity != null) { 369 | return currentActivity; 370 | } 371 | try { 372 | Class activityThreadClass = Class.forName("android.app.ActivityThread"); 373 | Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null); 374 | Field activitiesField = activityThreadClass.getDeclaredField("mActivities"); 375 | activitiesField.setAccessible(true); 376 | //16~18 HashMap 377 | //19~27 ArrayMap 378 | Map activities; 379 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 380 | activities = (HashMap) activitiesField.get(activityThread); 381 | } else { 382 | activities = (ArrayMap) activitiesField.get(activityThread); 383 | } 384 | if (activities.size() < 1) { 385 | return null; 386 | } 387 | for (Object activityRecord : activities.values()) { 388 | Class activityRecordClass = activityRecord.getClass(); 389 | Field pausedField = activityRecordClass.getDeclaredField("paused"); 390 | pausedField.setAccessible(true); 391 | if (!pausedField.getBoolean(activityRecord)) { 392 | Field activityField = activityRecordClass.getDeclaredField("activity"); 393 | activityField.setAccessible(true); 394 | Activity activity = (Activity) activityField.get(activityRecord); 395 | return activity; 396 | } 397 | } 398 | } catch (Exception e) { 399 | Logs.e(e); 400 | } 401 | return null; 402 | } 403 | 404 | /** 405 | * 监听Application中Activity生命周期,获取顶部Activity 406 | */ 407 | private static class WindowLifecycle implements Application.ActivityLifecycleCallbacks, Closeable { 408 | private Activity currentActivity; 409 | 410 | public WindowLifecycle(Context context) { 411 | if (context == null) { 412 | return; 413 | } 414 | if (context instanceof Application) { 415 | ((Application) context).registerActivityLifecycleCallbacks(this); 416 | } else { 417 | ((Application) context.getApplicationContext()).registerActivityLifecycleCallbacks(this); 418 | } 419 | } 420 | 421 | /** 422 | * 获取当前运行的activity 423 | */ 424 | Activity getCurrentActivity() { 425 | return this.currentActivity; 426 | } 427 | 428 | @Override 429 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 430 | 431 | } 432 | 433 | @Override 434 | public void onActivityStarted(Activity activity) { 435 | 436 | } 437 | 438 | @Override 439 | public void onActivityResumed(Activity activity) { 440 | this.currentActivity = activity; 441 | } 442 | 443 | @Override 444 | public void onActivityPaused(Activity activity) { 445 | this.currentActivity = null; 446 | } 447 | 448 | @Override 449 | public void onActivityStopped(Activity activity) { 450 | 451 | } 452 | 453 | @Override 454 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 455 | 456 | } 457 | 458 | @Override 459 | public void onActivityDestroyed(Activity activity) { 460 | 461 | } 462 | 463 | 464 | @Override 465 | public void close() throws IOException { 466 | if (currentActivity != null) { 467 | currentActivity = null; 468 | } 469 | } 470 | 471 | /** 472 | * 销毁资源 473 | */ 474 | public void destroyed() { 475 | if (currentActivity != null) { 476 | currentActivity = null; 477 | } 478 | } 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/database/Lite.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed.database; 2 | 3 | import android.content.Context; 4 | 5 | import com.litesuits.orm.LiteOrm; 6 | 7 | import net.androidwing.hotxposed.log.Logs; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | 12 | 13 | public class Lite { 14 | static LiteOrm liteOrm; 15 | 16 | public static LiteOrm getLiteOrm(Context context, File dbFile) { 17 | if (liteOrm == null) { 18 | if (!dbFile.exists()) { 19 | if (!dbFile.getParentFile().exists()) { 20 | dbFile.getParentFile().mkdirs(); 21 | } 22 | try { 23 | dbFile.createNewFile(); 24 | } catch (IOException e) { 25 | Logs.e(e); 26 | } 27 | } 28 | liteOrm = LiteOrm.newSingleInstance(context, dbFile.getPath()); 29 | } 30 | liteOrm.setDebugged(false); // open the log 31 | return liteOrm; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/database/SqliteHelper.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed.database; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | /** 7 | * 数据库操作类 8 | * 9 | * @author z.houbin 10 | */ 11 | public class SqliteHelper { 12 | 13 | public static String getCursorValue(Cursor cursor, String columnName) { 14 | int columnIndex = cursor.getColumnIndex(columnName); 15 | String v = ""; 16 | switch (cursor.getType(columnIndex)) { 17 | case Cursor.FIELD_TYPE_BLOB: 18 | v = new String(cursor.getBlob(columnIndex)); 19 | break; 20 | case Cursor.FIELD_TYPE_FLOAT: 21 | v = String.valueOf(cursor.getFloat(columnIndex)); 22 | break; 23 | case Cursor.FIELD_TYPE_INTEGER: 24 | v = String.valueOf(cursor.getLong(columnIndex)); 25 | break; 26 | case Cursor.FIELD_TYPE_STRING: 27 | v = cursor.getString(columnIndex); 28 | break; 29 | default: 30 | v = ""; 31 | } 32 | return v; 33 | } 34 | 35 | public static String dumpCursor(Cursor cursor) { 36 | StringBuilder builder = new StringBuilder(); 37 | String[] columnNames = cursor.getColumnNames(); 38 | while (cursor.moveToNext()) { 39 | for (String columnName : columnNames) { 40 | int columnIndex = cursor.getColumnIndex(columnName); 41 | builder.append(columnName); 42 | builder.append(":"); 43 | Object v = new Object(); 44 | switch (cursor.getType(columnIndex)) { 45 | case Cursor.FIELD_TYPE_BLOB: 46 | v = cursor.getBlob(columnIndex); 47 | break; 48 | case Cursor.FIELD_TYPE_FLOAT: 49 | v = cursor.getFloat(columnIndex); 50 | break; 51 | case Cursor.FIELD_TYPE_INTEGER: 52 | v = cursor.getLong(columnIndex); 53 | break; 54 | case Cursor.FIELD_TYPE_STRING: 55 | v = cursor.getString(columnIndex); 56 | break; 57 | default: 58 | v = ""; 59 | } 60 | 61 | builder.append(v); 62 | builder.append(", "); 63 | } 64 | builder.append("\r\n"); 65 | } 66 | return builder.toString(); 67 | } 68 | 69 | public static ContentValues cursorToContents(Cursor cursor) { 70 | ContentValues values = new ContentValues(); 71 | if (cursor != null) { 72 | String[] columnNames = cursor.getColumnNames(); 73 | for (String columnName : columnNames) { 74 | int columnIndex = cursor.getColumnIndex(columnName); 75 | switch (cursor.getType(columnIndex)) { 76 | case Cursor.FIELD_TYPE_BLOB: 77 | values.put(columnName, cursor.getBlob(columnIndex)); 78 | break; 79 | case Cursor.FIELD_TYPE_FLOAT: 80 | values.put(columnName, cursor.getFloat(columnIndex)); 81 | break; 82 | case Cursor.FIELD_TYPE_INTEGER: 83 | values.put(columnName, cursor.getLong(columnIndex)); 84 | break; 85 | case Cursor.FIELD_TYPE_STRING: 86 | values.put(columnName, cursor.getString(columnIndex)); 87 | break; 88 | default: 89 | values.put(columnName, ""); 90 | } 91 | } 92 | } 93 | return values; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/database/Student.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed.database; 2 | 3 | import com.litesuits.orm.db.annotation.Column; 4 | import com.litesuits.orm.db.annotation.PrimaryKey; 5 | import com.litesuits.orm.db.annotation.Table; 6 | import com.litesuits.orm.db.enums.AssignType; 7 | 8 | @Table("student") 9 | public class Student { 10 | @PrimaryKey(AssignType.AUTO_INCREMENT) 11 | private int id; 12 | 13 | @Column("name") 14 | private String name; 15 | 16 | @Column("age") 17 | private int age; 18 | 19 | public int getId() { 20 | return id; 21 | } 22 | 23 | public void setId(int id) { 24 | this.id = id; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | public int getAge() { 36 | return age; 37 | } 38 | 39 | public void setAge(int age) { 40 | this.age = age; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/debug/DebugListner.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed.debug; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | 7 | import de.robv.android.xposed.XC_MethodHook; 8 | 9 | /** 10 | * @author z.houbin 11 | */ 12 | public class DebugListner { 13 | private ArrayList methodNames = new ArrayList<>(); 14 | 15 | public DebugListner(String... method) { 16 | if (method != null) { 17 | methodNames.addAll(Arrays.asList(method)); 18 | } 19 | } 20 | 21 | public void onMethodBefore(XC_MethodHook.MethodHookParam method) { 22 | 23 | } 24 | 25 | public void onMethodAfter(XC_MethodHook.MethodHookParam method) { 26 | 27 | } 28 | 29 | public boolean isDebug(XC_MethodHook.MethodHookParam method) { 30 | boolean match = false; 31 | if (method != null) { 32 | String methodName = method.method.getName(); 33 | if (method.method instanceof Constructor) { 34 | methodName = "init"; 35 | } 36 | match = methodNames.contains(methodName); 37 | } 38 | return match; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/debug/Trace.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed.debug; 2 | 3 | import net.androidwing.hotxposed.log.Logs; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import de.robv.android.xposed.XC_MethodHook; 10 | import de.robv.android.xposed.XposedBridge; 11 | 12 | /** 13 | * 类调用最终 14 | * 15 | * @author z.houbin 16 | */ 17 | public class Trace { 18 | /** 19 | * 追踪类函数调用 20 | * 21 | * @param cls 类 22 | * @param debugListner 函数调用回调 23 | */ 24 | public static void traceClass(Class cls, DebugListner debugListner) { 25 | Logs.e("Trace Class " + cls); 26 | if (cls == null) { 27 | return; 28 | } 29 | List methodNames = new ArrayList<>(); 30 | Method[] declaredMethods = cls.getDeclaredMethods(); 31 | for (Method method : declaredMethods) { 32 | if (!methodNames.contains(method.getName())) { 33 | methodNames.add(method.getName()); 34 | } 35 | } 36 | declaredMethods = cls.getMethods(); 37 | for (Method method : declaredMethods) { 38 | if (!methodNames.contains(method.getName())) { 39 | methodNames.add(method.getName()); 40 | } 41 | } 42 | DebugMethod debugMethod = new DebugMethod(debugListner); 43 | for (String name : methodNames) { 44 | XposedBridge.hookAllMethods(cls, name, debugMethod); 45 | } 46 | XposedBridge.hookAllConstructors(cls, debugMethod); 47 | } 48 | 49 | private static class DebugMethod extends XC_MethodHook { 50 | private DebugListner debugListner; 51 | 52 | DebugMethod(DebugListner debugListner) { 53 | this.debugListner = debugListner; 54 | } 55 | 56 | @Override 57 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 58 | super.beforeHookedMethod(param); 59 | if (debugListner != null && debugListner.isDebug(param)) { 60 | debugListner.onMethodBefore(param); 61 | } 62 | if (param.thisObject != null) { 63 | String clsName = param.thisObject.getClass().getName(); 64 | String methodName = param.method.getName(); 65 | try { 66 | Logs.printMethodParam(clsName + "--(" + methodName + ") ", param); 67 | } catch (Exception e) { 68 | //e.printStackTrace(); 69 | } 70 | } else { 71 | //静态 72 | String methodName = param.method.getName(); 73 | try { 74 | Logs.printMethodParam("???" + "--(" + methodName + ") ", param); 75 | } catch (Exception e) { 76 | //e.printStackTrace(); 77 | } 78 | } 79 | } 80 | 81 | @Override 82 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 83 | super.afterHookedMethod(param); 84 | if (debugListner != null && debugListner.isDebug(param)) { 85 | debugListner.onMethodAfter(param); 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/hot/BaseHook.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed.hot; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | import android.widget.Toast; 7 | 8 | import net.androidwing.hotxposed.log.Logs; 9 | 10 | import de.robv.android.xposed.IXposedHookLoadPackage; 11 | import de.robv.android.xposed.XC_MethodReplacement; 12 | import de.robv.android.xposed.XposedHelpers; 13 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 14 | 15 | public class BaseHook implements IXposedHookLoadPackage, IHookerDispatcher, Application.ActivityLifecycleCallbacks { 16 | public Activity focusActivity; 17 | protected XC_LoadPackage.LoadPackageParam packageParam; 18 | 19 | @Override 20 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { 21 | //startHotXPosed(loadPackageParam); 22 | } 23 | 24 | protected void startHotXPosed(Class clz, XC_LoadPackage.LoadPackageParam loadPackageParam, String localePackage, String[] targetPackages) { 25 | if (!loadPackageParam.packageName.equals("android")) { 26 | 27 | for (String targetPackage : targetPackages) { 28 | if (loadPackageParam.packageName.equals(targetPackage)) { 29 | try { 30 | HotXPosed.hook(clz, loadPackageParam, localePackage); 31 | } catch (Exception e) { 32 | //Logs.e(e); 33 | } 34 | } else if (loadPackageParam.packageName.equals(localePackage)) { 35 | XposedHelpers.findAndHookMethod("z.houbin.xposed.lib.XposedUtil", loadPackageParam.classLoader, "isHook", XC_MethodReplacement.returnConstant(true)); 36 | } 37 | } 38 | 39 | } 40 | } 41 | 42 | protected void startHotXPosed(Class clz, XC_LoadPackage.LoadPackageParam loadPackageParam, String localePackage) { 43 | if (!loadPackageParam.packageName.equals("android")) { 44 | if (loadPackageParam.packageName.equals(localePackage)) { 45 | XposedHelpers.findAndHookMethod("z.houbin.xposed.lib.XposedUtil", loadPackageParam.classLoader, "isHook", XC_MethodReplacement.returnConstant(true)); 46 | } else { 47 | try { 48 | HotXPosed.hook(clz, loadPackageParam, localePackage); 49 | } catch (Exception e) { 50 | //Logs.e(e); 51 | } 52 | } 53 | } 54 | } 55 | 56 | @Override 57 | public void dispatch(XC_LoadPackage.LoadPackageParam loadPackageParam) { 58 | //Config.init(loadPackageParam.packageName); 59 | this.packageParam = loadPackageParam; 60 | } 61 | 62 | public void dispatchAttach(Application application) { 63 | application.registerActivityLifecycleCallbacks(this); 64 | } 65 | 66 | public ClassLoader getClassLoader() { 67 | if (this.packageParam != null) { 68 | return this.packageParam.classLoader; 69 | } 70 | 71 | if (focusActivity != null) { 72 | return focusActivity.getClassLoader(); 73 | } 74 | 75 | return null; 76 | } 77 | 78 | public Class load(String name) { 79 | ClassLoader classLoader = getClassLoader(); 80 | if (classLoader != null) { 81 | try { 82 | return classLoader.loadClass(name); 83 | } catch (ClassNotFoundException e) { 84 | Logs.e(e); 85 | } 86 | } 87 | return null; 88 | } 89 | 90 | public void toast(final String message) { 91 | if (focusActivity != null) { 92 | focusActivity.runOnUiThread(new Runnable() { 93 | @Override 94 | public void run() { 95 | Toast.makeText(focusActivity, String.valueOf(message), Toast.LENGTH_SHORT).show(); 96 | } 97 | }); 98 | } 99 | } 100 | 101 | public void toastLong(final String message) { 102 | if (focusActivity != null) { 103 | focusActivity.runOnUiThread(new Runnable() { 104 | @Override 105 | public void run() { 106 | Toast.makeText(focusActivity, String.valueOf(message), Toast.LENGTH_LONG).show(); 107 | } 108 | }); 109 | } 110 | } 111 | 112 | @Override 113 | public void onActivityCreated(Activity activity, Bundle bundle) { 114 | 115 | } 116 | 117 | @Override 118 | public void onActivityStarted(Activity activity) { 119 | 120 | } 121 | 122 | @Override 123 | public void onActivityResumed(Activity activity) { 124 | focusActivity = activity; 125 | } 126 | 127 | @Override 128 | public void onActivityPaused(Activity activity) { 129 | 130 | } 131 | 132 | @Override 133 | public void onActivityStopped(Activity activity) { 134 | 135 | } 136 | 137 | @Override 138 | public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { 139 | 140 | } 141 | 142 | @Override 143 | public void onActivityDestroyed(Activity activity) { 144 | 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/hot/HotXPosed.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed.hot; 2 | 3 | import android.text.TextUtils; 4 | 5 | import net.androidwing.hotxposed.shell.Shell; 6 | 7 | import java.io.File; 8 | 9 | import dalvik.system.PathClassLoader; 10 | import de.robv.android.xposed.XposedBridge; 11 | import de.robv.android.xposed.XposedHelpers; 12 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 13 | 14 | public class HotXPosed { 15 | 16 | public static void hook(Class clazz, XC_LoadPackage.LoadPackageParam lpparam, String packageName) { 17 | try { 18 | File apkFile = getApkFile(packageName); 19 | if (apkFile == null || !apkFile.exists()) { 20 | XposedBridge.log("apk file not found,hot load :" + packageName); 21 | return; 22 | } 23 | PathClassLoader classLoader = new PathClassLoader(apkFile.getAbsolutePath(), lpparam.getClass().getClassLoader()); 24 | XposedHelpers.callMethod(classLoader.loadClass(clazz.getName()).newInstance(), "dispatch", lpparam); 25 | } catch (Exception e) { 26 | //Logs.e(e); 27 | } 28 | } 29 | 30 | private static File getApkFile(String packageName) { 31 | String filePath = Shell.run("pm path " + packageName).getStdout(); 32 | XposedBridge.log(filePath); 33 | if (TextUtils.isEmpty(filePath)) { 34 | return null; 35 | } 36 | filePath = filePath.substring(filePath.indexOf(":") + 1); 37 | XposedBridge.log(filePath); 38 | File apkFile = new File(filePath); 39 | if (!apkFile.exists()) { 40 | filePath = String.format("/data/app/%s-%s/base.apk", packageName, 2); 41 | apkFile = new File(filePath); 42 | } 43 | return apkFile; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/hot/IHookerDispatcher.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed.hot; 2 | 3 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 4 | 5 | public interface IHookerDispatcher { 6 | void dispatch(XC_LoadPackage.LoadPackageParam loadPackageParam); 7 | } 8 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/log/Logs.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed.log; 2 | 3 | import android.database.Cursor; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | 7 | import net.androidwing.hotxposed.database.SqliteHelper; 8 | 9 | import java.lang.reflect.Field; 10 | import java.lang.reflect.Member; 11 | import java.lang.reflect.Method; 12 | import java.util.Locale; 13 | 14 | import de.robv.android.xposed.XC_MethodHook; 15 | 16 | /** 17 | * 日志工具 18 | */ 19 | public class Logs { 20 | public static String TAG = "Xposed.Lib"; 21 | 22 | public static void init(String t) { 23 | TAG = t + " "; 24 | } 25 | 26 | public static void i(String text) { 27 | Log.i(TAG, text); 28 | } 29 | 30 | public static void i(String tag, String text) { 31 | if (tag == null) { 32 | tag = TAG; 33 | } 34 | Log.i(tag, text); 35 | } 36 | 37 | public static void e(String text) { 38 | Log.e(TAG, text); 39 | //Config.writeLog(text); 40 | } 41 | 42 | public static void e(String tag, String text) { 43 | Log.e(TAG + " - " + tag, text); 44 | //Config.writeLog(tag + ": " + text); 45 | } 46 | 47 | public static void e(Object cls, String log) { 48 | Logs.e(cls.getClass().getName(), log); 49 | } 50 | 51 | public static void e(Class cls, String log) { 52 | Logs.e(cls.getName(), log); 53 | } 54 | 55 | /** 56 | * 打印异常信息 57 | * 58 | * @param e 异常 59 | */ 60 | public static void e(Throwable e) { 61 | e(TAG, Log.getStackTraceString(e)); 62 | } 63 | 64 | public static void e(String tag, Throwable e) { 65 | e(TAG + " - " + tag, Log.getStackTraceString(e)); 66 | } 67 | 68 | /** 69 | * 打印函数参数 70 | * 71 | * @param tag 标签 72 | * @param param 参数 73 | */ 74 | public static void printMethodParam(String tag, XC_MethodHook.MethodHookParam param) { 75 | StringBuilder builder = new StringBuilder(tag); 76 | builder.append(" "); 77 | try { 78 | Member method = param.method; 79 | method.getName(); 80 | 81 | Object[] params = param.args; 82 | for (int i = 0; i < params.length; i++) { 83 | builder.append("p").append(i); 84 | builder.append(":"); 85 | Object p = params[i]; 86 | if (p == null) { 87 | builder.append("null"); 88 | } else { 89 | builder.append("("); 90 | builder.append(p.getClass().getName()); 91 | builder.append(")"); 92 | builder.append(p.toString()); 93 | } 94 | builder.append(","); 95 | } 96 | } catch (Exception e) { 97 | e(e); 98 | } 99 | Logs.e(builder.toString()); 100 | } 101 | 102 | /** 103 | * 打印成员 104 | * 105 | * @param obj 对象 106 | */ 107 | public static void printField(Object obj) { 108 | StringBuilder builder = new StringBuilder(); 109 | try { 110 | Class cls = obj.getClass(); 111 | Field[] fields = cls.getDeclaredFields(); 112 | for (int i = 0; i < fields.length; i++) { 113 | Field field = fields[i]; 114 | // 对于每个属性,获取属性名 115 | String varName = field.getName(); 116 | try { 117 | boolean access = field.isAccessible(); 118 | if (!access) { 119 | field.setAccessible(true); 120 | } 121 | 122 | //从obj中获取field变量 123 | Object o = field.get(obj); 124 | builder.append("变量: " + varName + " = " + o); 125 | builder.append("\r\n"); 126 | if (!access) { 127 | field.setAccessible(false); 128 | } 129 | } catch (Exception ex) { 130 | e(ex); 131 | } 132 | } 133 | 134 | //函数 135 | Method[] methods = cls.getDeclaredMethods(); 136 | for (Method method : methods) { 137 | // 对于每个属性,获取属性名 138 | //得到方法的返回值类型的类类型 139 | builder.append("\r\n"); 140 | Class returnType = method.getReturnType(); 141 | builder.append(returnType.getName()); 142 | builder.append(" "); 143 | //得到方法的名称 144 | builder.append(method.getName()); 145 | builder.append("("); 146 | //获取参数类型--->得到的是参数列表的类型的类类型 147 | Class[] paramTypes = method.getParameterTypes(); 148 | for (Class class1 : paramTypes) { 149 | builder.append(class1.getName()); 150 | builder.append(","); 151 | } 152 | builder.append(")"); 153 | } 154 | Logs.e(cls.getName() + " method " + builder.toString()); 155 | } catch (Exception e) { 156 | e(e); 157 | } 158 | Logs.e(builder.toString()); 159 | } 160 | 161 | /** 162 | * 打印当前堆栈信息 163 | */ 164 | public static void printStackTrace() { 165 | StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); 166 | StringBuilder builder = new StringBuilder(); 167 | String lineSeparator = System.getProperty("line.separator"); 168 | for (StackTraceElement trace : stackTrace) { 169 | if (builder.length() > 0) { 170 | builder.append(lineSeparator); 171 | } 172 | builder.append(java.text.MessageFormat.format("{0}.{1}() {2}" 173 | , trace.getClassName() 174 | , trace.getMethodName() 175 | , trace.getLineNumber())); 176 | } 177 | Logs.e("StackTrace \r\n" + builder.toString()); 178 | } 179 | 180 | public static void e(Object tag, Object... log) { 181 | if (log.length == 1) { 182 | Logs.e(tag, log[0]); 183 | } else if (log.length > 1) { 184 | //多个格式化 185 | Logs.e(TAG, String.format(Locale.CHINA, tag.toString(), log)); 186 | } 187 | } 188 | 189 | private static void e(Object tag, Object log) { 190 | Logs.e(getTag(tag), getLog(log)); 191 | } 192 | 193 | private static String getLog(Object log) { 194 | StringBuilder builder = new StringBuilder(); 195 | if (log instanceof Bundle) { 196 | builder.append(getBundleLog((Bundle) log)); 197 | } else if (log instanceof Cursor) { 198 | builder.append(getCursorLog((Cursor) log)); 199 | } else if (log.getClass().isArray()) { 200 | builder.append(getArrayLog((Object[]) log)); 201 | } else { 202 | builder.append(log.toString()); 203 | } 204 | return builder.toString(); 205 | } 206 | 207 | private static String getArrayLog(Object[] arr) { 208 | StringBuilder builder = new StringBuilder(); 209 | if (arr != null) { 210 | for (int i = 0; i < arr.length; i++) { 211 | builder.append("["); 212 | builder.append(i); 213 | builder.append("]"); 214 | builder.append(": "); 215 | builder.append(arr[i]); 216 | builder.append(" "); 217 | } 218 | } 219 | return builder.toString(); 220 | } 221 | 222 | private static String getBundleLog(Bundle bundle) { 223 | StringBuilder builder = new StringBuilder(); 224 | for (String key : bundle.keySet()) { 225 | Object v = bundle.get(key); 226 | if (v == null) { 227 | v = ""; 228 | } 229 | builder.append(key); 230 | builder.append(":"); 231 | builder.append(v.toString()); 232 | builder.append("--"); 233 | } 234 | return builder.toString(); 235 | } 236 | 237 | private static String getCursorLog(Cursor cursor) { 238 | StringBuilder builder = new StringBuilder(); 239 | try { 240 | builder.append(SqliteHelper.dumpCursor(cursor)); 241 | } catch (Exception e) { 242 | Logs.e(e); 243 | } 244 | return builder.toString(); 245 | } 246 | 247 | private static String getTag(Object tag) { 248 | String t = TAG; 249 | if (tag == null) { 250 | t += ""; 251 | } else if (tag instanceof String) { 252 | t += tag.toString(); 253 | } else if (tag instanceof Class) { 254 | t += ((Class) tag).getName(); 255 | } else { 256 | t += tag.getClass().getSimpleName(); 257 | } 258 | return t; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/shell/CommandResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.androidwing.hotxposed.shell; 18 | 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * Results of running a command in a shell. Results contain stdout, stderr, and the exit status. 24 | */ 25 | public class CommandResult implements ShellExitCode { 26 | 27 | private static String toString(List lines) { 28 | StringBuilder sb = new StringBuilder(); 29 | if (lines != null) { 30 | String emptyOrNewLine = ""; 31 | for (String line : lines) { 32 | sb.append(emptyOrNewLine).append(line); 33 | emptyOrNewLine = "\n"; 34 | } 35 | } 36 | return sb.toString(); 37 | } 38 | 39 | public final List stdout; 40 | public final List stderr; 41 | public final int exitCode; 42 | 43 | public CommandResult(List stdout, List stderr, int exitCode) { 44 | this.stdout = stdout; 45 | this.stderr = stderr; 46 | this.exitCode = exitCode; 47 | } 48 | 49 | /** 50 | * Check if the exit code is 0. 51 | * 52 | * @return {@code true} if the {@link #exitCode} is equal to {@link ShellExitCode#SUCCESS}. 53 | */ 54 | public boolean isSuccessful() { 55 | return exitCode == SUCCESS; 56 | } 57 | 58 | /** 59 | * Get the standard output. 60 | * 61 | * @return The standard output as a string. 62 | */ 63 | public String getStdout() { 64 | return toString(stdout); 65 | } 66 | 67 | /** 68 | * Get the standard error. 69 | * 70 | * @return The standard error as a string. 71 | */ 72 | public String getStderr() { 73 | return toString(stderr); 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return getStdout(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/shell/ShellExitCode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.androidwing.hotxposed.shell; 18 | 19 | @SuppressWarnings("unused") 20 | public interface ShellExitCode { 21 | 22 | int SUCCESS = 0; 23 | 24 | int WATCHDOG_EXIT = -1; 25 | 26 | int SHELL_DIED = -2; 27 | 28 | int SHELL_EXEC_FAILED = -3; 29 | 30 | int SHELL_WRONG_UID = -4; 31 | 32 | int SHELL_NOT_FOUND = -5; 33 | 34 | int TERMINATED = 130; 35 | 36 | int COMMAND_NOT_EXECUTABLE = 126; 37 | 38 | int COMMAND_NOT_FOUND = 127; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/shell/ShellNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.androidwing.hotxposed.shell; 18 | 19 | import java.io.IOException; 20 | 21 | /** 22 | * Exception thrown when a shell could not be opened. 23 | */ 24 | public class ShellNotFoundException extends IOException { 25 | 26 | /** 27 | * Constructs a new {@code Exception} with the current stack trace and the specified detail message. 28 | * 29 | * @param detailMessage 30 | * the detail message for this exception. 31 | */ 32 | public ShellNotFoundException(String detailMessage) { 33 | super(detailMessage); 34 | } 35 | 36 | /** 37 | * Constructs a new {@code Exception} with the current stack trace and the specified cause. 38 | * 39 | * @param message 40 | * the detail message for this exception. 41 | * @param cause 42 | * the cause of this exception. 43 | */ 44 | public ShellNotFoundException(String message, Throwable cause) { 45 | super(message, cause); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/shell/StreamGobbler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.androidwing.hotxposed.shell; 18 | 19 | import java.io.BufferedReader; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.InputStreamReader; 23 | import java.util.List; 24 | 25 | /** 26 | * Thread utility class continuously reading from an InputStream 27 | */ 28 | public class StreamGobbler extends Thread { 29 | 30 | /** 31 | * Line callback interface 32 | */ 33 | public interface OnLineListener { 34 | 35 | /** 36 | *

Line callback

37 | * 38 | *

This callback should process the line as quickly as possible. Delays in this callback may pause the 39 | * native process or even type in a deadlock

40 | * 41 | * @param line 42 | * String that was gobbled 43 | */ 44 | void onLine(String line); 45 | } 46 | 47 | private final BufferedReader reader; 48 | private List writer; 49 | private OnLineListener listener; 50 | 51 | /** 52 | *

StreamGobbler constructor

53 | * 54 | *

We use this class because shell STDOUT and STDERR should be readConfig as quickly as possible to prevent a 55 | * deadlock from occurring, or Process.waitFor() never returning (as the buffer is full, pausing the native 56 | * process)

57 | * 58 | * @param inputStream 59 | * InputStream to readConfig from 60 | * @param outputList 61 | * List to write to, or null 62 | */ 63 | public StreamGobbler(InputStream inputStream, List outputList) { 64 | reader = new BufferedReader(new InputStreamReader(inputStream)); 65 | writer = outputList; 66 | } 67 | 68 | /** 69 | *

StreamGobbler constructor

70 | * 71 | *

We use this class because shell STDOUT and STDERR should be readConfig as quickly as possible to prevent a 72 | * deadlock from occurring, or Process.waitFor() never returning (as the buffer is full, pausing the native 73 | * process)

74 | * 75 | * @param inputStream 76 | * InputStream to readConfig from 77 | * @param onLineListener 78 | * OnLineListener callback 79 | */ 80 | public StreamGobbler(InputStream inputStream, OnLineListener onLineListener) { 81 | reader = new BufferedReader(new InputStreamReader(inputStream)); 82 | listener = onLineListener; 83 | } 84 | 85 | @Override 86 | public void run() { 87 | // keep reading the InputStream until it ends (or an error occurs) 88 | try { 89 | String line; 90 | while ((line = reader.readLine()) != null) { 91 | if (writer != null) { 92 | writer.add(line); 93 | } 94 | if (listener != null) { 95 | listener.onLine(line); 96 | } 97 | } 98 | } catch (IOException e) { 99 | // reader probably closed, expected exit condition 100 | } 101 | 102 | // make sure our stream is closed and resources will be freed 103 | try { 104 | reader.close(); 105 | } catch (IOException ignored) { 106 | } 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /hotxposed/src/main/java/net/androidwing/hotxposed/thread/ThreadPool.java: -------------------------------------------------------------------------------- 1 | package net.androidwing.hotxposed.thread; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.ScheduledExecutorService; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * 线程池工具 11 | * 12 | * @author z.houbin 13 | */ 14 | public class ThreadPool { 15 | private static ThreadPoolExecutor executor; 16 | private static ScheduledExecutorService scheduledExecutorService; 17 | 18 | private static void init() { 19 | if (executor == null) { 20 | executor = new ThreadPoolExecutor(4, 10, 5000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(10), new ThreadPoolExecutor.DiscardPolicy()); 21 | } 22 | if (scheduledExecutorService == null) { 23 | scheduledExecutorService = Executors.newScheduledThreadPool(5); 24 | } 25 | } 26 | 27 | public static void post(Runnable runnable) { 28 | init(); 29 | executor.submit(runnable); 30 | } 31 | 32 | public static void postDelay(Runnable runnable, long delay) { 33 | init(); 34 | scheduledExecutorService.schedule(runnable, delay, TimeUnit.MILLISECONDS); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /hotxposed/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | HotXposed 3 | 4 | -------------------------------------------------------------------------------- /image/screenshot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/image/screenshot1.jpg -------------------------------------------------------------------------------- /image/screenshot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/image/screenshot2.jpg -------------------------------------------------------------------------------- /image/screenshot3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysnows/wx_hook/05f4877bbd63b33d92343236c28aef261393e0fb/image/screenshot3.jpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':hotxposed' 2 | --------------------------------------------------------------------------------