From f55d14b4439092cdb7e61f3235214677f4cde6de Mon Sep 17 00:00:00 2001
From: arm64v8a <48624112+arm64v8a@users.noreply.github.com>
Date: Sat, 18 Mar 2023 18:01:33 +0900
Subject: [PATCH] add shadowtls gui
---
.../2.json | 12 +++-
app/src/main/AndroidManifest.xml | 3 +
.../sagernet/database/ProxyEntity.kt | 23 +++++--
.../nekohasekai/sagernet/fmt/ConfigBuilder.kt | 6 +-
.../sagernet/fmt/KryoConverters.java | 7 ++
.../sagernet/ui/ConfigurationFragment.kt | 4 ++
.../nb4a/proxy/shadowtls/ShadowTLSBean.java | 61 +++++++++++++++++
.../nb4a/proxy/shadowtls/ShadowTLSFmt.kt | 15 +++++
.../shadowtls/ShadowTLSSettingsActivity.kt | 55 ++++++++++++++++
app/src/main/res/menu/add_profile_menu.xml | 3 +
app/src/main/res/values-es/strings.xml | 16 +----
app/src/main/res/values-zh-rCN/strings.xml | 4 ++
app/src/main/res/values/arrays.xml | 5 ++
app/src/main/res/values/strings.xml | 2 +
.../main/res/xml/shadowtls_preferences.xml | 65 +++++++++++++++++++
15 files changed, 257 insertions(+), 24 deletions(-)
create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSBean.java
create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSFmt.kt
create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSSettingsActivity.kt
create mode 100644 app/src/main/res/xml/shadowtls_preferences.xml
diff --git a/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/2.json b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/2.json
index 1322923..983ebc7 100644
--- a/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/2.json
+++ b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/2.json
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 2,
- "identityHash": "937a517378a0cb35dc1f8bd181683882",
+ "identityHash": "9ec160533656482a17cbd563e9e3e416",
"entities": [
{
"tableName": "proxy_groups",
@@ -80,7 +80,7 @@
},
{
"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, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)",
+ "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, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)",
"fields": [
{
"fieldPath": "id",
@@ -208,6 +208,12 @@
"affinity": "BLOB",
"notNull": false
},
+ {
+ "fieldPath": "shadowTLSBean",
+ "columnName": "shadowTLSBean",
+ "affinity": "BLOB",
+ "notNull": false
+ },
{
"fieldPath": "chainBean",
"columnName": "chainBean",
@@ -342,7 +348,7 @@
"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, '937a517378a0cb35dc1f8bd181683882')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9ec160533656482a17cbd563e9e3e416')"
]
}
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f785528..f06850e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -184,6 +184,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 602d233..228c625 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt
@@ -15,6 +15,7 @@ import io.nekohasekai.sagernet.fmt.naive.NaiveBean
import io.nekohasekai.sagernet.fmt.naive.buildNaiveConfig
import io.nekohasekai.sagernet.fmt.naive.toUri
import io.nekohasekai.sagernet.fmt.shadowsocks.*
+import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean
import io.nekohasekai.sagernet.fmt.socks.SOCKSBean
import io.nekohasekai.sagernet.fmt.socks.toUri
import io.nekohasekai.sagernet.fmt.ssh.SSHBean
@@ -34,6 +35,7 @@ import moe.matsuri.nb4a.Protocols
import moe.matsuri.nb4a.proxy.config.ConfigBean
import moe.matsuri.nb4a.proxy.config.ConfigSettingActivity
import moe.matsuri.nb4a.proxy.neko.*
+import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSSettingsActivity
@Entity(
tableName = "proxy_entities", indices = [Index("groupId", name = "groupId")]
@@ -60,6 +62,7 @@ data class ProxyEntity(
var tuicBean: TuicBean? = null,
var sshBean: SSHBean? = null,
var wgBean: WireGuardBean? = null,
+ var shadowTLSBean: ShadowTLSBean? = null,
var chainBean: ChainBean? = null,
var nekoBean: NekoBean? = null,
var configBean: ConfigBean? = null,
@@ -70,16 +73,17 @@ data class ProxyEntity(
const val TYPE_HTTP = 1
const val TYPE_SS = 2
const val TYPE_VMESS = 4
-
const val TYPE_TROJAN = 6
+
const val TYPE_TROJAN_GO = 7
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_TUIC = 20
+ const val TYPE_SHADOWTLS = 19
const val TYPE_CONFIG = 998
const val TYPE_NEKO = 999
@@ -103,10 +107,6 @@ data class ProxyEntity(
}
}
- @Ignore
- @Transient
- var info: String = ""
-
@Ignore
@Transient
var dirty: Boolean = false
@@ -167,6 +167,7 @@ data class ProxyEntity(
TYPE_SSH -> sshBean = KryoConverters.sshDeserialize(byteArray)
TYPE_WG -> wgBean = KryoConverters.wireguardDeserialize(byteArray)
TYPE_TUIC -> tuicBean = KryoConverters.tuicDeserialize(byteArray)
+ TYPE_SHADOWTLS -> shadowTLSBean = KryoConverters.shadowTLSDeserialize(byteArray)
TYPE_CHAIN -> chainBean = KryoConverters.chainDeserialize(byteArray)
TYPE_NEKO -> nekoBean = KryoConverters.nekoDeserialize(byteArray)
TYPE_CONFIG -> configBean = KryoConverters.configDeserialize(byteArray)
@@ -185,6 +186,7 @@ data class ProxyEntity(
TYPE_SSH -> "SSH"
TYPE_WG -> "WireGuard"
TYPE_TUIC -> "TUIC"
+ TYPE_SHADOWTLS -> "ShadowTLS"
TYPE_CHAIN -> chainName
TYPE_NEKO -> nekoBean!!.displayType()
TYPE_CONFIG -> configBean!!.displayType()
@@ -207,6 +209,7 @@ data class ProxyEntity(
TYPE_SSH -> sshBean
TYPE_WG -> wgBean
TYPE_TUIC -> tuicBean
+ TYPE_SHADOWTLS -> shadowTLSBean
TYPE_CHAIN -> chainBean
TYPE_NEKO -> nekoBean
TYPE_CONFIG -> configBean
@@ -226,6 +229,7 @@ data class ProxyEntity(
is TuicBean -> false
is SSHBean -> false
is WireGuardBean -> false
+ is ShadowTLSBean -> false
is NekoBean -> nekoBean!!.haveStandardLink()
is ConfigBean -> false
else -> true
@@ -245,6 +249,7 @@ data class ProxyEntity(
is SSHBean -> toUniversalLink()
is WireGuardBean -> toUniversalLink()
is TuicBean -> toUniversalLink()
+ is ShadowTLSBean -> toUniversalLink()
is ConfigBean -> toUniversalLink()
is NekoBean -> shareLink()
else -> null
@@ -329,6 +334,7 @@ data class ProxyEntity(
sshBean = null
wgBean = null
tuicBean = null
+ shadowTLSBean = null
chainBean = null
configBean = null
nekoBean = null
@@ -378,6 +384,10 @@ data class ProxyEntity(
type = TYPE_TUIC
tuicBean = bean
}
+ is ShadowTLSBean -> {
+ type = TYPE_SHADOWTLS
+ shadowTLSBean = bean
+ }
is ChainBean -> {
type = TYPE_CHAIN
chainBean = bean
@@ -409,6 +419,7 @@ data class ProxyEntity(
TYPE_SSH -> SSHSettingsActivity::class.java
TYPE_WG -> WireGuardSettingsActivity::class.java
TYPE_TUIC -> TuicSettingsActivity::class.java
+ TYPE_SHADOWTLS -> ShadowTLSSettingsActivity::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/fmt/ConfigBuilder.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
index 691a18a..72babe0 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
@@ -32,6 +32,8 @@ import moe.matsuri.nb4a.DNS.makeSingBoxRule
import moe.matsuri.nb4a.SingBoxOptions.*
import moe.matsuri.nb4a.plugin.Plugins
import moe.matsuri.nb4a.proxy.config.ConfigBean
+import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean
+import moe.matsuri.nb4a.proxy.shadowtls.buildSingBoxOutboundShadowTLSBean
import moe.matsuri.nb4a.utils.JavaUtil.gson
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@@ -360,7 +362,9 @@ fun buildConfig(
currentOutbound = when (bean) {
is ConfigBean ->
gson.fromJson(bean.config, currentOutbound.javaClass)
- is StandardV2RayBean ->
+ is ShadowTLSBean -> // before StandardV2RayBean
+ buildSingBoxOutboundShadowTLSBean(bean).asMap()
+ is StandardV2RayBean -> // http/trojan/vmess/vless
buildSingBoxOutboundStandardV2RayBean(bean).asMap()
is HysteriaBean ->
buildSingBoxOutboundHysteriaBean(bean).asMap()
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 8000c02..b9a6338 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java
@@ -15,6 +15,7 @@ import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean;
import io.nekohasekai.sagernet.fmt.internal.ChainBean;
import io.nekohasekai.sagernet.fmt.naive.NaiveBean;
import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean;
+import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean;
import io.nekohasekai.sagernet.fmt.socks.SOCKSBean;
import io.nekohasekai.sagernet.fmt.ssh.SSHBean;
import io.nekohasekai.sagernet.fmt.trojan.TrojanBean;
@@ -128,6 +129,12 @@ public class KryoConverters {
return deserialize(new TuicBean(), bytes);
}
+ @TypeConverter
+ public static ShadowTLSBean shadowTLSDeserialize(byte[] bytes) {
+ if (JavaUtil.isEmpty(bytes)) return null;
+ return deserialize(new ShadowTLSBean(), bytes);
+ }
+
@TypeConverter
public static ChainBean chainDeserialize(byte[] bytes) {
if (JavaUtil.isEmpty(bytes)) return 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 7565501..a396644 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt
@@ -61,6 +61,7 @@ import moe.matsuri.nb4a.proxy.config.ConfigSettingActivity
import moe.matsuri.nb4a.proxy.neko.NekoJSInterface
import moe.matsuri.nb4a.proxy.neko.NekoSettingActivity
import moe.matsuri.nb4a.proxy.neko.canShare
+import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSSettingsActivity
import okhttp3.internal.closeQuietly
import java.net.InetAddress
import java.net.InetSocketAddress
@@ -354,6 +355,9 @@ class ConfigurationFragment @JvmOverloads constructor(
R.id.action_new_wg -> {
startActivity(Intent(requireActivity(), WireGuardSettingsActivity::class.java))
}
+ R.id.action_new_shadowtls -> {
+ startActivity(Intent(requireActivity(), ShadowTLSSettingsActivity::class.java))
+ }
R.id.action_new_config -> {
startActivity(Intent(requireActivity(), ConfigSettingActivity::class.java))
}
diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSBean.java b/app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSBean.java
new file mode 100644
index 0000000..43cf283
--- /dev/null
+++ b/app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSBean.java
@@ -0,0 +1,61 @@
+package moe.matsuri.nb4a.proxy.shadowtls;
+
+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.KryoConverters;
+import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean;
+
+public class ShadowTLSBean extends StandardV2RayBean {
+
+ public Integer version;
+ public String password;
+
+ @Override
+ public void initializeDefaultValues() {
+ super.initializeDefaultValues();
+
+ security = "tls";
+ if (version == null) version = 3;
+ if (password == null) password = "";
+ }
+
+ @Override
+ public void serialize(ByteBufferOutput output) {
+ output.writeInt(0);
+ super.serialize(output);
+ output.writeInt(version);
+ output.writeString(password);
+ }
+
+ @Override
+ public void deserialize(ByteBufferInput input) {
+ int version_ = input.readInt();
+ super.deserialize(input);
+ version = input.readInt();
+ password = input.readString();
+ }
+
+ @NotNull
+ @Override
+ public ShadowTLSBean clone() {
+ return KryoConverters.deserialize(new ShadowTLSBean(), KryoConverters.serialize(this));
+ }
+
+ public static final Creator CREATOR = new CREATOR() {
+ @NonNull
+ @Override
+ public ShadowTLSBean newInstance() {
+ return new ShadowTLSBean();
+ }
+
+ @Override
+ public ShadowTLSBean[] newArray(int size) {
+ return new ShadowTLSBean[size];
+ }
+ };
+}
diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSFmt.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSFmt.kt
new file mode 100644
index 0000000..e0aa5dc
--- /dev/null
+++ b/app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSFmt.kt
@@ -0,0 +1,15 @@
+package moe.matsuri.nb4a.proxy.shadowtls
+
+import io.nekohasekai.sagernet.fmt.v2ray.buildSingBoxOutboundTLS
+import moe.matsuri.nb4a.SingBoxOptions
+
+fun buildSingBoxOutboundShadowTLSBean(bean: ShadowTLSBean): SingBoxOptions.Outbound_ShadowTLSOptions {
+ return SingBoxOptions.Outbound_ShadowTLSOptions().apply {
+ type = "shadowtls"
+ server = bean.serverAddress
+ server_port = bean.serverPort
+ version = bean.version
+ password = bean.password
+ tls = buildSingBoxOutboundTLS(bean)
+ }
+}
diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSSettingsActivity.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSSettingsActivity.kt
new file mode 100644
index 0000000..3999170
--- /dev/null
+++ b/app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSSettingsActivity.kt
@@ -0,0 +1,55 @@
+package moe.matsuri.nb4a.proxy.shadowtls
+
+import android.os.Bundle
+import androidx.preference.EditTextPreference
+import androidx.preference.PreferenceFragmentCompat
+import io.nekohasekai.sagernet.R
+import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers
+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 ShadowTLSSettingsActivity : ProfileSettingsActivity() {
+
+ override fun createEntity() = ShadowTLSBean()
+
+ 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 version = pbm.add(PreferenceBinding(Type.TextToInt, "version"))
+ 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 ShadowTLSBean.init() {
+ pbm.writeToCacheAll(this)
+
+ }
+
+ override fun ShadowTLSBean.serialize() {
+ pbm.fromCacheAll(this)
+ }
+
+ override fun PreferenceFragmentCompat.createPreferences(
+ savedInstanceState: Bundle?,
+ rootKey: String?,
+ ) {
+ addPreferencesFromResource(R.xml.shadowtls_preferences)
+ pbm.setPreferenceFragment(this)
+
+ serverPort.preference.apply {
+ this as EditTextPreference
+ setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
+ }
+ password.preference.apply {
+ this as EditTextPreference
+ 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 af2360e..52d264b 100644
--- a/app/src/main/res/menu/add_profile_menu.xml
+++ b/app/src/main/res/menu/add_profile_menu.xml
@@ -60,6 +60,9 @@
+
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 4750333..c48c746 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -193,20 +193,8 @@
Editar
Compartir
Añadir perfil
- Seleccionar perfil
- SOCKS
- HTTP
- Shadowsocks
- ShadowsocksR
- VMess
- Trojan
- Trojan Go
- Naïve
- Ping Tunnel
- Hysteria
- SSH
- WireGuard
- Cadena del proxy
+ Seleccionar perfil
+ Cadena del proxy
Configuración personalizada
Equilibrador
Ajustes del equilibrador
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 6409c1c..dc61b1a 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -462,4 +462,8 @@
日志级别
推广
重新启动应用程序以应用更改
+ 启用 selector (免重载切换节点)
+ 前置代理
+ 落地代理
+ ShadowTLS 版本
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 99037dc..747124d 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -507,4 +507,9 @@
- trace
+
+ - 2
+ - 3
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6f8634a..ceef3fe 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -505,5 +505,7 @@ Anyone can write advanced plugins, which can control NekoBox. please download an
Use selector
Front proxy
Landing Proxy
+ ShadowTLS
+ ShadowTLS Version
\ No newline at end of file
diff --git a/app/src/main/res/xml/shadowtls_preferences.xml b/app/src/main/res/xml/shadowtls_preferences.xml
new file mode 100644
index 0000000..215c608
--- /dev/null
+++ b/app/src/main/res/xml/shadowtls_preferences.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+