Skip to content
This repository has been archived by the owner on Nov 14, 2024. It is now read-only.

Commit

Permalink
Add HYSTERIA2 for v2fly
Browse files Browse the repository at this point in the history
  • Loading branch information
2dust authored and AnGgIt86 committed Sep 28, 2024
1 parent 00768b9 commit 97bdd77
Show file tree
Hide file tree
Showing 24 changed files with 461 additions and 29 deletions.
2 changes: 2 additions & 0 deletions app/src/main/kotlin/com/neko/v2ray/AppConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,6 @@ object AppConfig {
const val VLESS = "vless://"
const val TROJAN = "trojan://"
const val WIREGUARD = "wireguard://"
const val TUIC = "tuic://"
const val HYSTERIA2 = "hysteria2://"
}
2 changes: 2 additions & 0 deletions app/src/main/kotlin/com/neko/v2ray/dto/EConfigType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ enum class EConfigType(val value: Int, val protocolScheme: String) {
VLESS(5, AppConfig.VLESS),
TROJAN(6, AppConfig.TROJAN),
WIREGUARD(7, AppConfig.WIREGUARD),
// TUIC(8, AppConfig.TUIC),
HYSTERIA2(9, AppConfig.HYSTERIA2),
HTTP(10, AppConfig.HTTP);

companion object {
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/kotlin/com/neko/v2ray/dto/ServerConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ data class ServerConfig(
companion object {
fun create(configType: EConfigType): ServerConfig {
when (configType) {
EConfigType.VMESS, EConfigType.VLESS ->
EConfigType.VMESS,
EConfigType.VLESS ->
return ServerConfig(
configType = configType,
outboundBean = V2rayConfig.OutboundBean(
Expand All @@ -38,7 +39,8 @@ data class ServerConfig(
EConfigType.SHADOWSOCKS,
EConfigType.SOCKS,
EConfigType.HTTP,
EConfigType.TROJAN ->
EConfigType.TROJAN,
EConfigType.HYSTERIA2 ->
return ServerConfig(
configType = configType,
outboundBean = V2rayConfig.OutboundBean(
Expand Down
22 changes: 19 additions & 3 deletions app/src/main/kotlin/com/neko/v2ray/dto/V2rayConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,6 @@ data class V2rayConfig(
val ivCheck: Boolean? = null,
var users: List<SocksUsersBean>? = null
) {


data class SocksUsersBean(
var user: String = "",
var pass: String = "",
Expand Down Expand Up @@ -177,6 +175,7 @@ data class V2rayConfig(
var quicSettings: QuicSettingBean? = null,
var realitySettings: TlsSettingsBean? = null,
var grpcSettings: GrpcSettingsBean? = null,
var hy2steriaSettings: Hy2steriaSettingsBean? = null,
val dsSettings: Any? = null,
var sockopt: SockoptBean? = null
) {
Expand Down Expand Up @@ -295,6 +294,18 @@ data class V2rayConfig(
var health_check_timeout: Int? = null
)

data class Hy2steriaSettingsBean(
var password: String? = null,
var use_udp_extension: Boolean? = true,
var congestion: Hy2CongestionBean? = null
) {
data class Hy2CongestionBean(
var type: String? = "bbr",
var up_mbps: Int? = null,
var down_mbps: Int? = null,
)
}

fun populateTransportSettings(
transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
Expand Down Expand Up @@ -427,6 +438,7 @@ data class V2rayConfig(
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.HTTP.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
) {
return settings?.servers?.get(0)?.address
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
Expand All @@ -444,6 +456,7 @@ data class V2rayConfig(
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.HTTP.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
) {
return settings?.servers?.get(0)?.port
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
Expand All @@ -465,10 +478,12 @@ data class V2rayConfig(
return settings?.vnext?.get(0)?.users?.get(0)?.id
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
) {
return settings?.servers?.get(0)?.password
} else if (protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.HTTP.name, true)) {
|| protocol.equals(EConfigType.HTTP.name, true)
) {
return settings?.servers?.get(0)?.users?.get(0)?.pass
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
return settings?.secretKey
Expand Down Expand Up @@ -597,6 +612,7 @@ data class V2rayConfig(
) {

data class RulesBean(
var type: String = "field",
var ip: ArrayList<String>? = null,
var domain: ArrayList<String>? = null,
var outboundTag: String = "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ object V2RayServiceManager {
set(value) {
field = value
Seq.setContext(value?.get()?.getService()?.applicationContext)
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()), Utils.getDeviceIdForXUDPBaseKey())
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()))//, Utils.getDeviceIdForXUDPBaseKey())
}
var currentConfig: ServerConfig? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class V2RayTestService : Service() {
override fun onCreate() {
super.onCreate()
Seq.setContext(this)
Libv2ray.initV2Env(Utils.userAssetPath(this), Utils.getDeviceIdForXUDPBaseKey())
Libv2ray.initV2Env(Utils.userAssetPath(this)) //Utils.getDeviceIdForXUDPBaseKey())
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/kotlin/com/neko/v2ray/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true
}

R.id.import_manually_hysteria2 -> {
importManually(EConfigType.HYSTERIA2.value)
true
}

R.id.import_config_custom_clipboard -> {
importConfigCustomClipboard()
true
Expand Down
53 changes: 31 additions & 22 deletions app/src/main/kotlin/com/neko/v2ray/ui/ServerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class ServerActivity : BaseActivity() {
EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
EConfigType.WIREGUARD -> setContentView(R.layout.activity_server_wireguard)
EConfigType.HYSTERIA2 -> setContentView(R.layout.activity_server_hysteria2)
}

val toolbar = findViewById<MaterialToolbar>(R.id.toolbar)
Expand Down Expand Up @@ -467,7 +468,10 @@ class ServerActivity : BaseActivity() {
&& config.configType != EConfigType.HTTP
&& TextUtils.isEmpty(et_id.text.toString())
) {
if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.SHADOWSOCKS) {
if (config.configType == EConfigType.TROJAN
|| config.configType == EConfigType.SHADOWSOCKS
|| config.configType == EConfigType.HYSTERIA2
) {
toast(R.string.server_lab_id3)
} else {
toast(R.string.server_lab_id)
Expand Down Expand Up @@ -499,8 +503,10 @@ class ServerActivity : BaseActivity() {
wireguard?.peers?.get(0)?.let { _ ->
savePeer(wireguard, port)
}

config.outboundBean?.streamSettings?.let {
saveStreamSettings(it)
val sni = saveStreamSettings(it)
saveTls(it, sni)
}
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
config.subscriptionId = subscriptionId.orEmpty()
Expand Down Expand Up @@ -549,7 +555,7 @@ class ServerActivity : BaseActivity() {
socksUsersBean.pass = et_id.text.toString().trim()
server.users = listOf(socksUsersBean)
}
} else if (config.configType == EConfigType.TROJAN) {
} else if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.HYSTERIA2) {
server.password = et_id.text.toString().trim()
}
}
Expand All @@ -571,21 +577,13 @@ class ServerActivity : BaseActivity() {
wireguard.mtu = Utils.parseInt(et_local_mtu?.text.toString())
}

private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) {
val network = sp_network?.selectedItemPosition ?: return
val type = sp_header_type?.selectedItemPosition ?: return
val requestHost = et_request_host?.text?.toString()?.trim() ?: return
val path = et_path?.text?.toString()?.trim() ?: return
val sniField = et_sni?.text?.toString()?.trim() ?: return
val allowInsecureField = sp_allow_insecure?.selectedItemPosition ?: return
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
val utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: return
val alpnIndex = sp_stream_alpn?.selectedItemPosition ?: return
val publicKey = et_public_key?.text?.toString()?.trim() ?: return
val shortId = et_short_id?.text?.toString()?.trim() ?: return
val spiderX = et_spider_x?.text?.toString()?.trim() ?: return
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean): String? {
val network = sp_network?.selectedItemPosition ?: return null
val type = sp_header_type?.selectedItemPosition ?: return null
val requestHost = et_request_host?.text?.toString()?.trim() ?: return null
val path = et_path?.text?.toString()?.trim() ?: return null

var sni = streamSetting.populateTransportSettings(
val sni = streamSetting.populateTransportSettings(
transport = networks[network],
headerType = transportTypes(networks[network])[type],
host = requestHost,
Expand All @@ -597,10 +595,21 @@ class ServerActivity : BaseActivity() {
serviceName = path,
authority = requestHost,
)
if (sniField.isNotBlank()) {
sni = sniField
}
val allowInsecure = if (allowinsecures[allowInsecureField].isBlank()) {

return sni
}

private fun saveTls(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean, sni: String?) {
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
val sniField = et_sni?.text?.toString()?.trim()
val allowInsecureField = sp_allow_insecure?.selectedItemPosition
val utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: 0
val alpnIndex = sp_stream_alpn?.selectedItemPosition ?: 0
val publicKey = et_public_key?.text?.toString()
val shortId = et_short_id?.text?.toString()
val spiderX = et_spider_x?.text?.toString()

val allowInsecure = if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
} else {
allowinsecures[allowInsecureField].toBoolean()
Expand All @@ -609,7 +618,7 @@ class ServerActivity : BaseActivity() {
streamSetting.populateTlsSettings(
streamSecurity = streamSecuritys[streamSecurity],
allowInsecure = allowInsecure,
sni = sni,
sni = sniField ?: sni ?: "",
fingerprint = uTlsItems[utlsIndex],
alpns = alpns[alpnIndex],
publicKey = publicKey,
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/kotlin/com/neko/v2ray/util/AngConfigManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.neko.v2ray.AppConfig
import com.neko.v2ray.R
import com.neko.v2ray.dto.*
import com.neko.v2ray.util.MmkvManager.settingsStorage
import com.neko.v2ray.util.fmt.Hysteria2Fmt
import com.neko.v2ray.util.fmt.ShadowsocksFmt
import com.neko.v2ray.util.fmt.SocksFmt
import com.neko.v2ray.util.fmt.TrojanFmt
Expand Down Expand Up @@ -49,6 +50,8 @@ object AngConfigManager {
VlessFmt.parseVless(str)
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
WireguardFmt.parseWireguard(str)
} else if (str.startsWith(EConfigType.HYSTERIA2.protocolScheme)) {
Hysteria2Fmt.parseHysteria2(str)
} else {
null
}
Expand Down Expand Up @@ -91,6 +94,7 @@ object AngConfigManager {
EConfigType.VLESS -> VlessFmt.toUri(config)
EConfigType.TROJAN -> TrojanFmt.toUri(config)
EConfigType.WIREGUARD -> WireguardFmt.toUri(config)
EConfigType.HYSTERIA2 -> Hysteria2Fmt.toUri(config)
}
} catch (e: Exception) {
e.printStackTrace()
Expand Down
1 change: 1 addition & 0 deletions app/src/main/kotlin/com/neko/v2ray/util/V2rayConfigUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ object V2rayConfigUtil {
|| protocol.equals(EConfigType.HTTP.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.WIREGUARD.name, true)
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
) {
muxEnabled = false
} else if (protocol.equals(EConfigType.VLESS.name, true)
Expand Down
73 changes: 73 additions & 0 deletions app/src/main/kotlin/com/neko/v2ray/util/fmt/Hysteria2Fmt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.neko.v2ray.util.fmt

import android.text.TextUtils
import com.neko.v2ray.AppConfig
import com.neko.v2ray.dto.EConfigType
import com.neko.v2ray.dto.ServerConfig
import com.neko.v2ray.dto.V2rayConfig
import com.neko.v2ray.extension.idnHost
import com.neko.v2ray.util.MmkvManager.settingsStorage
import com.neko.v2ray.util.Utils
import java.net.URI

object Hysteria2Fmt {

fun parseHysteria2(str: String): ServerConfig {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.HYSTERIA2)

val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())

val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }

config.outboundBean?.streamSettings?.populateTlsSettings(
V2rayConfig.TLS,
if ((queryParam["allowInsecure"].orEmpty()) == "1") true else allowInsecure,
queryParam["sni"] ?: uri.idnHost,
null,
queryParam["alpn"],
null,
null,
null
)

config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = uri.userInfo
}
return config
}

fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()

val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
streamSetting.tlsSettings?.let { tlsSetting ->
dicQuery["insecure"] = if (tlsSetting.allowInsecure) "1" else "0"
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] = Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
}
}

val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })

val url = String.format(
"%s@%s:%s",
outbound.getPassword(),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + query + remark
}
}
Loading

0 comments on commit 97bdd77

Please sign in to comment.