diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index a2de179e31..5db7d176b9 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu-latest, windows-latest ] + os: [ ubuntu-latest ] steps: - name: Check out @@ -28,6 +28,11 @@ jobs: distribution: 'temurin' java-version: '17' cache: 'gradle' + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + - uses: seanmiddleditch/gha-setup-ninja@master + with: + version: 1.12.0 - name: Set up ccache uses: hendrikmuhs/ccache-action@v1.2 with: @@ -35,6 +40,7 @@ jobs: restore-keys: ${{ runner.os }} - name: Build with Gradle run: | + sudo rm -rf $ANDROID_HOME/cmake echo 'org.gradle.caching=true' >> gradle.properties echo 'org.gradle.parallel=true' >> gradle.properties echo 'org.gradle.vfs.watch=true' >> gradle.properties diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index c71945a4c0..7c1df324e3 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -23,6 +23,11 @@ jobs: distribution: 'temurin' java-version: 17 cache: 'gradle' + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + - uses: seanmiddleditch/gha-setup-ninja@master + with: + version: 1.12.0 - name: Retrieve version run: | echo VERSION=$(echo ${{ github.event.head_commit.id }} | head -c 10) >> $GITHUB_ENV @@ -33,6 +38,7 @@ jobs: restore-keys: ${{ runner.os }} - name: Build with Gradle run: | + sudo rm -rf $ANDROID_HOME/cmake echo 'org.gradle.caching=true' >> gradle.properties echo 'org.gradle.parallel=true' >> gradle.properties echo 'org.gradle.vfs.watch=true' >> gradle.properties diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f78ab0a73f..b3b1d32871 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -34,8 +34,11 @@ cmaker { default { targets("biliroaming") abiFilters("armeabi-v7a", "arm64-v8a", "x86") - arguments += "-DANDROID_STL=none" - cppFlags += "-Wno-c++2b-extensions" + arguments += arrayOf( + "-DANDROID_STL=none", + "-DCMAKE_CXX_STANDARD=23", + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", + ) } buildTypes { @@ -47,7 +50,7 @@ android { namespace = "me.iacn.biliroaming" compileSdk = 34 buildToolsVersion = "34.0.0" - ndkVersion = "26.0.10792818" + ndkVersion = "27.0.11718014-beta1" buildFeatures { prefab = true @@ -115,7 +118,7 @@ android { externalNativeBuild { cmake { path("src/main/jni/CMakeLists.txt") - version = "3.22.1+" + version = "3.28.0+" } } } diff --git a/app/src/main/java/me/iacn/biliroaming/BiliBiliPackage.kt b/app/src/main/java/me/iacn/biliroaming/BiliBiliPackage.kt index eedebeeb35..07967bf98c 100644 --- a/app/src/main/java/me/iacn/biliroaming/BiliBiliPackage.kt +++ b/app/src/main/java/me/iacn/biliroaming/BiliBiliPackage.kt @@ -161,6 +161,7 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex } val searchAllResponseClass by Weak { "com.bapis.bilibili.polymer.app.search.v1.SearchAllResponse" from mClassLoader } val searchVideoCardClass by Weak { "com.bapis.bilibili.polymer.app.search.v1.SearchVideoCard" from mClassLoader } + val playSpeedManager by Weak { mHookInfo.playSpeedManager from mClassLoader } val ids: Map by lazy { mHookInfo.mapIds.idsMap @@ -231,12 +232,6 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex fun getPlaybackSpeed() = mHookInfo.playerCoreService.getPlaybackSpeed.orNull - fun setPlaybackSpeed() = mHookInfo.playerCoreService.setPlaybackSpeed.orNull - - fun theseusPlayerSetSpeed() = mHookInfo.playerCoreService.theseusPlayerSetSpeed.orNull - - fun playerOnPrepare() = mHookInfo.playerCoreService.playerOnPrepare.orNull - fun urlField() = mHookInfo.okHttp.request.url.orNull fun gsonToJson() = mHookInfo.gsonHelper.toJson.orNull @@ -1136,7 +1131,7 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex dexHelper.decodeMethodIndex(it) } } ?: doSeekToMethod - } ?: return@playerCoreService + } val playerCoreServiceClass = seekToMethod.declaringClass seekTo = method { name = seekToMethod.name } class_ = class_ { name = playerCoreServiceClass.name } @@ -1173,58 +1168,34 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex dexHelper.decodeMethodIndex(it) }?.name ?: return@method } - dexHelper.findMethodUsingString( - "player_key_video_speed", - true, - -1, - 1, - "VF", - dexHelper.encodeClassIndex(playerCoreServiceClass), - null, - null, - null, - true - ).asSequence().firstOrNull()?.also { mIndex -> - dexHelper.decodeMethodIndex(mIndex)?.let { - setPlaybackSpeed = method { - name = it.name - } - } - }?.let { - dexHelper.findMethodInvoked( - it, - -1, + } + val playSpeedManagerClass = ("com.bilibili.player.tangram.basic.PlaySpeedManagerImpl" from classloader) ?: run { + val pcsFacadeClass = dexHelper.findMethodUsingString( + "Cannot switch to quality ", + false, -1, - "VF", -1, null, - null, - null, - true - ).asSequence().firstNotNullOfOrNull { - dexHelper.decodeMethodIndex(it) - }?.let { - theseusPlayerSetSpeed = method { - name = it.name - } - } - } - playerOnPrepare = method { - name = dexHelper.findMethodUsingString( - "[ijk][callback]player onPrepared", - true, - -1, -1, - "VLL", - dexHelper.encodeClassIndex(playerCoreServiceClass), null, null, null, true - ).asSequence().firstNotNullOfOrNull { - dexHelper.decodeMethodIndex(it) - }?.name ?: return@method + ).asSequence().firstNotNullOfOrNull { + dexHelper.decodeMethodIndex(it) + }?.declaringClass ?: return@run null + + val playSpeedManagerInterface = pcsFacadeClass.declaredFields.firstNotNullOfOrNull { f -> + if (f.type.isInterface && f.type.declaredMethods.size == 1) f.type else null } + classesList.filter { + it.startsWith("com.bilibili.player.tangram") + }.firstNotNullOfOrNull { c -> + c.findClass(classloader).takeIf { it.interfaces.contains(playSpeedManagerInterface) } + } + } + playSpeedManager = class_ { + name = playSpeedManagerClass?.name ?: return@class_ } generalResponse = class_ { name = "com.bilibili.okretro.GeneralResponse" diff --git a/app/src/main/java/me/iacn/biliroaming/Constant.kt b/app/src/main/java/me/iacn/biliroaming/Constant.kt index a7068d6337..c13a5ecd19 100644 --- a/app/src/main/java/me/iacn/biliroaming/Constant.kt +++ b/app/src/main/java/me/iacn/biliroaming/Constant.kt @@ -23,6 +23,5 @@ object Constant { const val CUSTOM_COLOR_KEY = "biliroaming_custom_color" const val CURRENT_COLOR_KEY = "theme_entries_current_key" const val DEFAULT_CUSTOM_COLOR = -0xe6b7d - const val infoUrl = "https://api.bilibili.com/client_info" const val zoneUrl = "https://api.bilibili.com/x/web-interface/zone" } diff --git a/app/src/main/java/me/iacn/biliroaming/SettingDialog.kt b/app/src/main/java/me/iacn/biliroaming/SettingDialog.kt index f049470df9..25e2b54f25 100644 --- a/app/src/main/java/me/iacn/biliroaming/SettingDialog.kt +++ b/app/src/main/java/me/iacn/biliroaming/SettingDialog.kt @@ -7,9 +7,12 @@ import android.app.Activity import android.app.Activity.RESULT_CANCELED import android.app.AlertDialog import android.content.ActivityNotFoundException +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.content.SharedPreferences +import android.content.res.Resources import android.graphics.Bitmap import android.graphics.Color import android.graphics.Typeface @@ -32,6 +35,7 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.* import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.Toast import androidx.documentfile.provider.DocumentFile import kotlinx.coroutines.* import me.iacn.biliroaming.BiliBiliPackage.Companion.instance @@ -104,6 +108,7 @@ class SettingDialog(context: Context) : AlertDialog.Builder(context) { findPreference("customize_dynamic")?.onPreferenceClickListener = this findPreference("filter_search")?.onPreferenceClickListener = this findPreference("filter_comment")?.onPreferenceClickListener = this + findPreference("copy_access_key")?.onPreferenceClickListener = this checkCompatibleVersion() searchItems = retrieve(preferenceScreen) checkUpdate() @@ -846,6 +851,16 @@ class SettingDialog(context: Context) : AlertDialog.Builder(context) { }.show() return true } + + private fun onCopyAccessKeyClick(): Boolean { + val manager = context.getSystemService(ClipboardManager::class.java) + + manager.setPrimaryClip(ClipData.newPlainText("access_key", instance.accessKey)) + Toast.makeText(context, R.string.copy_access_key_toast, Toast.LENGTH_SHORT).show() + + return true + } + @Deprecated("Deprecated in Java") override fun onPreferenceClick(preference: Preference) = when (preference.key) { "version" -> onVersionClick() @@ -866,6 +881,7 @@ class SettingDialog(context: Context) : AlertDialog.Builder(context) { "default_speed" -> onDefaultSpeedClick() "filter_search" -> onFilterSearchClick() "filter_comment" -> onFilterCommentClick() + "copy_access_key" -> onCopyAccessKeyClick() else -> false } } @@ -1060,7 +1076,16 @@ class SettingDialog(context: Context) : AlertDialog.Builder(context) { } fun show(context: Context) { - SettingDialog(context).show() + try { + SettingDialog(context).show() + } catch (e: Resources.NotFoundException) { + AlertDialog.Builder(context) + .setTitle("需要重启") + .setMessage("哔哩漫游更新了") + .setPositiveButton("重启") { _, _ -> + restartApplication(context as Activity) + }.show() + } } const val SPLASH_SELECTION = 0 diff --git a/app/src/main/java/me/iacn/biliroaming/XposedInit.kt b/app/src/main/java/me/iacn/biliroaming/XposedInit.kt index 9e2739253f..f358c6c8bd 100644 --- a/app/src/main/java/me/iacn/biliroaming/XposedInit.kt +++ b/app/src/main/java/me/iacn/biliroaming/XposedInit.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.MainScope import kotlinx.coroutines.future.future import me.iacn.biliroaming.hook.* import me.iacn.biliroaming.utils.* -import org.json.JSONObject import java.util.concurrent.CompletableFuture @@ -69,16 +68,10 @@ class XposedInit : IXposedHookLoadPackage, IXposedHookZygoteInit { ) country = MainScope().future(Dispatchers.IO) { - fun JSONObject.optStringFix(name: String, fallback: String = "") = - if (isNull(name)) fallback else optString(name, fallback) - when (fetchJson(Constant.infoUrl)?.optJSONObject("data") - ?.optStringFix("country").orEmpty().ifEmpty { - fetchJson(Constant.zoneUrl)?.optJSONObject("data") - ?.optStringFix("country") - }) { - "中国" -> "cn" - "香港", "澳门" -> "hk" - "台湾" -> "tw" + when (fetchJson(Constant.zoneUrl)?.optJSONObject("data")?.optInt("country_code", 0)?.or(0)) { + 86 -> "cn" + 852, 853 -> "hk" + 886 -> "tw" else -> "global" }.also { Log.d("当前地区: $it") } } @@ -127,6 +120,7 @@ class XposedInit : IXposedHookLoadPackage, IXposedHookZygoteInit { startHook(PublishToFollowingHook(lpparam.classLoader)) startHook(UposReplaceHook(lpparam.classLoader)) startHook(SpeedHook(lpparam.classLoader)) + startHook(MultiWindowHook(lpparam.classLoader)) } lpparam.processName.endsWith(":web") -> { diff --git a/app/src/main/java/me/iacn/biliroaming/hook/AllowMiniPlayHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/AllowMiniPlayHook.kt index ce575e4289..0235bcfd12 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/AllowMiniPlayHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/AllowMiniPlayHook.kt @@ -2,6 +2,7 @@ package me.iacn.biliroaming.hook import me.iacn.biliroaming.utils.from import me.iacn.biliroaming.utils.getStaticObjectField +import me.iacn.biliroaming.utils.hookBeforeAllConstructors import me.iacn.biliroaming.utils.hookBeforeConstructor import me.iacn.biliroaming.utils.sPrefs @@ -10,18 +11,27 @@ class AllowMiniPlayHook(classLoader: ClassLoader) : BaseHook(classLoader) { if (!sPrefs.getBoolean("main_func", false)) return if (sPrefs.getBoolean("allow_mini_play", false)) { - "com.bilibili.lib.media.resource.PlayConfig\$PlayMenuConfig".from(mClassLoader) - ?.hookBeforeConstructor( - Boolean::class.javaPrimitiveType, + val miniPlayerType = "com.bilibili.lib.media.resource.PlayConfig\$PlayConfigType" + .from(mClassLoader)?.getStaticObjectField("MINIPLAYER") + "com.bilibili.lib.media.resource.PlayConfig\$PlayMenuConfig".from(mClassLoader)?.run { + hookBeforeConstructor( + Boolean::class.javaPrimitiveType, + "com.bilibili.lib.media.resource.PlayConfig\$PlayConfigType" + ) { param -> + val type = param.args[1] + if (type == miniPlayerType) + param.args[0] = true + } + hookBeforeConstructor(Boolean::class.javaPrimitiveType, + "com.bilibili.lib.media.resource.PlayConfig\$PlayConfigType", + List::class.java ) { param -> val type = param.args[1] - val miniPlayerType = - "com.bilibili.lib.media.resource.PlayConfig\$PlayConfigType" - .from(mClassLoader)?.getStaticObjectField("MINIPLAYER") if (type == miniPlayerType) param.args[0] = true } + } } } } diff --git a/app/src/main/java/me/iacn/biliroaming/hook/BangumiPlayUrlHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/BangumiPlayUrlHook.kt index ca4991adfc..4e0c51f7f4 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/BangumiPlayUrlHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/BangumiPlayUrlHook.kt @@ -32,6 +32,8 @@ class BangumiPlayUrlHook(classLoader: ClassLoader) : BaseHook(classLoader) { val qnApplied = AtomicBoolean(false) private const val PGC_ANY_MODEL_TYPE_URL = "type.googleapis.com/bilibili.app.playerunite.pgcanymodel.PGCAnyModel" + private const val UGC_ANY_MODEL_TYPE_URL = + "type.googleapis.com/bilibili.app.playerunite.ugcanymodel.UGCAnyModel" private val codecMap = mapOf(CodeType.CODE264 to 7, CodeType.CODE265 to 12, CodeType.CODEAV1 to 13) val supportedPlayArcIndices = arrayOf( @@ -168,7 +170,7 @@ class BangumiPlayUrlHook(classLoader: ClassLoader) : BaseHook(classLoader) { val req = PlayViewReq.parseFrom(serializedRequest) val seasonId = req.seasonId.toString().takeIf { it != "0" } ?: lastSeasonInfo["season_id"] ?: "0" - val (thaiSeason, thaiEp) = getThaiSeason(seasonId, req.epId) + val (thaiSeason, thaiEp) = getSeasonLazy(seasonId, req.epId) val content = getPlayUrl(reconstructQuery(req, response, thaiEp)) content?.let { Log.toast("已从代理服务器获取播放地址\n如加载缓慢或黑屏,可去漫游设置中测速并设置 UPOS") @@ -244,7 +246,7 @@ class BangumiPlayUrlHook(classLoader: ClassLoader) : BaseHook(classLoader) { val req = PlayViewReq.parseFrom(serializedRequest) val seasonId = req.seasonId.toString().takeIf { it != "0" } ?: lastSeasonInfo["season_id"] ?: "0" - val (thaiSeason, thaiEp) = getThaiSeason(seasonId, req.epId) + val (thaiSeason, thaiEp) = getSeasonLazy(seasonId, req.epId) val content = getPlayUrl(reconstructQuery(req, response, thaiEp)) content?.let { Log.toast("已从代理服务器获取播放地址\n如加载缓慢或黑屏,可去漫游设置中测速并设置 UPOS") @@ -320,7 +322,7 @@ class BangumiPlayUrlHook(classLoader: ClassLoader) : BaseHook(classLoader) { try { val serializedRequest = request.callMethodAs("toByteArray") val req = PlayViewUniteReq.parseFrom(serializedRequest) - val (thaiSeason, thaiEp) = getThaiSeason(seasonId, reqEpId) + val (thaiSeason, thaiEp) = getSeasonLazy(seasonId, reqEpId) val content = getPlayUrl(reconstructQueryUnite(req, supplement, thaiEp)) content?.let { Log.toast("已从代理服务器获取播放地址\n如加载缓慢或黑屏,可去漫游设置中测速并设置 UPOS") @@ -396,7 +398,7 @@ class BangumiPlayUrlHook(classLoader: ClassLoader) : BaseHook(classLoader) { try { val serializedRequest = request.callMethodAs("toByteArray") val req = PlayViewUniteReq.parseFrom(serializedRequest) - val (thaiSeason, thaiEp) = getThaiSeason(seasonId, reqEpId) + val (thaiSeason, thaiEp) = getSeasonLazy(seasonId, reqEpId) val content = getPlayUrl(reconstructQueryUnite(req, supplement, thaiEp)) content?.let { Log.toast("已从代理服务器获取播放地址\n如加载缓慢或黑屏,可去漫游设置中测速并设置 UPOS") @@ -440,11 +442,11 @@ class BangumiPlayUrlHook(classLoader: ClassLoader) : BaseHook(classLoader) { } } - private fun getThaiSeason( + private fun getSeasonLazy( seasonId: String, reqEpId: Long ): Pair, Lazy> { val season = lazy { - getSeason(mapOf("season_id" to seasonId, "ep_id" to reqEpId.toString()), true) + getSeason(mapOf("season_id" to seasonId, "ep_id" to reqEpId.toString()), null) ?.toJSONObject()?.optJSONObject("result") ?: throw CustomServerException(mapOf("解析服务器错误" to "无法获取剧集信息")) } diff --git a/app/src/main/java/me/iacn/biliroaming/hook/BangumiSeasonHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/BangumiSeasonHook.kt index a01a7bbf51..b70de56877 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/BangumiSeasonHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/BangumiSeasonHook.kt @@ -23,7 +23,6 @@ import me.iacn.biliroaming.network.BiliRoamingApi.getAreaSearchBangumi import me.iacn.biliroaming.network.BiliRoamingApi.getContent import me.iacn.biliroaming.network.BiliRoamingApi.getSeason import me.iacn.biliroaming.network.BiliRoamingApi.getSpace -import me.iacn.biliroaming.network.BiliRoamingApi.getThaiSeason import me.iacn.biliroaming.utils.* import org.json.JSONObject import java.io.InputStream @@ -63,6 +62,8 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { private const val PGC_ANY_MODEL_TYPE_URL = "type.googleapis.com/bilibili.app.viewunite.pgcanymodel.ViewPgcAny" + private const val UGC_ANY_MODEL_TYPE_URL = + "type.googleapis.com/bilibili.app.viewunite.ugcanymodel.ViewUgcAny" private val needUnlockDownload = sPrefs.getBoolean("allow_download", false) } @@ -251,7 +252,7 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { val newStream = json?.toString()?.byteInputStream() body.setObjectField( instance.okio(), - okioBuffer?.javaClass?.newInstance()?.apply { + okioBuffer?.javaClass?.new()?.apply { callMethod(instance.okioReadFrom(), newStream) }) body.setLongField(instance.okioLength(), newStream?.available()?.toLong() ?: 0L) @@ -336,15 +337,15 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { if (instance.networkExceptionClass?.isInstance(param.throwable) == true) return@hookAfterMethod val response = param.result if (response == null) { - Log.toast("发现东南亚区域番剧,尝试解锁……") val req = param.args[0].callMethodAs("toByteArray").let { ViewUniteReq.parseFrom(it) } - fixViewProto(req)?.toByteArray()?.let { + val av = (if (req.hasAid()) req.aid.takeIf { it != 0L } else if (req.hasBvid()) bv2av(req.bvid) else null)?.toString() + fixViewProto(req, av)?.toByteArray()?.let { param.result = - "com.bapis.bilibili.app.viewunite.v1.ViewReply".from(mClassLoader) - ?.callStaticMethod("parseFrom", it) - } ?: Log.toast("东南亚区域番剧解锁失败!", force = true) + "com.bapis.bilibili.app.viewunite.v1.ViewReply".from(mClassLoader) + ?.callStaticMethod("parseFrom", it) + } ?: Log.toast("解锁失败!", force = true) return@hookAfterMethod } val supplementAny = response.callMethod("getSupplement") @@ -378,6 +379,7 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { "com.bilibili.bangumi.ui.page.search.BangumiSearchResultFragment".from(mClassLoader) ?: "com.bilibili.search.result.bangumi.ogv.BangumiSearchResultFragment" .from(mClassLoader) ?: "com.bilibili.search.ogv.OgvSearchResultFragment" + .from(mClassLoader) ?: "com.bilibili.search2.ogv.OgvSearchResultFragment" .from(mClassLoader) searchResultFragment?.run { val intTypeFields = declaredFields.filter { @@ -501,15 +503,8 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { (sPrefs.getBoolean("search_area_bangumi", false) || sPrefs.getBoolean("search_area_movie", false)) ) { - Class.forName( - "com.bilibili.search.result.pages.BiliMainSearchResultPage\$PageTypes", - true, mClassLoader - ).reconstructPageType() - if (getVersionCode(packageName) < 7390000) return - Class.forName( - "com.bilibili.search2.result.pages.BiliMainSearchResultPage\$PageTypes", - true, mClassLoader - ).reconstructPageType() + "com.bilibili.search.result.pages.BiliMainSearchResultPage\$PageTypes".findClassOrNull(mClassLoader)?.reconstructPageType() + "com.bilibili.search2.result.pages.BiliMainSearchResultPage\$PageTypes".findClassOrNull(mClassLoader)?.reconstructPageType() } } @@ -801,7 +796,7 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { Log.d("Info: $lastSeasonInfo") val (newCode, newJsonResult) = getSeason( lastSeasonInfo, - jsonResult == null + jsonResult )?.toJSONObject()?.let { it.optInt("code", FAIL_CODE) to it.optJSONObject("result") } ?: (FAIL_CODE to null) @@ -1051,6 +1046,169 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { } } + private fun fixViewProto(req: ViewUniteReq, av: String?): ViewUniteReply? { + av ?: return fixViewProto(req) + + Log.toast("发现区域限制视频,尝试解锁……") + val query = Uri.Builder().run { + appendQueryParameter("id", av) + appendQueryParameter("bvid", req.bvid.toString()) + appendQueryParameter("from", req.from.toString()) + appendQueryParameter("trackid", req.trackId.toString()) + appendQueryParameter("ad_extra", req.adExtra.toString()) + appendQueryParameter("qn", req.playerArgs.qn.toString()) + appendQueryParameter("fnver", req.playerArgs.fnver.toString()) + appendQueryParameter("fnval", req.playerArgs.fnval.toString()) + appendQueryParameter("force_host", req.playerArgs.forceHost.toString()) + appendQueryParameter("spmid", req.spmid.toString()) + build() + }.query + + BiliRoamingApi.getPagelist(av) ?: return null + + Log.toast("发现区域限制视频,尝试解锁……") + + val content = BiliRoamingApi.getView(query)?.toJSONObject() ?: return null + val result = content.optJSONObject("v2_app_api") ?: return null + Log.w(result) + return viewUniteReply { + arc = viewUniteArc { + stat = viewUniteStat { + result.optJSONObject("stat")?.run { + reply = optLong("reply") + fav = optLong("favorite") + coin = optLong("coin") + share = optLong("share") + like = optLong("like") + } + } + result.run { + aid = optLong("aid") + copyright = optInt("copyright") + duration = optLong("duration") + title = optString("title") + typeId = optLong("tid") + cover = optString("pic") + bvid = optString("bvid") + cid = optLong("cid") + } + right = viewUniteArcRights { + download = true + onlyVipDownload = true + } + } + supplement = any { + typeUrl = UGC_ANY_MODEL_TYPE_URL + value = viewUgcAny { + shareSubtitle = result.optString("share_subtitle") + shortLink = result.optString("short_link") + + val pages = result.optJSONArray("pages") + for (page in pages.orEmpty()) { + this.pages += ugcPage { + page.run { + dlSubtitle = optString("download_subtitle") + dlTitle = optString("download_title") + cid = optLong("cid") + part = optString("part") + duration = optLong("duration") + dimension = ugcDimension { + optJSONObject("dimension")?.run { + width = optLong("width") + height = optLong("height") + rotate = optLong("rotate") + } + } + } + } + } + }.toByteString() + } + viewBase = viewBase { + bizType = 1 + } + tab = tab { + tabModule += tabModule { + tabType = TabType.TAB_INTRODUCTION + introduction = introductionTab { + title = "简介" + modules += module { + type = ModuleType.OWNER + } + modules += module { + type = ModuleType.UGC_HEADLINE + headLine = headline { + this.content = result.optString("title") + } + } + modules += module { + type = ModuleType.UGC_INTRODUCTION + ugcIntroduction = ugcIntroduction { + desc += descV2 { + text = result.optString("desc") + type = DescType.DescTypeText + } + pubdate = result.optLong("ctime") + val tags = result.optJSONArray("tag") + for (tag in tags.orEmpty()) { + this.tags += ugcTag { + tag.run { + id = optLong("tag_id") + name = optString("tag_name") + tagType = optString("tag_type") + uri = optString("uri") + } + } + } + } + } + addKingPosition() + } + } + tabModule += tabModule { + tabType = TabType.TAB_REPLY + reply = replyTab { + title = "评论" + replyStyle = replyStyle { + badgeType = 0L + } + } + } + } + owner = viewUniteOwner { + result.optJSONObject("owner_ext")?.run { + officialVerify = viewUniteOfficialVerify { + optJSONObject("official_verify")?.run { + type = optInt("type") + desc = optString("desc") + } + } + vip = viewUniteVip { + optJSONObject("vip")?.run { + type = optInt("vipType") + isVip = if (optInt("vipStatus") != 0) 1 else 0 + status = optInt("vipStatus") + themeType = optInt("themeType") + vipLabel = viewUniteVipLabel { + optJSONObject("label")?.run { + path = optString("path") + text = optString("text") + labelTheme = optString("label_theme") + } + } + } + } + fans = optLong("fans").toString() + arcCount = optString("arc_count") + } + result.optJSONObject("owner")?.run { + title = optString("name") + face = optString("face") + mid = optLong("mid") + } + } + } + } private fun fixViewProto(resp: Any, supplement: ViewPgcAny) { val isAreaLimit = supplement.ogvData.rights.areaLimit != 0 @@ -1092,12 +1250,12 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { Tab.parseFrom(it) }.copy { val newTabModule = tabModule.map { tabModule -> - tabModule.copy { - if (!hasIntroduction()) return@copy + tabModule.copy tab_copy@ { + if (!hasIntroduction()) return@tab_copy introduction = introduction.copy { val newModules = modules.map { module -> - module.copy { - if (!hasSectionData()) return@copy + module.copy module_copy@ { + if (!hasSectionData()) return@module_copy sectionData = sectionData.copy { val newEpisodes = episodes.map { it.copy { @@ -1136,7 +1294,31 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { } } + private fun IntroductionTabKt.Dsl.addKingPosition() { + modules += module { + type = ModuleType.KING_POSITION + kingPosition = kingPosition { + kingPos += kingPos { + type = KingPos.KingPositionType.LIKE + } + kingPos += kingPos { + type = KingPos.KingPositionType.COIN + } + kingPos += kingPos { + type = KingPos.KingPositionType.FAV + } + kingPos += kingPos { + type = KingPos.KingPositionType.CACHE + } + kingPos += kingPos { + type = KingPos.KingPositionType.SHARE + } + } + } + } + private fun fixViewProto(req: ViewUniteReq): ViewUniteReply? { + Log.toast("发现东南亚区域番剧,尝试解锁……") val reqEpId = req.extraContentMap["ep_id"]?.also { lastSeasonInfo.clear() lastSeasonInfo["ep_id"] = it @@ -1146,7 +1328,7 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { lastSeasonInfo["season_id"] = it } ?: "0" - val seasonInfo = getThaiSeason(reqSeasonId, reqEpId)?.let { + val seasonInfo = getSeason(mapOf("season_id" to reqSeasonId, "ep_id" to reqEpId), null)?.toJSONObject()?.let { val eCode = it.optLong("code") if (eCode != 0L) { Log.e("Invalid thai season info reply, code $eCode, message ${it.optString("message")}") @@ -1172,16 +1354,16 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { } } - val introductionTab = IntroductionTab.newBuilder().apply { + val introductionTab = introductionTab { title = "简介" - module { + modules += module { type = ModuleType.OGV_TITLE ogvTitle = ogvTitle { title = seasonInfo.optString("title") reserveId = 0 } - }.let { addModules(it) } - module { + } + modules += module { type = ModuleType.OGV_INTRODUCTION ogvIntroduction = ogvIntroduction { followers = @@ -1193,12 +1375,13 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { value = seasonInfo.optJSONObject("stat")?.optLong("views") ?: 0 } } - }.let { addModules(it) } + } + addKingPosition() // seasons seasonInfo.optJSONObject("series")?.optJSONArray("seasons")?.takeIf { it.length() > 0 } ?.let { seasonArray -> - module { + modules += module { type = ModuleType.OGV_SEASONS ogvSeasons = ogvSeasons { seasonArray.iterator().forEach { season -> @@ -1210,7 +1393,7 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { } } } - }.let { addModules(it) } + } } val reconstructSectionData = { module: JSONObject -> @@ -1245,6 +1428,7 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { height = optLong("height") } } + duration = episode.optInt("duration") epId = episode.optLong("id") epIndex = episode.optInt("index") from = episode.optString("from") @@ -1285,23 +1469,32 @@ class BangumiSeasonHook(classLoader: ClassLoader) : BaseHook(classLoader) { seasonInfo.optJSONArray("modules")?.iterator()?.forEach { module -> val style = module.optString("style") if (style == "positive") { - module { + modules += module { type = ModuleType.POSITIVE sectionData = reconstructSectionData(module) - }.let { addModules(it) } + } } else { - module { + modules += module { type = ModuleType.SECTION sectionData = reconstructSectionData(module) - }.let { addModules(it) } + } } } - }.build() + } val tabDefault = tab { - tabModule.add(tabModule { + tabModule += tabModule { tabType = TabType.TAB_INTRODUCTION introduction = introductionTab - }) + } + tabModule += tabModule { + tabType = TabType.TAB_REPLY + reply = replyTab { + replyStyle = replyStyle { + badgeType = 0L + } + title = "评论" + } + } } val viewPgcAny = viewPgcAny { diff --git a/app/src/main/java/me/iacn/biliroaming/hook/CommentImageHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/CommentImageHook.kt index 18e7d0727a..a4176f4119 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/CommentImageHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/CommentImageHook.kt @@ -21,7 +21,7 @@ class CommentImageHook(classLoader: ClassLoader) : BaseHook(classLoader) { companion object { fun saveImage(url: String) = runCatching { URL(url).openStream().use { stream -> - val relativePath = "${Environment.DIRECTORY_PICTURES}${File.separator}bilibili" + val relativePath = "${Environment.DIRECTORY_PICTURES}${File.separator}bili" val fullFilename = url.substringAfterLast('/') val filename = fullFilename.substringBeforeLast('.') @@ -41,7 +41,7 @@ class CommentImageHook(classLoader: ClassLoader) : BaseHook(classLoader) { val path = File( Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES - ), "bilibili" + ), "bili" ).also { it.mkdirs() } put(MediaStore.MediaColumns.DATA, File(path, fullFilename).absolutePath) } diff --git a/app/src/main/java/me/iacn/biliroaming/hook/JsonHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/JsonHook.kt index 45e27e1ff1..04832cf8ff 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/JsonHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/JsonHook.kt @@ -367,12 +367,11 @@ class JsonHook(classLoader: ClassLoader) : BaseHook(classLoader) { } } - val searchRankClass = "com.bilibili.search.api.SearchRank".findClass(mClassLoader) + val searchRankClass = "com.bilibili.search.api.SearchRank".findClassOrNull(mClassLoader) val searchGuessClass = - "com.bilibili.search.api.SearchReferral\$Guess".findClass(mClassLoader) - val searchRankV2Class = "com.bilibili.search2.api.SearchRank".from(mClassLoader) - val searchGuessV2Class = "com.bilibili.search2.api.SearchReferral\$Guess".from(mClassLoader) - val categoryClass = "tv.danmaku.bili.category.CategoryMeta".findClass(mClassLoader) + "com.bilibili.search.api.SearchReferral\$Guess".findClassOrNull(mClassLoader) + val searchRankV2Class = "com.bilibili.search2.api.SearchRank".findClassOrNull(mClassLoader) + val searchGuessV2Class = "com.bilibili.search2.api.SearchReferral\$Guess".findClassOrNull(mClassLoader) instance.fastJsonClass?.hookAfterMethod( "parseArray", diff --git a/app/src/main/java/me/iacn/biliroaming/hook/MultiWindowHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/MultiWindowHook.kt new file mode 100644 index 0000000000..7044026e13 --- /dev/null +++ b/app/src/main/java/me/iacn/biliroaming/hook/MultiWindowHook.kt @@ -0,0 +1,21 @@ +package me.iacn.biliroaming.hook + +import android.app.Activity +import me.iacn.biliroaming.utils.Log +import me.iacn.biliroaming.utils.hookBeforeAllMethods +import me.iacn.biliroaming.utils.replaceMethod +import me.iacn.biliroaming.utils.sPrefs + +class MultiWindowHook(mClassLoader: ClassLoader) : BaseHook(mClassLoader) { + override fun startHook() { + if (sPrefs.getBoolean("fake_non_multiwindow", false).not()) return + Log.d("startHook: MultiWindowHook") + Activity::class.java + .getDeclaredMethod("isInMultiWindowMode") + .replaceMethod { false } + Activity::class.java + .hookBeforeAllMethods("onMultiWindowModeChanged") { param -> + param.args[0] = false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/iacn/biliroaming/hook/PlayArcConfHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/PlayArcConfHook.kt index 42fcd97a4f..dc718da690 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/PlayArcConfHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/PlayArcConfHook.kt @@ -32,15 +32,23 @@ class PlayArcConfHook(classLoader: ClassLoader) : BaseHook(classLoader) { callMethod("setDisabled", false) callMethod("setIsSupport", true) } - instance.playerMossClass?.hookAfterMethod( - "playViewUnite", instance.playViewUniteReqClass - ) { param -> - param.result?.callMethod("getPlayArcConf") - ?.callMethodAs>("internalGetMutableArcConfs") + val arcConfs = "com.bapis.bilibili.playershared.PlayArcConf" + .from(mClassLoader)?.callStaticMethod("getDefaultInstance") + arcConfs?.callMethodAs>("internalGetMutableArcConfs") ?.run { // CASTCONF,BACKGROUNDPLAY,SMALLWINDOW,LISTEN intArrayOf(2, 9, 23, 36).forEach { this[it] = supportedArcConf } } + + instance.playerMossClass?.hookAfterMethod( + "playViewUnite", instance.playViewUniteReqClass + ) { param -> + param.result?.callMethod("mergePlayArcConf", arcConfs) + } + instance.playerMossClass?.hookAfterMethod("playViewUnite", instance.playViewUniteReqClass, instance.mossResponseHandlerClass) { param -> + param.args[1] = param.args[1].mossResponseHandlerProxy { resp -> + resp?.callMethod("mergePlayArcConf", arcConfs) + } } "com.bapis.bilibili.app.listener.v1.ListenerMoss".from(mClassLoader)?.run { val playlistHook = { param: MethodHookParam -> diff --git a/app/src/main/java/me/iacn/biliroaming/hook/ProtoBufHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/ProtoBufHook.kt index 61274022f3..464f569149 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/ProtoBufHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/ProtoBufHook.kt @@ -306,7 +306,7 @@ class ProtoBufHook(classLoader: ClassLoader) : BaseHook(classLoader) { if (videoCard.getLongField("mid_") in searchFilterUid) return@filter false if (videoCard.getObjectFieldAs("author_") in searchFilterUpNames) return@filter false if (searchFilterContentRegexMode) { - if (searchFilterContentRegexes.any { it.matches(videoCard.getObjectFieldAs("title_")) }) + if (searchFilterContentRegexes.any { it.containsMatchIn(videoCard.getObjectFieldAs("title_")) }) return@filter false } else { if (searchFilterContents.any { videoCard.getObjectFieldAs("title_").contains(it) }) return@filter false diff --git a/app/src/main/java/me/iacn/biliroaming/hook/ShareHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/ShareHook.kt index a97ad16434..321a0abbfb 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/ShareHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/ShareHook.kt @@ -49,10 +49,10 @@ class ShareHook(classLoader: ClassLoader) : BaseHook(classLoader) { it[0] == "start_progress" -> "start_progress=${it[1]}&t=${it[1].toLong() / 1000}" else -> null } - }.joinToString("&", postfix = "&unique_k=114514") + }.joinToString("&", postfix = "&unique_k=2333") newUrl.encodedQuery(query) } else { - newUrl.appendQueryParameter("unique_k", "114514") + newUrl.appendQueryParameter("unique_k", "2333") } return newUrl.build().toString() } @@ -69,7 +69,7 @@ class ShareHook(classLoader: ClassLoader) : BaseHook(classLoader) { it.startsWith("https://b23.tv") || it.startsWith("http://b23.tv") }?.let { val targetUrl = Uri.parse(it).buildUpon().query("").build().toString() - param.result = targetUrl.resolveB23URL() + param.result = targetUrl.resolveB23URL().also { r -> param.thisObject.setObjectField("link", r) } } } hookAfterMethod("getContent") { param -> @@ -78,8 +78,14 @@ class ShareHook(classLoader: ClassLoader) : BaseHook(classLoader) { contentUrlPattern.matchEntire(it)?.groups?.get(1)?.value }?.let { contentUrl -> val resolvedUrl = (param.thisObject.getObjectField("link")?.let { it as String } ?: contentUrl) - .resolveB23URL() - param.result = content.replace(contentUrl, transformUrl(resolvedUrl, miniProgramEnabled)) + .let { + if (it.startsWith("https://b23.tv") || it.startsWith("http://b23.tv")) + it.resolveB23URL() + else it + } + param.result = content.replace(contentUrl, transformUrl(resolvedUrl, miniProgramEnabled)).also { r -> + param.thisObject.setObjectField("content", r) + } } } } diff --git a/app/src/main/java/me/iacn/biliroaming/hook/SpeedHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/SpeedHook.kt index 84109e12a6..fd1f368493 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/SpeedHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/SpeedHook.kt @@ -3,29 +3,24 @@ package me.iacn.biliroaming.hook import me.iacn.biliroaming.BiliBiliPackage.Companion.instance import me.iacn.biliroaming.utils.Log import me.iacn.biliroaming.utils.callMethod -import me.iacn.biliroaming.utils.hookAfterMethod +import me.iacn.biliroaming.utils.callMethodOrNullAs +import me.iacn.biliroaming.utils.getObjectField +import me.iacn.biliroaming.utils.hookAfterAllConstructors import me.iacn.biliroaming.utils.sPrefs class SpeedHook(classLoader: ClassLoader) : BaseHook(classLoader) { - override fun startHook() { Log.d("startHook: SpeedHook") val defaultPlaybackSpeed = sPrefs.getInt("default_speed", 100) / 100f if (defaultPlaybackSpeed == 1f) return - instance.playerCoreServiceV2Class?.hookAfterMethod( - instance.playerOnPrepare(), - instance.playerCoreServiceV2Class, - "tv.danmaku.ijk.media.player.IMediaPlayer" - ) { - val currentPlaybackSpeed = it.args[0].callMethod(instance.getPlaybackSpeed(), false) - if (currentPlaybackSpeed == 1.0f) { - instance.setPlaybackSpeed() - ?.let { mName -> - it.args[0].callMethod(mName, defaultPlaybackSpeed) - Log.toast("已设置 $defaultPlaybackSpeed 倍速") - } - instance.theseusPlayerSetSpeed() - ?.let { mName -> it.args[0].callMethod(mName, defaultPlaybackSpeed) } + instance.playSpeedManager?.hookAfterAllConstructors { + for (f in it.thisObject.javaClass.declaredFields) { + val o = it.thisObject.getObjectField(f.name) + val v = o?.callMethodOrNullAs("getValue") ?: continue + if (v != 1f) continue + o.callMethod("setValue", defaultPlaybackSpeed) + Log.toast("已设置 $defaultPlaybackSpeed 倍速") + break } } } diff --git a/app/src/main/java/me/iacn/biliroaming/hook/SplashHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/SplashHook.kt index 57365a5eb8..de6ab2ac31 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/SplashHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/SplashHook.kt @@ -1,5 +1,7 @@ package me.iacn.biliroaming.hook +import android.content.res.Configuration +import android.graphics.Color import android.net.Uri import android.os.Bundle import android.view.View @@ -14,7 +16,7 @@ class SplashHook(classLoader: ClassLoader) : BaseHook(classLoader) { "custom_splash_logo", false ) - && !sPrefs.getBoolean("full_splash", false) + && !sPrefs.getBoolean("full_splash", false) && !sPrefs.getBoolean("auto_dark_splash", false) ) return Log.d("startHook: Splash") @@ -34,6 +36,14 @@ class SplashHook(classLoader: ClassLoader) : BaseHook(classLoader) { Bundle::class.java ) { param -> val view = param.args[0] as View + val containerId = getId("splash_container") + if (sPrefs.getBoolean("auto_dark_splash", false)) + view.findViewById(containerId) + .setBackgroundColor( + if (view.resources.configuration.uiMode.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) + Color.BLACK + else Color.WHITE + ) if (sPrefs.getBoolean("custom_splash", false)) { val brandId = getId("brand_splash") val fullId = getId("full_brand_splash") diff --git a/app/src/main/java/me/iacn/biliroaming/network/BiliRoamingApi.kt b/app/src/main/java/me/iacn/biliroaming/network/BiliRoamingApi.kt index 91c86d8b5e..63b2029e65 100644 --- a/app/src/main/java/me/iacn/biliroaming/network/BiliRoamingApi.kt +++ b/app/src/main/java/me/iacn/biliroaming/network/BiliRoamingApi.kt @@ -31,13 +31,10 @@ import java.util.zip.InflaterInputStream * Email i@iacn.me */ object BiliRoamingApi { - private const val BILI_SEASON_URL = "api.bilibili.com/pgc/view/web/season" - private const val BILI_HIDDEN_SEASON_URL = "bangumi.bilibili.com/view/web_api/season" + private const val BILI_SEASON_URL = "api.bilibili.com/pgc/view/v2/app/season" private const val BILI_SEARCH_URL = "/x/v2/search/type" private const val BILIPLUS_VIEW_URL = "www.biliplus.com/api/view" - private const val BILI_REVIEW_URL = "api.bilibili.com/pgc/review/user" - private const val BILI_USER_STATUS_URL = "api.bilibili.com/pgc/view/web/season/user/status" - private const val BILI_MEDIA_URL = "bangumi.bilibili.com/view/web_api/media" + private const val BILI_MEDIA_URL = "www.bilibili.com/bangumi/media/md" private const val BILI_SECTION_URL = "api.bilibili.com/pgc/web/season/section" private const val BILI_CARD_URL = "https://account.bilibili.com/api/member/getCardByMid" private const val BILI_PAGELIST = "api.bilibili.com/x/player/pagelist" @@ -59,125 +56,75 @@ object BiliRoamingApi { const val mainlandTestParams = "cid=13073143&ep_id=100615&otype=json&fnval=16&module=pgc&platform=android&test=true" - private val seasonCache: AtomicReference, CountDownLatch>?> = + private val seasonCache: AtomicReference, CountDownLatch>?> = AtomicReference(null) @JvmStatic - fun getSeason(info: Map, hiddenHint: Boolean): String? { - val seasonId = info.getOrDefault("season_id", null)?.toInt() + fun getSeason(info: Map, season: JSONObject?): String? { + val seasonId = info.getOrDefault("season_id", null)?.toInt() ?: return null val cache = seasonCache.get() - val cacheTuple = if (seasonId != null && seasonId != 0) { + val cacheTuple = if (seasonId != 0) { if (cache?.first == seasonId) { cache.third.await() return cache.second.get() } else { - Triple(seasonId, AtomicReference(), CountDownLatch(1)).also { + Triple(seasonId, AtomicReference(null), CountDownLatch(1)).also { seasonCache.compareAndSet(cache, it) } } } else null - var hidden = hiddenHint - val builder = Uri.Builder() - builder.scheme("https") - .encodedAuthority(if (hidden) BILI_HIDDEN_SEASON_URL else BILI_SEASON_URL) - info.filter { !it.value.isNullOrEmpty() } - .forEach { builder.appendQueryParameter(it.key, it.value) } - var seasonJson = getContent(builder.toString())?.toJSONObject()?.let { - if (it.optInt("code") == -404) { - hidden = true - getContent( - builder.encodedAuthority(BILI_HIDDEN_SEASON_URL).toString() - )?.toJSONObject() - } else it + + var seasonJson = season + + seasonJson = seasonJson ?: run { + val query = mapOf( + "season_id" to seasonId.toString(), + ) + val content = getContent(Uri.Builder().scheme("https").encodedAuthority(BILI_SEASON_URL).encodedQuery(signQuery(query)).toString()) + content?.toJSONObject()?.optJSONObject("data") } ?: run { - cacheTuple?.third?.countDown() - return null + val content = getContent(Uri.Builder().scheme("https").encodedAuthority("${BILI_MEDIA_URL}${seasonId}").toString()) ?: return@run null + val json = Regex("""[\w\W]*window\.__INITIAL_STATE__=(.*);\(function\(\)[\w\W]*""").matchEntire(content)?.groupValues?.get(1)?.toJSONObject() ?: return@run null + json.optJSONObject("mediaInfo")?.also { + it.put("modules", JSONArray().put(JSONObject().put("data", JSONObject().put("seasons", it.optJSONObject("seasons"))))) + } } - var fixThailandSeasonFlag = false - seasonJson.optJSONObject("result")?.also { - if (hidden) fixHiddenSeason(it) - if (hidden || it.has("section_bottom_desc")) fixSection(it) + + seasonJson = seasonJson?.let { + fixSection(it) fixEpisodes(it) fixPrevueSection(it) reconstructModules(it) fixRight(it) - if (hidden) getExtraInfo(it, instance.accessKey) - if ((it.optJSONArray("episodes")?.length() == 0 && it.optJSONObject("publish") - ?.optInt("is_started", -1) != 0) - || (it.optJSONObject("up_info") - ?.optInt("mid") - // 677043260 Classic_Anime - // 688418886 Anime_Ongoing - ?.let { mid -> mid == 677043260 || mid == 688418886 } == true) - || (it.has("total_ep") && it.optInt("total_ep") != -1 && it.optInt("total_ep") - .toString() != it.optJSONObject("newest_ep")?.optString("index")) - ) { - fixThailandSeasonFlag = true - } - } - val thUrl = sPrefs.getString("th_server", null) - val mobiApp = sPrefs.getString("th_server_platform", platform)!! - if (thUrl != null && (seasonJson.optInt("code") == -404 || fixThailandSeasonFlag)) { - builder.scheme("https").encodedAuthority(thUrl + THAILAND_PATH_SEASON) - .appendQueryParameter("s_locale", "zh_SG") - .appendQueryParameter("access_key", instance.getCustomizeAccessKey("th_server")) - .appendQueryParameter("mobi_app", "bstar_a") - .appendQueryParameter("build", "1080003") - getContent(builder.toString(), mobiApp)?.toJSONObject()?.also { - it.optJSONObject("result")?.let { result -> - fixThailandSeason(result) - seasonJson = it + JSONObject().put("code", 0).put("result", it) + } ?: run { + val thUrl = sPrefs.getString("th_server", null) + val mobiApp = sPrefs.getString("th_server_platform", platform)!! + if (thUrl != null) { + val builder = Uri.Builder() + builder.scheme("https").encodedAuthority(thUrl) + .encodedPath(THAILAND_PATH_SEASON) + .appendQueryParameter("s_locale", "zh_SG") + .appendQueryParameter("access_key", instance.getCustomizeAccessKey("th_server")) + .appendQueryParameter("mobi_app", "bstar_a") + .appendQueryParameter("build", "1080003") + .appendQueryParameter("season_id", seasonId.toString()) + .appendQueryParameter("ep_id", info.getOrDefault("ep_id", "0")) + getContent(builder.toString(), mobiApp)?.toJSONObject()?.also { + it.optJSONObject("result")?.let { result -> + fixThailandSeason(result) + return@run it + } + checkErrorToast(it, true) } - checkErrorToast(it, true) - } - } else { - checkErrorToast(seasonJson) - } - return seasonJson.toString().also { - if (seasonJson.optInt("code", -1) == 0) - cacheTuple?.second?.set(it) - cacheTuple?.third?.countDown() - } - } - - @JvmStatic - fun getThaiSeason(seasonId: String, epId: String): JSONObject? { - val seasonIdStr = seasonId.toInt() - val cache = seasonCache.get() - val cacheTuple = if (seasonIdStr != 0) { - if (cache?.first == seasonIdStr) { - cache.third.await() - return cache.second.get().toJSONObject() } else { - Triple(seasonIdStr, AtomicReference(), CountDownLatch(1)).also { - seasonCache.compareAndSet(cache, it) - } - } - } else null - val thUrl = sPrefs.getString("th_server", null) ?: return null - val mobiApp = sPrefs.getString("th_server_platform", platform)!! - val fullUrl = Uri.Builder() - .scheme("https") - .encodedAuthority(thUrl + THAILAND_PATH_SEASON) - .appendQueryParameter("season_id", seasonId) - .appendQueryParameter("ep_id", epId) - .appendQueryParameter("s_locale", "zh_SG") - .appendQueryParameter("access_key", instance.getCustomizeAccessKey("th_server")) - .appendQueryParameter("mobi_app", "bstar_a") - .appendQueryParameter("build", "1080003") - .toString() - val seasonJson = getContent(fullUrl, mobiApp)?.toJSONObject()?.also { - it.optJSONObject("result")?.let { result -> - fixThailandSeason(result) + checkErrorToast("""{"code" = -404, "message" = "未设置泰区解析服务器"}""".toJSONObject()) } - checkErrorToast(it, true) - } ?: run { - cacheTuple?.third?.countDown() - return null + null } - return seasonJson.also { - if (seasonJson.optInt("code", -1) == 0) - cacheTuple?.second?.set(it.toString()) + return seasonJson?.toString().also { + if (seasonJson?.optInt("code", -1) == 0) + cacheTuple?.second?.set(it) cacheTuple?.third?.countDown() } } @@ -193,14 +140,11 @@ object BiliRoamingApi { val sectionJson = getContent(uri).toJSONObject().optJSONObject("result") ?: return val sections = sectionJson.optJSONArray("section") ?: return - val episodeMap = result.optJSONArray("episodes")?.iterator()?.asSequence() - ?.map { it.optInt("ep_id") to it }?.toMap() - ?: return for ((i, section) in sections.iterator().withIndex()) { section.put("episode_id", i) val newEpisodes = JSONArray() for (episode in section.optJSONArray("episodes").orEmpty()) { - newEpisodes.put(episodeMap[episode.optInt("id")] ?: episode) + newEpisodes.put(episode) } section.put("episodes", newEpisodes) } @@ -213,7 +157,7 @@ object BiliRoamingApi { val newEpisodes = JSONArray() for (episode in sectionJson.optJSONObject("main_section")?.optJSONArray("episodes") .orEmpty()) { - newEpisodes.put(episodeMap[episode.optInt("id")] ?: episode) + newEpisodes.put(episode) } result.put("episodes", newEpisodes) } @@ -234,7 +178,8 @@ object BiliRoamingApi { val thUrl = sPrefs.getString("th_server", null) ?: return null val uri = Uri.Builder() .scheme("https") - .encodedAuthority(thUrl + THAILAND_PATH_SUBTITLES) + .encodedAuthority(thUrl) + .encodedPath(THAILAND_PATH_SUBTITLES) .appendQueryParameter("ep_id", epId) .toString() return getContent(uri) @@ -248,7 +193,8 @@ object BiliRoamingApi { val hostUrl = sPrefs.getString(area + "_server", null) ?: return null val uri = Uri.Builder() .scheme("https") - .encodedAuthority(hostUrl + BILI_SEARCH_URL) + .encodedAuthority(hostUrl) + .encodedPath(BILI_SEARCH_URL) .encodedQuery( signQuery( query, mapOf( @@ -267,7 +213,8 @@ object BiliRoamingApi { val thUrl = sPrefs.getString("th_server", null) ?: return null val uri = Uri.Builder() .scheme("https") - .encodedAuthority(thUrl + THAILAND_PATH_SEARCH) + .encodedAuthority(thUrl) + .encodedPath(THAILAND_PATH_SEARCH) .encodedQuery( signQuery( query, mapOf( @@ -290,27 +237,6 @@ object BiliRoamingApi { ) } - @JvmStatic - private fun fixHiddenSeason(result: JSONObject) { - for (episode in result.optJSONArray("episodes").orEmpty()) { - val epId = episode.optString("ep_id") - episode.put( - "link", - "https://www.bilibili.com/bangumi/play/ep${episode.optString("ep_id")}" - ) - episode.put( - "long_title", - episode.optString("indexTitle", episode.optString("index_title")) - ) - episode.put("id", epId) - episode.put("title", episode.optString("index")) - episode.put("rights", BILI_RIGHT_TEMPLATE.toJSONObject()) - episode.put("status", episode.optInt("episode_status")) - episode.put("share_url", "https://www.bilibili.com/bangumi/play/ep$epId") - episode.put("short_link", "https://b23.tv/ep$epId") - } - } - @JvmStatic private fun fixPrevueSection(result: JSONObject) { result.put("prevueSection", result.optJSONObject("section")) @@ -381,90 +307,7 @@ object BiliRoamingApi { put("allow_dm", 1) } ?: run { result.put("rights", BILI_RIGHT_TEMPLATE.toJSONObject()) } } - - @JvmStatic - private fun getExtraInfo(result: JSONObject, accessKey: String?) { - val mediaId = result.optString("media_id") - getMediaInfo(result, mediaId, accessKey) - val seasonId = result.optString("season_id") - getUserStatus(result, seasonId, mediaId, accessKey) - } - - @JvmStatic - private fun getMediaInfo(result: JSONObject, mediaId: String, accessKey: String?) { - val uri = Uri.Builder() - .scheme("https") - .encodedAuthority(BILI_MEDIA_URL) - .appendQueryParameter("media_id", mediaId) - .appendQueryParameter("access_key", accessKey) - .toString() - val mediaJson = getContent(uri)?.toJSONObject() - val mediaResult = mediaJson?.optJSONObject("result") - val actors = mediaResult?.optString("actors") - result.put("actor", "{\"info\": \"$actors\", \"title\": \"角色声优\"}".toJSONObject()) - val staff = mediaResult?.optString("staff") - result.put("staff", "{\"info\": \"$staff\", \"title\": \"制作信息\"}".toJSONObject()) - for (field in listOf("alias", "areas", "origin_name", "style", "type_name")) { - result.put(field, mediaResult?.opt(field)) - } - } - - @JvmStatic - private fun getReviewInfo(userStatus: JSONObject, mediaId: String, accessKey: String?) { - val uri = Uri.Builder() - .scheme("https") - .encodedAuthority(BILI_REVIEW_URL) - .appendQueryParameter("media_id", mediaId) - .appendQueryParameter("access_key", accessKey) - .toString() - val reviewJson = getContent(uri)?.toJSONObject() - val reviewResult = reviewJson?.optJSONObject("result") - val review = reviewResult?.optJSONObject("review") - review?.put( - "article_url", - "https://member.bilibili.com/article-text/mobile?media_id=$mediaId" - ) - userStatus.put("review", review) - } - - @JvmStatic - private fun getUserStatus( - result: JSONObject, - seasonId: String, - mediaId: String, - accessKey: String? - ) { - try { - val uri = Uri.Builder() - .scheme("https") - .encodedAuthority(BILI_USER_STATUS_URL) - .appendQueryParameter("season_id", seasonId) - .appendQueryParameter("access_key", accessKey) - .toString() - val statusJson = getContent(uri)?.toJSONObject() - val statusResult = statusJson?.optJSONObject("result") - val userStatus = JSONObject() - for (field in listOf( - "follow", - "follow_status", - "pay", - "progress", - "sponsor", - "paster" - )) { - userStatus.put(field, statusResult?.opt(field)) - } - if (statusResult?.optJSONObject("vip_info")?.optInt("status") == 1) { - userStatus.put("vip", 1) - } - getReviewInfo(userStatus, mediaId, accessKey) - result.put("user_status", userStatus) - } catch (e: Throwable) { - Log.e(e) - } - } - - class CustomServerException(val errors: Map) : Throwable() { + class CustomServerException(private val errors: Map) : Throwable() { override val message: String get() = errors.asSequence().joinToString("\n") { "${it.key}: ${it.value}" }.trim() } @@ -540,7 +383,8 @@ object BiliRoamingApi { val path = if (area == "th") THAILAND_PATH_PLAYURL else PATH_PLAYURL val uri = Uri.Builder() .scheme("https") - .encodedAuthority(host + path) + .encodedAuthority(host) + .encodedPath(path) .encodedQuery(signQuery(queryString, extraMap)) .toString() getContent(uri, mobiApp)?.let { @@ -673,6 +517,7 @@ object BiliRoamingApi { if (!sPrefs.getBoolean("force_th_comment", false)) ep.optJSONObject("rights")?.put("area_limit", 1) } + ep.put("duration", 114514) episodes.put(ep) } data.put("id", sid) diff --git a/app/src/main/java/me/iacn/biliroaming/utils/Utils.kt b/app/src/main/java/me/iacn/biliroaming/utils/Utils.kt index d3d66c36fa..f96468e98a 100644 --- a/app/src/main/java/me/iacn/biliroaming/utils/Utils.kt +++ b/app/src/main/java/me/iacn/biliroaming/utils/Utils.kt @@ -51,15 +51,16 @@ val systemContext: Context return activityThread.callMethodAs("getSystemContext") } +// https://socialsisteryi.github.io/bilibili-API-collect/docs/misc/bvid_desc.html#bv-av%E7%AE%97%E6%B3%95 fun bv2av(bv: String): Long { val table = HashMap() - "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF".forEachIndexed { i, b -> + "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf".forEachIndexed { i, b -> table[b] = i } - val r = intArrayOf(11, 10, 3, 8, 4, 6).withIndex().sumOf { (i, p) -> + val r = intArrayOf(11, 10, 3, 8, 4, 6, 5, 7, 9).withIndex().sumOf { (i, p) -> table[bv[p]]!! * BigInteger.valueOf(58).pow(i).toLong() } - return (r - 8728348608).xor(177451812) + return r.and(2251799813685247L).xor(23442827791579L) } fun getPackageVersion(packageName: String) = try { diff --git a/app/src/main/jni/CMakeLists.txt b/app/src/main/jni/CMakeLists.txt index 130cb3e778..c11a7f6de5 100644 --- a/app/src/main/jni/CMakeLists.txt +++ b/app/src/main/jni/CMakeLists.txt @@ -1,6 +1,9 @@ -cmake_minimum_required(VERSION 3.4.1) +cmake_minimum_required(VERSION 3.28) project(biliroaming) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_SCAN_FOR_MODULES ON) + find_package(cxx REQUIRED CONFIG) link_libraries(cxx::cxx) diff --git a/app/src/main/jni/biliroaming.cc b/app/src/main/jni/biliroaming.cc index 5d6fbd7894..6910cc508a 100644 --- a/app/src/main/jni/biliroaming.cc +++ b/app/src/main/jni/biliroaming.cc @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -8,6 +7,10 @@ #include #include #include +#include +#include + +import dex_helper; #define LOG_TAG "BiliRoaming" #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) diff --git a/app/src/main/jni/dex_builder b/app/src/main/jni/dex_builder index b5a00f2ea9..61c4480aa2 160000 --- a/app/src/main/jni/dex_builder +++ b/app/src/main/jni/dex_builder @@ -1 +1 @@ -Subproject commit b5a00f2ea94ad4c3b92054fd53896dbb429298f9 +Subproject commit 61c4480aa2b8ab7cb8e3caf6b2b837d4dd4c45b3 diff --git a/app/src/main/proto/me/iacn/biliroaming/api.proto b/app/src/main/proto/me/iacn/biliroaming/api.proto index a5180c5bb0..9a12a3cacd 100644 --- a/app/src/main/proto/me/iacn/biliroaming/api.proto +++ b/app/src/main/proto/me/iacn/biliroaming/api.proto @@ -715,11 +715,35 @@ message ViewReply { optional UgcSeason ugc_season = 0x18; optional string vip_active = 0xd; } - +message ViewUniteOfficialVerify { + optional int32 type = 0x1; + optional string desc = 0x2; +} +message ViewUniteVipLabel { + optional string path = 0x1; + optional string text = 0x2; + optional string label_theme = 0x3; +} +message ViewUniteVip { + optional int32 type = 0x1; + optional int32 status = 0x2; + optional int32 theme_type = 0x3; + optional ViewUniteVipLabel vip_label = 0x4; + optional int32 is_vip = 0x5; +} +message ViewUniteOwner { + optional string title = 0x3; + optional string fans = 0x4; + optional string arc_count = 0x5; + optional ViewUniteVip vip = 0x9; + optional string face = 0xb; + optional int64 mid = 0xc; + optional ViewUniteOfficialVerify official_verify = 0xd; +} message ViewUniteReply { optional ViewBase view_base = 0x1; optional ViewUniteArc arc = 0x2; - // optional Owner owner = 0x4; + optional ViewUniteOwner owner = 0x4; optional Tab tab = 0x5; optional google.protobuf.Any supplement = 0x6; } @@ -731,11 +755,20 @@ message ViewBase { optional int32 biz_type = 0x1; optional PageType page_type = 0x2; } +message ViewUniteStat { + optional int64 reply = 0x3; + optional int64 fav = 0x4; + optional int64 coin = 0x5; + optional int64 share = 0x6; + optional int64 like = 0x7; + optional int64 follow = 0x8; +} // Renamed due to name collision message ViewUniteArc { optional int64 aid = 0x1; optional int64 cid = 0x2; optional int64 duration = 0x3; + optional ViewUniteStat stat = 0x4; optional string bvid = 0x5; optional int32 copyright = 0x6; optional ViewUniteArcRights right = 0x7; @@ -749,6 +782,25 @@ message ViewUniteArcRights { optional bool no_reprint = 0x2; optional bool download = 0x3; } +message UgcDimension { + optional int64 width = 0x1; + optional int64 height = 0x2; + optional int64 rotate = 0x3; +} +message UgcPage { + optional int64 cid = 0x1; + optional string part = 0x2; + optional int64 duration = 0x3; + optional string desc = 0x4; + optional UgcDimension dimension = 0x5; + optional string dl_title = 0x6; + optional string dl_subtitle = 0x7; +} +message ViewUgcAny { + optional string short_link = 0x3; + optional string share_subtitle = 0x4; + repeated UgcPage pages = 0x5; +} message ViewPgcAny { optional OgvData ogv_data = 0x1; } @@ -950,7 +1002,28 @@ message OgvIntroduction { optional string score = 0x2; optional StatInfo play_data = 0x3; } -message UgcIntroduction {} +enum DescType { + DescTypeUnknown = 0; + DescTypeText = 1; + DescTypeAt = 2; +} +message DescV2 { + optional string text = 0x1; + optional DescType type = 0x2; + + optional int64 rid = 0x4; +} +message UgcTag { + optional int64 id = 0x1; + optional string name = 0x2; + optional string uri = 0x3; + optional string tag_type = 0x4; +} +message UgcIntroduction { + repeated UgcTag tags = 0x1; + optional int64 pubdate = 0x7; + repeated DescV2 desc = 0x8; +} message KingPosition { repeated KingPos king_pos = 0x1; repeated KingPos extenf = 0x2; @@ -978,7 +1051,9 @@ message KingPos { CoinExtend coin = 0x7; } } -message Headline {} +message Headline { + optional string content = 0x2; +} message OgvTitle { optional string title = 0x1; optional BadgeInfo badge_info = 0x2; diff --git a/app/src/main/proto/me/iacn/biliroaming/configs.proto b/app/src/main/proto/me/iacn/biliroaming/configs.proto index bcb9e032f3..715d9754cb 100644 --- a/app/src/main/proto/me/iacn/biliroaming/configs.proto +++ b/app/src/main/proto/me/iacn/biliroaming/configs.proto @@ -79,9 +79,11 @@ message PlayerCoreService { optional Method seek_to = 3; optional Method on_seek_complete = 4; optional Class seek_complete_listener = 5; - optional Method set_playback_speed = 6; - optional Method player_on_prepare = 7; - optional Method theseus_player_set_speed = 8; + reserved 6, 7, 8; +} + +message PcsFacade { + optional Class play_speed_manager = 1; } message PegasusFeed { @@ -312,4 +314,5 @@ message HookInfo { optional Class publish_to_following_config = 90; optional Comment3Copy comment3_copy = 91; repeated PlayerQualityService player_quality_service = 92; + optional Class play_speed_manager = 93; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 82c00bf0b8..7535303e88 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -290,4 +290,11 @@ 屏蔽指定等级以下用户评论 推荐封面比例 自定义首页推荐小卡(双列显示的)封面比例 + 伪装处于非多窗口模式 + 比如视频多窗口模式下全屏 + 复制 access_key + 请勿泄露给他人 + 已复制 + 启动屏背景适配深色 + 防瞎眼 diff --git a/app/src/main/res/xml/prefs_setting.xml b/app/src/main/res/xml/prefs_setting.xml index fab7ee11a3..e99b86847f 100644 --- a/app/src/main/res/xml/prefs_setting.xml +++ b/app/src/main/res/xml/prefs_setting.xml @@ -67,6 +67,10 @@ android:key="custom_theme" android:summary="@string/custom_theme_summary" android:title="@string/custom_theme_title" /> + + + + diff --git a/gradle.properties b/gradle.properties index effd9bb81b..53fd490a3e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,6 +13,5 @@ # org.gradle.parallel=true android.useAndroidX=true android.enableAppCompileTimeRClass=true -android.experimental.enableNewResourceShrinker.preciseShrinking=true appVerName=1.6.13 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index db55609e13..d4d6eb84c4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,20 +1,18 @@ [versions] -protobuf = "3.25.1" -coroutine = "1.7.3" -kotlin = "1.9.22" +protobuf = "4.27.0" +coroutine = "1.8.1" +kotlin = "2.0.0" [plugins] kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -agp-app = { id = "com.android.application", version = "8.2.0" } +agp-app = { id = "com.android.application", version = "8.4.1" } protobuf = { id = "com.google.protobuf", version = "0.9.4" } lsplugin-jgit = { id = "org.lsposed.lsplugin.jgit", version = "1.1" } -lsplugin-resopt = { id = "org.lsposed.lsplugin.resopt", version = "1.5" } +lsplugin-resopt = { id = "org.lsposed.lsplugin.resopt", version = "1.6" } lsplugin-apksign = { id = "org.lsposed.lsplugin.apksign", version = "1.4" } lsplugin-apktransform = { id = "org.lsposed.lsplugin.apktransform", version = "1.2" } lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version = "1.2" } - - [libraries] xposed = { module = "de.robv.android.xposed:api", version = "82" } cxx = { module = "dev.rikka.ndk.thirdparty:cxx", version = "1.2.0" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49..e6441136f3 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3fa8f862f7..b82aa23a4f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a53..1aa94a4269 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85bee..7101f8e467 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail