AnyTLS GUI

thanks husi
This commit is contained in:
armv9 2025-02-23 20:18:31 +09:00
parent 4483a85df2
commit 6a0d998eaf
20 changed files with 1116 additions and 32 deletions

View File

@ -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')"
]
}
}

View File

@ -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')"
]
}
}

View File

@ -188,6 +188,9 @@
<activity
android:name="moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSSettingsActivity"
android:configChanges="uiMode" />
<activity
android:name="moe.matsuri.nb4a.proxy.anytls.AnyTLSSettingsActivity"
android:configChanges="uiMode" />
<activity
android:name="moe.matsuri.nb4a.proxy.neko.NekoSettingActivity"
android:configChanges="uiMode" />

View File

@ -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

View File

@ -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

View File

@ -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")
}

View File

@ -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;

View File

@ -16,6 +16,7 @@ object TypeMap : HashMap<String, Int>() {
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
}

View File

@ -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<String>))
bean.alpn = alpn?.joinToString("\n")
}
}
}
proxies.add(bean)
}
"hysteria" -> {
val bean = HysteriaBean()
bean.protocolVersion = 1

View File

@ -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 <T> Iterable<T>.forEachTry(action: (T) -> Unit) {
var result: Exception? = null

View File

@ -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()
}
}

View File

@ -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<String> network_type;
public List<String> 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;
}
}

View File

@ -113,7 +113,6 @@ fun SingBoxOptions.Rule_DefaultOptions.makeSingBoxRule(list: List<String>, isIP:
if (isIP) {
if (it.startsWith("geoip:")) {
rule_set.plusAssign(it)
rule_set_ipcidr_match_source = false
} else {
ip_cidr.plusAssign(it)
}

View File

@ -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<AnyTLSBean> CREATOR = new CREATOR<AnyTLSBean>() {
@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));
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -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<AnyTLSBean>() {
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<EditTextPreference>(Key.SERVER_PORT)!!.apply {
setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
}
findPreference<EditTextPreference>("password")!!.apply {
summaryProvider = PasswordSummaryProvider
}
}
}

View File

@ -57,15 +57,18 @@
<item
android:id="@+id/action_new_tuic"
android:title="@string/action_tuic" />
<item
android:id="@+id/action_new_shadowtls"
android:title="@string/action_shadowtls" />
<item
android:id="@+id/action_new_anytls"
android:title="@string/action_anytls" />
<item
android:id="@+id/action_new_ssh"
android:title="@string/action_ssh" />
<item
android:id="@+id/action_new_wg"
android:title="@string/action_wireguard" />
<item
android:id="@+id/action_new_shadowtls"
android:title="@string/action_shadowtls" />
<item
android:id="@+id/action_new_config"
android:title="@string/custom_config" />

View File

@ -216,7 +216,7 @@
<string name="action_trojan_go" translatable="false">Trojan Go</string>
<string name="action_mieru" translatable="false">Mieru</string>
<string name="action_naive" translatable="false">Naïve</string>
<string name="action_ping_tunnel" translatable="false">Ping Tunnel</string>
<string name="action_anytls" translatable="false">AnyTLS</string>
<string name="action_hysteria" translatable="false">Hysteria</string>
<string name="action_ssh" translatable="false">SSH</string>
<string name="action_wireguard" translatable="false">WireGuard</string>

View File

@ -0,0 +1,57 @@
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<EditTextPreference
app:icon="@drawable/ic_social_emoji_symbols"
app:key="profileName"
app:title="@string/profile_name"
app:useSimpleSummaryProvider="true" />
<PreferenceCategory app:title="@string/proxy_cat">
<EditTextPreference
app:icon="@drawable/ic_hardware_router"
app:key="serverAddress"
app:title="@string/server_address"
app:useSimpleSummaryProvider="true" />
<EditTextPreference
app:icon="@drawable/ic_maps_directions_boat"
app:key="serverPort"
app:title="@string/server_port"
app:useSimpleSummaryProvider="true" />
<EditTextPreference
app:icon="@drawable/ic_settings_password"
app:key="password"
app:title="@string/password" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/security_settings">
<EditTextPreference
app:icon="@drawable/ic_action_copyright"
app:key="sni"
app:title="@string/sni"
app:useSimpleSummaryProvider="true" />
<SwitchPreference
app:icon="@drawable/ic_notification_enhanced_encryption"
app:key="allowInsecure"
app:title="@string/allow_insecure" />
<EditTextPreference
app:icon="@drawable/ic_baseline_legend_toggle_24"
app:key="alpn"
app:title="@string/alpn"
app:useSimpleSummaryProvider="true" />
<EditTextPreference
app:icon="@drawable/ic_baseline_vpn_key_24"
app:key="certificates"
app:title="@string/certificates"
app:useSimpleSummaryProvider="true" />
<moe.matsuri.nb4a.ui.SimpleMenuPreference
app:defaultValue=""
app:entries="@array/utls_fingerprint_entry"
app:entryValues="@array/utls_fingerprint_entry"
app:icon="@drawable/ic_baseline_fingerprint_24"
app:key="utlsFingerprint"
app:title="@string/utls_fingerprint"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
</PreferenceScreen>

View File

@ -2,10 +2,6 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="@string/general_settings">
<io.nekohasekai.sagernet.widget.AppListPreference
app:icon="@drawable/ic_baseline_android_24"
app:key="nekoPlugins"
app:title="@string/neko_plugin" />
<SwitchPreference
app:defaultValue="false"
app:icon="@drawable/ic_communication_phonelink_ring"
@ -88,6 +84,10 @@
app:key="logLevel"
app:title="@string/log_level"
app:useSimpleSummaryProvider="true" />
<io.nekohasekai.sagernet.widget.AppListPreference
app:icon="@drawable/ic_baseline_android_24"
app:key="nekoPlugins"
app:title="@string/neko_plugin" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/cag_route">
@ -234,14 +234,14 @@
app:title="@string/app_tls_version"
app:useSimpleSummaryProvider="true" />
<SwitchPreference
app:key="showBottomBar"
app:title="@string/show_bottom_bar" />
app:icon="@drawable/ic_action_lock_open"
app:key="globalAllowInsecure"
app:title="@string/global_allow_insecure" />
<SwitchPreference
app:key="allowInsecureOnRequest"
app:title="@string/allow_insecure_on_request_sum" />
<SwitchPreference
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" />
</PreferenceCategory>
</PreferenceScreen>