diff --git a/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/36.json b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/36.json
index 61aec12a907..bb1277afaca 100644
--- a/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/36.json
+++ b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/36.json
@@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 36,
- "identityHash": "2ad66cce1d4ba5df1fd06814cfaae7e9",
+ "identityHash": "a767e2240b7af68735ec4f7b90813e3c",
"entities": [
{
"tableName": "proxy_groups",
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL DEFAULT -1, `landingProxy` INTEGER NOT NULL DEFAULT -1)",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL DEFAULT -1, `landingProxy` INTEGER NOT NULL DEFAULT -1, `groupUtlsFingerprint` TEXT NOT NULL DEFAULT '')",
"fields": [
{
"fieldPath": "id",
@@ -61,6 +61,13 @@
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "-1"
+ },
+ {
+ "fieldPath": "groupUtlsFingerprint",
+ "columnName": "groupUtlsFingerprint",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
}
],
"primaryKey": {
@@ -455,7 +462,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2ad66cce1d4ba5df1fd06814cfaae7e9')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a767e2240b7af68735ec4f7b90813e3c')"
]
}
}
\ No newline at end of file
diff --git a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt
index a5228b63275..b6f856ef723 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt
@@ -295,6 +295,7 @@ object Key {
const val GROUP_LANDING_PROXY_OUTBOUND = "groupLandingOutbound"
const val GROUP_FRONT_PROXY = "groupFrontProxy"
const val GROUP_LANDING_PROXY = "groupLandingProxy"
+ const val GROUP_UTLS_FINGERPRINT = "groupUtlsFingerprint"
const val GROUP_SUBSCRIPTION = "groupSubscription"
const val SUBSCRIPTION_TYPE = "subscriptionType"
diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
index 50c3a5af3b0..d57a3228e3f 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
@@ -436,6 +436,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var landingProxyOutbound by profileCacheStore.long(Key.GROUP_LANDING_PROXY_OUTBOUND)
var frontProxy by profileCacheStore.stringToInt(Key.GROUP_FRONT_PROXY)
var landingProxy by profileCacheStore.stringToInt(Key.GROUP_LANDING_PROXY)
+ var groupUtlsFingerprint by profileCacheStore.string(Key.GROUP_UTLS_FINGERPRINT)
var serverConfig by profileCacheStore.string(Key.SERVER_CONFIG)
diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyGroup.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyGroup.kt
index 977450d40eb..7d31c7cdd12 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyGroup.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyGroup.kt
@@ -39,7 +39,8 @@ data class ProxyGroup(
var subscription: SubscriptionBean? = null,
var order: Int = GroupOrder.ORIGIN,
@ColumnInfo(defaultValue = (-1L).toString()) var frontProxy: Long = -1L,
- @ColumnInfo(defaultValue = (-1L).toString()) var landingProxy: Long = -1L
+ @ColumnInfo(defaultValue = (-1L).toString()) var landingProxy: Long = -1L,
+ @ColumnInfo(defaultValue = "") var groupUtlsFingerprint: String = ""
) : Serializable() {
@Transient
@@ -69,6 +70,7 @@ data class ProxyGroup(
output.writeInt(order)
output.writeLong(frontProxy)
output.writeLong(landingProxy)
+ output.writeString(groupUtlsFingerprint)
}
}
@@ -97,6 +99,13 @@ data class ProxyGroup(
frontProxy = input.readLong()
landingProxy = input.readLong()
}
+ if (version >= 1) {
+ try {
+ groupUtlsFingerprint = input.readString()
+ } catch (e: Exception) {
+ groupUtlsFingerprint = ""
+ }
+ }
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
index 4b1a06edf4c..6a7a05f880b 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
@@ -602,6 +602,10 @@ fun buildV2RayConfig(
val bean = proxyEntity.requireBean()
currentOutbound = OutboundObject()
+ // Получаем группу для применения группового uTLS
+ val proxyGroup = SagerDatabase.groupDao.getById(proxyEntity.groupId)
+ val groupUtls = proxyGroup?.groupUtlsFingerprint?.takeIf { it.isNotEmpty() }
+
val tagIn: String
var needGlobal: Boolean
@@ -888,6 +892,8 @@ fun buildV2RayConfig(
val overrideFingerprint = DataStore.experimentalFlagsProperties.getProperty("overrideUTLSFingerprintForTLS")
if (!overrideFingerprint.isNullOrEmpty()) {
fingerprint = overrideFingerprint
+ } else if (groupUtls != null) {
+ fingerprint = groupUtls
} else if (bean.utlsFingerprint.isNotEmpty()) {
fingerprint = bean.utlsFingerprint
}
@@ -918,6 +924,8 @@ fun buildV2RayConfig(
val overrideFingerprint = DataStore.experimentalFlagsProperties.getProperty("overrideUTLSFingerprintForREALITY")
if (!overrideFingerprint.isNullOrEmpty()) {
fingerprint = overrideFingerprint
+ } else if (groupUtls != null) {
+ fingerprint = groupUtls
} else if (bean.realityFingerprint.isNotEmpty()) {
fingerprint = bean.realityFingerprint
}
@@ -1616,6 +1624,8 @@ fun buildV2RayConfig(
val overrideFingerprint = DataStore.experimentalFlagsProperties.getProperty("overrideUTLSFingerprintForTLS")
if (!overrideFingerprint.isNullOrEmpty()) {
fingerprint = overrideFingerprint
+ } else if (groupUtls != null) {
+ fingerprint = groupUtls
} else if (bean.utlsFingerprint.isNotEmpty()) {
fingerprint = bean.utlsFingerprint
}
@@ -1643,6 +1653,8 @@ fun buildV2RayConfig(
val overrideFingerprint = DataStore.experimentalFlagsProperties.getProperty("overrideUTLSFingerprintForREALITY")
if (!overrideFingerprint.isNullOrEmpty()) {
fingerprint = overrideFingerprint
+ } else if (groupUtls != null) {
+ fingerprint = groupUtls
} else if (bean.realityFingerprint.isNotEmpty()) {
fingerprint = bean.realityFingerprint
}
@@ -1833,6 +1845,8 @@ fun buildV2RayConfig(
val overrideFingerprint = DataStore.experimentalFlagsProperties.getProperty("overrideUTLSFingerprintForTLS")
if (!overrideFingerprint.isNullOrEmpty()) {
fingerprint = overrideFingerprint
+ } else if (groupUtls != null) {
+ fingerprint = groupUtls
} else if (bean.utlsFingerprint.isNotEmpty()) {
fingerprint = bean.utlsFingerprint
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt
index 526d99614ba..bc39ba03bb6 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt
@@ -102,6 +102,7 @@ class GroupSettingsActivity(
DataStore.landingProxyOutbound = landingProxy
DataStore.frontProxy = if (frontProxy >= 0) 1 else 0
DataStore.landingProxy = if (landingProxy >= 0) 1 else 0
+ DataStore.groupUtlsFingerprint = groupUtlsFingerprint ?: ""
}
fun ProxyGroup.serialize() {
@@ -116,6 +117,7 @@ class GroupSettingsActivity(
frontProxy = if (DataStore.frontProxy == 1) DataStore.frontProxyOutbound else -1
landingProxy = if (DataStore.landingProxy == 1) DataStore.landingProxyOutbound else -1
+ groupUtlsFingerprint = DataStore.groupUtlsFingerprint
if (type == GroupType.SUBSCRIPTION) {
subscription = SubscriptionBean().applyDefaultValues().apply {
type = DataStore.subscriptionType
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 800a22f6944..08a9ea5e93c 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -441,7 +441,7 @@
URL test
Изменить ID
URL
- uTLS-отпечаток
+ Принудительный uTLS
Правило маршрутизации `%s` требует доступа к вашему **Wi-Fi SSID**, но для этого требуется **включенное местоположение**
Правило маршрутизации `%s` требует доступа к вашему **Wi-Fi SSID**, но для этого требуется **разрешение на точное определение местоположения**
Загрузочный DNS
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 499fe41b385..b25dac2ab81 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -334,7 +334,7 @@
Experimental settings
Make the length of each payload segment no longer malleable. This experiment requires the server and client use the same version of v2ray-core. More breaking updates on this experiment is expected.
Do not send connection single duplex termination signal for TCP connection when transferred over VMess. This will break some applications.
- uTLS fingerprint
+ Force uTLS
Sort
By original order
Alphabetically
diff --git a/app/src/main/res/xml/group_preferences.xml b/app/src/main/res/xml/group_preferences.xml
index 4f5c5bcf506..e538c200510 100644
--- a/app/src/main/res/xml/group_preferences.xml
+++ b/app/src/main/res/xml/group_preferences.xml
@@ -37,6 +37,14 @@
app:key="groupLandingProxy"
app:title="@string/landing_proxy" />
+
+