diff --git a/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/4.json b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/4.json new file mode 100644 index 0000000..4f9fe26 --- /dev/null +++ b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/4.json @@ -0,0 +1,360 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "cff00d0142d9e53d2ca24a6a55cd213c", + "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, `isSelector` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL, `landingProxy` INTEGER NOT NULL)", + "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", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscription", + "columnName": "subscription", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSelector", + "columnName": "isSelector", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "frontProxy", + "columnName": "frontProxy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "landingProxy", + "columnName": "landingProxy", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "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, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `mieruBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` 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", + "notNull": false + }, + { + "fieldPath": "socksBean", + "columnName": "socksBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "httpBean", + "columnName": "httpBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "ssBean", + "columnName": "ssBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "vmessBean", + "columnName": "vmessBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "trojanBean", + "columnName": "trojanBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "trojanGoBean", + "columnName": "trojanGoBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "mieruBean", + "columnName": "mieruBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "naiveBean", + "columnName": "naiveBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "hysteriaBean", + "columnName": "hysteriaBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "tuicBean", + "columnName": "tuicBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "sshBean", + "columnName": "sshBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "wgBean", + "columnName": "wgBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "shadowTLSBean", + "columnName": "shadowTLSBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "chainBean", + "columnName": "chainBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "nekoBean", + "columnName": "nekoBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "configBean", + "columnName": "configBean", + "affinity": "BLOB", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "groupId", + "unique": false, + "columnNames": [ + "groupId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)" + } + ], + "foreignKeys": [] + }, + { + "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, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)", + "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": "outbound", + "columnName": "outbound", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packages", + "columnName": "packages", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "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, 'cff00d0142d9e53d2ca24a6a55cd213c')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/5.json b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/5.json new file mode 100644 index 0000000..b5af39f --- /dev/null +++ b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/5.json @@ -0,0 +1,366 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "1dbf667053726c13d139a4d83c41f895", + "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, `isSelector` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL, `landingProxy` INTEGER NOT NULL)", + "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", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscription", + "columnName": "subscription", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSelector", + "columnName": "isSelector", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "frontProxy", + "columnName": "frontProxy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "landingProxy", + "columnName": "landingProxy", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "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, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `mieruBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `anyTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` 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", + "notNull": false + }, + { + "fieldPath": "socksBean", + "columnName": "socksBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "httpBean", + "columnName": "httpBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "ssBean", + "columnName": "ssBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "vmessBean", + "columnName": "vmessBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "trojanBean", + "columnName": "trojanBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "trojanGoBean", + "columnName": "trojanGoBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "mieruBean", + "columnName": "mieruBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "naiveBean", + "columnName": "naiveBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "hysteriaBean", + "columnName": "hysteriaBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "tuicBean", + "columnName": "tuicBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "sshBean", + "columnName": "sshBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "wgBean", + "columnName": "wgBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "shadowTLSBean", + "columnName": "shadowTLSBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "anyTLSBean", + "columnName": "anyTLSBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "chainBean", + "columnName": "chainBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "nekoBean", + "columnName": "nekoBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "configBean", + "columnName": "configBean", + "affinity": "BLOB", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "groupId", + "unique": false, + "columnNames": [ + "groupId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)" + } + ], + "foreignKeys": [] + }, + { + "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, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)", + "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": "outbound", + "columnName": "outbound", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packages", + "columnName": "packages", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "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, '1dbf667053726c13d139a4d83c41f895')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4b1fc88..c0ed87d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -188,6 +188,9 @@ + diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt index 8d2b129..3a9cc1e 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt @@ -32,8 +32,9 @@ import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean import io.nekohasekai.sagernet.ktx.app import io.nekohasekai.sagernet.ktx.applyDefaultValues import io.nekohasekai.sagernet.ui.profile.* -import moe.matsuri.nb4a.Protocols import moe.matsuri.nb4a.SingBoxOptions.MultiplexOptions +import moe.matsuri.nb4a.proxy.anytls.AnyTLSBean +import moe.matsuri.nb4a.proxy.anytls.AnyTLSSettingsActivity import moe.matsuri.nb4a.proxy.config.ConfigBean import moe.matsuri.nb4a.proxy.config.ConfigSettingActivity import moe.matsuri.nb4a.proxy.neko.* @@ -66,6 +67,7 @@ data class ProxyEntity( var sshBean: SSHBean? = null, var wgBean: WireGuardBean? = null, var shadowTLSBean: ShadowTLSBean? = null, + var anyTLSBean: AnyTLSBean? = null, var chainBean: ChainBean? = null, var nekoBean: NekoBean? = null, var configBean: ConfigBean? = null, @@ -78,16 +80,16 @@ data class ProxyEntity( const val TYPE_VMESS = 4 const val TYPE_TROJAN = 6 - const val TYPE_TROJAN_GO = 7 - const val TYPE_MIERU = 21 - const val TYPE_NAIVE = 9 - const val TYPE_HYSTERIA = 15 - const val TYPE_TUIC = 20 - const val TYPE_SSH = 17 const val TYPE_WG = 18 + const val TYPE_TROJAN_GO = 7 + const val TYPE_NAIVE = 9 + const val TYPE_HYSTERIA = 15 const val TYPE_SHADOWTLS = 19 + const val TYPE_TUIC = 20 + const val TYPE_MIERU = 21 + const val TYPE_ANYTLS = 22 const val TYPE_CONFIG = 998 const val TYPE_NEKO = 999 @@ -173,6 +175,7 @@ data class ProxyEntity( TYPE_WG -> wgBean = KryoConverters.wireguardDeserialize(byteArray) TYPE_TUIC -> tuicBean = KryoConverters.tuicDeserialize(byteArray) TYPE_SHADOWTLS -> shadowTLSBean = KryoConverters.shadowTLSDeserialize(byteArray) + TYPE_ANYTLS -> anyTLSBean = KryoConverters.anyTLSDeserialize(byteArray) TYPE_CHAIN -> chainBean = KryoConverters.chainDeserialize(byteArray) TYPE_NEKO -> nekoBean = KryoConverters.nekoDeserialize(byteArray) TYPE_CONFIG -> configBean = KryoConverters.configDeserialize(byteArray) @@ -193,6 +196,7 @@ data class ProxyEntity( TYPE_WG -> "WireGuard" TYPE_TUIC -> "TUIC" TYPE_SHADOWTLS -> "ShadowTLS" + TYPE_ANYTLS -> "AnyTLS" TYPE_CHAIN -> chainName TYPE_NEKO -> nekoBean!!.displayType() TYPE_CONFIG -> configBean!!.displayType() @@ -217,6 +221,7 @@ data class ProxyEntity( TYPE_WG -> wgBean TYPE_TUIC -> tuicBean TYPE_SHADOWTLS -> shadowTLSBean + TYPE_ANYTLS -> anyTLSBean TYPE_CHAIN -> chainBean TYPE_NEKO -> nekoBean TYPE_CONFIG -> configBean @@ -236,6 +241,7 @@ data class ProxyEntity( is SSHBean -> false is WireGuardBean -> false is ShadowTLSBean -> false + is AnyTLSBean -> false is NekoBean -> nekoBean!!.haveStandardLink() is ConfigBean -> false else -> true @@ -352,6 +358,7 @@ data class ProxyEntity( wgBean = null tuicBean = null shadowTLSBean = null + anyTLSBean = null chainBean = null configBean = null nekoBean = null @@ -422,6 +429,11 @@ data class ProxyEntity( shadowTLSBean = bean } + is AnyTLSBean -> { + type = TYPE_ANYTLS + anyTLSBean = bean + } + is ChainBean -> { type = TYPE_CHAIN chainBean = bean @@ -458,6 +470,7 @@ data class ProxyEntity( TYPE_WG -> WireGuardSettingsActivity::class.java TYPE_TUIC -> TuicSettingsActivity::class.java TYPE_SHADOWTLS -> ShadowTLSSettingsActivity::class.java + TYPE_ANYTLS -> AnyTLSSettingsActivity::class.java TYPE_CHAIN -> ChainSettingsActivity::class.java TYPE_NEKO -> NekoSettingActivity::class.java TYPE_CONFIG -> ConfigSettingActivity::class.java 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 a235f2a..cb9cade 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt @@ -1,5 +1,6 @@ package io.nekohasekai.sagernet.database +import androidx.room.AutoMigration import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase @@ -15,7 +16,11 @@ import kotlinx.coroutines.launch @Database( entities = [ProxyGroup::class, ProxyEntity::class, RuleEntity::class], - version = 3 + version = 5, + autoMigrations = [ + AutoMigration(from = 3, to = 4), + AutoMigration(from = 4, to = 5) + ] ) @TypeConverters(value = [KryoConverters::class, GsonConverters::class]) @GenerateRoomMigrations 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 686bce1..dd6e816 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt @@ -29,6 +29,8 @@ import io.nekohasekai.sagernet.utils.PackageCache import moe.matsuri.nb4a.* import moe.matsuri.nb4a.SingBoxOptions.* import moe.matsuri.nb4a.plugin.Plugins +import moe.matsuri.nb4a.proxy.anytls.AnyTLSBean +import moe.matsuri.nb4a.proxy.anytls.buildSingBoxOutboundAnyTLSBean import moe.matsuri.nb4a.proxy.config.ConfigBean import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean import moe.matsuri.nb4a.proxy.shadowtls.buildSingBoxOutboundShadowTLSBean @@ -375,6 +377,9 @@ fun buildConfig( is SSHBean -> buildSingBoxOutboundSSHBean(bean).asMap() + is AnyTLSBean -> + buildSingBoxOutboundAnyTLSBean(bean).asMap() + else -> throw IllegalStateException("can't reach") } diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java index 627ea35..8fb9951 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java @@ -16,6 +16,7 @@ import io.nekohasekai.sagernet.fmt.internal.ChainBean; import io.nekohasekai.sagernet.fmt.mieru.MieruBean; import io.nekohasekai.sagernet.fmt.naive.NaiveBean; import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean; +import moe.matsuri.nb4a.proxy.anytls.AnyTLSBean; import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean; import io.nekohasekai.sagernet.fmt.socks.SOCKSBean; import io.nekohasekai.sagernet.fmt.ssh.SSHBean; @@ -142,6 +143,13 @@ public class KryoConverters { return deserialize(new ShadowTLSBean(), bytes); } + @TypeConverter + public static AnyTLSBean anyTLSDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new AnyTLSBean(), bytes); + } + + @TypeConverter public static ChainBean chainDeserialize(byte[] bytes) { if (JavaUtil.isEmpty(bytes)) return null; diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt index 12554a1..1a91eef 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt @@ -16,6 +16,7 @@ object TypeMap : HashMap() { this["ssh"] = ProxyEntity.TYPE_SSH this["wg"] = ProxyEntity.TYPE_WG this["tuic"] = ProxyEntity.TYPE_TUIC + this["anytls"] = ProxyEntity.TYPE_ANYTLS this["neko"] = ProxyEntity.TYPE_NEKO this["config"] = ProxyEntity.TYPE_CONFIG } diff --git a/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt b/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt index e2d0f72..4b989c4 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt @@ -22,6 +22,7 @@ import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean import io.nekohasekai.sagernet.ktx.* import libcore.Libcore import moe.matsuri.nb4a.Protocols +import moe.matsuri.nb4a.proxy.anytls.AnyTLSBean import moe.matsuri.nb4a.proxy.config.ConfigBean import moe.matsuri.nb4a.utils.Util import org.ini4j.Ini @@ -510,6 +511,31 @@ object RawUpdater : GroupUpdater() { proxies.add(bean) } + "anytls" -> { + val bean = AnyTLSBean() + for (opt in proxy) { + if (opt.value == null) continue + when (opt.key.replace("_", "-")) { + "name" -> bean.name = opt.value.toString() + "server" -> bean.serverAddress = opt.value as String + "port" -> bean.serverPort = opt.value.toString().toInt() + "password" -> bean.password = opt.value.toString() + "client-fingerprint" -> bean.utlsFingerprint = + opt.value as String + + "sni" -> bean.sni = opt.value.toString() + "skip-cert-verify" -> bean.allowInsecure = + opt.value.toString() == "true" + + "alpn" -> { + val alpn = (opt.value as? (List)) + bean.alpn = alpn?.joinToString("\n") + } + } + } + proxies.add(bean) + } + "hysteria" -> { val bean = HysteriaBean() bean.protocolVersion = 1 diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt index 01af7cb..98bbc20 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt @@ -5,8 +5,11 @@ package io.nekohasekai.sagernet.ktx import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.annotation.SuppressLint -import android.content.* -import android.content.pm.PackageInfo +import android.content.ActivityNotFoundException +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.content.res.Resources import android.os.Build import android.system.Os @@ -33,10 +36,17 @@ import io.nekohasekai.sagernet.database.SagerDatabase import io.nekohasekai.sagernet.database.preference.PublicDatabase import io.nekohasekai.sagernet.ui.MainActivity import io.nekohasekai.sagernet.ui.ThemedActivity -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine import moe.matsuri.nb4a.utils.NGUtil import java.io.FileDescriptor -import java.net.* +import java.net.HttpURLConnection +import java.net.InetAddress +import java.net.Socket +import java.net.URLEncoder import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong @@ -48,6 +58,7 @@ import kotlin.reflect.KMutableProperty0 import kotlin.reflect.KProperty import kotlin.reflect.KProperty0 +fun String?.blankAsNull(): String? = if (isNullOrBlank()) null else this inline fun Iterable.forEachTry(action: (T) -> Unit) { var result: Exception? = null diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt index 5b1c50f..ec53e1a 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt @@ -57,6 +57,7 @@ import kotlinx.coroutines.sync.withLock import moe.matsuri.nb4a.Protocols import moe.matsuri.nb4a.Protocols.getProtocolColor import moe.matsuri.nb4a.plugin.NekoPluginManager +import moe.matsuri.nb4a.proxy.anytls.AnyTLSSettingsActivity import moe.matsuri.nb4a.proxy.config.ConfigSettingActivity import moe.matsuri.nb4a.proxy.neko.NekoJSInterface import moe.matsuri.nb4a.proxy.neko.NekoSettingActivity @@ -148,7 +149,7 @@ class ConfigurationFragment @JvmOverloads constructor( searchView.setOnQueryTextListener(this) searchView.maxWidth = Int.MAX_VALUE - searchView.setOnQueryTextFocusChangeListener { _, hasFocus -> + searchView.setOnQueryTextFocusChangeListener { _, hasFocus -> if (!hasFocus) { cancelSearch(searchView) } @@ -390,6 +391,10 @@ class ConfigurationFragment @JvmOverloads constructor( startActivity(Intent(requireActivity(), ShadowTLSSettingsActivity::class.java)) } + R.id.action_new_anytls -> { + startActivity(Intent(requireActivity(), AnyTLSSettingsActivity::class.java)) + } + R.id.action_new_config -> { startActivity(Intent(requireActivity(), ConfigSettingActivity::class.java)) } @@ -1711,9 +1716,9 @@ class ConfigurationFragment @JvmOverloads constructor( } } - private fun cancelSearch(searchView: SearchView) { - searchView.onActionViewCollapsed() - searchView.clearFocus() - } + private fun cancelSearch(searchView: SearchView) { + searchView.onActionViewCollapsed() + searchView.clearFocus() + } } diff --git a/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java b/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java index 0b98da0..83f1428 100644 --- a/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java +++ b/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java @@ -4335,7 +4335,6 @@ public class SingBoxOptions { public Boolean source_ip_is_private; - public Boolean rule_set_ipcidr_match_source; public Boolean ip_is_private; // Generate note: Listable @@ -4509,4 +4508,57 @@ public class SingBoxOptions { } + // sing-box Options 生成器已经坏了,以下是从 husi 抄的 + + public static class Outbound_AnyTLSOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public String connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean tcp_multi_path; + + public Boolean udp_fragment; + + public String domain_strategy; + + public String network_strategy; + + public List network_type; + + public List fallback_network_type; + + public String fallback_delay; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // Generate note: nested type OutboundTLSOptionsContainer + public OutboundTLSOptions tls; + + public String password; + + public String idle_session_check_interval; + + public String idle_session_timeout; + + } + } diff --git a/app/src/main/java/moe/matsuri/nb4a/SingBoxOptionsUtil.kt b/app/src/main/java/moe/matsuri/nb4a/SingBoxOptionsUtil.kt index 0c2b1e9..c7059b2 100644 --- a/app/src/main/java/moe/matsuri/nb4a/SingBoxOptionsUtil.kt +++ b/app/src/main/java/moe/matsuri/nb4a/SingBoxOptionsUtil.kt @@ -113,7 +113,6 @@ fun SingBoxOptions.Rule_DefaultOptions.makeSingBoxRule(list: List, isIP: if (isIP) { if (it.startsWith("geoip:")) { rule_set.plusAssign(it) - rule_set_ipcidr_match_source = false } else { ip_cidr.plusAssign(it) } diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSBean.java b/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSBean.java new file mode 100644 index 0000000..43f4144 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSBean.java @@ -0,0 +1,82 @@ +package moe.matsuri.nb4a.proxy.anytls; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; + +public class AnyTLSBean extends AbstractBean { + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public AnyTLSBean newInstance() { + return new AnyTLSBean(); + } + + @Override + public AnyTLSBean[] newArray(int size) { + return new AnyTLSBean[size]; + } + }; + public String password; + public String sni; + public String alpn; + public String certificates; + public String utlsFingerprint; + public Boolean allowInsecure; + // In sing-box, this seemed can be used with REALITY. + // But even mihomo appended many options, it still not provide REALITY. + // https://github.com/anytls/anytls-go/blob/4636d90462fa21a510420512d7706a9acf69c7b9/docs/faq.md?plain=1#L25-L37 + + public String echConfig; + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + if (password == null) password = ""; + if (sni == null) sni = ""; + if (alpn == null) alpn = ""; + if (certificates == null) certificates = ""; + if (utlsFingerprint == null) utlsFingerprint = ""; + if (allowInsecure == null) allowInsecure = false; + if (echConfig == null) echConfig = ""; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(0); + super.serialize(output); + output.writeString(password); + output.writeString(sni); + output.writeString(alpn); + output.writeString(certificates); + output.writeString(utlsFingerprint); + output.writeBoolean(allowInsecure); + output.writeString(echConfig); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + password = input.readString(); + sni = input.readString(); + alpn = input.readString(); + certificates = input.readString(); + utlsFingerprint = input.readString(); + allowInsecure = input.readBoolean(); + echConfig = input.readString(); + } + + @NotNull + @Override + public AnyTLSBean clone() { + return KryoConverters.deserialize(new AnyTLSBean(), KryoConverters.serialize(this)); + } +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSFmt.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSFmt.kt new file mode 100644 index 0000000..06100d1 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSFmt.kt @@ -0,0 +1,37 @@ +package moe.matsuri.nb4a.proxy.anytls + +import io.nekohasekai.sagernet.ktx.blankAsNull +import moe.matsuri.nb4a.SingBoxOptions +import moe.matsuri.nb4a.utils.listByLineOrComma + +fun buildSingBoxOutboundAnyTLSBean(bean: AnyTLSBean): SingBoxOptions.Outbound_AnyTLSOptions { + return SingBoxOptions.Outbound_AnyTLSOptions().apply { + type = "anytls" + server = bean.serverAddress + server_port = bean.serverPort + password = bean.password + + tls = SingBoxOptions.OutboundTLSOptions().apply { + enabled = true + server_name = bean.sni.blankAsNull() + if (bean.allowInsecure) insecure = true + alpn = bean.alpn.blankAsNull()?.listByLineOrComma() + bean.certificates.blankAsNull()?.let { + certificate = it + } + bean.utlsFingerprint.blankAsNull()?.let { + utls = SingBoxOptions.OutboundUTLSOptions().apply { + enabled = true + fingerprint = it + } + } + bean.echConfig.blankAsNull()?.let { + // In new version, some complex options will be deprecated, so we just do this. + ech = SingBoxOptions.OutboundECHOptions().apply { + enabled = true + config = listOf(it) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSSettingsActivity.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSSettingsActivity.kt new file mode 100644 index 0000000..13d256d --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSSettingsActivity.kt @@ -0,0 +1,51 @@ +package moe.matsuri.nb4a.proxy.anytls + +import android.os.Bundle +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceFragmentCompat +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.ktx.applyDefaultValues +import io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity +import moe.matsuri.nb4a.proxy.PreferenceBinding +import moe.matsuri.nb4a.proxy.PreferenceBindingManager +import moe.matsuri.nb4a.proxy.Type + +class AnyTLSSettingsActivity : ProfileSettingsActivity() { + override fun createEntity() = AnyTLSBean().applyDefaultValues() + + private val pbm = PreferenceBindingManager() + private val name = pbm.add(PreferenceBinding(Type.Text, "name")) + private val serverAddress = pbm.add(PreferenceBinding(Type.Text, "serverAddress")) + private val serverPort = pbm.add(PreferenceBinding(Type.TextToInt, "serverPort")) + private val password = pbm.add(PreferenceBinding(Type.Text, "password")) + private val sni = pbm.add(PreferenceBinding(Type.Text, "sni")) + private val alpn = pbm.add(PreferenceBinding(Type.Text, "alpn")) + private val certificates = pbm.add(PreferenceBinding(Type.Text, "certificates")) + private val allowInsecure = pbm.add(PreferenceBinding(Type.Bool, "allowInsecure")) + private val utlsFingerprint = pbm.add(PreferenceBinding(Type.Text, "utlsFingerprint")) + + override fun AnyTLSBean.init() { + pbm.writeToCacheAll(this) + + } + + override fun AnyTLSBean.serialize() { + pbm.fromCacheAll(this) + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String? + ) { + addPreferencesFromResource(R.xml.anytls_preferences) + + findPreference(Key.SERVER_PORT)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + } + findPreference("password")!!.apply { + summaryProvider = PasswordSummaryProvider + } + } +} diff --git a/app/src/main/res/menu/add_profile_menu.xml b/app/src/main/res/menu/add_profile_menu.xml index 129d320..9ab6929 100644 --- a/app/src/main/res/menu/add_profile_menu.xml +++ b/app/src/main/res/menu/add_profile_menu.xml @@ -57,15 +57,18 @@ + + - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 196858c..920679f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -216,7 +216,7 @@ Trojan Go Mieru Naïve - Ping Tunnel + AnyTLS Hysteria SSH WireGuard diff --git a/app/src/main/res/xml/anytls_preferences.xml b/app/src/main/res/xml/anytls_preferences.xml new file mode 100644 index 0000000..d77f793 --- /dev/null +++ b/app/src/main/res/xml/anytls_preferences.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/global_preferences.xml b/app/src/main/res/xml/global_preferences.xml index 463e92a..cc51866 100644 --- a/app/src/main/res/xml/global_preferences.xml +++ b/app/src/main/res/xml/global_preferences.xml @@ -2,10 +2,6 @@ - + @@ -234,14 +234,14 @@ app:title="@string/app_tls_version" app:useSimpleSummaryProvider="true" /> + app:icon="@drawable/ic_action_lock_open" + app:key="globalAllowInsecure" + app:title="@string/global_allow_insecure" /> + app:key="showBottomBar" + app:title="@string/show_bottom_bar" />