diff --git a/.idea/misc.xml b/.idea/misc.xml index 974f06ab..f5c6d9eb 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 67211375..52c0fe6e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "com.raincat.dolby_beta" minSdkVersion 14 targetSdkVersion 23 - versionCode 203 - versionName "2.0.3" + versionCode 210 + versionName "2.1.0" } buildTypes { release { diff --git a/app/src/main/java/com/raincat/dolby_beta/HookerDispatcher.java b/app/src/main/java/com/raincat/dolby_beta/HookerDispatcher.java index d31b45fb..1003fd20 100644 --- a/app/src/main/java/com/raincat/dolby_beta/HookerDispatcher.java +++ b/app/src/main/java/com/raincat/dolby_beta/HookerDispatcher.java @@ -2,6 +2,7 @@ import android.content.Context; +import com.raincat.dolby_beta.db.ExtraDao; import com.raincat.dolby_beta.hook.AdAndUpdateHook; import com.raincat.dolby_beta.hook.AutoSignInHook; import com.raincat.dolby_beta.hook.BlackHook; @@ -74,8 +75,10 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (processName.equals(CloudMusicPackage.PACKAGE_NAME)) { CloudMusicPackage.init(neteaseContext); //UnblockMusic代理 - if (Setting.isProxyEnabled()) + if (Setting.isProxyEnabled()) { + ExtraDao.getInstance(neteaseContext).saveExtra("ScriptRunning", "0"); new UnblockMusicHook(neteaseContext, CloudMusicPackage.versionCode, false); + } //黑胶 if (Setting.isBlackEnabled()) { Tools.deleteDirectory(CloudMusicPackage.CACHE_PATH); diff --git a/app/src/main/java/com/raincat/dolby_beta/db/ExtraDao.java b/app/src/main/java/com/raincat/dolby_beta/db/ExtraDao.java index 46385978..0460b7b5 100644 --- a/app/src/main/java/com/raincat/dolby_beta/db/ExtraDao.java +++ b/app/src/main/java/com/raincat/dolby_beta/db/ExtraDao.java @@ -35,7 +35,7 @@ public static synchronized ExtraDao getInstance(Context context) { /** * 保存额外记录 */ - public void saveExtra(String key, String value) { + public synchronized void saveExtra(String key, String value) { SQLiteDatabase db = dbHelper.getWritableDatabase(); if (db.isOpen()) { ContentValues values = new ContentValues(); @@ -49,7 +49,7 @@ public void saveExtra(String key, String value) { /** * 获取某个额外记录 */ - public String getExtra(String key) { + public synchronized String getExtra(String key) { SQLiteDatabase db = dbHelper.getReadableDatabase(); if (db.isOpen()) { Cursor cursor = db.rawQuery("select * from " + TABLE_NAME + " where " + EXTRA_KEY + " = '" + key + "'", null); @@ -65,7 +65,7 @@ public String getExtra(String key) { /** * 删除一个人的某条额外记录 */ - public void deleteExtra(String key) { + public synchronized void deleteExtra(String key) { SQLiteDatabase db = dbHelper.getWritableDatabase(); if (db.isOpen()) { db.delete(TABLE_NAME, EXTRA_KEY + " = ? ", new String[]{key}); @@ -76,7 +76,7 @@ public void deleteExtra(String key) { /** * 删除所有额外记录 */ - public void deleteAllExtra() { + public synchronized void deleteAllExtra() { SQLiteDatabase db = dbHelper.getWritableDatabase(); if (db.isOpen()) { db.delete(TABLE_NAME, null, null); diff --git a/app/src/main/java/com/raincat/dolby_beta/hook/UnblockMusicHook.java b/app/src/main/java/com/raincat/dolby_beta/hook/UnblockMusicHook.java index 7ba34661..8e677d18 100644 --- a/app/src/main/java/com/raincat/dolby_beta/hook/UnblockMusicHook.java +++ b/app/src/main/java/com/raincat/dolby_beta/hook/UnblockMusicHook.java @@ -1,5 +1,6 @@ package com.raincat.dolby_beta.hook; +import android.app.Activity; import android.content.Context; import android.os.Bundle; @@ -12,8 +13,11 @@ import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.Proxy; +import java.util.Arrays; import java.util.List; +import javax.net.ssl.SSLSocketFactory; + import de.robv.android.xposed.XC_MethodHook; import static de.robv.android.xposed.XposedBridge.hookAllConstructors; @@ -23,7 +27,6 @@ /** *
  *     author : RainCat
- *     org    : Shenzhen JingYu Network Technology Co., Ltd.
  *     e-mail : nining377@gmail.com
  *     time   : 2021/03/10
  *     desc   : 音乐代理hook
@@ -33,26 +36,40 @@
 
 public class UnblockMusicHook {
     private final static String STOP_PROXY = "killall -9 node >/dev/null 2>&1";
-    private final static String START_PROXY = "./node app.js -o kuwo qq migu kugou -p 23338";
+    private final static String START_PROXY = "./node app.js -o kuwo qq migu kugou -a 127.0.0.1 -p 23338:23339";
 
     private static String dataPath;
+    private static SSLSocketFactory socketFactory;
+    private static Object objectProxy;
+    private static Object objectSSLSocketFactory;
 
     private final String classMainActivity = "com.netease.cloudmusic.activity.MainActivity";
-    private String classRealCall ;
+    private String classRealCall;
     private String fieldHttpUrl = "url";
     private String fieldProxy = "proxy";
+    private String fieldSSLSocketFactory;
+
+    private final List whiteUrlList = Arrays.asList(
+            "song/enhance/player/url", "song/enhance/download/url");
 
+    private final List blackUrlList = Arrays.asList("eapi/playlist/subscribe",
+            "163yun.com");
+    
     public UnblockMusicHook(Context context, int versionCode, boolean isPlayProcess) {
         if (versionCode >= 7001080) {
             classRealCall = "okhttp3.internal.connection.RealCall";
+            fieldSSLSocketFactory = "sslSocketFactoryOrNull";
         } else if (versionCode >= 138) {
             classRealCall = "okhttp3.RealCall";
+            fieldSSLSocketFactory = "sslSocketFactory";
         } else {
             classRealCall = "okhttp3.z";
+            fieldSSLSocketFactory = "o";
             fieldHttpUrl = "a";
             fieldProxy = "d";
         }
 
+        dataPath = context.getFilesDir().getAbsolutePath() + File.separator + "script";
         final Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 23338));
         hookAllConstructors(findClass(classRealCall, context.getClassLoader()), new XC_MethodHook() {
             @Override
@@ -67,13 +84,40 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                     proxyField.setAccessible(true);
 
                     Object urlObj = urlField.get(request);
-                    if (urlObj.toString().contains("song/enhance/player/url") || urlObj.toString().contains("song/enhance/download/url")) {
+
+                    for (String url : blackUrlList) {
+                        if (urlObj.toString().contains(url)) {
+                            return;
+                        }
+                    }
+
+                    if (Setting.isWhiteEnabled()) {
+                        for (String url : whiteUrlList) {
+                            if (urlObj.toString().contains(url)) {
+                                if (ExtraDao.getInstance(context).getExtra("ScriptRunning").equals("1"))
+                                    proxyField.set(client, proxy);
+                                else
+                                    Tools.showToastOnLooper(context, "node未成功运行,请到模块内选择正确的脚本与Node路径,若已使用存储重定向等APP请保证网易云音乐也可访问到脚本路径!");
+                                break;
+                            }
+                        }
+                    } else {
+                        Field sslSocketFactoryField = client.getClass().getDeclaredField(fieldSSLSocketFactory);
+                        sslSocketFactoryField.setAccessible(true);
+                        if (objectProxy == null)
+                            objectProxy = proxyField.get(client);
+                        if (objectSSLSocketFactory == null)
+                            objectSSLSocketFactory = sslSocketFactoryField.get(client);
+
                         if (ExtraDao.getInstance(context).getExtra("ScriptRunning").equals("0")) {
-                            Tools.showToastOnLooper(context, "node未运行,请保证脚本与Node文件路径正确!");
-                        } else
+                            proxyField.set(client, objectProxy);
+                            sslSocketFactoryField.set(client, objectSSLSocketFactory);
+                        } else {
+                            if (socketFactory == null)
+                                socketFactory = Tools.getSLLContext(dataPath + File.separator + "ca.crt").getSocketFactory();
                             proxyField.set(client, proxy);
-                    } else {
-                        proxyField.set(client, null);
+                            sslSocketFactoryField.set(client, socketFactory);
+                        }
                     }
                 }
             }
@@ -85,25 +129,21 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
         findAndHookMethod(classMainActivity, context.getClassLoader(), "onCreate", Bundle.class, new XC_MethodHook() {
             @Override
             protected void afterHookedMethod(MethodHookParam param) {
-                ExtraDao.getInstance(context).saveExtra("ScriptRunning", "0");
-                initScript(context);
+                final Context neteaseContext = (Context) param.thisObject;
+                initScript(neteaseContext);
             }
         });
 
         findAndHookMethod(classMainActivity, context.getClassLoader(), "onDestroy", new XC_MethodHook() {
             @Override
-            protected void beforeHookedMethod(MethodHookParam param) {
+            protected void afterHookedMethod(MethodHookParam param) {
                 Command stop = new Command(0, STOP_PROXY);
                 Tools.shell(context, stop);
-                dataPath = null;
             }
         });
     }
 
     private void initScript(final Context c) {
-        if (dataPath != null)
-            return;
-
         long scriptLastUpdateTime = Long.parseLong(ExtraDao.getInstance(c).getExtra("script_time"));
         long nodeSize = Long.parseLong(ExtraDao.getInstance(c).getExtra("node_size"));
         String scriptFilePath = Setting.getScriptFile(), nodeFilePath = Setting.getNodeFile();
@@ -115,7 +155,6 @@ else if (!scriptFile.exists() && scriptLastUpdateTime < 0)
         else if (!nodeFile.exists())
             Tools.showToastOnLooper(c, "node文件不存在!");
         else {
-            dataPath = c.getFilesDir().getAbsolutePath() + File.separator + "script";
             File dataFile = new File(dataPath);
             if (!dataFile.exists())
                 dataFile.mkdirs();
@@ -134,19 +173,30 @@ else if (!nodeFile.exists())
                 Tools.shell(c, auth);
             }
 
-            Command start = new Command(0, STOP_PROXY, "cd " + dataPath, START_PROXY) {
-                @Override
-                public void commandOutput(int id, String line) {
-                    if (line.contains("Error")) {
-                        Tools.showToastOnLooper(c, "运行失败,错误为:" + line);
-                    } else if (line.contains("HTTP Server running")) {
-                        Tools.showToastOnLooper(c, "UnblockNeteaseMusic运行成功");
-                        ExtraDao.getInstance(context).saveExtra("ScriptRunning", "1");
+            startScrip(c);
+        }
+    }
+
+    private void startScrip(final Context c) {
+        Command start = new Command(0, STOP_PROXY, "cd " + dataPath, START_PROXY) {
+            @Override
+            public void commandOutput(int id, String line) {
+                if (line.contains("Error")) {
+                    ExtraDao.getInstance(context).saveExtra("ScriptRunning", "0");
+                    Tools.showToastOnLooper(c, "运行失败,错误为:" + line);
+                } else if (line.contains("HTTP Server running")) {
+                    ExtraDao.getInstance(context).saveExtra("ScriptRunning", "1");
+                    Tools.showToastOnLooper(c, "UnblockNeteaseMusic运行成功");
+                } else if (line.contains("Killed")) {
+                    ExtraDao.getInstance(context).saveExtra("ScriptRunning", "0");
+                    if (!((Activity) c).isFinishing()) {
+                        Tools.showToastOnLooper(c, "Node被Killed,可能手机运存已耗尽,正在尝试重启……");
+                        startScrip(c);
                     }
                 }
-            };
-            Tools.shell(c, start);
-        }
+            }
+        };
+        Tools.shell(c, start);
     }
 
     /**
diff --git a/app/src/main/java/com/raincat/dolby_beta/utils/FileUnits.java b/app/src/main/java/com/raincat/dolby_beta/utils/FileUnits.java
index 5f32aec9..2ef40081 100644
--- a/app/src/main/java/com/raincat/dolby_beta/utils/FileUnits.java
+++ b/app/src/main/java/com/raincat/dolby_beta/utils/FileUnits.java
@@ -12,7 +12,6 @@
 /**
  * 
  *     author : RainCat
- *     org    : Shenzhen JingYu Network Technology Co., Ltd.
  *     e-mail : nining377@gmail.com
  *     time   : 2021/03/13
  *     desc   : 说明
diff --git a/app/src/main/java/com/raincat/dolby_beta/utils/Setting.java b/app/src/main/java/com/raincat/dolby_beta/utils/Setting.java
index 686b3e27..e185e040 100644
--- a/app/src/main/java/com/raincat/dolby_beta/utils/Setting.java
+++ b/app/src/main/java/com/raincat/dolby_beta/utils/Setting.java
@@ -76,6 +76,13 @@ public static boolean isQualityEnabled() {
         return getModuleSharedPreferences().getBoolean(getModuleResString(R.string.quality_key), defaultValue);
     }
 
+    public static boolean isWhiteEnabled() {
+        String valueString = getModuleResString(R.string.white_default_value);
+        boolean defaultValue = Boolean.parseBoolean(valueString);
+
+        return getModuleSharedPreferences().getBoolean(getModuleResString(R.string.white_key), defaultValue);
+    }
+
     public static boolean isForceEnabled() {
         String valueString = getModuleResString(R.string.force_default_value);
         boolean defaultValue = Boolean.parseBoolean(valueString);
diff --git a/app/src/main/java/com/raincat/dolby_beta/utils/Tools.java b/app/src/main/java/com/raincat/dolby_beta/utils/Tools.java
index d62c60b3..bd1d8756 100644
--- a/app/src/main/java/com/raincat/dolby_beta/utils/Tools.java
+++ b/app/src/main/java/com/raincat/dolby_beta/utils/Tools.java
@@ -25,7 +25,14 @@
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.lang.reflect.Method;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
 import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
@@ -33,6 +40,9 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+
 /**
  * 
  *     author : RainCat
@@ -335,6 +345,37 @@ public static boolean unZipFile(String zipFileString, String outPathString) {
         return true;
     }
 
+    /**
+     * 获取CA证书
+     */
+    public static SSLContext getSLLContext(String caPath) {
+        SSLContext sslContext = null;
+        try {
+            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+            File ca = new File(caPath);
+            InputStream certificate = new FileInputStream(ca);
+            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+            keyStore.load(null);
+            String certificateAlias = Integer.toString(0);
+            keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
+            sslContext = SSLContext.getInstance("TLS");
+            final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            trustManagerFactory.init(keyStore);
+            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
+        } catch (CertificateException e) {
+            e.printStackTrace();
+        } catch (KeyStoreException e) {
+            e.printStackTrace();
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (KeyManagementException e) {
+            e.printStackTrace();
+        }
+        return sslContext;
+    }
+
     /**
      * 判断是否为64bit手机
      *
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5869c964..1637c75b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -42,6 +42,11 @@
     优先获取无损音质
     仅能从酷我音源获取无损音质
 
+    white
+    true
+    白名单模式
+    白名单:仅对播放及下载api进行代理,其他功能(不变灰等)由杜比大喇叭完成\n~建议仅在白名单模式无效时关闭选项~
+
     script
     脚本ZIP压缩包路径
 
diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml
index 28cd97a5..34d3d291 100644
--- a/app/src/main/res/xml/pref_general.xml
+++ b/app/src/main/res/xml/pref_general.xml
@@ -58,6 +58,13 @@
             android:summary="@string/quality_summary"
             android:title="@string/quality_title" />
 
+        
+