From 8f447c0f9e557b1dba75dd80bb235dd547ca7fc3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 23:26:57 +0000 Subject: [PATCH 1/5] feat: add cipherSuites option and "unsafe" fingerprint to TLS UI settings --- .../com/v2ray/ang/core/CoreOutboundBuilder.kt | 1 + .../com/v2ray/ang/dto/entities/ProfileItem.kt | 1 + .../java/com/v2ray/ang/ui/ServerActivity.kt | 8 +++++++ .../app/src/main/res/layout/layout_tls.xml | 21 +++++++++++++++++++ V2rayNG/app/src/main/res/values/arrays.xml | 1 + V2rayNG/app/src/main/res/values/strings.xml | 1 + 6 files changed, 33 insertions(+) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreOutboundBuilder.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreOutboundBuilder.kt index 213a090fa5..66f685c5ec 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreOutboundBuilder.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreOutboundBuilder.kt @@ -566,6 +566,7 @@ object CoreOutboundBuilder { serverName = sni.nullIfBlank(), fingerprint = profileItem.fingerPrint.nullIfBlank(), alpn = profileItem.alpn?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }.takeIf { !it.isNullOrEmpty() }, + cipherSuites = profileItem.cipherSuites.nullIfBlank(), echConfigList = profileItem.echConfigList.nullIfBlank(), verifyPeerCertByName = profileItem.verifyPeerCertByName.nullIfBlank(), pinnedPeerCertSha256 = profileItem.pinnedCA256.nullIfBlank(), diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt index 9a4a87d6c8..6352e8419e 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt @@ -40,6 +40,7 @@ data class ProfileItem( var sni: String? = null, var alpn: String? = null, var fingerPrint: String? = null, + var cipherSuites: String? = null, var insecure: Boolean? = null, var echConfigList: String? = null, var verifyPeerCertByName: String? = null, diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerActivity.kt index a8dd86723a..f1ecc7f43a 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerActivity.kt @@ -121,6 +121,8 @@ class ServerActivity : BaseActivity() { private val et_path: EditText? by lazy { findViewById(R.id.et_path) } private val sp_stream_alpn: Spinner? by lazy { findViewById(R.id.sp_stream_alpn) } //uTLS private val container_alpn: LinearLayout? by lazy { findViewById(R.id.lay_stream_alpn) } + private val et_cipher_suites: EditText? by lazy { findViewById(R.id.et_cipher_suites) } + private val container_cipher_suites: LinearLayout? by lazy { findViewById(R.id.lay_cipher_suites) } private val et_public_key: EditText? by lazy { findViewById(R.id.et_public_key) } private val et_preshared_key: EditText? by lazy { findViewById(R.id.et_preshared_key) } private val container_public_key: LinearLayout? by lazy { findViewById(R.id.lay_public_key) } @@ -298,6 +300,7 @@ class ServerActivity : BaseActivity() { container_sni, container_fingerprint, container_alpn, + container_cipher_suites, container_allow_insecure, container_public_key, container_short_id, @@ -315,6 +318,7 @@ class ServerActivity : BaseActivity() { container_sni, container_fingerprint, container_alpn, + container_cipher_suites, container_allow_insecure, container_ech_config_list, container_verify_peer_cert_by_name, @@ -336,6 +340,7 @@ class ServerActivity : BaseActivity() { ).forEach { it?.visibility = View.VISIBLE } listOf( container_alpn, + container_cipher_suites, container_allow_insecure, container_ech_config_list, container_verify_peer_cert_by_name, @@ -424,6 +429,7 @@ class ServerActivity : BaseActivity() { if (allowinsecure >= 0) { sp_allow_insecure?.setSelection(allowinsecure) } + et_cipher_suites?.text = Utils.getEditable(config.cipherSuites) 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) @@ -628,6 +634,7 @@ class ServerActivity : BaseActivity() { val shortId = et_short_id?.text?.toString() val spiderX = et_spider_x?.text?.toString() val mldsa65Verify = et_mldsa65_verify?.text?.toString() + val cipherSuites = et_cipher_suites?.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() @@ -644,6 +651,7 @@ class ServerActivity : BaseActivity() { config.sni = sniField config.fingerPrint = uTlsItems[utlsIndex] config.alpn = alpns[alpnIndex] + config.cipherSuites = cipherSuites config.publicKey = publicKey config.shortId = shortId config.spiderX = spiderX diff --git a/V2rayNG/app/src/main/res/layout/layout_tls.xml b/V2rayNG/app/src/main/res/layout/layout_tls.xml index 76f23645a5..4f6ba44610 100644 --- a/V2rayNG/app/src/main/res/layout/layout_tls.xml +++ b/V2rayNG/app/src/main/res/layout/layout_tls.xml @@ -92,6 +92,27 @@ android:layout_marginTop="@dimen/padding_spacing_dp8" android:layout_marginBottom="@dimen/padding_spacing_dp8" android:entries="@array/streamsecurity_alpn" + android:nextFocusDown="@+id/et_cipher_suites" /> + + + + + + + + diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index 6fb75c98c2..3ee85e70eb 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -82,6 +82,7 @@ qq random randomized + unsafe diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 419e5b6a7f..b586824660 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -71,6 +71,7 @@ TLS Fingerprint Alpn + Cipher Suites allowInsecure SNI address From 40289d7d5728919d45aac54226561fec7e8ad41e Mon Sep 17 00:00:00 2001 From: patterniha <71074308+patterniha@users.noreply.github.com> Date: Fri, 10 Jul 2026 05:22:29 +0330 Subject: [PATCH 2/5] fix: complete cipherSuites follow-ups missed in #1 Add the server_lab_cipher_suites label to all 8 locale strings.xml files (ar, bn, bqi-rIR, fa, ru, vi, zh-rCN, zh-rTW), which #1 only added to the default values/strings.xml. Also compare cipherSuites in ProfileItem.equals so profiles differing only in cipher suites are not treated as duplicates and deleted by "remove duplicate servers". Co-Authored-By: Claude Fable 5 --- .../app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt | 1 + V2rayNG/app/src/main/res/values-ar/strings.xml | 1 + V2rayNG/app/src/main/res/values-bn/strings.xml | 1 + V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml | 1 + V2rayNG/app/src/main/res/values-fa/strings.xml | 1 + V2rayNG/app/src/main/res/values-ru/strings.xml | 1 + V2rayNG/app/src/main/res/values-vi/strings.xml | 1 + V2rayNG/app/src/main/res/values-zh-rCN/strings.xml | 1 + V2rayNG/app/src/main/res/values-zh-rTW/strings.xml | 1 + 9 files changed, 9 insertions(+) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt index 6352e8419e..da3bcbd5a2 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/entities/ProfileItem.kt @@ -114,6 +114,7 @@ data class ProfileItem( && this.sni == obj.sni && this.alpn == obj.alpn && this.fingerPrint == obj.fingerPrint + && this.cipherSuites == obj.cipherSuites && this.publicKey == obj.publicKey && this.shortId == obj.shortId diff --git a/V2rayNG/app/src/main/res/values-ar/strings.xml b/V2rayNG/app/src/main/res/values-ar/strings.xml index 855b51d745..1c189c361d 100644 --- a/V2rayNG/app/src/main/res/values-ar/strings.xml +++ b/V2rayNG/app/src/main/res/values-ar/strings.xml @@ -71,6 +71,7 @@ TLS البصمة بروتوكول الطبقة الآخرى التالية (ALPN) + مجموعات التشفير (Cipher Suites) السماح غير الآمن SNI العنوان diff --git a/V2rayNG/app/src/main/res/values-bn/strings.xml b/V2rayNG/app/src/main/res/values-bn/strings.xml index 8bc94b694c..46e9e507a8 100644 --- a/V2rayNG/app/src/main/res/values-bn/strings.xml +++ b/V2rayNG/app/src/main/res/values-bn/strings.xml @@ -70,6 +70,7 @@ TLS ফিঙ্গারপ্রিন্ট Alpn + সাইফার স্যুট (Cipher Suites) অনিরাপদ অনুমতি দিন SNI ঠিকানা diff --git a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml index 4b8ad28082..e3a2837bc5 100644 --- a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml +++ b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml @@ -70,6 +70,7 @@ TLS Fingerprint Alpn + Cipher Suites هشتن SSL نا ٱمن SNI نشۊوی diff --git a/V2rayNG/app/src/main/res/values-fa/strings.xml b/V2rayNG/app/src/main/res/values-fa/strings.xml index 4cd6cab641..d39af8d756 100644 --- a/V2rayNG/app/src/main/res/values-fa/strings.xml +++ b/V2rayNG/app/src/main/res/values-fa/strings.xml @@ -70,6 +70,7 @@ TLS اثرانگشت Alpn + مجموعه رمزنگاری (Cipher Suites) مجوز SSL ناامن SNI نشانی diff --git a/V2rayNG/app/src/main/res/values-ru/strings.xml b/V2rayNG/app/src/main/res/values-ru/strings.xml index 7890ea8efd..24becb5a42 100644 --- a/V2rayNG/app/src/main/res/values-ru/strings.xml +++ b/V2rayNG/app/src/main/res/values-ru/strings.xml @@ -70,6 +70,7 @@ TLS Отпечаток ALPN + Наборы шифров (Cipher Suites) Разрешать небезопасные соединения SNI Адрес diff --git a/V2rayNG/app/src/main/res/values-vi/strings.xml b/V2rayNG/app/src/main/res/values-vi/strings.xml index 97ff5cd510..63ad88cf8b 100644 --- a/V2rayNG/app/src/main/res/values-vi/strings.xml +++ b/V2rayNG/app/src/main/res/values-vi/strings.xml @@ -70,6 +70,7 @@ TLS Fingerprint Alpn + Cipher Suites Bỏ qua xác minh chứng chỉ SNI Địa chỉ diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index e49cd3fb56..ebb3108065 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -70,6 +70,7 @@ 传输层安全 (TLS) Fingerprint Alpn + 加密套件 (Cipher Suites) 跳过证书验证 (allowInsecure) SNI 服务器地址 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 7768fd16c7..712b8a6b62 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -70,6 +70,7 @@ 傳輸層安全 (TLS) Fingerprint Alpn + 加密套件 (Cipher Suites) 跳過憑證驗證 (allowInsecure) SNI 伺服器位址 From dc85b6b5c3b14d11b5d329e756786c79c042aaac Mon Sep 17 00:00:00 2001 From: patterniha <71074308+patterniha@users.noreply.github.com> Date: Fri, 10 Jul 2026 05:54:17 +0330 Subject: [PATCH 3/5] feat: round-trip cipherSuites in share links and VMess QR code The fork already round-trips its custom TLS fields (ech, vcn, pcs, pqv) through share-link query parameters and the VMess QR-code JSON, but cipherSuites was missing, so sharing a profile silently dropped it. Add a "cs" query parameter to FmtBase import/export and a "cs" field to VmessQRCode, mapped in VmessFmt parse/toUri, following the existing pattern (emitted only when non-blank). Co-Authored-By: Claude Fable 5 --- V2rayNG/app/src/main/java/com/v2ray/ang/dto/VmessQRCode.kt | 1 + V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt | 2 ++ V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VmessFmt.kt | 2 ++ 3 files changed, 5 insertions(+) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/VmessQRCode.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/VmessQRCode.kt index ba17b765ef..d8f731c5c5 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/VmessQRCode.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/VmessQRCode.kt @@ -16,6 +16,7 @@ data class VmessQRCode( var sni: String = "", var alpn: String = "", var fp: String = "", + var cs: String = "", var insecure: String = "", var vcn: String = "", var pcs: String = "" diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt index 4aab0a9770..cf9a351185 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt @@ -83,6 +83,7 @@ open class FmtBase { config.sni = queryParam["sni"] config.fingerPrint = queryParam["fp"] config.alpn = queryParam["alpn"] + config.cipherSuites = queryParam["cs"] config.echConfigList = queryParam["ech"] config.verifyPeerCertByName = queryParam["vcn"] config.pinnedCA256 = queryParam["pcs"] @@ -104,6 +105,7 @@ open class FmtBase { dicQuery["security"] = config.security?.ifEmpty { "none" }.orEmpty() config.sni?.nullIfBlank()?.let { dicQuery["sni"] = it } config.alpn?.nullIfBlank()?.let { dicQuery["alpn"] = it } + config.cipherSuites?.nullIfBlank()?.let { dicQuery["cs"] = it } config.echConfigList?.nullIfBlank()?.let { dicQuery["ech"] = it } config.verifyPeerCertByName?.nullIfBlank()?.let { dicQuery["vcn"] = it } config.pinnedCA256?.nullIfBlank()?.let { dicQuery["pcs"] = it } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VmessFmt.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VmessFmt.kt index ef102270a2..ba9b07e8ea 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VmessFmt.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VmessFmt.kt @@ -82,6 +82,7 @@ object VmessFmt : FmtBase() { config.sni = vmessQRCode.sni config.fingerPrint = vmessQRCode.fp config.alpn = vmessQRCode.alpn + config.cipherSuites = vmessQRCode.cs config.insecure = when (vmessQRCode.insecure) { "1" -> true "0" -> false @@ -138,6 +139,7 @@ object VmessFmt : FmtBase() { vmessQRCode.sni = config.sni.orEmpty() vmessQRCode.fp = config.fingerPrint.orEmpty() vmessQRCode.alpn = config.alpn.orEmpty() + vmessQRCode.cs = config.cipherSuites.orEmpty() vmessQRCode.insecure = when (config.insecure) { true -> "1" false -> "0" From c36001b945bd581c3f47829dbfd28abe447d2eac Mon Sep 17 00:00:00 2001 From: patterniha <71074308+patterniha@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:29:37 +0330 Subject: [PATCH 4/5] feat: carry cipherSuites through the v2rayN inner-URI import The V2rayN Inner URI feature (2dust/v2rayNG@1338ec8e) imports profiles via V2rayNShareItem, a hand-written mirror of v2rayN's ProfileItem entity. v2rayN serializes its entity generically, so links it exports include CipherSuites, but the mirror lacked the field and dropped the value on import. Add CipherSuites to V2rayNShareItem and map it in toProfileItem(). The DTO is only constructed by gson deserialization, so no other call sites are affected. Co-Authored-By: Claude Fable 5 --- V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayNShareItem.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayNShareItem.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayNShareItem.kt index b2e2c433f8..a572bd3aed 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayNShareItem.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayNShareItem.kt @@ -26,6 +26,7 @@ data class V2rayNShareItem( val Sni: String?, val Alpn: String?, val Fingerprint: String?, + val CipherSuites: String?, val PublicKey: String?, val ShortId: String?, val SpiderX: String?, @@ -130,6 +131,7 @@ data class V2rayNShareItem( sni = Sni, alpn = Alpn, fingerPrint = Fingerprint, + cipherSuites = CipherSuites, insecure = AllowInsecure?.toBoolean(), echConfigList = EchConfigList, verifyPeerCertByName = VerifyPeerCertByName, From b1476ad384a25df3a551c2e091319e2388783d0b Mon Sep 17 00:00:00 2001 From: patterniha <71074308+patterniha@users.noreply.github.com> Date: Sat, 18 Jul 2026 12:41:06 +0330 Subject: [PATCH 5/5] feat: port cipherSuites into refactored per-protocol server editor Upstream ac03b0e8 split ServerActivity into per-protocol activities with shared BaseServerActivity/ServerUiState. Git carried the old cipherSuites lines into the moved ui/server/ServerActivity.kt, but the live editor path is the new state class and shared TLS section. Add cipherSuites to ServerUiState (constructor, state property, toProfileItem, fromProfileItem - the Saver round-trips through these so it also survives process death) and render the field in BaseServerActivity's TLS-only section after the alpn dropdown. The "unsafe" fingerprint needs no port: the new dropdown reads the same streamsecurity_utls array. Co-Authored-By: Claude Fable 5 --- .../main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt | 5 +++++ .../src/main/java/com/v2ray/ang/ui/server/ServerUiState.kt | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt index c6fe92051d..8005d898f0 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt @@ -275,6 +275,11 @@ abstract class BaseServerActivity : BaseComponentActivity() { options.alpnOptions, { state.alpn = it } ) + FormTextField( + stringResource(R.string.server_lab_cipher_suites), + state.cipherSuites, + { state.cipherSuites = it } + ) FormTextField( stringResource(R.string.server_lab_ech_config_list), state.echConfigList, diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerUiState.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerUiState.kt index 2604728de7..7d1f33c500 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerUiState.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerUiState.kt @@ -49,6 +49,7 @@ class ServerUiState( allowInsecure: Boolean = false, fingerPrint: String = "", alpn: String = "", + cipherSuites: String = "", publicKeyReality: String = "", shortId: String = "", spiderX: String = "", @@ -92,6 +93,7 @@ class ServerUiState( var allowInsecure by mutableStateOf(allowInsecure) var fingerPrint by mutableStateOf(fingerPrint) var alpn by mutableStateOf(alpn) + var cipherSuites by mutableStateOf(cipherSuites) var publicKeyReality by mutableStateOf(publicKeyReality) var shortId by mutableStateOf(shortId) var spiderX by mutableStateOf(spiderX) @@ -155,6 +157,7 @@ class ServerUiState( insecure = allowInsecure, fingerPrint = fingerPrint, alpn = alpn, + cipherSuites = cipherSuites, shortId = shortId, spiderX = spiderX, mldsa65Verify = mldsa65Verify, @@ -204,6 +207,7 @@ class ServerUiState( allowInsecure = initialConfig.insecure == true, fingerPrint = initialConfig.fingerPrint ?: "", alpn = initialConfig.alpn ?: "", + cipherSuites = initialConfig.cipherSuites ?: "", publicKeyReality = initialConfig.publicKey ?: "", shortId = initialConfig.shortId ?: "", spiderX = initialConfig.spiderX ?: "",