Skip to content

Commit 97bdd77

Browse files
2dustAnGgIt886
authored andcommitted
Add HYSTERIA2 for v2fly
1 parent 00768b9 commit 97bdd77

File tree

24 files changed

+461
-29
lines changed

24 files changed

+461
-29
lines changed

app/src/main/kotlin/com/neko/v2ray/AppConfig.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,6 @@ object AppConfig {
149149
const val VLESS = "vless://"
150150
const val TROJAN = "trojan://"
151151
const val WIREGUARD = "wireguard://"
152+
const val TUIC = "tuic://"
153+
const val HYSTERIA2 = "hysteria2://"
152154
}

app/src/main/kotlin/com/neko/v2ray/dto/EConfigType.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ enum class EConfigType(val value: Int, val protocolScheme: String) {
1111
VLESS(5, AppConfig.VLESS),
1212
TROJAN(6, AppConfig.TROJAN),
1313
WIREGUARD(7, AppConfig.WIREGUARD),
14+
// TUIC(8, AppConfig.TUIC),
15+
HYSTERIA2(9, AppConfig.HYSTERIA2),
1416
HTTP(10, AppConfig.HTTP);
1517

1618
companion object {

app/src/main/kotlin/com/neko/v2ray/dto/ServerConfig.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ data class ServerConfig(
1616
companion object {
1717
fun create(configType: EConfigType): ServerConfig {
1818
when (configType) {
19-
EConfigType.VMESS, EConfigType.VLESS ->
19+
EConfigType.VMESS,
20+
EConfigType.VLESS ->
2021
return ServerConfig(
2122
configType = configType,
2223
outboundBean = V2rayConfig.OutboundBean(
@@ -38,7 +39,8 @@ data class ServerConfig(
3839
EConfigType.SHADOWSOCKS,
3940
EConfigType.SOCKS,
4041
EConfigType.HTTP,
41-
EConfigType.TROJAN ->
42+
EConfigType.TROJAN,
43+
EConfigType.HYSTERIA2 ->
4244
return ServerConfig(
4345
configType = configType,
4446
outboundBean = V2rayConfig.OutboundBean(

app/src/main/kotlin/com/neko/v2ray/dto/V2rayConfig.kt

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,6 @@ data class V2rayConfig(
147147
val ivCheck: Boolean? = null,
148148
var users: List<SocksUsersBean>? = null
149149
) {
150-
151-
152150
data class SocksUsersBean(
153151
var user: String = "",
154152
var pass: String = "",
@@ -177,6 +175,7 @@ data class V2rayConfig(
177175
var quicSettings: QuicSettingBean? = null,
178176
var realitySettings: TlsSettingsBean? = null,
179177
var grpcSettings: GrpcSettingsBean? = null,
178+
var hy2steriaSettings: Hy2steriaSettingsBean? = null,
180179
val dsSettings: Any? = null,
181180
var sockopt: SockoptBean? = null
182181
) {
@@ -295,6 +294,18 @@ data class V2rayConfig(
295294
var health_check_timeout: Int? = null
296295
)
297296

297+
data class Hy2steriaSettingsBean(
298+
var password: String? = null,
299+
var use_udp_extension: Boolean? = true,
300+
var congestion: Hy2CongestionBean? = null
301+
) {
302+
data class Hy2CongestionBean(
303+
var type: String? = "bbr",
304+
var up_mbps: Int? = null,
305+
var down_mbps: Int? = null,
306+
)
307+
}
308+
298309
fun populateTransportSettings(
299310
transport: String, headerType: String?, host: String?, path: String?, seed: String?,
300311
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
@@ -427,6 +438,7 @@ data class V2rayConfig(
427438
|| protocol.equals(EConfigType.SOCKS.name, true)
428439
|| protocol.equals(EConfigType.HTTP.name, true)
429440
|| protocol.equals(EConfigType.TROJAN.name, true)
441+
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
430442
) {
431443
return settings?.servers?.get(0)?.address
432444
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
@@ -444,6 +456,7 @@ data class V2rayConfig(
444456
|| protocol.equals(EConfigType.SOCKS.name, true)
445457
|| protocol.equals(EConfigType.HTTP.name, true)
446458
|| protocol.equals(EConfigType.TROJAN.name, true)
459+
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
447460
) {
448461
return settings?.servers?.get(0)?.port
449462
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
@@ -465,10 +478,12 @@ data class V2rayConfig(
465478
return settings?.vnext?.get(0)?.users?.get(0)?.id
466479
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
467480
|| protocol.equals(EConfigType.TROJAN.name, true)
481+
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
468482
) {
469483
return settings?.servers?.get(0)?.password
470484
} else if (protocol.equals(EConfigType.SOCKS.name, true)
471-
|| protocol.equals(EConfigType.HTTP.name, true)) {
485+
|| protocol.equals(EConfigType.HTTP.name, true)
486+
) {
472487
return settings?.servers?.get(0)?.users?.get(0)?.pass
473488
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
474489
return settings?.secretKey
@@ -597,6 +612,7 @@ data class V2rayConfig(
597612
) {
598613

599614
data class RulesBean(
615+
var type: String = "field",
600616
var ip: ArrayList<String>? = null,
601617
var domain: ArrayList<String>? = null,
602618
var outboundTag: String = "",

app/src/main/kotlin/com/neko/v2ray/service/V2RayServiceManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ object V2RayServiceManager {
5151
set(value) {
5252
field = value
5353
Seq.setContext(value?.get()?.getService()?.applicationContext)
54-
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()), Utils.getDeviceIdForXUDPBaseKey())
54+
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()))//, Utils.getDeviceIdForXUDPBaseKey())
5555
}
5656
var currentConfig: ServerConfig? = null
5757

app/src/main/kotlin/com/neko/v2ray/service/V2RayTestService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class V2RayTestService : Service() {
2424
override fun onCreate() {
2525
super.onCreate()
2626
Seq.setContext(this)
27-
Libv2ray.initV2Env(Utils.userAssetPath(this), Utils.getDeviceIdForXUDPBaseKey())
27+
Libv2ray.initV2Env(Utils.userAssetPath(this)) //Utils.getDeviceIdForXUDPBaseKey())
2828
}
2929

3030
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

app/src/main/kotlin/com/neko/v2ray/ui/MainActivity.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
428428
true
429429
}
430430

431+
R.id.import_manually_hysteria2 -> {
432+
importManually(EConfigType.HYSTERIA2.value)
433+
true
434+
}
435+
431436
R.id.import_config_custom_clipboard -> {
432437
importConfigCustomClipboard()
433438
true

app/src/main/kotlin/com/neko/v2ray/ui/ServerActivity.kt

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ class ServerActivity : BaseActivity() {
148148
EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
149149
EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
150150
EConfigType.WIREGUARD -> setContentView(R.layout.activity_server_wireguard)
151+
EConfigType.HYSTERIA2 -> setContentView(R.layout.activity_server_hysteria2)
151152
}
152153

153154
val toolbar = findViewById<MaterialToolbar>(R.id.toolbar)
@@ -467,7 +468,10 @@ class ServerActivity : BaseActivity() {
467468
&& config.configType != EConfigType.HTTP
468469
&& TextUtils.isEmpty(et_id.text.toString())
469470
) {
470-
if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.SHADOWSOCKS) {
471+
if (config.configType == EConfigType.TROJAN
472+
|| config.configType == EConfigType.SHADOWSOCKS
473+
|| config.configType == EConfigType.HYSTERIA2
474+
) {
471475
toast(R.string.server_lab_id3)
472476
} else {
473477
toast(R.string.server_lab_id)
@@ -499,8 +503,10 @@ class ServerActivity : BaseActivity() {
499503
wireguard?.peers?.get(0)?.let { _ ->
500504
savePeer(wireguard, port)
501505
}
506+
502507
config.outboundBean?.streamSettings?.let {
503-
saveStreamSettings(it)
508+
val sni = saveStreamSettings(it)
509+
saveTls(it, sni)
504510
}
505511
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
506512
config.subscriptionId = subscriptionId.orEmpty()
@@ -549,7 +555,7 @@ class ServerActivity : BaseActivity() {
549555
socksUsersBean.pass = et_id.text.toString().trim()
550556
server.users = listOf(socksUsersBean)
551557
}
552-
} else if (config.configType == EConfigType.TROJAN) {
558+
} else if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.HYSTERIA2) {
553559
server.password = et_id.text.toString().trim()
554560
}
555561
}
@@ -571,21 +577,13 @@ class ServerActivity : BaseActivity() {
571577
wireguard.mtu = Utils.parseInt(et_local_mtu?.text.toString())
572578
}
573579

574-
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) {
575-
val network = sp_network?.selectedItemPosition ?: return
576-
val type = sp_header_type?.selectedItemPosition ?: return
577-
val requestHost = et_request_host?.text?.toString()?.trim() ?: return
578-
val path = et_path?.text?.toString()?.trim() ?: return
579-
val sniField = et_sni?.text?.toString()?.trim() ?: return
580-
val allowInsecureField = sp_allow_insecure?.selectedItemPosition ?: return
581-
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
582-
val utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: return
583-
val alpnIndex = sp_stream_alpn?.selectedItemPosition ?: return
584-
val publicKey = et_public_key?.text?.toString()?.trim() ?: return
585-
val shortId = et_short_id?.text?.toString()?.trim() ?: return
586-
val spiderX = et_spider_x?.text?.toString()?.trim() ?: return
580+
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean): String? {
581+
val network = sp_network?.selectedItemPosition ?: return null
582+
val type = sp_header_type?.selectedItemPosition ?: return null
583+
val requestHost = et_request_host?.text?.toString()?.trim() ?: return null
584+
val path = et_path?.text?.toString()?.trim() ?: return null
587585

588-
var sni = streamSetting.populateTransportSettings(
586+
val sni = streamSetting.populateTransportSettings(
589587
transport = networks[network],
590588
headerType = transportTypes(networks[network])[type],
591589
host = requestHost,
@@ -597,10 +595,21 @@ class ServerActivity : BaseActivity() {
597595
serviceName = path,
598596
authority = requestHost,
599597
)
600-
if (sniField.isNotBlank()) {
601-
sni = sniField
602-
}
603-
val allowInsecure = if (allowinsecures[allowInsecureField].isBlank()) {
598+
599+
return sni
600+
}
601+
602+
private fun saveTls(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean, sni: String?) {
603+
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
604+
val sniField = et_sni?.text?.toString()?.trim()
605+
val allowInsecureField = sp_allow_insecure?.selectedItemPosition
606+
val utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: 0
607+
val alpnIndex = sp_stream_alpn?.selectedItemPosition ?: 0
608+
val publicKey = et_public_key?.text?.toString()
609+
val shortId = et_short_id?.text?.toString()
610+
val spiderX = et_spider_x?.text?.toString()
611+
612+
val allowInsecure = if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
604613
settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
605614
} else {
606615
allowinsecures[allowInsecureField].toBoolean()
@@ -609,7 +618,7 @@ class ServerActivity : BaseActivity() {
609618
streamSetting.populateTlsSettings(
610619
streamSecurity = streamSecuritys[streamSecurity],
611620
allowInsecure = allowInsecure,
612-
sni = sni,
621+
sni = sniField ?: sni ?: "",
613622
fingerprint = uTlsItems[utlsIndex],
614623
alpns = alpns[alpnIndex],
615624
publicKey = publicKey,

app/src/main/kotlin/com/neko/v2ray/util/AngConfigManager.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.neko.v2ray.AppConfig
1414
import com.neko.v2ray.R
1515
import com.neko.v2ray.dto.*
1616
import com.neko.v2ray.util.MmkvManager.settingsStorage
17+
import com.neko.v2ray.util.fmt.Hysteria2Fmt
1718
import com.neko.v2ray.util.fmt.ShadowsocksFmt
1819
import com.neko.v2ray.util.fmt.SocksFmt
1920
import com.neko.v2ray.util.fmt.TrojanFmt
@@ -49,6 +50,8 @@ object AngConfigManager {
4950
VlessFmt.parseVless(str)
5051
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
5152
WireguardFmt.parseWireguard(str)
53+
} else if (str.startsWith(EConfigType.HYSTERIA2.protocolScheme)) {
54+
Hysteria2Fmt.parseHysteria2(str)
5255
} else {
5356
null
5457
}
@@ -91,6 +94,7 @@ object AngConfigManager {
9194
EConfigType.VLESS -> VlessFmt.toUri(config)
9295
EConfigType.TROJAN -> TrojanFmt.toUri(config)
9396
EConfigType.WIREGUARD -> WireguardFmt.toUri(config)
97+
EConfigType.HYSTERIA2 -> Hysteria2Fmt.toUri(config)
9498
}
9599
} catch (e: Exception) {
96100
e.printStackTrace()

app/src/main/kotlin/com/neko/v2ray/util/V2rayConfigUtil.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ object V2rayConfigUtil {
380380
|| protocol.equals(EConfigType.HTTP.name, true)
381381
|| protocol.equals(EConfigType.TROJAN.name, true)
382382
|| protocol.equals(EConfigType.WIREGUARD.name, true)
383+
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
383384
) {
384385
muxEnabled = false
385386
} else if (protocol.equals(EConfigType.VLESS.name, true)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.neko.v2ray.util.fmt
2+
3+
import android.text.TextUtils
4+
import com.neko.v2ray.AppConfig
5+
import com.neko.v2ray.dto.EConfigType
6+
import com.neko.v2ray.dto.ServerConfig
7+
import com.neko.v2ray.dto.V2rayConfig
8+
import com.neko.v2ray.extension.idnHost
9+
import com.neko.v2ray.util.MmkvManager.settingsStorage
10+
import com.neko.v2ray.util.Utils
11+
import java.net.URI
12+
13+
object Hysteria2Fmt {
14+
15+
fun parseHysteria2(str: String): ServerConfig {
16+
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
17+
val config = ServerConfig.create(EConfigType.HYSTERIA2)
18+
19+
val uri = URI(Utils.fixIllegalUrl(str))
20+
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
21+
22+
val queryParam = uri.rawQuery.split("&")
23+
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
24+
25+
config.outboundBean?.streamSettings?.populateTlsSettings(
26+
V2rayConfig.TLS,
27+
if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure,
28+
queryParam["sni"] ?: uri.idnHost,
29+
null,
30+
queryParam["alpn"],
31+
null,
32+
null,
33+
null
34+
)
35+
36+
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
37+
server.address = uri.idnHost
38+
server.port = uri.port
39+
server.password = uri.userInfo
40+
}
41+
return config
42+
}
43+
44+
fun toUri(config: ServerConfig): String {
45+
val outbound = config.getProxyOutbound() ?: return ""
46+
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
47+
48+
val remark = "#" + Utils.urlEncode(config.remarks)
49+
val dicQuery = HashMap<String, String>()
50+
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
51+
streamSetting.tlsSettings?.let { tlsSetting ->
52+
dicQuery["insecure"] = if (tlsSetting.allowInsecure) "1" else "0"
53+
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
54+
dicQuery["sni"] = tlsSetting.serverName
55+
}
56+
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
57+
dicQuery["alpn"] = Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
58+
}
59+
}
60+
61+
val query = "?" + dicQuery.toList().joinToString(
62+
separator = "&",
63+
transform = { it.first + "=" + it.second })
64+
65+
val url = String.format(
66+
"%s@%s:%s",
67+
outbound.getPassword(),
68+
Utils.getIpv6Address(outbound.getServerAddress()),
69+
outbound.getServerPort()
70+
)
71+
return url + query + remark
72+
}
73+
}

0 commit comments

Comments
 (0)