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 @@
* 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 ListwhiteUrlList = 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" /> ++