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:
nerpa committed 2026-09-12 06:49:43 +03:00
1 parent 5ecf566d74
commit 269cbf1349
15 files changed
+591 -14

No files matched your search

+4
View File
@@ -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>