diff --git a/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/37.json b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/37.json new file mode 100644 index 00000000000..0a08035367e --- /dev/null +++ b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/37.json @@ -0,0 +1,475 @@ +{ + "formatVersion": 1, + "database": { + "version": 37, + "identityHash": "286ab111b45c46a16635823c2396e034", + "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, `utlsFingerprintForTLS` TEXT NOT NULL DEFAULT '', `utlsFingerprintForReality` TEXT NOT NULL DEFAULT '')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userOrder", + "columnName": "userOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ungrouped", + "columnName": "ungrouped", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscription", + "columnName": "subscription", + "affinity": "BLOB" + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "frontProxy", + "columnName": "frontProxy", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "landingProxy", + "columnName": "landingProxy", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "utlsFingerprintForTLS", + "columnName": "utlsFingerprintForTLS", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "utlsFingerprintForReality", + "columnName": "utlsFingerprintForReality", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "proxy_entities", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `ssrBean` BLOB, `vmessBean` BLOB, `vlessBean` BLOB, `trojanBean` BLOB, `naiveBean` BLOB, `hysteria2Bean` BLOB, `mieruBean` BLOB, `tuic5Bean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `juicityBean` BLOB, `http3Bean` BLOB, `anytlsBean` BLOB, `shadowquicBean` BLOB, `trustTunnelBean` BLOB, `configBean` BLOB, `chainBean` BLOB, `balancerBean` BLOB)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "groupId", + "columnName": "groupId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userOrder", + "columnName": "userOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tx", + "columnName": "tx", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "rx", + "columnName": "rx", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ping", + "columnName": "ping", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT" + }, + { + "fieldPath": "socksBean", + "columnName": "socksBean", + "affinity": "BLOB" + }, + { + "fieldPath": "httpBean", + "columnName": "httpBean", + "affinity": "BLOB" + }, + { + "fieldPath": "ssBean", + "columnName": "ssBean", + "affinity": "BLOB" + }, + { + "fieldPath": "ssrBean", + "columnName": "ssrBean", + "affinity": "BLOB" + }, + { + "fieldPath": "vmessBean", + "columnName": "vmessBean", + "affinity": "BLOB" + }, + { + "fieldPath": "vlessBean", + "columnName": "vlessBean", + "affinity": "BLOB" + }, + { + "fieldPath": "trojanBean", + "columnName": "trojanBean", + "affinity": "BLOB" + }, + { + "fieldPath": "naiveBean", + "columnName": "naiveBean", + "affinity": "BLOB" + }, + { + "fieldPath": "hysteria2Bean", + "columnName": "hysteria2Bean", + "affinity": "BLOB" + }, + { + "fieldPath": "mieruBean", + "columnName": "mieruBean", + "affinity": "BLOB" + }, + { + "fieldPath": "tuic5Bean", + "columnName": "tuic5Bean", + "affinity": "BLOB" + }, + { + "fieldPath": "sshBean", + "columnName": "sshBean", + "affinity": "BLOB" + }, + { + "fieldPath": "wgBean", + "columnName": "wgBean", + "affinity": "BLOB" + }, + { + "fieldPath": "juicityBean", + "columnName": "juicityBean", + "affinity": "BLOB" + }, + { + "fieldPath": "http3Bean", + "columnName": "http3Bean", + "affinity": "BLOB" + }, + { + "fieldPath": "anytlsBean", + "columnName": "anytlsBean", + "affinity": "BLOB" + }, + { + "fieldPath": "shadowquicBean", + "columnName": "shadowquicBean", + "affinity": "BLOB" + }, + { + "fieldPath": "trustTunnelBean", + "columnName": "trustTunnelBean", + "affinity": "BLOB" + }, + { + "fieldPath": "configBean", + "columnName": "configBean", + "affinity": "BLOB" + }, + { + "fieldPath": "chainBean", + "columnName": "chainBean", + "affinity": "BLOB" + }, + { + "fieldPath": "balancerBean", + "columnName": "balancerBean", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "groupId", + "unique": false, + "columnNames": [ + "groupId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)" + } + ] + }, + { + "tableName": "rules", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `attrs` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL, `ssid` TEXT NOT NULL DEFAULT '', `networkType` TEXT NOT NULL DEFAULT '', `customPackageNames` TEXT NOT NULL DEFAULT '')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userOrder", + "columnName": "userOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "domains", + "columnName": "domains", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ip", + "columnName": "ip", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sourcePort", + "columnName": "sourcePort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "network", + "columnName": "network", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "protocol", + "columnName": "protocol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attrs", + "columnName": "attrs", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "outbound", + "columnName": "outbound", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packages", + "columnName": "packages", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ssid", + "columnName": "ssid", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "networkType", + "columnName": "networkType", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "customPackageNames", + "columnName": "customPackageNames", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "trafficStats", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uid` INTEGER NOT NULL, `tcpConnections` INTEGER NOT NULL, `udpConnections` INTEGER NOT NULL, `uplink` INTEGER NOT NULL, `downlink` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tcpConnections", + "columnName": "tcpConnections", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "udpConnections", + "columnName": "udpConnections", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uplink", + "columnName": "uplink", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downlink", + "columnName": "downlink", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "assets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + } + ], + "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, '286ab111b45c46a16635823c2396e034')" + ] + } +} \ 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..cd59471f7ff 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt @@ -295,6 +295,8 @@ 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_FOR_TLS = "groupUTLSFingerprintForTLS" + const val GROUP_UTLS_FINGERPRINT_FOR_REALITY = "groupUTLSFingerprintForReality" 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..90dc839c49d 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,8 @@ 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 groupUTLSFingerprintForTLS by profileCacheStore.string(Key.GROUP_UTLS_FINGERPRINT_FOR_TLS) + var groupUTLSFingerprintForReality by profileCacheStore.string(Key.GROUP_UTLS_FINGERPRINT_FOR_REALITY) 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..7d3ab70aa42 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,9 @@ 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 utlsFingerprintForTLS: String = "", + @ColumnInfo(defaultValue = "") var utlsFingerprintForReality: String = "", ) : Serializable() { @Transient @@ -57,7 +59,7 @@ data class ProxyGroup( val subscription = subscription!! subscription.serializeForShare(output) } else { - output.writeInt(1) + output.writeInt(2) output.writeLong(id) output.writeLong(userOrder) output.writeBoolean(ungrouped) @@ -69,6 +71,8 @@ data class ProxyGroup( output.writeInt(order) output.writeLong(frontProxy) output.writeLong(landingProxy) + output.writeString(utlsFingerprintForTLS) + output.writeString(utlsFingerprintForReality) } } @@ -97,6 +101,10 @@ data class ProxyGroup( frontProxy = input.readLong() landingProxy = input.readLong() } + if (version >= 2) { + utlsFingerprintForTLS = input.readString() + utlsFingerprintForReality = input.readString() + } } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt b/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt index a941276e17f..f7d1cc0f2e6 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt @@ -29,7 +29,7 @@ import kotlinx.coroutines.launch @Database( entities = [ProxyGroup::class, ProxyEntity::class, RuleEntity::class, StatsEntity::class, AssetEntity::class], - version = 36, + version = 37, autoMigrations = [AutoMigration( from = 12, to = 14, @@ -105,6 +105,9 @@ import kotlinx.coroutines.launch from = 35, to = 36, spec = SagerDatabase_Migration_35_36::class + ), AutoMigration( + from = 36, + to = 37, )] ) @TypeConverters(value = [KryoConverters::class, GsonConverters::class]) 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..bbde85c0348 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() + val proxyGroup = SagerDatabase.groupDao.getById(proxyEntity.groupId) + val groupUTLSFingerprintForTLS = proxyGroup?.utlsFingerprintForTLS ?: "" + val groupUTLSFingerprintForReality = proxyGroup?.utlsFingerprintForReality ?: "" + 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 (!groupUTLSFingerprintForTLS.isNotEmpty()) { + fingerprint = groupUTLSFingerprintForTLS } 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 (groupUTLSFingerprintForReality.isNotEmpty()) { + fingerprint = groupUTLSFingerprintForReality } 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 (groupUTLSFingerprintForTLS.isNotEmpty()) { + fingerprint = groupUTLSFingerprintForTLS } 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 (groupUTLSFingerprintForReality.isNotEmpty()) { + fingerprint = groupUTLSFingerprintForReality } 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 (groupUTLSFingerprintForTLS.isNotEmpty()) { + fingerprint = groupUTLSFingerprintForTLS } 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..468f57812f6 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,8 @@ class GroupSettingsActivity( DataStore.landingProxyOutbound = landingProxy DataStore.frontProxy = if (frontProxy >= 0) 1 else 0 DataStore.landingProxy = if (landingProxy >= 0) 1 else 0 + DataStore.groupUTLSFingerprintForTLS = utlsFingerprintForTLS + DataStore.groupUTLSFingerprintForReality = utlsFingerprintForReality } fun ProxyGroup.serialize() { @@ -116,6 +118,8 @@ class GroupSettingsActivity( frontProxy = if (DataStore.frontProxy == 1) DataStore.frontProxyOutbound else -1 landingProxy = if (DataStore.landingProxy == 1) DataStore.landingProxyOutbound else -1 + utlsFingerprintForTLS = DataStore.groupUTLSFingerprintForTLS + utlsFingerprintForReality = DataStore.groupUTLSFingerprintForReality if (type == GroupType.SUBSCRIPTION) { subscription = SubscriptionBean().applyDefaultValues().apply { type = DataStore.subscriptionType diff --git a/app/src/main/res/xml/group_preferences.xml b/app/src/main/res/xml/group_preferences.xml index eba545b17ac..c80b89032ac 100644 --- a/app/src/main/res/xml/group_preferences.xml +++ b/app/src/main/res/xml/group_preferences.xml @@ -37,6 +37,22 @@ app:key="groupLandingProxy" app:title="@string/landing_proxy" /> + + + + diff --git a/version.properties b/version.properties index 43342ab86b8..1905a5027bf 100644 --- a/version.properties +++ b/version.properties @@ -1,6 +1,6 @@ PACKAGE_NAME=com.github.dyhkwong.sagernet VERSION_NAME=0.17.46 -VERSION_CODE=364 +VERSION_CODE=365 NAIVE_VERSION_NAME=149.0.7827.114-1 NAIVE_VERSION=275