mirror of
https://github.com/2dust/v2rayNG.git
synced 2026-10-29 22:49:23 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
007a4e674f | ||
|
|
46af9d803c | ||
|
|
32a9263e0a | ||
|
|
253e171fec | ||
|
|
31b66a705b | ||
|
|
23c7cd5b95 | ||
|
|
32a37dd626 | ||
|
|
53e84d5fd8 | ||
|
|
0519d02001 | ||
|
|
6dfed5bbc2 | ||
|
|
dcd060e350 | ||
|
|
71f94db929 | ||
|
|
d06b9cb999 | ||
|
|
f53a91241c | ||
|
|
3fb067dc99 | ||
|
|
02103287e5 |
No files matched your search
+1
-1
Submodule AndroidLibXrayLite updated: e194d22cf1...85f272f37c.
@@ -3,7 +3,7 @@
|
||||
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
||||
|
||||
[](https://developer.android.com/about/versions/lollipop)
|
||||
[](https://kotlinlang.org)
|
||||
[](https://kotlinlang.org)
|
||||
[](https://github.com/2dust/v2rayNG/commits/master)
|
||||
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
||||
[](https://github.com/2dust/v2rayNG/releases)
|
||||
|
||||
@@ -12,8 +12,8 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 24
|
||||
targetSdk = 37
|
||||
versionCode = 733
|
||||
versionName = "2.2.3"
|
||||
versionCode = 734
|
||||
versionName = "2.2.4"
|
||||
multiDexEnabled = true
|
||||
|
||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||
@@ -21,7 +21,7 @@ android {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
if (abiFilterList != null && abiFilterList.isNotEmpty()) {
|
||||
if (!abiFilterList.isNullOrEmpty()) {
|
||||
include(*abiFilterList.toTypedArray())
|
||||
} else {
|
||||
include(
|
||||
|
||||
@@ -203,13 +203,19 @@ object AppConfig {
|
||||
const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com"
|
||||
|
||||
// Android Private DNS constants
|
||||
const val DNS_DNSPOD_DOMAIN = "dot.pub"
|
||||
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
|
||||
const val DNS_CISCO_SSE_DOMAIN = "dns.sse.cisco.com"
|
||||
const val DNS_CISCO_UMBRELLA_DOMAIN = "dns.umbrella.com"
|
||||
const val DNS_CLOUDFLARE_ONE_DOMAIN = "one.one.one.one"
|
||||
const val DNS_CLOUDFLARE_ONEDOT_DNS_DOMAIN = "1dot1dot1dot1.cloudflare-dns.com"
|
||||
const val DNS_CLOUDFLARE_DNS_COM_DOMAIN = "dns.cloudflare.com"
|
||||
const val DNS_CLOUDFLARE_DNS_DOMAIN = "cloudflare-dns.com"
|
||||
const val DNS_CLOUDFLARE_WARP_DOMAIN = "engage.cloudflareclient.com"
|
||||
const val DNS_DNSPOD_DOH_DOMAIN = "doh.pub"
|
||||
const val DNS_DNSPOD_DOT_DOMAIN = "dot.pub"
|
||||
const val DNS_GOOGLE_DOMAIN = "dns.google"
|
||||
const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
|
||||
const val DNS_SB_DOMAIN = "dns.sb"
|
||||
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
|
||||
|
||||
const val DEFAULT_PORT = 443
|
||||
@@ -223,12 +229,18 @@ object AppConfig {
|
||||
const val UNIDENTIFIED_PACKAGE = "__unknown_app__"
|
||||
|
||||
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
|
||||
val DNS_CISCO_SSE_ADDRESSES = arrayListOf("208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53")
|
||||
val DNS_CISCO_UMBRELLA_ADDRESSES = arrayListOf("208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53")
|
||||
val DNS_CLOUDFLARE_ONE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
|
||||
val DNS_CLOUDFLARE_DNS_COM_ADDRESSES = arrayListOf("104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5")
|
||||
val DNS_CLOUDFLARE_ONEDOT_DNS_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
|
||||
val DNS_CLOUDFLARE_DNS_COM_ADDRESSES = arrayListOf("162.159.61.8", "172.64.41.8", "2a06:98c1:52::8", "2803:f800:53::8")
|
||||
val DNS_CLOUDFLARE_DNS_ADDRESSES = arrayListOf("104.16.248.249", "104.16.249.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9")
|
||||
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
|
||||
val DNS_CLOUDFLARE_WARP_ADDRESSES = arrayListOf("162.159.192.1", "2606:4700:d0::a29f:c001")
|
||||
val DNS_DNSPOD_DOH_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
|
||||
val DNS_DNSPOD_DOT_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
|
||||
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
|
||||
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
|
||||
val DNS_SB_ADDRESSES = arrayListOf("45.11.45.11", "185.222.222.222", "2a09::", "2a11::")
|
||||
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
|
||||
|
||||
//minimum list https://serverfault.com/a/304791
|
||||
|
||||
@@ -710,12 +710,18 @@ object CoreConfigManager {
|
||||
|
||||
// hardcode popular Android Private DNS rule to fix localhost DNS problem
|
||||
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
|
||||
hosts[AppConfig.DNS_CISCO_SSE_DOMAIN] = AppConfig.DNS_CISCO_SSE_ADDRESSES
|
||||
hosts[AppConfig.DNS_CISCO_UMBRELLA_DOMAIN] = AppConfig.DNS_CISCO_UMBRELLA_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_ONE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONE_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_ONEDOT_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONEDOT_DNS_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_DNS_COM_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_COM_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_ADDRESSES
|
||||
hosts[AppConfig.DNS_DNSPOD_DOMAIN] = AppConfig.DNS_DNSPOD_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_WARP_DOMAIN] = AppConfig.DNS_CLOUDFLARE_WARP_ADDRESSES
|
||||
hosts[AppConfig.DNS_DNSPOD_DOH_DOMAIN] = AppConfig.DNS_DNSPOD_DOH_ADDRESSES
|
||||
hosts[AppConfig.DNS_DNSPOD_DOT_DOMAIN] = AppConfig.DNS_DNSPOD_DOT_ADDRESSES
|
||||
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
|
||||
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
|
||||
hosts[AppConfig.DNS_SB_DOMAIN] = AppConfig.DNS_SB_ADDRESSES
|
||||
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
|
||||
|
||||
//User DNS hosts
|
||||
|
||||
@@ -564,6 +564,7 @@ object CoreOutboundBuilder {
|
||||
fingerprint = profileItem.fingerPrint.nullIfBlank(),
|
||||
alpn = profileItem.alpn?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }.takeIf { !it.isNullOrEmpty() },
|
||||
echConfigList = profileItem.echConfigList.nullIfBlank(),
|
||||
verifyPeerCertByName = profileItem.verifyPeerCertByName.nullIfBlank(),
|
||||
pinnedPeerCertSha256 = profileItem.pinnedCA256.nullIfBlank(),
|
||||
publicKey = profileItem.publicKey.nullIfBlank(),
|
||||
shortId = profileItem.shortId.nullIfBlank(),
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class CertSha256Request(
|
||||
val address: String = "",
|
||||
val port: Int = 443,
|
||||
val serverName: String? = null,
|
||||
val timeoutMs: Long = 5000L,
|
||||
)
|
||||
|
||||
data class CertSha256Result(
|
||||
val sha256: String = "",
|
||||
val error: String = "",
|
||||
)
|
||||
@@ -253,6 +253,7 @@ data class V2rayConfig(
|
||||
val disableSystemRoot: Boolean? = null,
|
||||
val enableSessionResumption: Boolean? = null,
|
||||
var echConfigList: String? = null,
|
||||
var verifyPeerCertByName: String? = null,
|
||||
var pinnedPeerCertSha256: String? = null,
|
||||
// REALITY settings
|
||||
val show: Boolean = false,
|
||||
|
||||
@@ -16,5 +16,7 @@ data class VmessQRCode(
|
||||
var sni: String = "",
|
||||
var alpn: String = "",
|
||||
var fp: String = "",
|
||||
var insecure: String = ""
|
||||
var insecure: String = "",
|
||||
var vcn: String = "",
|
||||
var pcs: String = ""
|
||||
)
|
||||
@@ -42,6 +42,7 @@ data class ProfileItem(
|
||||
var fingerPrint: String? = null,
|
||||
var insecure: Boolean? = null,
|
||||
var echConfigList: String? = null,
|
||||
var verifyPeerCertByName: String? = null,
|
||||
var pinnedCA256: String? = null,
|
||||
|
||||
var publicKey: String? = null,
|
||||
|
||||
@@ -84,6 +84,7 @@ open class FmtBase {
|
||||
config.fingerPrint = queryParam["fp"]
|
||||
config.alpn = queryParam["alpn"]
|
||||
config.echConfigList = queryParam["ech"]
|
||||
config.verifyPeerCertByName = queryParam["vcn"]
|
||||
config.pinnedCA256 = queryParam["pcs"]
|
||||
config.publicKey = queryParam["pbk"]
|
||||
config.shortId = queryParam["sid"]
|
||||
@@ -104,6 +105,7 @@ open class FmtBase {
|
||||
config.sni?.nullIfBlank()?.let { dicQuery["sni"] = it }
|
||||
config.alpn?.nullIfBlank()?.let { dicQuery["alpn"] = it }
|
||||
config.echConfigList?.nullIfBlank()?.let { dicQuery["ech"] = it }
|
||||
config.verifyPeerCertByName?.nullIfBlank()?.let { dicQuery["vcn"] = it }
|
||||
config.pinnedCA256?.nullIfBlank()?.let { dicQuery["pcs"] = it }
|
||||
config.fingerPrint?.nullIfBlank()?.let { dicQuery["fp"] = it }
|
||||
config.publicKey?.nullIfBlank()?.let { dicQuery["pbk"] = it }
|
||||
|
||||
@@ -88,6 +88,9 @@ object VmessFmt : FmtBase() {
|
||||
"0" -> false
|
||||
else -> false
|
||||
}
|
||||
config.verifyPeerCertByName = vmessQRCode.vcn
|
||||
config.pinnedCA256 = vmessQRCode.pcs
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -141,6 +144,8 @@ object VmessFmt : FmtBase() {
|
||||
false -> "0"
|
||||
else -> ""
|
||||
}
|
||||
vmessQRCode.vcn = config.verifyPeerCertByName.orEmpty()
|
||||
vmessQRCode.pcs = config.pinnedCA256.orEmpty()
|
||||
|
||||
val json = JsonUtil.toJson(vmessQRCode)
|
||||
return Utils.encode(json)
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.v2ray.ang.handler
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.CertSha256Request
|
||||
import com.v2ray.ang.dto.CertSha256Result
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.util.HttpUtil
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import libv2ray.Libv2ray
|
||||
|
||||
object CertificateFingerprintManager {
|
||||
private const val TIMEOUT_MS = 5000L
|
||||
|
||||
fun fetchForManualFill(profile: ProfileItem): String? {
|
||||
val request = buildRequest(profile) ?: return null
|
||||
val result = if (profile.configType == EConfigType.HYSTERIA2) {
|
||||
fetch("quic", request) { Libv2ray.fetchQuicCertSha256(it) }
|
||||
} else {
|
||||
fetch("tls", request) { Libv2ray.fetchTlsCertSha256(it) }
|
||||
}
|
||||
|
||||
return result
|
||||
?.takeIf { it.error.isBlank() }
|
||||
?.sha256
|
||||
?.takeIf { it.isNotBlank() }
|
||||
}
|
||||
|
||||
private fun buildRequest(profile: ProfileItem): CertSha256Request? {
|
||||
if (!isFetchable(profile)) return null
|
||||
|
||||
val server = profile.server?.trim()?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val port = profile.serverPort?.toIntOrNull()?.takeIf { it > 0 } ?: AppConfig.DEFAULT_PORT
|
||||
|
||||
return CertSha256Request(
|
||||
address = resolveDialAddress(server),
|
||||
port = port,
|
||||
serverName = inferServerName(profile),
|
||||
timeoutMs = TIMEOUT_MS,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isFetchable(profile: ProfileItem): Boolean {
|
||||
return profile.configType == EConfigType.HYSTERIA2 || profile.security == AppConfig.TLS
|
||||
}
|
||||
|
||||
private fun fetch(
|
||||
type: String,
|
||||
request: CertSha256Request,
|
||||
fetcher: (String) -> String,
|
||||
): CertSha256Result? {
|
||||
return try {
|
||||
JsonUtil.fromJsonSafe(fetcher(JsonUtil.toJson(request)), CertSha256Result::class.java)
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
LogUtil.e(AppConfig.TAG, "Fetch $type cert SHA-256 API missing in libv2ray", e)
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
LogUtil.e(AppConfig.TAG, "Fetch $type cert SHA-256 failed", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveDialAddress(server: String): String {
|
||||
if (Utils.isPureIpAddress(server) || !Utils.isDomainName(server)) return server
|
||||
|
||||
val preferIpv6 = MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6, false)
|
||||
return HttpUtil.resolveHostToIP(server, preferIpv6)
|
||||
?.firstOrNull()
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?: server
|
||||
}
|
||||
|
||||
private fun inferServerName(profile: ProfileItem): String? {
|
||||
val sni = profile.sni?.takeIf { it.isNotBlank() }
|
||||
return sni?.takeUnless { Utils.isPureIpAddress(it) }
|
||||
}
|
||||
}
|
||||
@@ -226,6 +226,7 @@ class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>(),
|
||||
private fun removeServerSub(guid: String, position: Int) {
|
||||
mainViewModel.removeServer(guid)
|
||||
adapter.removeServerSub(guid, position)
|
||||
ownerActivity.refreshGroupTabTitles()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,23 +76,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
binding.viewPager.isUserInputEnabled = true
|
||||
|
||||
// setup navigation drawer
|
||||
val toggle = ActionBarDrawerToggle(
|
||||
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
||||
)
|
||||
binding.drawerLayout.addDrawerListener(toggle)
|
||||
toggle.syncState()
|
||||
binding.navView.setNavigationItemSelectedListener(this)
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
} else {
|
||||
isEnabled = false
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
isEnabled = true
|
||||
}
|
||||
}
|
||||
})
|
||||
setupNavigationDrawer()
|
||||
|
||||
binding.fab.setOnClickListener { handleFabAction() }
|
||||
binding.layoutTest.setOnClickListener { handleLayoutTestClick() }
|
||||
@@ -106,6 +90,31 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNavigationDrawer() {
|
||||
val toggle = ActionBarDrawerToggle(
|
||||
this,
|
||||
binding.drawerLayout,
|
||||
binding.toolbar,
|
||||
R.string.navigation_drawer_open,
|
||||
R.string.navigation_drawer_close
|
||||
)
|
||||
binding.drawerLayout.addDrawerListener(toggle)
|
||||
toggle.syncState()
|
||||
binding.navView.setNavigationItemSelectedListener(this)
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
} else {
|
||||
isEnabled = false
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
isEnabled = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupViewModel() {
|
||||
mainViewModel.updateTestResultAction.observe(this) { setTestState(it) }
|
||||
mainViewModel.isRunning.observe(this) { isRunning ->
|
||||
@@ -131,6 +140,26 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
binding.viewPager.setCurrentItem(targetIndex, false)
|
||||
|
||||
binding.tabGroup.isVisible = groups.size > 1
|
||||
refreshGroupTabTitles(true)
|
||||
}
|
||||
|
||||
fun refreshGroupTabTitles(refreshAll: Boolean = false) {
|
||||
val groupsToRefresh = if (refreshAll || mainViewModel.subscriptionId.isEmpty()) {
|
||||
groupPagerAdapter.groups
|
||||
} else {
|
||||
groupPagerAdapter.groups.filter { it.id == mainViewModel.subscriptionId }
|
||||
}
|
||||
|
||||
groupsToRefresh.forEach { group ->
|
||||
if (group.id.isEmpty()) {
|
||||
return@forEach
|
||||
}
|
||||
val tabIndex = groupPagerAdapter.groups.indexOfFirst { it.id == group.id }
|
||||
if (tabIndex >= 0) {
|
||||
val count = MmkvManager.decodeServerList(group.id).size
|
||||
binding.tabGroup.getTabAt(tabIndex)?.text = "${group.remarks} ($count)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFabAction() {
|
||||
@@ -416,6 +445,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
count > 0 -> {
|
||||
toast(getString(R.string.title_import_config_count, count))
|
||||
mainViewModel.reloadServerList()
|
||||
refreshGroupTabTitles()
|
||||
}
|
||||
|
||||
countSub > 0 -> setupGroupTab()
|
||||
@@ -471,6 +501,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
}
|
||||
if (result.configCount > 0) {
|
||||
mainViewModel.reloadServerList()
|
||||
refreshGroupTabTitles()
|
||||
}
|
||||
hideLoading()
|
||||
}
|
||||
@@ -500,6 +531,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
val ret = mainViewModel.removeAllServer()
|
||||
launch(Dispatchers.Main) {
|
||||
mainViewModel.reloadServerList()
|
||||
refreshGroupTabTitles()
|
||||
toast(getString(R.string.title_del_config_count, ret))
|
||||
hideLoading()
|
||||
}
|
||||
@@ -519,6 +551,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
val ret = mainViewModel.removeDuplicateServer()
|
||||
launch(Dispatchers.Main) {
|
||||
mainViewModel.reloadServerList()
|
||||
refreshGroupTabTitles()
|
||||
toast(getString(R.string.title_del_duplicate_config_count, ret))
|
||||
hideLoading()
|
||||
}
|
||||
@@ -538,6 +571,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
val ret = mainViewModel.removeInvalidServer()
|
||||
launch(Dispatchers.Main) {
|
||||
mainViewModel.reloadServerList()
|
||||
refreshGroupTabTitles()
|
||||
toast(getString(R.string.title_del_config_count, ret))
|
||||
hideLoading()
|
||||
}
|
||||
|
||||
@@ -159,8 +159,12 @@ class MainRecyclerAdapter(
|
||||
|
||||
// Security: hide blank or tls
|
||||
profile.security?.let { sec ->
|
||||
if (sec.isNotBlank() && !sec.equals("tls", ignoreCase = true)) {
|
||||
parts.add(sec)
|
||||
if (sec.isNotBlank()) {
|
||||
if (profile.insecure == true && sec.equals("tls", ignoreCase = true)) {
|
||||
parts.add("$sec insecure") // TODO
|
||||
} else {
|
||||
parts.add(sec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.DEFAULT_PORT
|
||||
import com.v2ray.ang.AppConfig.REALITY
|
||||
@@ -27,10 +29,14 @@ import com.v2ray.ang.extension.nullIfBlank
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.AngConfigManager
|
||||
import com.v2ray.ang.handler.CertificateFingerprintManager
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsChangeManager
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ServerActivity : BaseActivity() {
|
||||
|
||||
@@ -140,7 +146,10 @@ class ServerActivity : BaseActivity() {
|
||||
private val layout_extra: LinearLayout? by lazy { findViewById(R.id.layout_extra) }
|
||||
private val et_ech_config_list: EditText? by lazy { findViewById(R.id.et_ech_config_list) }
|
||||
private val container_ech_config_list: LinearLayout? by lazy { findViewById(R.id.lay_ech_config_list) }
|
||||
private val et_verify_peer_cert_by_name: EditText? by lazy { findViewById(R.id.et_verify_peer_cert_by_name) }
|
||||
private val container_verify_peer_cert_by_name: LinearLayout? by lazy { findViewById(R.id.lay_verify_peer_cert_by_name) }
|
||||
private val et_pinned_ca256: EditText? by lazy { findViewById(R.id.et_pinned_ca256) }
|
||||
private val btn_pinned_ca256_action: Button? by lazy { findViewById(R.id.btn_pinned_ca256_action) }
|
||||
private val container_pinned_ca256: LinearLayout? by lazy { findViewById(R.id.lay_pinned_ca256) }
|
||||
private val layout_browser_dialer: LinearLayout? by lazy { findViewById(R.id.layout_browser_dialer) }
|
||||
private val sp_browser_dialer_mode: Spinner? by lazy { findViewById(R.id.sp_browser_dialer_mode) }
|
||||
@@ -295,6 +304,7 @@ class ServerActivity : BaseActivity() {
|
||||
container_spider_x,
|
||||
container_mldsa65_verify,
|
||||
container_ech_config_list,
|
||||
container_verify_peer_cert_by_name,
|
||||
container_pinned_ca256
|
||||
).forEach { it?.visibility = View.GONE }
|
||||
}
|
||||
@@ -307,6 +317,7 @@ class ServerActivity : BaseActivity() {
|
||||
container_alpn,
|
||||
container_allow_insecure,
|
||||
container_ech_config_list,
|
||||
container_verify_peer_cert_by_name,
|
||||
container_pinned_ca256
|
||||
).forEach { it?.visibility = View.VISIBLE }
|
||||
listOf(
|
||||
@@ -327,6 +338,7 @@ class ServerActivity : BaseActivity() {
|
||||
container_alpn,
|
||||
container_allow_insecure,
|
||||
container_ech_config_list,
|
||||
container_verify_peer_cert_by_name,
|
||||
container_pinned_ca256
|
||||
).forEach { it?.visibility = View.GONE }
|
||||
listOf(
|
||||
@@ -343,6 +355,9 @@ class ServerActivity : BaseActivity() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
btn_pinned_ca256_action?.setOnClickListener {
|
||||
fetchPinnedCA256ForCurrentConfig()
|
||||
}
|
||||
if (config != null) {
|
||||
bindingServer(config)
|
||||
} else {
|
||||
@@ -410,6 +425,7 @@ class ServerActivity : BaseActivity() {
|
||||
sp_allow_insecure?.setSelection(allowinsecure)
|
||||
}
|
||||
et_ech_config_list?.text = Utils.getEditable(config.echConfigList)
|
||||
et_verify_peer_cert_by_name?.text = Utils.getEditable(config.verifyPeerCertByName)
|
||||
et_pinned_ca256?.text = Utils.getEditable(config.pinnedCA256)
|
||||
} else if (config.security == REALITY) {
|
||||
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
|
||||
@@ -613,6 +629,7 @@ class ServerActivity : BaseActivity() {
|
||||
val spiderX = et_spider_x?.text?.toString()
|
||||
val mldsa65Verify = et_mldsa65_verify?.text?.toString()
|
||||
val echConfigList = et_ech_config_list?.text?.toString()
|
||||
val verifyPeerCertByName = et_verify_peer_cert_by_name?.text?.toString()
|
||||
val pinnedCA256 = et_pinned_ca256?.text?.toString()
|
||||
|
||||
val allowInsecure =
|
||||
@@ -632,9 +649,51 @@ class ServerActivity : BaseActivity() {
|
||||
config.spiderX = spiderX
|
||||
config.mldsa65Verify = mldsa65Verify
|
||||
config.echConfigList = echConfigList
|
||||
config.verifyPeerCertByName = verifyPeerCertByName
|
||||
config.pinnedCA256 = pinnedCA256
|
||||
}
|
||||
|
||||
private fun fetchPinnedCA256ForCurrentConfig() {
|
||||
val config = buildCurrentProfileForCertificateFetch() ?: return
|
||||
|
||||
lifecycleScope.launch {
|
||||
btn_pinned_ca256_action?.isEnabled = false
|
||||
try {
|
||||
val sha256 = withContext(Dispatchers.IO) {
|
||||
CertificateFingerprintManager.fetchForManualFill(config)
|
||||
}
|
||||
if (sha256.isNullOrBlank()) {
|
||||
toast(R.string.toast_fetch_cert_sha256_failed)
|
||||
} else {
|
||||
et_pinned_ca256?.text = Utils.getEditable(sha256)
|
||||
toastSuccess(R.string.toast_fetch_cert_sha256_success)
|
||||
}
|
||||
} finally {
|
||||
btn_pinned_ca256_action?.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildCurrentProfileForCertificateFetch(): ProfileItem? {
|
||||
if (TextUtils.isEmpty(et_address.text.toString())) {
|
||||
toast(R.string.server_lab_address)
|
||||
return null
|
||||
}
|
||||
|
||||
val configType = MmkvManager.decodeServerConfig(editGuid)?.configType ?: createConfigType
|
||||
if (configType != EConfigType.HYSTERIA2 && Utils.parseInt(et_port.text.toString()) <= 0) {
|
||||
toast(R.string.server_lab_port)
|
||||
return null
|
||||
}
|
||||
|
||||
val config = ProfileItem.create(configType)
|
||||
saveCommon(config)
|
||||
saveStreamSettings(config)
|
||||
saveTls(config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
private fun transportTypes(network: String?): Array<out String> {
|
||||
return when (network) {
|
||||
NetworkType.TCP.type -> {
|
||||
|
||||
@@ -341,11 +341,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
if (subscriptionId.isEmpty() && keywordFilter.isEmpty()) {
|
||||
MmkvManager.removeAllServer()
|
||||
} else {
|
||||
val serversCopy = serversCache.toList()
|
||||
for (item in serversCopy) {
|
||||
MmkvManager.removeServer(item.guid)
|
||||
val serverList = MmkvManager.decodeServerList(subscriptionId)
|
||||
for (guid in serverList) {
|
||||
MmkvManager.removeServer(guid)
|
||||
}
|
||||
serversCache.toList().count()
|
||||
serverList.count()
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
||||
android:layout_marginBottom="@dimen/padding_spacing_dp8"
|
||||
android:entries="@array/allowinsecures"
|
||||
android:nextFocusDown="@+id/et_ech_config_list"/>
|
||||
android:nextFocusDown="@+id/et_verify_peer_cert_by_name"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
@@ -138,6 +138,27 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/lay_verify_peer_cert_by_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/padding_spacing_dp16"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/server_lab_verify_peer_cert_by_name" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_verify_peer_cert_by_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"
|
||||
android:nextFocusDown="@+id/et_ech_config_list" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/lay_pinned_ca256"
|
||||
android:layout_width="match_parent"
|
||||
@@ -157,6 +178,14 @@
|
||||
android:inputType="text"
|
||||
android:nextFocusDown="@+id/et_public_key" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_pinned_ca256_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
||||
android:layout_marginBottom="@dimen/padding_spacing_dp8"
|
||||
android:text="@string/pinned_ca256_action_fetch" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -88,6 +88,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_pinned_ca256_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
||||
android:layout_marginBottom="@dimen/padding_spacing_dp8"
|
||||
android:text="@string/pinned_ca256_action_fetch" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -118,7 +118,11 @@
|
||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">فشل نسخ الملف، يرجى استخدام مدير الملفات</string>
|
||||
|
||||
@@ -117,7 +117,11 @@
|
||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">ফাইল কপি ব্যর্থ, অনুগ্রহ করে ফাইল ম্যানেজার ব্যবহার করুন</string>
|
||||
|
||||
@@ -117,7 +117,11 @@
|
||||
<string name="server_lab_xhttp_extra">XHTTP Extra خام JSON، قالوو: { XHTTPObject }</string>
|
||||
<string name="server_lab_final_mask">finalMask خام JSON، قالوو: { FinalMaskObject }</string>
|
||||
<string name="server_lab_ech_config_list">نومگه کانفیگ Ech</string>
|
||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||
<string name="server_lab_verify_peer_cert_by_name">تئیڌ گوواهی هومتا و ری نوم</string>
|
||||
<string name="server_lab_pinned_ca256">جا کلک گوواهی (SHA-256)</string>
|
||||
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">لف گیری فایل ٱنجوم نوابی، ز ی برنومه دؽوۉداری فایل هیاری بگرین</string>
|
||||
@@ -357,7 +361,7 @@
|
||||
<string name="connection_not_connected">منپیز نؽڌ</string>
|
||||
<string name="connection_runing_task_left">تعداد وزیفه یل آزمایشی ک ره اۊفتانه: %s</string>
|
||||
|
||||
<string name="import_subscription_success">اشتراک وا موفقیت زفت زابی</string>
|
||||
<string name="import_subscription_success">اشتراک وا مووفقیت زفت وابی</string>
|
||||
<string name="import_subscription_failure">اشتراک زفت نوابی</string>
|
||||
<string name="title_fragment_settings">سامووا فرگمنت</string>
|
||||
<string name="title_pref_fragment_packets">کتنا فرگمنت</string>
|
||||
@@ -445,7 +449,7 @@
|
||||
</string-array>
|
||||
<string-array name="policy_group_type">
|
||||
<item>کم ترین پینگ</item>
|
||||
<item>کم ترین بار(لود)</item>
|
||||
<item>کم ترین بار (لود)</item>
|
||||
<item>تساڌۊفی</item>
|
||||
<item>گردشی</item>
|
||||
</string-array>
|
||||
|
||||
@@ -117,7 +117,11 @@
|
||||
<string name="server_lab_xhttp_extra">خام JSON XHTTP Extra، قالب: { XHTTPObject }</string>
|
||||
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
||||
<string name="server_lab_ech_config_list">لیست کانفیگ Ech</string>
|
||||
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<string name="menu_item_import_config_qrcode">Импорт из QR-кода</string>
|
||||
<string name="menu_item_import_config_clipboard">Импорт из буфера обмена</string>
|
||||
<string name="menu_item_import_config_local">Импорт из файла</string>
|
||||
<string name="menu_item_import_config_policy_group">Добавить группу политик</string>
|
||||
<string name="menu_item_import_config_policy_group">Добавить политику группы</string>
|
||||
<string name="menu_item_import_config_proxy_chain">Добавить цепочку прокси</string>
|
||||
<string name="menu_item_import_config_manually_vmess">Добавить VMess</string>
|
||||
<string name="menu_item_import_config_manually_vless">Добавить VLESS</string>
|
||||
@@ -117,9 +117,14 @@
|
||||
<string name="server_lab_xhttp_extra">Необработанный JSON XHTTP Extra, формат: { XHTTPObject }</string>
|
||||
<string name="server_lab_final_mask">Необработанный JSON FinalMask, формат: { FinalMaskObject }</string>
|
||||
<string name="server_lab_ech_config_list">ECHConfigList</string>
|
||||
<string name="server_lab_verify_peer_cert_by_name">Проверять сертификат узла по имени</string>
|
||||
<string name="server_lab_pinned_ca256">Отпечаток сертификата (SHA-256)</string>
|
||||
<string name="server_lab_browser_dialer">Использовать переадресацию браузера</string>
|
||||
<string name="server_lab_browser_dialer_tip">Поддерживаются только исходящие соединения XHTTP (packet-up) и WS; настройки, связанные с TLS, могут быть проигнорированы или конфликтовать</string>
|
||||
<string name="toast_allow_insecure_deprecated">ВНИМАНИЕ: пропуск проверки сертификата («Разрешать небезопасные соединения») будет отключён в августе 2026 года. Пожалуйста, используйте отпечатки сертификатов как можно скорее. Функция будет недоступна после истечения срока действия.</string>
|
||||
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">Невозможно скопировать файл, используйте файловый менеджер</string>
|
||||
@@ -288,7 +293,7 @@
|
||||
<string name="title_logcat">Журнал</string>
|
||||
<string name="logcat_copy">Копировать</string>
|
||||
<string name="logcat_clear">Очистить</string>
|
||||
<string name="logcat_share">Поделиться логом</string>
|
||||
<string name="logcat_share">Поделиться журналом</string>
|
||||
<string name="title_service_restart">Перезапуск службы</string>
|
||||
<string name="title_del_all_config">Удалить профили</string>
|
||||
<string name="title_del_duplicate_config">Удалить дубликаты профилей</string>
|
||||
@@ -377,7 +382,7 @@
|
||||
<string name="update_check_pre_release">Искать предварительный выпуск</string>
|
||||
<string name="update_checking_for_update">Проверка обновления…</string>
|
||||
|
||||
<string name="title_policy_group_type">Тип группы политик</string>
|
||||
<string name="title_policy_group_type">Тип политики группы</string>
|
||||
<string name="title_policy_group_subscription_id">Из группы подписки</string>
|
||||
<string name="title_policy_group_subscription_filter">Название фильтра</string>
|
||||
|
||||
@@ -424,6 +429,12 @@
|
||||
<item>Прокси</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="browser_dialer_mode">
|
||||
<item>Отключено</item>
|
||||
<item>OkHttp</item>
|
||||
<item>WebView</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="ui_mode_night">
|
||||
<item>Как в системе</item>
|
||||
<item>Светлая</item>
|
||||
|
||||
@@ -117,7 +117,11 @@
|
||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">Không thể sao chép tệp tin, hãy dùng trình quản lý tệp!</string>
|
||||
|
||||
@@ -117,10 +117,14 @@
|
||||
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
|
||||
<string name="server_lab_final_mask">FinalMask 原始 JSON 格式: { FinalMaskObject }</string>
|
||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||
<string name="server_lab_pinned_ca256">证书指纹 (SHA-256)</string>
|
||||
<string name="server_lab_browser_dialer">启用浏览器转发</string>
|
||||
<string name="server_lab_browser_dialer_tip">仅支持 xhttp (packet-up) 和 ws。与优选域名冲突,utls, alpn, ech 等 TLS 设置将被忽略</string>
|
||||
<string name="toast_allow_insecure_deprecated">警告:将在 2026.8.1 禁用跳过证书验证 "allowInsecure" ,请尽快改用证书指纹。到期后无法使用。 </string>
|
||||
<string name="toast_allow_insecure_deprecated">警告:将在 2026.8.1 移除跳过证书验证 "allowInsecure" ,请尽快改用证书指纹或修改服务端。否则到期后会无法使用。 </string>
|
||||
<string name="pinned_ca256_action_fetch">获取并填充指纹证书</string>
|
||||
<string name="toast_fetch_cert_sha256_success">获取指纹证书完成</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">获取证书指纹失败</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">失败, 请使用文件管理器</string>
|
||||
|
||||
@@ -117,8 +117,12 @@
|
||||
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
|
||||
<string name="server_lab_final_mask">FinalMask 原始 JSON 格式: { FinalMaskObject }</string>
|
||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||
<string name="server_lab_pinned_ca256">證書指紋 (SHA-256)</string>
|
||||
<string name="toast_allow_insecure_deprecated">警告:將在 2026.8.1 停用跳過憑證驗證 "allowInsecure" ,請盡快改用憑證指紋。到期後無法使用。 </string>
|
||||
<string name="pinned_ca256_action_fetch">獲取並填充指紋證書</string>
|
||||
<string name="toast_fetch_cert_sha256_success">獲取指紋證書完成</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">獲取證書指紋失敗</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">失敗,請使用檔案總管</string>
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
|
||||
<string-array name="fragment_packets" translatable="false">
|
||||
<item>tlshello</item>
|
||||
<item>1-1</item>
|
||||
<item>1-2</item>
|
||||
<item>1-3</item>
|
||||
<item>1-5</item>
|
||||
|
||||
@@ -118,10 +118,14 @@
|
||||
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
|
||||
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
|
||||
<string name="server_lab_ech_config_list">EchConfigList</string>
|
||||
<string name="server_lab_verify_peer_cert_by_name">Verify Peer Cert By Name</string>
|
||||
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string>
|
||||
<string name="server_lab_browser_dialer">Enable Browser Dialer</string>
|
||||
<string name="server_lab_browser_dialer_tip">Only supports xhttp (packet-up) and ws outbound; TLS-related settings may be ignored or conflict</string>
|
||||
<string name="toast_allow_insecure_deprecated">WARNING: Skipping certificate verification "allowInsecure" will be disabled in August 2026. Please use certificate fingerprints as soon as possible. This feature will not be available after its expiration.</string>
|
||||
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">File copy failed, please use File Manager</string>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
agp = "9.2.1"
|
||||
desugarJdkLibs = "2.1.5"
|
||||
gradleLicensePlugin = "0.9.8"
|
||||
kotlin = "2.3.21"
|
||||
kotlin = "2.4.0"
|
||||
coreKtx = "1.17.0"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.3.0"
|
||||
|
||||
Reference in new issue
Block a user