feat: add AmneziaWG support and Russian localization
- Add EConfigType.AMNEZIA with dedicated parsing (AmneziaFmt) and outbound generation (settings: Jc, Jmin, Jmax, S1-S4, H1-H4, etc.) - Detect Amnezia vs plain WireGuard configs by inspecting [Interface] parameters during .conf import - Add Russian (ru) translations for `Add` section
This commit is contained in:
15 files changed
+591
-14
No files matched your search
@@ -107,6 +107,10 @@
|
||||
android:name=".ui.server.ServerWireguardActivity"
|
||||
android:exported="false"
|
||||
android:windowSoftInputMode="stateUnchanged|adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.server.ServerAmneziaActivity"
|
||||
android:exported="false"
|
||||
android:windowSoftInputMode="stateUnchanged|adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.server.ServerHysteria2Activity"
|
||||
android:exported="false"
|
||||
|
||||
@@ -213,6 +213,7 @@ object AppConfig {
|
||||
const val VLESS = "vless://"
|
||||
const val TROJAN = "trojan://"
|
||||
const val WIREGUARD = "wireguard://"
|
||||
const val AMNEZIA = "amnezia://"
|
||||
const val TUIC = "tuic://"
|
||||
const val HYSTERIA = "hysteria://"
|
||||
const val HYSTERIA2 = "hysteria2://"
|
||||
|
||||
@@ -30,6 +30,7 @@ object CoreOutboundBuilder {
|
||||
EConfigType.VLESS -> toOutboundVless(profileItem)
|
||||
EConfigType.TROJAN -> toOutboundTrojan(profileItem)
|
||||
EConfigType.WIREGUARD -> toOutboundWireguard(profileItem)
|
||||
EConfigType.AMNEZIA -> toOutboundAmnezia(profileItem)
|
||||
EConfigType.HYSTERIA2 -> toOutboundHysteria2(profileItem)
|
||||
EConfigType.HTTP -> toOutboundHttp(profileItem)
|
||||
else -> null
|
||||
@@ -93,6 +94,7 @@ object CoreOutboundBuilder {
|
||||
streamSettings = OutboundBean.StreamSettingsBean()
|
||||
)
|
||||
|
||||
EConfigType.AMNEZIA,
|
||||
EConfigType.WIREGUARD -> OutboundBean(
|
||||
protocol = configType.name.lowercase(),
|
||||
settings = OutboundBean.OutSettingsBean(
|
||||
@@ -235,9 +237,8 @@ object CoreOutboundBuilder {
|
||||
return outboundBean
|
||||
}
|
||||
|
||||
private fun toOutboundWireguard(profileItem: ProfileItem): OutboundBean? {
|
||||
val outboundBean = createInitOutbound(EConfigType.WIREGUARD)
|
||||
|
||||
// Help function to not make double-init for Wireguard and Amnenzia same fields
|
||||
private fun fillWireguardSettings(outboundBean: OutboundBean?, profileItem: ProfileItem): OutboundBean? {
|
||||
val rawAddresses = profileItem.localAddress
|
||||
?.split(",")
|
||||
?.map { it.trim() }
|
||||
@@ -261,7 +262,8 @@ object CoreOutboundBuilder {
|
||||
peer.endpoint = Utils.getIpv6Address(profileItem.server) + ":${profileItem.serverPort}"
|
||||
}
|
||||
wireguard.mtu = profileItem.mtu
|
||||
wireguard.reserved = profileItem.reserved?.takeIf { it.isNotBlank() }?.split(",")?.filter { it.isNotBlank() }?.map { it.trim().toInt() }
|
||||
wireguard.reserved = profileItem.reserved?.takeIf { it.isNotBlank() }
|
||||
?.split(",")?.filter { it.isNotBlank() }?.map { it.trim().toInt() }
|
||||
}
|
||||
|
||||
if (!profileItem.finalMask.isNullOrBlank()) {
|
||||
@@ -274,6 +276,37 @@ object CoreOutboundBuilder {
|
||||
return outboundBean
|
||||
}
|
||||
|
||||
private fun toOutboundWireguard(profileItem: ProfileItem): OutboundBean? {
|
||||
val outboundBean = createInitOutbound(EConfigType.WIREGUARD)
|
||||
return fillWireguardSettings(outboundBean, profileItem)
|
||||
}
|
||||
|
||||
private fun toOutboundAmnezia(profileItem: ProfileItem): OutboundBean? {
|
||||
val wireguardPart = createInitOutbound(EConfigType.AMNEZIA)
|
||||
val outboundBean = fillWireguardSettings(wireguardPart, profileItem)
|
||||
|
||||
outboundBean?.settings?.let { amnezia ->
|
||||
amnezia.jc = profileItem.jc
|
||||
amnezia.jMin = profileItem.jMin
|
||||
amnezia.jMax = profileItem.jMax
|
||||
amnezia.s1 = profileItem.s1
|
||||
amnezia.s2 = profileItem.s2
|
||||
amnezia.s3 = profileItem.s3
|
||||
amnezia.s4 = profileItem.s4
|
||||
amnezia.h1 = profileItem.h1
|
||||
amnezia.h2 = profileItem.h2
|
||||
amnezia.h3 = profileItem.h3
|
||||
amnezia.h4 = profileItem.h4
|
||||
amnezia.i1 = profileItem.i1
|
||||
amnezia.i2 = profileItem.i2
|
||||
amnezia.i3 = profileItem.i3
|
||||
amnezia.i4 = profileItem.i4
|
||||
amnezia.i5 = profileItem.i5
|
||||
}
|
||||
return outboundBean
|
||||
}
|
||||
|
||||
|
||||
private fun toOutboundHysteria2(profileItem: ProfileItem): OutboundBean? {
|
||||
val outboundBean = createInitOutbound(EConfigType.HYSTERIA2) ?: return null
|
||||
profileItem.network = NetworkType.HYSTERIA.type
|
||||
|
||||
@@ -93,12 +93,29 @@ data class V2rayConfig(
|
||||
var method: String? = null,
|
||||
/*Hysteria/Hysteria2*/
|
||||
var version: Int? = null,
|
||||
/*Wireguard*/
|
||||
/*Wireguard/Amnezia*/
|
||||
var secretKey: String? = null,
|
||||
val peers: List<WireGuardBean>? = null,
|
||||
var reserved: List<Int>? = null,
|
||||
var mtu: Int? = null,
|
||||
var domainStrategy: String? = null,
|
||||
/*Amnezia*/
|
||||
var jc: Int? = null,
|
||||
var jMin: Int? = null,
|
||||
var jMax: Int? = null,
|
||||
var s1: Int? = null,
|
||||
var s2: Int? = null,
|
||||
var s3: Int? = null,
|
||||
var s4: Int? = null,
|
||||
var h1: String? = null,
|
||||
var h2: String? = null,
|
||||
var h3: String? = null,
|
||||
var h4: String? = null,
|
||||
var i1: String? = null,
|
||||
var i2: String? = null,
|
||||
var i3: String? = null,
|
||||
var i4: String? = null,
|
||||
var i5: String? = null,
|
||||
) {
|
||||
data class WireGuardBean(
|
||||
var publicKey: String = "",
|
||||
|
||||
@@ -56,7 +56,22 @@ data class ProfileItem(
|
||||
var localAddress: String? = null,
|
||||
var reserved: String? = null,
|
||||
var mtu: Int? = null,
|
||||
|
||||
var jc: Int? = null,
|
||||
var jMin: Int? = null,
|
||||
var jMax: Int? = null,
|
||||
var s1: Int? = null,
|
||||
var s2: Int? = null,
|
||||
var s3: Int? = null,
|
||||
var s4: Int? = null,
|
||||
var h1: String? = null,
|
||||
var h2: String? = null,
|
||||
var h3: String? = null,
|
||||
var h4: String? = null,
|
||||
var i1: String? = null,
|
||||
var i2: String? = null,
|
||||
var i3: String? = null,
|
||||
var i4: String? = null,
|
||||
var i5: String? = null,
|
||||
var obfsPassword: String? = null,
|
||||
var portHopping: String? = null,
|
||||
var portHoppingInterval: String? = null,
|
||||
|
||||
@@ -10,6 +10,7 @@ enum class EConfigType(val value: Int, val protocolScheme: String) {
|
||||
VLESS(5, AppConfig.VLESS),
|
||||
TROJAN(6, AppConfig.TROJAN),
|
||||
WIREGUARD(7, AppConfig.WIREGUARD),
|
||||
AMNEZIA(11, AppConfig.AMNEZIA),
|
||||
|
||||
// TUIC(8, AppConfig.TUIC),
|
||||
HYSTERIA2(9, AppConfig.HYSTERIA2),
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.extension.nullIfBlank
|
||||
import com.v2ray.ang.extension.removeWhiteSpace
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.net.URI
|
||||
|
||||
object AmneziaFmt : FmtBase() {
|
||||
/**
|
||||
* Parses a URI string into a ProfileItem object.
|
||||
*
|
||||
* @param str the URI string to parse
|
||||
* @return the parsed ProfileItem object, or null if parsing fails
|
||||
*/
|
||||
fun parse(str: String): ProfileItem? {
|
||||
val config = ProfileItem.create(EConfigType.AMNEZIA)
|
||||
|
||||
val uri = URI(Utils.fixIllegalUrl(str))
|
||||
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||
val queryParam = getQueryParam(uri)
|
||||
|
||||
config.remarks = Utils.decodeURIComponent(uri.fragment.orEmpty()).let { it.ifEmpty { "none" } }
|
||||
config.server = uri.idnHost
|
||||
config.serverPort = uri.port.toString()
|
||||
|
||||
config.secretKey = uri.userInfo.orEmpty()
|
||||
config.localAddress = queryParam["address"] ?: AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||
config.publicKey = queryParam["publickey"].orEmpty()
|
||||
config.preSharedKey = queryParam["presharedkey"]?.nullIfBlank()
|
||||
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
||||
config.reserved = queryParam["reserved"] ?: "0,0,0"
|
||||
config.jc = Utils.parseInt(queryParam["jc"].orEmpty())
|
||||
config.jMin = Utils.parseInt(queryParam["jmin"].orEmpty())
|
||||
config.jMax = Utils.parseInt(queryParam["jmax"].orEmpty())
|
||||
config.s1 = Utils.parseInt(queryParam["s1"].orEmpty())
|
||||
config.s2 = Utils.parseInt(queryParam["s2"].orEmpty())
|
||||
config.s3 = Utils.parseInt(queryParam["s3"].orEmpty())
|
||||
config.s4 = Utils.parseInt(queryParam["s4"].orEmpty())
|
||||
config.h1 = queryParam["h1"].orEmpty()
|
||||
config.h2 = queryParam["h2"].orEmpty()
|
||||
config.h3 = queryParam["h3"].orEmpty()
|
||||
config.h4 = queryParam["h4"].orEmpty()
|
||||
config.i1 = queryParam["i1"].orEmpty()
|
||||
config.i2 = queryParam["i2"].orEmpty()
|
||||
config.i3 = queryParam["i3"].orEmpty()
|
||||
config.i4 = queryParam["i4"].orEmpty()
|
||||
config.i5 = queryParam["i5"].orEmpty()
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an Amnezia configuration file string into a ProfileItem object.
|
||||
*
|
||||
* @param str the Amnezia configuration file string to parse
|
||||
* @return the parsed ProfileItem object, or null if parsing fails
|
||||
*/
|
||||
fun parseAmneziaConfFile(str: String): ProfileItem {
|
||||
val config = ProfileItem.create(EConfigType.AMNEZIA)
|
||||
|
||||
val interfaceParams: MutableMap<String, String> = mutableMapOf()
|
||||
val peerParams: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
var currentSection: String? = null
|
||||
|
||||
str.lines().forEach { line ->
|
||||
val trimmedLine = line.trim()
|
||||
|
||||
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
when {
|
||||
trimmedLine.startsWith("[Interface]", ignoreCase = true) -> currentSection = "Interface"
|
||||
trimmedLine.startsWith("[Peer]", ignoreCase = true) -> currentSection = "Peer"
|
||||
else -> {
|
||||
if (currentSection != null) {
|
||||
val parts = trimmedLine.split("=", limit = 2).map { it.trim() }
|
||||
if (parts.size == 2) {
|
||||
val key = parts[0].lowercase()
|
||||
val value = parts[1]
|
||||
when (currentSection) {
|
||||
"Interface" -> interfaceParams[key] = value
|
||||
"Peer" -> peerParams[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.secretKey = interfaceParams["privatekey"].orEmpty()
|
||||
config.remarks = System.currentTimeMillis().toString()
|
||||
config.localAddress = interfaceParams["address"] ?: AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||
config.mtu = Utils.parseInt(interfaceParams["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
||||
config.jc = Utils.parseInt(interfaceParams["jc"].orEmpty())
|
||||
config.jMin = Utils.parseInt(interfaceParams["jmin"].orEmpty())
|
||||
config.jMax = Utils.parseInt(interfaceParams["jmax"].orEmpty())
|
||||
config.s1 = Utils.parseInt(interfaceParams["s1"].orEmpty())
|
||||
config.s2 = Utils.parseInt(interfaceParams["s2"].orEmpty())
|
||||
config.s3 = Utils.parseInt(interfaceParams["s3"].orEmpty())
|
||||
config.s4 = Utils.parseInt(interfaceParams["s4"].orEmpty())
|
||||
config.h1 = interfaceParams["h1"].orEmpty()
|
||||
config.h2 = interfaceParams["h2"].orEmpty()
|
||||
config.h3 = interfaceParams["h3"].orEmpty()
|
||||
config.h4 = interfaceParams["h4"].orEmpty()
|
||||
config.i1 = interfaceParams["i1"].orEmpty()
|
||||
config.i2 = interfaceParams["i2"].orEmpty()
|
||||
config.i3 = interfaceParams["i3"].orEmpty()
|
||||
config.i4 = interfaceParams["i4"].orEmpty()
|
||||
config.i5 = interfaceParams["i5"].orEmpty()
|
||||
config.publicKey = peerParams["publickey"].orEmpty()
|
||||
config.preSharedKey = peerParams["presharedkey"]?.nullIfBlank()
|
||||
val endpoint = peerParams["endpoint"].orEmpty()
|
||||
val endpointParts = endpoint.split(":", limit = 2)
|
||||
if (endpointParts.size == 2) {
|
||||
config.server = endpointParts[0]
|
||||
config.serverPort = endpointParts[1]
|
||||
} else {
|
||||
config.server = endpoint
|
||||
config.serverPort = ""
|
||||
}
|
||||
config.reserved = peerParams["reserved"] ?: "0,0,0"
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a ProfileItem object to a URI string.
|
||||
*
|
||||
* @param config the ProfileItem object to convert
|
||||
* @return the converted URI string
|
||||
*/
|
||||
fun toUri(config: ProfileItem): String {
|
||||
val dicQuery = HashMap<String, String>()
|
||||
|
||||
dicQuery["publickey"] = config.publicKey.orEmpty()
|
||||
if (config.reserved != null) {
|
||||
dicQuery["reserved"] = config.reserved.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
dicQuery["address"] = config.localAddress.removeWhiteSpace().orEmpty()
|
||||
if (config.mtu != null) {
|
||||
dicQuery["mtu"] = config.mtu.toString()
|
||||
}
|
||||
if (config.preSharedKey != null) {
|
||||
dicQuery["presharedkey"] = config.preSharedKey.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
if (config.jc != null) {
|
||||
dicQuery["jc"] = config.jc.toString()
|
||||
}
|
||||
if (config.jMin != null) {
|
||||
dicQuery["jmin"] = config.jMin.toString()
|
||||
}
|
||||
if (config.jMax != null) {
|
||||
dicQuery["jmax"] = config.jMax.toString()
|
||||
}
|
||||
if (config.s1 != null) {
|
||||
dicQuery["s1"] = config.s1.toString()
|
||||
}
|
||||
if (config.s2 != null) {
|
||||
dicQuery["s2"] = config.s2.toString()
|
||||
}
|
||||
if (config.s3 != null) {
|
||||
dicQuery["s3"] = config.s3.toString()
|
||||
}
|
||||
if (config.s4 != null) {
|
||||
dicQuery["s4"] = config.s4.toString()
|
||||
}
|
||||
if (config.h1 != null) {
|
||||
dicQuery["h1"] = config.h1.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
if (config.h2 != null) {
|
||||
dicQuery["h2"] = config.h2.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
if (config.h3 != null) {
|
||||
dicQuery["h3"] = config.h3.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
if (config.h4 != null) {
|
||||
dicQuery["h4"] = config.h4.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
if (config.i1 != null) {
|
||||
dicQuery["i1"] = config.i1.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
if (config.i2 != null) {
|
||||
dicQuery["i2"] = config.i2.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
if (config.i3 != null) {
|
||||
dicQuery["i3"] = config.i3.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
if (config.i4 != null) {
|
||||
dicQuery["i4"] = config.i4.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
if (config.i5 != null) {
|
||||
dicQuery["i5"] = config.i5.removeWhiteSpace().orEmpty()
|
||||
}
|
||||
return toUri(config, config.secretKey, dicQuery)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.v2ray.ang.dto.entities.SubscriptionCache
|
||||
import com.v2ray.ang.dto.entities.SubscriptionItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
import com.v2ray.ang.fmt.AmneziaFmt
|
||||
import com.v2ray.ang.fmt.CustomFmt
|
||||
import com.v2ray.ang.fmt.Hysteria2Fmt
|
||||
import com.v2ray.ang.fmt.ShadowsocksFmt
|
||||
@@ -28,6 +29,39 @@ import com.v2ray.ang.util.QRCodeDecoder
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.net.URI
|
||||
|
||||
object WireguardConfigDetector {
|
||||
|
||||
private val AMNEZIA_MARKER_KEYS = setOf(
|
||||
"jc", "jmin", "jmax",
|
||||
"s1", "s2", "s3", "s4",
|
||||
"h1", "h2", "h3", "h4",
|
||||
"i1", "i2", "i3", "i4", "i5",
|
||||
"j1", "j2", "j3"
|
||||
)
|
||||
|
||||
/**
|
||||
* check type of configuration: Amnezia/Wireguard
|
||||
*/
|
||||
fun isAmneziaConfig(content: String): Boolean {
|
||||
val interfaceKeys = mutableSetOf<String>()
|
||||
var insideInterface = false
|
||||
|
||||
content.lines().forEach { rawLine ->
|
||||
val line = rawLine.trim()
|
||||
when {
|
||||
line.equals("[Interface]", ignoreCase = true) -> insideInterface = true
|
||||
line.startsWith("[") -> insideInterface = false
|
||||
insideInterface && line.contains("=") -> {
|
||||
val key = line.substringBefore("=").trim().lowercase()
|
||||
interfaceKeys.add(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return interfaceKeys.any { it in AMNEZIA_MARKER_KEYS }
|
||||
}
|
||||
}
|
||||
|
||||
object AngConfigManager {
|
||||
|
||||
private data class ParsedProfile(
|
||||
@@ -46,6 +80,7 @@ object AngConfigManager {
|
||||
EConfigType.TROJAN.protocolScheme to TrojanFmt::parse,
|
||||
EConfigType.VLESS.protocolScheme to VlessFmt::parse,
|
||||
EConfigType.WIREGUARD.protocolScheme to WireguardFmt::parse,
|
||||
EConfigType.AMNEZIA.protocolScheme to AmneziaFmt::parse,
|
||||
EConfigType.HYSTERIA2.protocolScheme to Hysteria2Fmt::parse,
|
||||
AppConfig.HY2 to Hysteria2Fmt::parse,
|
||||
)
|
||||
@@ -162,6 +197,7 @@ object AngConfigManager {
|
||||
EConfigType.VLESS -> VlessFmt.toUri(config)
|
||||
EConfigType.TROJAN -> TrojanFmt.toUri(config)
|
||||
EConfigType.WIREGUARD -> WireguardFmt.toUri(config)
|
||||
EConfigType.AMNEZIA -> AmneziaFmt.toUri(config)
|
||||
EConfigType.HYSTERIA2 -> Hysteria2Fmt.toUri(config)
|
||||
else -> {}
|
||||
}
|
||||
@@ -371,7 +407,11 @@ object AngConfigManager {
|
||||
return 0
|
||||
} else if (server.startsWith("[Interface]") && server.contains("[Peer]")) {
|
||||
try {
|
||||
val config = WireguardFmt.parseWireguardConfFile(server)
|
||||
val config = if (WireguardConfigDetector.isAmneziaConfig(server)) {
|
||||
AmneziaFmt.parseAmneziaConfFile(server)
|
||||
} else {
|
||||
WireguardFmt.parseWireguardConfFile(server)
|
||||
}
|
||||
config.subscriptionId = subid
|
||||
config.description = generateDescription(config)
|
||||
commitProfiles(
|
||||
@@ -383,7 +423,7 @@ object AngConfigManager {
|
||||
} catch (e: ProfileStorageException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
LogUtil.e(AppConfig.TAG, "Failed to parse WireGuard config file", e)
|
||||
LogUtil.e(AppConfig.TAG, "Failed to parse WireGuard/Amnezia config file", e)
|
||||
}
|
||||
return 0
|
||||
} else {
|
||||
|
||||
@@ -111,6 +111,7 @@ class RealPingWorkerService(
|
||||
if (!config.configType.isComplexType()
|
||||
&& config.configType != EConfigType.HYSTERIA2
|
||||
&& config.configType != EConfigType.WIREGUARD
|
||||
&& config.configType != EConfigType.AMNEZIA
|
||||
&& config.alpn?.startsWith("h3") != true
|
||||
&& config.server.isNotNullEmpty()
|
||||
&& config.serverPort?.toIntOrNull() != null
|
||||
@@ -139,6 +140,7 @@ class RealPingWorkerService(
|
||||
if (!config.configType.isComplexType()
|
||||
&& config.configType != EConfigType.HYSTERIA2
|
||||
&& config.configType != EConfigType.WIREGUARD
|
||||
&& config.configType != EConfigType.AMNEZIA
|
||||
&& config.alpn?.split(',')?.all { it.trim().startsWith("h3") } != true
|
||||
&& config.server.isNotNullEmpty()
|
||||
&& config.serverPort?.toIntOrNull() != null
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.v2ray.ang.ui.logcat.LogcatActivity
|
||||
import com.v2ray.ang.ui.perappproxy.PerAppProxyActivity
|
||||
import com.v2ray.ang.ui.routing.RoutingSettingActivity
|
||||
import com.v2ray.ang.ui.server.ProfileEditorResult
|
||||
import com.v2ray.ang.ui.server.ServerAmneziaActivity
|
||||
import com.v2ray.ang.ui.server.ServerCustomConfigActivity
|
||||
import com.v2ray.ang.ui.server.ServerGroupActivity
|
||||
import com.v2ray.ang.ui.server.ServerHttpActivity
|
||||
@@ -205,6 +206,7 @@ class MainActivity : HelperBaseComponentActivity() {
|
||||
EConfigType.HTTP.value -> Intent(this, ServerHttpActivity::class.java)
|
||||
EConfigType.TROJAN.value -> Intent(this, ServerTrojanActivity::class.java)
|
||||
EConfigType.WIREGUARD.value -> Intent(this, ServerWireguardActivity::class.java)
|
||||
EConfigType.AMNEZIA.value -> Intent(this, ServerAmneziaActivity::class.java)
|
||||
EConfigType.HYSTERIA2.value -> Intent(this, ServerHysteria2Activity::class.java)
|
||||
else -> Intent(this, ServerHttpActivity::class.java).apply {
|
||||
putExtra("createConfigType", createConfigType)
|
||||
@@ -257,6 +259,7 @@ class MainActivity : HelperBaseComponentActivity() {
|
||||
EConfigType.HTTP -> ServerHttpActivity::class.java
|
||||
EConfigType.TROJAN -> ServerTrojanActivity::class.java
|
||||
EConfigType.WIREGUARD -> ServerWireguardActivity::class.java
|
||||
EConfigType.AMNEZIA -> ServerAmneziaActivity::class.java
|
||||
EConfigType.HYSTERIA2 -> ServerHysteria2Activity::class.java
|
||||
else -> ServerHttpActivity::class.java
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ private enum class ImportMenuAction(@StringRes val labelRes: Int, val action: Ma
|
||||
Http(R.string.menu_item_import_config_manually_http, MainAction.ImportManually(EConfigType.HTTP.value)),
|
||||
Trojan(R.string.menu_item_import_config_manually_trojan, MainAction.ImportManually(EConfigType.TROJAN.value)),
|
||||
WireGuard(R.string.menu_item_import_config_manually_wireguard, MainAction.ImportManually(EConfigType.WIREGUARD.value)),
|
||||
Amnezia(R.string.menu_item_import_config_manually_amnezia, MainAction.ImportManually(EConfigType.AMNEZIA.value)),
|
||||
Hysteria2(R.string.menu_item_import_config_manually_hysteria2, MainAction.ImportManually(EConfigType.HYSTERIA2.value))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.v2ray.ang.ui.server
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.ui.compose.FormTextField
|
||||
|
||||
class ServerAmneziaActivity : BaseServerActivity() {
|
||||
|
||||
override val serverConfigType: EConfigType = EConfigType.AMNEZIA
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent() {
|
||||
val scope = rememberCoroutineScope()
|
||||
val uiState = rememberSaveable(saver = ServerUiState.Saver) {
|
||||
ServerUiState.from(
|
||||
initialConfig = initialConfig
|
||||
)
|
||||
}.apply {
|
||||
configType = EConfigType.AMNEZIA
|
||||
}
|
||||
|
||||
ServerEditorScaffold(
|
||||
title = serverConfigType.toString(),
|
||||
onSaveClick = { saveServer(uiState) }
|
||||
) {
|
||||
CommonBasicFields(uiState)
|
||||
AmneziaProtocolFields(uiState)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmneziaProtocolFields(state: ServerUiState) {
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_secret_key),
|
||||
state.secretKey,
|
||||
{ state.secretKey = it }
|
||||
)
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_public_key),
|
||||
state.publicKey,
|
||||
{ state.publicKey = it }
|
||||
)
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_preshared_key),
|
||||
state.preSharedKey,
|
||||
{ state.preSharedKey = it }
|
||||
)
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_reserved),
|
||||
state.reserved,
|
||||
{ state.reserved = it }
|
||||
)
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_address),
|
||||
state.localAddress,
|
||||
{ state.localAddress = it }
|
||||
)
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_mtu),
|
||||
state.mtu,
|
||||
{ state.mtu = it },
|
||||
keyboardType = KeyboardType.Number
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_final_mask),
|
||||
state.finalMask,
|
||||
{ state.finalMask = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_jc),
|
||||
state.jc,
|
||||
{ state.jc = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_jmin),
|
||||
state.jMin,
|
||||
{ state.jMin = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_jmax),
|
||||
state.jMax,
|
||||
{ state.jMax = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_s1),
|
||||
state.s1,
|
||||
{ state.s1 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_s2),
|
||||
state.s2,
|
||||
{ state.s2 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_s3),
|
||||
state.s3,
|
||||
{ state.s3 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_s4),
|
||||
state.s4,
|
||||
{ state.s4 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_h1),
|
||||
state.h1,
|
||||
{ state.h1 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_h2),
|
||||
state.h2,
|
||||
{ state.h2 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_h3),
|
||||
state.h3,
|
||||
{ state.h3 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_h4),
|
||||
state.h4,
|
||||
{ state.h4 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_i1),
|
||||
state.i1,
|
||||
{ state.i1 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_i2),
|
||||
state.i2,
|
||||
{ state.i2 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_i3),
|
||||
state.i3,
|
||||
{ state.i3 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_i4),
|
||||
state.i4,
|
||||
{ state.i4 = it }
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_local_i5),
|
||||
state.i5,
|
||||
{ state.i5 = it }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,22 @@ class ServerUiState(
|
||||
reserved: String = "0,0,0",
|
||||
localAddress: String = WIREGUARD_LOCAL_ADDRESS_V4,
|
||||
mtu: String = WIREGUARD_LOCAL_MTU,
|
||||
jc: String = "0",
|
||||
jMin: String = "0",
|
||||
jMax: String = "0",
|
||||
s1: String = "0",
|
||||
s2: String = "0",
|
||||
s3: String = "0",
|
||||
s4: String = "0",
|
||||
h1: String = "",
|
||||
h2: String = "",
|
||||
h3: String = "",
|
||||
h4: String = "",
|
||||
i1: String = "",
|
||||
i2: String = "",
|
||||
i3: String = "",
|
||||
i4: String = "",
|
||||
i5: String = "",
|
||||
obfsPassword: String = "",
|
||||
portHopping: String = "",
|
||||
portHoppingInterval: String = "",
|
||||
@@ -78,6 +94,22 @@ class ServerUiState(
|
||||
var reserved by mutableStateOf(reserved)
|
||||
var localAddress by mutableStateOf(localAddress)
|
||||
var mtu by mutableStateOf(mtu)
|
||||
var jc by mutableStateOf(jc)
|
||||
var jMin by mutableStateOf(jMin)
|
||||
var jMax by mutableStateOf(jMax)
|
||||
var s1 by mutableStateOf(s1)
|
||||
var s2 by mutableStateOf(s2)
|
||||
var s3 by mutableStateOf(s3)
|
||||
var s4 by mutableStateOf(s4)
|
||||
var h1 by mutableStateOf(h1)
|
||||
var h2 by mutableStateOf(h2)
|
||||
var h3 by mutableStateOf(h3)
|
||||
var h4 by mutableStateOf(h4)
|
||||
var i1 by mutableStateOf(i1)
|
||||
var i2 by mutableStateOf(i2)
|
||||
var i3 by mutableStateOf(i3)
|
||||
var i4 by mutableStateOf(i4)
|
||||
var i5 by mutableStateOf(i5)
|
||||
var obfsPassword by mutableStateOf(obfsPassword)
|
||||
var portHopping by mutableStateOf(portHopping)
|
||||
var portHoppingInterval by mutableStateOf(portHoppingInterval)
|
||||
@@ -117,6 +149,7 @@ class ServerUiState(
|
||||
val isShadowsocks = configType == EConfigType.SHADOWSOCKS
|
||||
val isSocksOrHttp = configType == EConfigType.SOCKS || configType == EConfigType.HTTP
|
||||
val isWireguard = configType == EConfigType.WIREGUARD
|
||||
val isAmnezia = configType == EConfigType.AMNEZIA
|
||||
val isHysteria2 = configType == EConfigType.HYSTERIA2
|
||||
|
||||
return initialConfig.copy(
|
||||
@@ -132,16 +165,32 @@ class ServerUiState(
|
||||
},
|
||||
flow = if (isVless) flow else null,
|
||||
username = if (isSocksOrHttp) username else null,
|
||||
secretKey = if (isWireguard) secretKey else null,
|
||||
secretKey = if (isWireguard or isAmnezia) secretKey else null,
|
||||
publicKey = when {
|
||||
isWireguard -> publicKey
|
||||
isWireguard or isAmnezia -> publicKey
|
||||
streamSecurity == REALITY -> publicKeyReality
|
||||
else -> null
|
||||
},
|
||||
preSharedKey = if (isWireguard) preSharedKey else null,
|
||||
reserved = if (isWireguard) reserved else null,
|
||||
localAddress = if (isWireguard) localAddress else null,
|
||||
mtu = if (isWireguard) mtu.toIntOrNull() else null,
|
||||
preSharedKey = if (isWireguard or isAmnezia) preSharedKey else null,
|
||||
reserved = if (isWireguard or isAmnezia) reserved else null,
|
||||
localAddress = if (isWireguard or isAmnezia) localAddress else null,
|
||||
mtu = if (isWireguard or isAmnezia) mtu.toIntOrNull() else null,
|
||||
jc = if (isAmnezia) jc.toInt() else null,
|
||||
jMin = if (isAmnezia) jMin.toInt() else null,
|
||||
jMax = if (isAmnezia) jMax.toInt() else null,
|
||||
s1 = if (isAmnezia) s1.toInt() else null,
|
||||
s2 = if (isAmnezia) s2.toInt() else null,
|
||||
s3 = if (isAmnezia) s3.toInt() else null,
|
||||
s4 = if (isAmnezia) s4.toInt() else null,
|
||||
h1 = if (isAmnezia) h1 else null,
|
||||
h2 = if (isAmnezia) h2 else null,
|
||||
h3 = if (isAmnezia) h3 else null,
|
||||
h4 = if (isAmnezia) h4 else null,
|
||||
i1 = if (isAmnezia) i1 else null,
|
||||
i2 = if (isAmnezia) i2 else null,
|
||||
i3 = if (isAmnezia) i3 else null,
|
||||
i4 = if (isAmnezia) i4 else null,
|
||||
i5 = if (isAmnezia) i5 else null,
|
||||
obfsPassword = if (isHysteria2) obfsPassword else null,
|
||||
portHopping = if (isHysteria2) portHopping else null,
|
||||
portHoppingInterval = if (isHysteria2) portHoppingInterval else null,
|
||||
@@ -199,6 +248,22 @@ class ServerUiState(
|
||||
reserved = initialConfig.reserved ?: "0,0,0",
|
||||
localAddress = initialConfig.localAddress ?: WIREGUARD_LOCAL_ADDRESS_V4,
|
||||
mtu = initialConfig.mtu?.toString() ?: WIREGUARD_LOCAL_MTU,
|
||||
jc = initialConfig.jc?.toString() ?: "0",
|
||||
jMin = initialConfig.jMin?.toString() ?: "0",
|
||||
jMax = initialConfig.jMax?.toString() ?: "0",
|
||||
s1 = initialConfig.s1?.toString() ?: "0",
|
||||
s2 = initialConfig.s2?.toString() ?: "0",
|
||||
s3 = initialConfig.s3?.toString() ?: "0",
|
||||
s4 = initialConfig.s4?.toString() ?: "0",
|
||||
h1 = initialConfig.h1 ?: "",
|
||||
h2 = initialConfig.h2 ?: "",
|
||||
h3 = initialConfig.h3 ?: "",
|
||||
h4 = initialConfig.h4 ?: "",
|
||||
i1 = initialConfig.i1 ?: "",
|
||||
i2 = initialConfig.i2 ?: "",
|
||||
i3 = initialConfig.i3 ?: "",
|
||||
i4 = initialConfig.i4 ?: "",
|
||||
i5 = initialConfig.i5 ?: "",
|
||||
obfsPassword = initialConfig.obfsPassword ?: "",
|
||||
portHopping = initialConfig.portHopping ?: "",
|
||||
portHoppingInterval = initialConfig.portHoppingInterval ?: "",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<string name="menu_item_import_config_manually_http">Добавить HTTP</string>
|
||||
<string name="menu_item_import_config_manually_trojan">Добавить Trojan</string>
|
||||
<string name="menu_item_import_config_manually_wireguard">Добавить WireGuard</string>
|
||||
<string name="menu_item_import_config_manually_amnezia">Добавить Amnezia</string>
|
||||
<string name="menu_item_import_config_manually_hysteria2">Добавить Hysteria2</string>
|
||||
<string name="confirm_delete_visible_profiles">Удалить все отображаемые профили?</string>
|
||||
<string name="confirm_delete_duplicate_profiles">Удалить все отображаемые дубликаты профилей?</string>
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<string name="menu_item_import_config_manually_http">Add [HTTP]</string>
|
||||
<string name="menu_item_import_config_manually_trojan">Add [Trojan]</string>
|
||||
<string name="menu_item_import_config_manually_wireguard">Add [WireGuard]</string>
|
||||
<string name="menu_item_import_config_manually_amnezia">Add [Amnezia]</string>
|
||||
<string name="menu_item_import_config_manually_hysteria2">Add [Hysteria2]</string>
|
||||
<string name="confirm_delete_visible_profiles">Are you sure you want to delete all profiles currently shown?</string>
|
||||
<string name="confirm_delete_duplicate_profiles">Are you sure you want to delete all duplicate profiles currently shown?</string>
|
||||
@@ -100,6 +101,22 @@
|
||||
<string name="server_lab_reserved">Reserved (optional, separated by commas)</string>
|
||||
<string name="server_lab_local_address">Local address (optional IPv4/IPv6, separated by commas)</string>
|
||||
<string name="server_lab_local_mtu">MTU (optional, default 1420)</string>
|
||||
<string name="server_lab_local_jc">jc (optional, junk packet count)</string>
|
||||
<string name="server_lab_local_jmin">jmin (optional, min junk packet size)</string>
|
||||
<string name="server_lab_local_jmax">jmax (optional, max junk packet size)</string>
|
||||
<string name="server_lab_local_s1">s1 (optional, handshake init padding)</string>
|
||||
<string name="server_lab_local_s2">s2 (optional, handshake response padding)</string>
|
||||
<string name="server_lab_local_s3">s3 (optional, cookie reply padding)</string>
|
||||
<string name="server_lab_local_s4">s4 (optional, transport packet padding)</string>
|
||||
<string name="server_lab_local_h1">h1 (optional, handshake init header)</string>
|
||||
<string name="server_lab_local_h2">h2 (optional, handshake response header)</string>
|
||||
<string name="server_lab_local_h3">h3 (optional, cookie reply header)</string>
|
||||
<string name="server_lab_local_h4">h4 (optional, transport packet header)</string>
|
||||
<string name="server_lab_local_i1">i1 (optional, custom signature packet #1)</string>
|
||||
<string name="server_lab_local_i2">i2 (optional, custom signature packet #2)</string>
|
||||
<string name="server_lab_local_i3">i3 (optional, custom signature packet #3)</string>
|
||||
<string name="server_lab_local_i4">i4 (optional, custom signature packet #4)</string>
|
||||
<string name="server_lab_local_i5">i5 (optional, custom signature packet #5)</string>
|
||||
<string name="server_lab_kcp_mtu">mKCP MTU (optional, default 1350)</string>
|
||||
<string name="server_lab_kcp_tti">mKCP TTI (optional, default 50 ms)</string>
|
||||
<string name="toast_success">Success</string>
|
||||
|
||||
Reference in new issue
Block a user