Compare commits

..

15 Commits
1.9.5 ... 1.9.8

Author SHA1 Message Date
2dust
5daef71147 up 1.9.8 2024-10-17 10:59:50 +08:00
Helium-Studio
5ffc5ec502 Clean up custom routing white (#3699)
Some IPs or domains are already included in geoip / geosite
2024-10-17 10:23:27 +08:00
2dust
35063db3e6 Improvement share uri 2024-10-17 10:21:13 +08:00
MMR
868c24bb8b Add Iran whitelist routing option (#3696)
* Add Iran whitelist routing option

* Update SettingsManager.kt

* Add files via upload

* Update custom_routing_white_iran

* Update strings.xml

* Update strings.xml

* Update strings.xml
2024-10-15 21:04:04 +08:00
2dust
7367baffb8 up 1.9.7 2024-10-09 17:39:16 +08:00
mayampi01
819ff2995a Add dns.pub to custom_routing_white (#3668) 2024-10-08 14:50:42 +08:00
2dust
3b5d04b717 Bug fix
https://github.com/2dust/v2rayNG/issues/3653
2024-10-08 10:26:22 +08:00
2dust
b673cd73ac Fix Violation of Broken Functionality policy 2024-10-08 10:25:39 +08:00
MH
649c1a022b Update strings.xml (#3652)
update persian strings
2024-10-05 17:52:41 +08:00
MH
034e58bc9d Update strings.xml (#3649) 2024-10-05 09:59:13 +08:00
2dust
a95f280102 up 1.9.6 2024-10-02 11:41:11 +08:00
2dust
df8da05f32 Fix routing rules 2024-10-02 11:13:41 +08:00
2dust
635581719b Fix latency test 2024-10-01 11:30:55 +08:00
NagisaEfi
77d5e203e8 Update strings.xml (#3639) 2024-10-01 09:50:51 +08:00
solokot
370d002b25 Update Russian translation (#3636) 2024-10-01 09:49:38 +08:00
26 changed files with 403 additions and 411 deletions

View File

@@ -11,8 +11,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 34
versionCode = 598
versionName = "1.9.5"
versionCode = 602
versionName = "1.9.8"
multiDexEnabled = true
splits {
abi {

View File

@@ -27,13 +27,6 @@
"geosite:category-ads-all"
]
},
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{
"remarks": "绕过局域网IP",
"outboundTag": "direct",
@@ -41,6 +34,13 @@
"geoip:private"
]
},
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{
"remarks": "代理GFW",
"outboundTag": "proxy",

View File

@@ -12,13 +12,6 @@
"geosite:category-ads-all"
]
},
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{
"remarks": "绕过局域网IP",
"outboundTag": "direct",
@@ -26,6 +19,13 @@
"geoip:private"
]
},
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{
"remarks": "最终代理",
"port": "0-65535",

View File

@@ -1,12 +1,4 @@
[
{
"remarks": "Google cn",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{
"remarks": "阻断udp443",
"outboundTag": "block",
@@ -20,13 +12,6 @@
"geosite:category-ads-all"
]
},
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{
"remarks": "绕过局域网IP",
"outboundTag": "direct",
@@ -35,47 +20,38 @@
]
},
{
"remarks": "绕过中国域名",
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"domain:dns.alidns.com",
"domain:doh.pub",
"domain:dot.pub",
"domain:doh.360.cn",
"domain:dot.360.cn",
"geosite:cn",
"geosite:geolocation-cn"
"geosite:private"
]
},
{
"remarks": "绕过中国IP",
"outboundTag": "direct",
"ip": [
"223.5.5.5/32",
"223.6.6.6/32",
"2400:3200::1/128",
"2400:3200:baba::1/128",
"119.29.29.29/32",
"1.12.12.12/32",
"120.53.53.53/32",
"2402:4e00::/128",
"2402:4e00:1::/128",
"180.76.76.76/32",
"2400:da00::6666/128",
"114.114.114.114/32",
"114.114.115.115/32",
"180.184.1.1/32",
"180.184.2.2/32",
"101.226.4.6/32",
"218.30.118.6/32",
"123.125.81.6/32",
"140.207.198.6/32",
"geoip:cn"
]
},
{
"remarks": "绕过中国域名",
"outboundTag": "direct",
"domain": [
"geosite:cn",
"geosite:geolocation-cn"
]
},
{
"remarks": "Google CN",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{
"remarks": "最终代理",
"port": "0-65535",
"outboundTag": "proxy"
}
]
]

View File

@@ -0,0 +1,49 @@
[
{
"remarks": "Block udp443",
"outboundTag": "block",
"port": "443",
"network": "udp"
},
{
"remarks": "Block ads and trackers",
"outboundTag": "block",
"domain": [
"geosite:category-ads-all"
]
},
{
"remarks": "Direct LAN IP",
"outboundTag": "direct",
"ip": [
"geoip:private"
]
},
{
"remarks": "Direct LAN domains",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{
"remarks": "Bypass Iran domains",
"outboundTag": "direct",
"domain": [
"domain:ir",
"geosite:category-ir"
]
},
{
"remarks": "Bypass Iran IP",
"outboundTag": "direct",
"ip": [
"geoip:ir"
]
},
{
"remarks": "Final Agent",
"port": "0-65535",
"outboundTag": "proxy"
}
]

View File

@@ -105,7 +105,10 @@ object AppConfig {
const val DNS_PROXY = "1.1.1.1"
const val DNS_DIRECT = "223.5.5.5"
const val DNS_VPN = "1.1.1.1"
const val GEOSITE_PRIVATE = "geosite:private"
const val GEOSITE_CN = "geosite:cn"
const val GEOIP_PRIVATE = "geoip:private"
const val GEOIP_CN = "geoip:cn"
/** Ports and addresses for various services. */
const val PORT_LOCAL_DNS = "10853"

View File

@@ -0,0 +1,45 @@
package com.v2ray.ang.service
import android.content.Context
import android.util.Log
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class ProcessService {
private val TAG = ANG_PACKAGE
private lateinit var process: Process
fun runProcess(context: Context, cmd: MutableList<String>) {
Log.d(TAG, cmd.toString())
try {
val proBuilder = ProcessBuilder(cmd)
proBuilder.redirectErrorStream(true)
process = proBuilder
.directory(context.filesDir)
.start()
CoroutineScope(Dispatchers.IO).launch {
Thread.sleep(50L)
Log.d(TAG, "runProcess check")
process.waitFor()
Log.d(TAG, "runProcess exited")
}
Log.d(TAG, process.toString())
} catch (e: Exception) {
Log.d(TAG, e.toString())
}
}
fun stopProcess() {
try {
Log.d(TAG, "runProcess destroy")
process?.destroy()
} catch (e: Exception) {
Log.d(TAG, e.toString())
}
}
}

View File

@@ -3,7 +3,6 @@ package com.v2ray.ang.service
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_CANCEL
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_SUCCESS
@@ -59,17 +58,8 @@ class V2RayTestService : Service() {
val server = MmkvManager.decodeServerConfig(guid) ?: return retFailure
if (server.getProxyOutbound()?.protocol?.equals(EConfigType.HYSTERIA2.name, true) == true) {
val socksPort = Utils.findFreePort(listOf(0))
PluginUtil.runPlugin(this, server, "0:${socksPort}")
Thread.sleep(1000L)
var delay = SpeedtestUtil.testConnection(this, socksPort)
if (delay.first < 0) {
Thread.sleep(10L)
delay = SpeedtestUtil.testConnection(this, socksPort)
}
PluginUtil.stopPlugin()
return delay.first
val delay = PluginUtil.realPingHy2(this, server)
return delay
} else {
val config = V2rayConfigUtil.getV2rayConfig(this, guid)
if (!config.status) {

View File

@@ -199,6 +199,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
fun startV2Ray() {
if (MmkvManager.getSelectServer().isNullOrEmpty()) {
toast(R.string.title_file_chooser)
return
}
V2RayServiceManager.startV2Ray(this)

View File

@@ -3,45 +3,33 @@ package com.v2ray.ang.util
import android.content.Context
import android.os.SystemClock
import android.util.Log
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.service.ProcessService
import com.v2ray.ang.util.fmt.Hysteria2Fmt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
object PluginUtil {
//private const val HYSTERIA2 = "hysteria2-plugin"
private const val HYSTERIA2 = "libhysteria2.so"
private const val packageName = ANG_PACKAGE
private lateinit var process: Process
private const val TAG = ANG_PACKAGE
private lateinit var procService: ProcessService
// fun initPlugin(name: String): PluginManager.InitResult {
// return PluginManager.init(name)!!
// }
fun runPlugin(context: Context, config: ServerConfig?, domainPort: String?) {
Log.d(packageName, "runPlugin")
Log.d(TAG, "runPlugin")
val outbound = config?.getProxyOutbound() ?: return
if (outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)) {
Log.d(packageName, "runPlugin $HYSTERIA2")
val configFile = genConfigHy2(context, config, domainPort) ?: return
val cmd = genCmdHy2(context, configFile)
val socksPort = domainPort?.split(":")?.last()
.let { if (it.isNullOrEmpty()) return else it.toInt() }
val hy2Config = Hysteria2Fmt.toNativeConfig(config, socksPort) ?: return
val configFile = File(context.noBackupFilesDir, "hy2_${SystemClock.elapsedRealtime()}.json")
Log.d(packageName, "runPlugin ${configFile.absolutePath}")
configFile.parentFile?.mkdirs()
configFile.writeText(JsonUtil.toJson(hy2Config))
Log.d(packageName, JsonUtil.toJson(hy2Config))
runHy2(context, configFile)
procService = ProcessService()
procService.runProcess(context, cmd)
}
}
@@ -49,8 +37,46 @@ object PluginUtil {
stopHy2()
}
private fun runHy2(context: Context, configFile: File) {
val cmd = mutableListOf(
fun realPingHy2(context: Context, config: ServerConfig?): Long {
Log.d(TAG, "realPingHy2")
val retFailure = -1L
val outbound = config?.getProxyOutbound() ?: return retFailure
if (outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)) {
val socksPort = Utils.findFreePort(listOf(0))
val configFile = genConfigHy2(context, config, "0:${socksPort}") ?: return retFailure
val cmd = genCmdHy2(context, configFile)
val proc = ProcessService()
proc.runProcess(context, cmd)
Thread.sleep(1000L)
val delay = SpeedtestUtil.testConnection(context, socksPort)
proc.stopProcess()
return delay.first
}
return retFailure
}
private fun genConfigHy2(context: Context, config: ServerConfig, domainPort: String?): File? {
Log.d(TAG, "runPlugin $HYSTERIA2")
val socksPort = domainPort?.split(":")?.last()
.let { if (it.isNullOrEmpty()) return null else it.toInt() }
val hy2Config = Hysteria2Fmt.toNativeConfig(config, socksPort) ?: return null
val configFile = File(context.noBackupFilesDir, "hy2_${SystemClock.elapsedRealtime()}.json")
Log.d(TAG, "runPlugin ${configFile.absolutePath}")
configFile.parentFile?.mkdirs()
configFile.writeText(JsonUtil.toJson(hy2Config))
Log.d(TAG, JsonUtil.toJson(hy2Config))
return configFile
}
private fun genCmdHy2(context: Context, configFile: File): MutableList<String> {
return mutableListOf(
File(context.applicationInfo.nativeLibraryDir, HYSTERIA2).absolutePath,
//initPlugin(HYSTERIA2).path,
"--disable-update-check",
@@ -60,34 +86,14 @@ object PluginUtil {
"warn",
"client"
)
Log.d(packageName, cmd.toString())
try {
val proBuilder = ProcessBuilder(cmd)
proBuilder.redirectErrorStream(true)
process = proBuilder
.directory(context.filesDir)
.start()
CoroutineScope(Dispatchers.IO).launch {
Thread.sleep(500L)
Log.d(packageName, "$HYSTERIA2 check")
process.waitFor()
Log.d(packageName, "$HYSTERIA2 exited")
}
Log.d(packageName, process.toString())
} catch (e: Exception) {
Log.d(packageName, e.toString())
}
}
private fun stopHy2() {
try {
Log.d(packageName, "$HYSTERIA2 destroy")
process?.destroy()
Log.d(TAG, "$HYSTERIA2 destroy")
procService?.stopProcess()
} catch (e: Exception) {
Log.d(packageName, e.toString())
Log.d(TAG, e.toString())
}
}
}

View File

@@ -4,6 +4,9 @@ import android.content.Context
import android.text.TextUtils
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.GEOIP_PRIVATE
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.util.MmkvManager.decodeProfileConfig
@@ -28,6 +31,7 @@ object SettingsManager {
0 -> "custom_routing_white"
1 -> "custom_routing_black"
2 -> "custom_routing_global"
3 -> "custom_routing_white_iran"
else -> "custom_routing_white"
}
val assets = Utils.readTextFromAssets(context, fileName)
@@ -109,7 +113,9 @@ object SettingsManager {
fun routingRulesetsBypassLan(): Boolean {
val rulesetItems = MmkvManager.decodeRoutingRulesets()
val exist = rulesetItems?.any { it.enabled && it.domain?.contains(":private") == true }
val exist = rulesetItems?.filter { it.enabled && it.outboundTag == TAG_DIRECT }?.any {
it.domain?.contains(GEOSITE_PRIVATE) == true || it.ip?.contains(GEOIP_PRIVATE) == true
}
return exist == true
}

View File

@@ -6,7 +6,10 @@ import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.GEOIP_CN
import com.v2ray.ang.AppConfig.GEOSITE_CN
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
import com.v2ray.ang.AppConfig.TAG_BLOCKED
import com.v2ray.ang.AppConfig.TAG_DIRECT
@@ -222,7 +225,9 @@ object V2rayConfigUtil {
rulesetItems?.forEach { key ->
if (key != null && key.enabled && key.outboundTag == tag && !key.domain.isNullOrEmpty()) {
key.domain?.forEach {
if (it.startsWith("geosite:") || it.startsWith("domain:")) {
if (it != GEOSITE_PRIVATE
&& (it.startsWith("geosite:") || it.startsWith("domain:"))
) {
domain.add(it)
}
}
@@ -235,7 +240,7 @@ object V2rayConfigUtil {
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
try {
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
val geositeCn = arrayListOf("geosite:cn")
val geositeCn = arrayListOf(GEOSITE_CN)
val proxyDomain = userRule2Domain(TAG_PROXY)
val directDomain = userRule2Domain(TAG_DIRECT)
// fakedns with all domains to make it always top priority
@@ -326,8 +331,8 @@ object V2rayConfigUtil {
// domestic DNS
val domesticDns = Utils.getDomesticDnsServers()
val directDomain = userRule2Domain(TAG_DIRECT)
val isCnRoutingMode = directDomain.contains("geosite:cn")
val geoipCn = arrayListOf("geoip:cn")
val isCnRoutingMode = directDomain.contains(GEOSITE_CN)
val geoipCn = arrayListOf(GEOIP_CN)
if (directDomain.size > 0) {
servers.add(
V2rayConfig.DnsBean.ServersBean(

View File

@@ -0,0 +1,104 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.util.Utils
open class FmtBase {
fun toUri(address: String?, port: Int?, userInfo: String?, dicQuery: HashMap<String, String>?, remark: String): String {
val query = if (dicQuery != null)
("?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + Utils.urlEncode(it.second) }))
else ""
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(userInfo ?: ""),
Utils.getIpv6Address(address),
port
)
return "${url}${query}#${Utils.urlEncode(remark)}"
}
fun getStdTransport(outbound: V2rayConfig.OutboundBean, streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean): HashMap<String, String> {
val dicQuery = HashMap<String, String>()
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
dicQuery["spx"] = tlsSetting.spiderX.orEmpty()
}
}
dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = transportDetails[1]
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = transportDetails[2]
}
}
"ws", "httpupgrade", "splithttp" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = transportDetails[1]
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = transportDetails[2]
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = transportDetails[1]
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = transportDetails[2]
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = transportDetails[1]
dicQuery["key"] = transportDetails[2]
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = transportDetails[1]
dicQuery["serviceName"] = transportDetails[2]
}
}
}
return dicQuery
}
}

View File

@@ -12,7 +12,7 @@ import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import java.net.URI
object Hysteria2Fmt {
object Hysteria2Fmt : FmtBase() {
fun parse(str: String): ServerConfig {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
@@ -51,7 +51,7 @@ object Hysteria2Fmt {
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 ->
@@ -68,17 +68,7 @@ object Hysteria2Fmt {
dicQuery["obfs-password"] = outbound.settings?.obfsPassword ?: ""
}
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
return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
}
fun toNativeConfig(config: ServerConfig, socksPort: Int): Hysteria2Bean? {

View File

@@ -8,7 +8,7 @@ import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.Utils
import java.net.URI
object ShadowsocksFmt {
object ShadowsocksFmt : FmtBase() {
fun parse(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.SHADOWSOCKS)
if (!tryResolveResolveSip002(str, config)) {
@@ -52,16 +52,10 @@ object ShadowsocksFmt {
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val pw =
Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
val url = String.format(
"%s@%s:%s",
pw,
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + remark
val pw = Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
return toUri(outbound.getServerAddress(), outbound.getServerPort(), pw, null, config.remarks)
}
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {

View File

@@ -5,7 +5,7 @@ import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.util.Utils
object SocksFmt {
object SocksFmt : FmtBase() {
fun parse(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.SOCKS)
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
@@ -52,18 +52,13 @@ object SocksFmt {
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val pw =
if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null)
"${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}"
else
":"
val url = String.format(
"%s@%s:%s",
Utils.encode(pw),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + remark
return toUri(outbound.getServerAddress(), outbound.getServerPort(), pw, null, config.remarks)
}
}

View File

@@ -10,7 +10,7 @@ import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import java.net.URI
object TrojanFmt {
object TrojanFmt : FmtBase() {
fun parse(str: String): ServerConfig {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
@@ -76,98 +76,15 @@ object TrojanFmt {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
val dicQuery = getStdTransport(outbound, streamSetting)
config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it
}
}
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX.orEmpty())
}
}
dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
}
}
"ws", "httpupgrade", "splithttp" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
}
}
}
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
return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
}
}

View File

@@ -10,7 +10,7 @@ import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import java.net.URI
object VlessFmt {
object VlessFmt : FmtBase() {
fun parse(str: String): ServerConfig? {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
@@ -63,8 +63,9 @@ object VlessFmt {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
val dicQuery = getStdTransport(outbound, streamSetting)
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it
@@ -74,91 +75,6 @@ object VlessFmt {
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
else outbound.getSecurityEncryption().orEmpty()
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX.orEmpty())
}
}
dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
}
}
"ws", "httpupgrade", "splithttp" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
}
}
}
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
return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
}
}

View File

@@ -14,7 +14,7 @@ import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import java.net.URI
object VmessFmt {
object VmessFmt : FmtBase() {
fun parse(str: String): ServerConfig? {
if (str.indexOf('?') > 0 && str.indexOf('&') > 0) {

View File

@@ -8,7 +8,7 @@ import com.v2ray.ang.extension.removeWhiteSpace
import com.v2ray.ang.util.Utils
import java.net.URI
object WireguardFmt {
object WireguardFmt : FmtBase() {
fun parse(str: String): ServerConfig? {
val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery != null) {
@@ -71,37 +71,22 @@ object WireguardFmt {
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>()
dicQuery["publickey"] =
Utils.urlEncode(outbound.settings?.peers?.get(0)?.publicKey.toString())
dicQuery["publickey"] = outbound.settings?.peers?.get(0)?.publicKey.toString()
if (outbound.settings?.reserved != null) {
dicQuery["reserved"] = Utils.urlEncode(
Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString(","))
.toString()
)
dicQuery["reserved"] = Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString(",")).toString()
}
dicQuery["address"] = Utils.urlEncode(
Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString(","))
.toString()
)
dicQuery["address"] = Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString(",")).toString()
if (outbound.settings?.mtu != null) {
dicQuery["mtu"] = outbound.settings?.mtu.toString()
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(outbound.getPassword().toString()),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + query + remark
return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
}
}

View File

@@ -127,8 +127,8 @@
<string name="title_vpn_settings">تنظیمات VPN</string>
<string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string>
<string name="summary_pref_per_app_proxy">عمومی: برنامه بررسی شده پروکسی است، اتصال مستقیم بدون بررسی است. \nحالت bypass: برنامه بررسی شده مستقیما متصل است، پراکسی بررسی نشده است. \nگزینهای برای انتخاب خودکار پروکسی برنامه در منو است</string>
<string name="title_pref_is_booted">Auto connect at startup</string>
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string>
<string name="title_pref_is_booted">اتصال خودکار هنگام راه اندازی</string>
<string name="summary_pref_is_booted">هنگام راه اندازی به طور خودکار به سرور انتخابی متصل می شود که ممکن است ناموفق باشد</string>
<string name="title_mux_settings">تنظیمات Mux</string>
<string name="title_pref_mux_enabled">فعال کردن Mux</string>
@@ -147,8 +147,8 @@
<string name="title_pref_sniffing_enabled">فعال کردن Sniffing</string>
<string name="summary_pref_sniffing_enabled">دامنه sniff را از بسته امتحان کنید (پیش‌فرض روشن)</string>
<string name="title_pref_route_only_enabled">Enable routeOnly</string>
<string name="summary_pref_route_only_enabled">Use the sniffed domain name for routing only, and keep the target address as the IP address.</string>
<string name="title_pref_route_only_enabled">فعال کردن routeOnly</string>
<string name="summary_pref_route_only_enabled">از نام دامنه sniffed فقط برای مسیریابی استفاده کنید و آدرس مورد نظر را به عنوان آدرس IP نگه دارید.</string>
<string name="title_pref_local_dns_enabled">فعال کردن DNS محلی</string>
@@ -175,8 +175,8 @@
<string name="summary_pref_proxy_sharing_enabled">دستگاه‌های دیگر می‌توانند از طریق socks/http به پراکسی توسط نشانی آی‌پی شما متصل شوند، فقط در شبکه مورد اعتماد فعال می‌شوند تا از اتصال غیرمجاز جلوگیری کنند</string>
<string name="toast_warning_pref_proxysharing_short">اتصالات از طریق LAN را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید</string>
<string name="title_pref_allow_insecure">allowInsecure</string>
<string name="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیش‌فرض allowInsecure فعال است</string>
<string name="title_pref_allow_insecure">موز ناامن</string>
<string name="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیش‌فرض مجوز ناامن فعال است</string>
<string name="title_pref_socks_port">پورت پروکسی SOCKS5</string>
<string name="summary_pref_socks_port">پورت پروکسی SOCKS5</string>
@@ -200,12 +200,12 @@
<string name="title_privacy_policy">حریم خصوصی</string>
<string name="title_about">درباره</string>
<string name="title_source_code">Source code</string>
<string name="title_tg_channel">Telegram channel</string>
<string name="title_configuration_backup">Backup configuration</string>
<string name="summary_configuration_backup">Storage location: [%s], The backup will be cleared after uninstalling the app or clearing the storage</string>
<string name="title_configuration_restore">Restore configuration</string>
<string name="title_configuration_share">Share configuration</string>
<string name="title_source_code">کد منبع</string>
<string name="title_tg_channel">کانال تلگرام</string>
<string name="title_configuration_backup">پشتیبانگیری از پیکربندی</string>
<string name="summary_configuration_backup">محل ذخیره سازی: [%s], پس از حذف نصب برنامه یا پاک کردن فضای ذخیره سازی، نسخه پشتیبان پاک می شود</string>
<string name="title_configuration_restore">بازیابی پیکربندی</string>
<string name="title_configuration_share">اشتراکگذاری پیکربندی</string>
<string name="title_pref_promotion">تبلیغات</string>
<string name="summary_pref_promotion">تبلیغات، برای جزئیات بیشتر کلیک کنید (کمک مالی کنید تا حذف شود)</string>
@@ -252,14 +252,14 @@
<string name="routing_settings_title">تنظیمات مسیریابی</string>
<string name="routing_settings_tips">با کاما (,) از هم جدا شوند، ذخیره کردن فراموش نشود</string>
<string name="routing_settings_save">ذخیره</string>
<string name="routing_settings_delete">پاک کردن</string>
<string name="routing_settings_rule_title">Routing Rule Settings</string>
<string name="routing_settings_add_rule">Add rule</string>
<string name="routing_settings_import_rulesets">Import ruleset</string>
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string>
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
<string name="routing_settings_delete">حذف</string>
<string name="routing_settings_rule_title">تنظیمات قانون مسیریابی</string>
<string name="routing_settings_add_rule">اضافه کردن قانون</string>
<string name="routing_settings_import_rulesets">وارد کردن مجموعه قوانین</string>
<string name="routing_settings_import_rulesets_tip">مجموعه قوانین موجود حذف خواهند شد، آیا مطمئن هستید که ادامه می دهید؟</string>
<string name="routing_settings_import_rulesets_from_clipboard">وارد کردن مجموعه قوانین از کلیپ بورد</string>
<string name="routing_settings_export_rulesets_to_clipboard">صادر کردن مجموعه قوانین به کلیپ بورد</string>
<string name="routing_settings_locked">قفل است، این قانون را هنگام وارد کردن از پیش تنظیم‌ها حفظ کنید</string>
<string name="connection_test_pending">اتصال را بررسی کنید</string>
<string name="connection_test_testing">در حال آزمایش...</string>
@@ -301,4 +301,11 @@
<item>Dark</item>
</string-array>
<string-array name="preset_rulesets">
<item>لیست سفید چین</item>
<item>لیست سیاه چین</item>
<item>جهانی(Global)</item>
<item>ایران</item>
</string-array>
</resources>

View File

@@ -110,7 +110,7 @@
<string name="msg_file_not_found">Файл не найден</string>
<string name="msg_remark_is_duplicate">Название уже существует</string>
<string name="toast_action_not_allowed">Это действие запрещено</string>
<string name="server_obfs_password">Obfs password</string>
<string name="server_obfs_password">Пароль obfs</string>
<!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">Загрузка…</string>
@@ -253,15 +253,15 @@
<string name="routing_settings_domain_strategy">Доменная стратегия</string>
<string name="routing_settings_title">Маршрутизация</string>
<string name="routing_settings_tips">Введите требуемые значения через запятую</string>
<string name="routing_settings_tips">Введите требуемые домены/IP через запятую</string>
<string name="routing_settings_save">Сохранить</string>
<string name="routing_settings_delete">Очистить</string>
<string name="routing_settings_rule_title">Настройка правил маршрутизации</string>
<string name="routing_settings_add_rule">Добавить правило</string>
<string name="routing_settings_import_rulesets">Импорт правил</string>
<string name="routing_settings_import_rulesets_tip">Существующие правила будут удалены. Продолжить?</string>
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
<string name="routing_settings_import_rulesets_from_clipboard">Импорт правил из буфера обмена</string>
<string name="routing_settings_export_rulesets_to_clipboard">Экспорт правил в буфер обмена</string>
<string name="routing_settings_locked">Постоянное (сохранится при импорте правил)</string>
<string name="routing_settings_domain">Домен</string>
<string name="routing_settings_ip">IP</string>

View File

@@ -306,6 +306,7 @@
<item>绕过大陆(Whitelist)</item>
<item>黑名单(Blacklist)</item>
<item>全局(Global)</item>
<item>伊朗(Iran)</item>
</string-array>
</resources>

View File

@@ -18,12 +18,12 @@
<string name="toast_services_failure">啟動服務失敗</string>
<!--ServerActivity-->
<string name="title_server">配置檔案</string>
<string name="menu_item_add_config">新增配置</string>
<string name="menu_item_save_config">儲存配置</string>
<string name="menu_item_del_config">刪除配置</string>
<string name="menu_item_import_config_qrcode">從 QR Code 匯入配置</string>
<string name="menu_item_import_config_clipboard">從剪貼簿匯入配置</string>
<string name="title_server">設定檔</string>
<string name="menu_item_add_config">新增設定</string>
<string name="menu_item_save_config">儲存設定</string>
<string name="menu_item_del_config">刪除設定</string>
<string name="menu_item_import_config_qrcode">從 QR Code 匯入設定</string>
<string name="menu_item_import_config_clipboard">從剪貼簿匯入設定</string>
<string name="menu_item_import_config_manually_vmess">手動鍵入 [VMess]</string>
<string name="menu_item_import_config_manually_vless">手動鍵入 [VLESS]</string>
<string name="menu_item_import_config_manually_ss">手動鍵入 [Shadowsocks]</string>
@@ -32,11 +32,11 @@
<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_hysteria2">手動鍵入 [Hysteria2]</string>
<string name="menu_item_import_config_custom">自訂配置</string>
<string name="menu_item_import_config_custom_clipboard">從剪貼簿匯入自訂配置</string>
<string name="menu_item_import_config_custom_local">從本地匯入自訂配置</string>
<string name="menu_item_import_config_custom_url">從 URL 匯入自訂配置</string>
<string name="menu_item_import_config_custom_url_scan">掃描 URL 匯入自訂配置</string>
<string name="menu_item_import_config_custom">自訂設定</string>
<string name="menu_item_import_config_custom_clipboard">從剪貼簿匯入自訂設定</string>
<string name="menu_item_import_config_custom_local">從本地匯入自訂設定</string>
<string name="menu_item_import_config_custom_url">從 URL 匯入自訂設定</string>
<string name="menu_item_import_config_custom_url_scan">掃描 URL 匯入自訂設定</string>
<string name="del_config_comfirm">確定刪除?</string>
<string name="del_invalid_config_comfirm">刪除前請先測試!確認刪除?</string>
<string name="server_lab_remarks">備註</string>
@@ -90,15 +90,15 @@
<string name="toast_none_data">無資料</string>
<string name="toast_incorrect_protocol">通訊協定不正確</string>
<string name="toast_decoding_failed">解碼失敗</string>
<string name="title_file_chooser">選取一個配置</string>
<string name="title_file_chooser">選取一個設定</string>
<string name="toast_require_file_manager">請安裝檔案總管。</string>
<string name="server_customize_config">自訂配置</string>
<string name="toast_config_file_invalid">無效配置</string>
<string name="server_customize_config">自訂設定</string>
<string name="toast_config_file_invalid">無效設定</string>
<string name="server_lab_content">內容</string>
<string name="toast_none_data_clipboard">剪貼簿內無資料</string>
<string name="toast_invalid_url">URL 無效</string>
<string name="server_lab_need_inbound">​​確保 inbounds port 和設定中的一致</string>
<string name="toast_malformed_josn">配置格式不正確</string>
<string name="toast_malformed_josn">設定格式不正確</string>
<string name="server_lab_request_host6">Host(SNI)(可選)</string>
<string name="toast_asset_copy_failed">失敗,請使用檔案總管</string>
<string name="menu_item_add_file">新增檔案</string>
@@ -188,8 +188,8 @@
<string name="title_pref_local_dns_port">本機 DNS 埠</string>
<string name="summary_pref_local_dns_port">本機 DNS 埠</string>
<string name="title_pref_confirm_remove">刪除配置檔案確認</string>
<string name="summary_pref_confirm_remove">刪除配置檔案是否需要用戶二次確認</string>
<string name="title_pref_confirm_remove">刪除設定檔確認</string>
<string name="summary_pref_confirm_remove">刪除設定檔是否需要用戶二次確認</string>
<string name="title_pref_start_scan_immediate">立即啟動掃碼</string>
<string name="summary_pref_start_scan_immediate">啟動時立即打開相機掃描,否則可在工具欄選擇掃碼或選照片</string>
@@ -202,10 +202,10 @@
<string name="title_about">關於</string>
<string name="title_source_code">原始碼</string>
<string name="title_tg_channel">Telegram 頻道</string>
<string name="title_configuration_backup">備份配置</string>
<string name="title_configuration_backup">備份設定</string>
<string name="summary_configuration_backup">儲存位置: [%s], 卸載App或清除儲存後備份將被清除</string>
<string name="title_configuration_restore">還原配置</string>
<string name="title_configuration_share">分享配置</string>
<string name="title_configuration_restore">還原設定</string>
<string name="title_configuration_share">分享設定</string>
<string name="title_pref_promotion">推廣</string>
<string name="summary_pref_promotion">一些推廣,輕觸以檢視 (捐贈可去除)</string>
@@ -225,10 +225,10 @@
<string name="logcat_copy">複製</string>
<string name="logcat_clear">清除</string>
<string name="title_service_restart">重啟服務</string>
<string name="title_del_all_config">刪除目前群組配置</string>
<string name="title_del_duplicate_config">刪除目前群組重複配置</string>
<string name="title_del_invalid_config">刪除目前群組無效配置</string>
<string name="title_export_all">匯出目前群組配置至剪貼簿</string>
<string name="title_del_all_config">刪除目前群組設定</string>
<string name="title_del_duplicate_config">刪除目前群組重複設定</string>
<string name="title_del_invalid_config">刪除目前群組無效設定</string>
<string name="title_export_all">匯出目前群組設定至剪貼簿</string>
<string name="title_sub_setting">訂閱分組設定</string>
<string name="sub_setting_remarks">備註</string>
<string name="sub_setting_url">可選位址(url)</string>
@@ -239,11 +239,11 @@
<string name="sub_setting_next_profile">落地代理別名</string>
<string name="sub_setting_pre_profile_tip">请确保别名存在并唯一</string>
<string name="title_sub_update">更新目前群組訂閱</string>
<string name="title_ping_all_server">偵測目前群組配置 Tcping</string>
<string name="title_real_ping_all_server">偵測目前群組配置真延遲</string>
<string name="title_ping_all_server">偵測目前群組設定 Tcping</string>
<string name="title_real_ping_all_server">偵測目前群組設定真延遲</string>
<string name="title_user_asset_setting">Geo 資源檔案</string>
<string name="title_sort_by_test_results">依偵測結果排序</string>
<string name="title_filter_config">過濾配置</string>
<string name="title_filter_config">過濾設定</string>
<string name="filter_config_all">所有分組</string>
<string name="title_del_duplicate_config_count">Delete %d duplicate configurations</string>
@@ -285,7 +285,7 @@
<string-array name="share_method">
<item>QR Code</item>
<item>匯出至剪貼簿</item>
<item>匯出完整配置至剪貼簿</item>
<item>匯出完整設定至剪貼簿</item>
</string-array>
<string-array name="share_sub_method">
@@ -308,6 +308,7 @@
<item>繞過大陸(Whitelist)</item>
<item>黑名單(Blacklist)</item>
<item>全域(Global)</item>
<item>伊朗(Iran)</item>
</string-array>
</resources>

View File

@@ -318,6 +318,7 @@
<item>China Whitelist</item>
<item>China Blacklist</item>
<item>Global</item>
<item>Iran Whitelist</item>
</string-array>
</resources>

View File

@@ -1,18 +1,18 @@
[versions]
activityKtx = "1.9.2"
activityKtx = "1.9.3"
appcompat = "1.7.0"
cardview = "1.0.0"
constraintlayout = "2.1.4"
core = "3.5.3"
editorkit = "2.9.0"
flexbox = "3.0.0"
fragmentKtx = "1.8.3"
fragmentKtx = "1.8.4"
gson = "2.11.0"
junit = "4.13.2"
kotlinReflect = "2.0.20"
kotlinxCoroutinesCore = "1.9.0"
legacySupportV4 = "1.0.0"
lifecycleViewmodelKtx = "2.8.5"
lifecycleViewmodelKtx = "2.8.6"
material = "1.12.0"
mmkvStatic = "1.3.9"
multidex = "2.0.1"