mirror of
https://github.com/2dust/v2rayNG.git
synced 2026-10-29 06:31:54 +00:00
Introduce CoreConfigContext and refactor manager
Add CoreConfigContext DTO and CoreResolvedType enum, and implement CoreConfigContextBuilder to centralize profile resolution (CUSTOM, POLICYGROUP, PROXYCHAIN/NORMAL) with policy-group filters and proxy-chain ordering (next -> current -> prev). Refactor CoreConfigManager to consume CoreConfigContext: replace prior branching with buildGroupConfig, buildProxyChainConfig and buildNormalConfig, unify CUSTOM handling, add postProcessForSpeedtest and toConfigResult helpers, and remove the old getMoreOutbounds logic. Adjust outbound assembly, DNS/route handling and speedtest post-processing accordingly. Minor cleanup: centralize outbound conversion comments in CoreOutboundBuilder and tidy several condition checks and log lines. Overall this extracts resolution logic, reduces duplication and clarifies the runtime build flow.
This commit is contained in:
1 parent
d0f1e21f49
commit
dc1d3a27dd
5 files changed
+295
-233
No files matched your search
@@ -0,0 +1,114 @@
|
|||||||
|
package com.v2ray.ang.core
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.dto.CoreConfigContext
|
||||||
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
|
import com.v2ray.ang.enums.CoreResolvedType
|
||||||
|
import com.v2ray.ang.enums.EConfigType
|
||||||
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
|
import com.v2ray.ang.handler.SettingsManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds [com.v2ray.ang.dto.CoreConfigContext] from the selected profile.
|
||||||
|
* Keeps parsing and resolution logic out of [CoreConfigManager].
|
||||||
|
*/
|
||||||
|
object CoreConfigContextBuilder {
|
||||||
|
|
||||||
|
/** Loads profile by guid and returns a resolved runtime context. */
|
||||||
|
fun build(context: Context, guid: String): CoreConfigContext? {
|
||||||
|
val config = MmkvManager.decodeServerConfig(guid) ?: return null
|
||||||
|
|
||||||
|
return when (config.configType) {
|
||||||
|
EConfigType.CUSTOM -> CoreConfigContext(
|
||||||
|
context = context,
|
||||||
|
guid = guid,
|
||||||
|
selectedProfile = config,
|
||||||
|
resolvedProfiles = listOf(config),
|
||||||
|
resolvedType = CoreResolvedType.CUSTOM,
|
||||||
|
)
|
||||||
|
|
||||||
|
EConfigType.POLICYGROUP -> {
|
||||||
|
val profiles = resolvePolicyGroupProfiles(config)
|
||||||
|
CoreConfigContext(
|
||||||
|
context = context,
|
||||||
|
guid = guid,
|
||||||
|
selectedProfile = config,
|
||||||
|
resolvedProfiles = profiles,
|
||||||
|
resolvedType = CoreResolvedType.POLICYGROUP,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val chainProfiles = resolveProxyChainProfiles(config)
|
||||||
|
CoreConfigContext(
|
||||||
|
context = context,
|
||||||
|
guid = guid,
|
||||||
|
selectedProfile = config,
|
||||||
|
resolvedProfiles = chainProfiles,
|
||||||
|
resolvedType = if (chainProfiles.size <= 1) CoreResolvedType.NORMAL else CoreResolvedType.PROXYCHAIN,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolves policy-group members with the same filters as runtime build. */
|
||||||
|
private fun resolvePolicyGroupProfiles(config: ProfileItem): List<ProfileItem> {
|
||||||
|
val serverList = MmkvManager.decodeAllServerList()
|
||||||
|
return serverList
|
||||||
|
.asSequence()
|
||||||
|
.mapNotNull { id -> MmkvManager.decodeServerConfig(id) }
|
||||||
|
.filter { profile ->
|
||||||
|
val subscriptionId = config.policyGroupSubscriptionId
|
||||||
|
if (subscriptionId.isNullOrBlank()) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
profile.subscriptionId == subscriptionId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter { profile ->
|
||||||
|
val filter = config.policyGroupFilter
|
||||||
|
if (filter.isNullOrBlank()) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Regex(filter).containsMatchIn(profile.remarks)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
profile.remarks.contains(filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter { it.server.isNotNullEmpty() }
|
||||||
|
.filter { !Utils.isPureIpAddress(it.server!!) || Utils.isValidUrl(it.server!!) }
|
||||||
|
.filter { it.configType != EConfigType.CUSTOM }
|
||||||
|
.filter { it.configType != EConfigType.POLICYGROUP }
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves chain nodes in fixed order: next -> current -> prev.
|
||||||
|
* If chain cannot be built, caller treats result as normal mode.
|
||||||
|
*/
|
||||||
|
private fun resolveProxyChainProfiles(config: ProfileItem): List<ProfileItem> {
|
||||||
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == true) {
|
||||||
|
return listOf(config)
|
||||||
|
}
|
||||||
|
if (config.subscriptionId.isEmpty()) {
|
||||||
|
return listOf(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
val subItem = MmkvManager.decodeSubscription(config.subscriptionId) ?: return listOf(config)
|
||||||
|
val resolved = mutableListOf<ProfileItem>()
|
||||||
|
|
||||||
|
// Keep the same practical chain order as current runtime assembly:
|
||||||
|
// next -> current -> prev
|
||||||
|
SettingsManager.getServerViaRemarks(subItem.nextProfile)?.let { resolved.add(it) }
|
||||||
|
resolved.add(config)
|
||||||
|
SettingsManager.getServerViaRemarks(subItem.prevProfile)?.let { resolved.add(it) }
|
||||||
|
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -5,9 +5,11 @@ import android.text.TextUtils
|
|||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.dto.ConfigResult
|
import com.v2ray.ang.dto.ConfigResult
|
||||||
|
import com.v2ray.ang.dto.CoreConfigContext
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.RulesetItem
|
import com.v2ray.ang.dto.RulesetItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.enums.CoreResolvedType
|
||||||
import com.v2ray.ang.enums.EConfigType
|
import com.v2ray.ang.enums.EConfigType
|
||||||
import com.v2ray.ang.extension.isNotNullEmpty
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
@@ -25,7 +27,7 @@ object CoreConfigManager {
|
|||||||
//region get config function
|
//region get config function
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the V2ray configuration for the given GUID.
|
* Builds normal runtime config JSON for the selected profile.
|
||||||
*
|
*
|
||||||
* @param context The context of the caller.
|
* @param context The context of the caller.
|
||||||
* @param guid The unique identifier for the V2ray configuration.
|
* @param guid The unique identifier for the V2ray configuration.
|
||||||
@@ -33,14 +35,18 @@ object CoreConfigManager {
|
|||||||
*/
|
*/
|
||||||
fun getV2rayConfig(context: Context, guid: String): ConfigResult {
|
fun getV2rayConfig(context: Context, guid: String): ConfigResult {
|
||||||
try {
|
try {
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false)
|
val configContext = CoreConfigContextBuilder.build(context, guid) ?: return ConfigResult(false)
|
||||||
return if (config.configType == EConfigType.CUSTOM) {
|
if (configContext.resolvedType == CoreResolvedType.CUSTOM) {
|
||||||
getV2rayCustomConfig(context, guid, config)
|
return getV2rayCustomConfig(configContext)
|
||||||
} else if (config.configType == EConfigType.POLICYGROUP) {
|
|
||||||
getV2rayGroupConfig(context, guid, config)
|
|
||||||
} else {
|
|
||||||
getV2rayNormalConfig(context, guid, config)
|
|
||||||
}
|
}
|
||||||
|
val v2rayConfig = when (configContext.resolvedType) {
|
||||||
|
CoreResolvedType.POLICYGROUP -> buildGroupConfig(configContext)
|
||||||
|
CoreResolvedType.PROXYCHAIN -> buildProxyChainConfig(configContext)
|
||||||
|
CoreResolvedType.NORMAL -> buildNormalConfig(configContext)
|
||||||
|
CoreResolvedType.CUSTOM -> null
|
||||||
|
} ?: return ConfigResult(false)
|
||||||
|
|
||||||
|
return toConfigResult(configContext.guid, v2rayConfig)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LogUtil.e(AppConfig.TAG, "Failed to get V2ray config", e)
|
LogUtil.e(AppConfig.TAG, "Failed to get V2ray config", e)
|
||||||
return ConfigResult(false)
|
return ConfigResult(false)
|
||||||
@@ -48,7 +54,10 @@ object CoreConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the speedtest V2ray configuration for the given GUID.
|
* Builds speedtest config for the selected profile.
|
||||||
|
*
|
||||||
|
* It reuses the same build flow as normal config, then removes
|
||||||
|
* unnecessary sections for delay testing.
|
||||||
*
|
*
|
||||||
* @param context The context of the caller.
|
* @param context The context of the caller.
|
||||||
* @param guid The unique identifier for the V2ray configuration.
|
* @param guid The unique identifier for the V2ray configuration.
|
||||||
@@ -56,15 +65,20 @@ object CoreConfigManager {
|
|||||||
*/
|
*/
|
||||||
fun getV2rayConfig4Speedtest(context: Context, guid: String): ConfigResult {
|
fun getV2rayConfig4Speedtest(context: Context, guid: String): ConfigResult {
|
||||||
try {
|
try {
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false)
|
val configContext = CoreConfigContextBuilder.build(context, guid) ?: return ConfigResult(false)
|
||||||
return if (config.configType == EConfigType.CUSTOM) {
|
if (configContext.resolvedType == CoreResolvedType.CUSTOM) {
|
||||||
getV2rayCustomConfig(context, guid, config)
|
return getV2rayCustomConfig(configContext)
|
||||||
} else if (config.configType == EConfigType.POLICYGROUP) {
|
|
||||||
// The number of policy groups will not be very large, so no special handling is needed.
|
|
||||||
getV2rayGroupConfig(context, guid, config)
|
|
||||||
} else {
|
|
||||||
getV2rayNormalConfig4Speedtest(context, guid, config)
|
|
||||||
}
|
}
|
||||||
|
val v2rayConfig = when (configContext.resolvedType) {
|
||||||
|
CoreResolvedType.POLICYGROUP -> buildGroupConfig(configContext)
|
||||||
|
CoreResolvedType.PROXYCHAIN -> buildProxyChainConfig(configContext)
|
||||||
|
CoreResolvedType.NORMAL -> buildNormalConfig(configContext)
|
||||||
|
CoreResolvedType.CUSTOM -> null
|
||||||
|
} ?: return ConfigResult(false)
|
||||||
|
|
||||||
|
postProcessForSpeedtest(v2rayConfig)
|
||||||
|
|
||||||
|
return toConfigResult(configContext.guid, v2rayConfig)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LogUtil.e(AppConfig.TAG, "Failed to get V2ray config for speedtest", e)
|
LogUtil.e(AppConfig.TAG, "Failed to get V2ray config for speedtest", e)
|
||||||
return ConfigResult(false)
|
return ConfigResult(false)
|
||||||
@@ -72,13 +86,11 @@ object CoreConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the custom V2ray configuration.
|
* Builds config result for CUSTOM profiles.
|
||||||
*
|
|
||||||
* @param guid The unique identifier for the V2ray configuration.
|
|
||||||
* @param config The profile item containing the configuration details.
|
|
||||||
* @return A ConfigResult object containing the result of the configuration retrieval.
|
|
||||||
*/
|
*/
|
||||||
private fun getV2rayCustomConfig(context: Context, guid: String, config: ProfileItem): ConfigResult {
|
private fun getV2rayCustomConfig(configContext: CoreConfigContext): ConfigResult {
|
||||||
|
val context = configContext.context
|
||||||
|
val guid = configContext.guid
|
||||||
val raw = MmkvManager.decodeServerRaw(guid) ?: return ConfigResult(false)
|
val raw = MmkvManager.decodeServerRaw(guid) ?: return ConfigResult(false)
|
||||||
val result = ConfigResult(true, guid, raw)
|
val result = ConfigResult(true, guid, raw)
|
||||||
if (!needTun()) {
|
if (!needTun()) {
|
||||||
@@ -127,109 +139,11 @@ object CoreConfigManager {
|
|||||||
return JsonUtil.toJsonPretty(json)?.let { ConfigResult(true, guid, it) } ?: result
|
return JsonUtil.toJsonPretty(json)?.let { ConfigResult(true, guid, it) } ?: result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Builds full config for policy-group mode. */
|
||||||
* Retrieves the group V2ray configuration.
|
private fun buildGroupConfig(configContext: CoreConfigContext): V2rayConfig? {
|
||||||
*
|
val context = configContext.context
|
||||||
* @param context The context in which the function is called.
|
val config = configContext.selectedProfile
|
||||||
* @param guid The unique identifier for the V2ray configuration.
|
val validConfigs = configContext.resolvedProfiles
|
||||||
* @param config The profile item containing the configuration details.
|
|
||||||
* @return A ConfigResult object containing the result of the configuration retrieval.
|
|
||||||
*/
|
|
||||||
private fun getV2rayGroupConfig(context: Context, guid: String, config: ProfileItem): ConfigResult {
|
|
||||||
val result = ConfigResult(false)
|
|
||||||
|
|
||||||
val serverList = MmkvManager.decodeAllServerList()
|
|
||||||
val configList = serverList
|
|
||||||
.mapNotNull { id -> MmkvManager.decodeServerConfig(id) }
|
|
||||||
.filter { profile ->
|
|
||||||
val subscriptionId = config.policyGroupSubscriptionId
|
|
||||||
if (subscriptionId.isNullOrBlank()) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
profile.subscriptionId == subscriptionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.filter { profile ->
|
|
||||||
val filter = config.policyGroupFilter
|
|
||||||
if (filter.isNullOrBlank()) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
Regex(filter).containsMatchIn(profile.remarks)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
profile.remarks.contains(filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val v2rayConfig = getV2rayMultipleConfig(context, config, configList) ?: return result
|
|
||||||
|
|
||||||
result.status = true
|
|
||||||
result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
|
|
||||||
result.guid = guid
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the normal V2ray configuration.
|
|
||||||
*
|
|
||||||
* @param context The context in which the function is called.
|
|
||||||
* @param guid The unique identifier for the V2ray configuration.
|
|
||||||
* @param config The profile item containing the configuration details.
|
|
||||||
* @return A ConfigResult object containing the result of the configuration retrieval.
|
|
||||||
*/
|
|
||||||
private fun getV2rayNormalConfig(context: Context, guid: String, config: ProfileItem): ConfigResult {
|
|
||||||
val result = ConfigResult(false)
|
|
||||||
|
|
||||||
val address = config.server ?: return result
|
|
||||||
if (!Utils.isPureIpAddress(address)) {
|
|
||||||
if (!Utils.isValidUrl(address)) {
|
|
||||||
LogUtil.w(AppConfig.TAG, "$address is an invalid ip or domain")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val v2rayConfig = initV2rayConfig(context) ?: return result
|
|
||||||
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
|
||||||
v2rayConfig.remarks = config.remarks
|
|
||||||
|
|
||||||
getInbounds(v2rayConfig)
|
|
||||||
|
|
||||||
getOutbounds(v2rayConfig, config) ?: return result
|
|
||||||
getMoreOutbounds(v2rayConfig, config.subscriptionId)
|
|
||||||
|
|
||||||
getRouting(context, v2rayConfig)
|
|
||||||
|
|
||||||
getFakeDns(v2rayConfig)
|
|
||||||
|
|
||||||
getDns(v2rayConfig)
|
|
||||||
|
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
|
||||||
getCustomLocalDns(v2rayConfig)
|
|
||||||
}
|
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) {
|
|
||||||
v2rayConfig.stats = null
|
|
||||||
v2rayConfig.policy = null
|
|
||||||
}
|
|
||||||
|
|
||||||
//Resolve and add to DNS Hosts
|
|
||||||
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") {
|
|
||||||
resolveOutboundDomainsToHosts(v2rayConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
result.status = true
|
|
||||||
result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
|
|
||||||
result.guid = guid
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getV2rayMultipleConfig(context: Context, config: ProfileItem, configList: List<ProfileItem>): V2rayConfig? {
|
|
||||||
val validConfigs = configList.asSequence().filter { it.server.isNotNullEmpty() }
|
|
||||||
.filter { !Utils.isPureIpAddress(it.server!!) || Utils.isValidUrl(it.server!!) }
|
|
||||||
.filter { it.configType != EConfigType.CUSTOM }
|
|
||||||
.filter { it.configType != EConfigType.POLICYGROUP }
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
if (validConfigs.isEmpty()) {
|
if (validConfigs.isEmpty()) {
|
||||||
LogUtil.w(AppConfig.TAG, "All configs are invalid")
|
LogUtil.w(AppConfig.TAG, "All configs are invalid")
|
||||||
@@ -245,21 +159,18 @@ object CoreConfigManager {
|
|||||||
v2rayConfig.outbounds.removeAt(0)
|
v2rayConfig.outbounds.removeAt(0)
|
||||||
val outboundsList = mutableListOf<V2rayConfig.OutboundBean>()
|
val outboundsList = mutableListOf<V2rayConfig.OutboundBean>()
|
||||||
var index = 0
|
var index = 0
|
||||||
for (config in validConfigs) {
|
for (item in validConfigs) {
|
||||||
index++
|
index++
|
||||||
val outbound = convertProfile2Outbound(config) ?: continue
|
val outbound = convertProfile2Outbound(item) ?: continue
|
||||||
outbound.tag = "proxy-$index"
|
outbound.tag = "proxy-$index-${item.remarks.trim()}"
|
||||||
outboundsList.add(outbound)
|
outboundsList.add(outbound)
|
||||||
}
|
}
|
||||||
outboundsList.addAll(v2rayConfig.outbounds)
|
outboundsList.addAll(v2rayConfig.outbounds)
|
||||||
v2rayConfig.outbounds = ArrayList(outboundsList)
|
v2rayConfig.outbounds = ArrayList(outboundsList)
|
||||||
|
|
||||||
getRouting(context, v2rayConfig)
|
getRouting(context, v2rayConfig)
|
||||||
|
|
||||||
getFakeDns(v2rayConfig)
|
getFakeDns(v2rayConfig)
|
||||||
|
|
||||||
getDns(v2rayConfig)
|
getDns(v2rayConfig)
|
||||||
|
|
||||||
getBalance(v2rayConfig, config)
|
getBalance(v2rayConfig, config)
|
||||||
|
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
|
||||||
@@ -270,7 +181,7 @@ object CoreConfigManager {
|
|||||||
v2rayConfig.policy = null
|
v2rayConfig.policy = null
|
||||||
}
|
}
|
||||||
|
|
||||||
//Resolve and add to DNS Hosts
|
// Resolve and add to DNS Hosts
|
||||||
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") {
|
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") {
|
||||||
resolveOutboundDomainsToHosts(v2rayConfig)
|
resolveOutboundDomainsToHosts(v2rayConfig)
|
||||||
}
|
}
|
||||||
@@ -279,29 +190,111 @@ object CoreConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the normal V2ray configuration for speedtest.
|
* Builds full V2Ray config for proxy-chain mode.
|
||||||
*
|
*
|
||||||
* @param context The context in which the function is called.
|
* Uses resolvedProfiles as an ordered chain and links each hop
|
||||||
* @param guid The unique identifier for the V2ray configuration.
|
* with dialerProxy.
|
||||||
* @param config The profile item containing the configuration details.
|
|
||||||
* @return A ConfigResult object containing the result of the configuration retrieval.
|
|
||||||
*/
|
*/
|
||||||
private fun getV2rayNormalConfig4Speedtest(context: Context, guid: String, config: ProfileItem): ConfigResult {
|
private fun buildProxyChainConfig(configContext: CoreConfigContext): V2rayConfig? {
|
||||||
val result = ConfigResult(false)
|
val context = configContext.context
|
||||||
|
val config = configContext.selectedProfile
|
||||||
|
val resolvedProfiles = configContext.resolvedProfiles
|
||||||
|
|
||||||
val address = config.server ?: return result
|
val address = config.server ?: return null
|
||||||
if (!Utils.isPureIpAddress(address)) {
|
if (!Utils.isPureIpAddress(address) && !Utils.isValidUrl(address)) {
|
||||||
if (!Utils.isValidUrl(address)) {
|
LogUtil.w(AppConfig.TAG, "$address is an invalid ip or domain")
|
||||||
LogUtil.w(AppConfig.TAG, "$address is an invalid ip or domain")
|
return null
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val v2rayConfig = initV2rayConfig(context) ?: return result
|
val v2rayConfig = initV2rayConfig(context) ?: return null
|
||||||
|
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
||||||
|
v2rayConfig.remarks = config.remarks
|
||||||
|
|
||||||
getOutbounds(v2rayConfig, config) ?: return result
|
getInbounds(v2rayConfig)
|
||||||
getMoreOutbounds(v2rayConfig, config.subscriptionId)
|
|
||||||
|
|
||||||
|
// Chain nodes are ordered as: next... -> current -> ...prev.
|
||||||
|
// Build and link the whole chain directly from resolvedProfiles.
|
||||||
|
val chainOutbounds = resolvedProfiles.mapNotNull { profile ->
|
||||||
|
convertProfile2Outbound(profile)
|
||||||
|
}.toMutableList()
|
||||||
|
if (chainOutbounds.size < 2) return null
|
||||||
|
|
||||||
|
chainOutbounds.forEachIndexed { index, outbound ->
|
||||||
|
outbound.tag = if (index == 0) AppConfig.TAG_PROXY else AppConfig.TAG_PROXY + index
|
||||||
|
}
|
||||||
|
for (index in 0 until chainOutbounds.size - 1) {
|
||||||
|
chainOutbounds[index].ensureSockopt().dialerProxy = chainOutbounds[index + 1].tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep built-in outbounds and place the chain before them.
|
||||||
|
val builtinOutbounds = if (v2rayConfig.outbounds.size > 1) {
|
||||||
|
v2rayConfig.outbounds.drop(1)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
v2rayConfig.outbounds = ArrayList(chainOutbounds + builtinOutbounds)
|
||||||
|
|
||||||
|
getRouting(context, v2rayConfig)
|
||||||
|
getFakeDns(v2rayConfig)
|
||||||
|
getDns(v2rayConfig)
|
||||||
|
|
||||||
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
||||||
|
getCustomLocalDns(v2rayConfig)
|
||||||
|
}
|
||||||
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) {
|
||||||
|
v2rayConfig.stats = null
|
||||||
|
v2rayConfig.policy = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve and add to DNS Hosts
|
||||||
|
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") {
|
||||||
|
resolveOutboundDomainsToHosts(v2rayConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v2rayConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds full config for normal single-node mode. */
|
||||||
|
private fun buildNormalConfig(configContext: CoreConfigContext): V2rayConfig? {
|
||||||
|
val context = configContext.context
|
||||||
|
val config = configContext.selectedProfile
|
||||||
|
|
||||||
|
val address = config.server ?: return null
|
||||||
|
if (!Utils.isPureIpAddress(address) && !Utils.isValidUrl(address)) {
|
||||||
|
LogUtil.w(AppConfig.TAG, "$address is an invalid ip or domain")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val v2rayConfig = initV2rayConfig(context) ?: return null
|
||||||
|
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
||||||
|
v2rayConfig.remarks = config.remarks
|
||||||
|
|
||||||
|
getInbounds(v2rayConfig)
|
||||||
|
getOutbounds(v2rayConfig, config) ?: return null
|
||||||
|
getRouting(context, v2rayConfig)
|
||||||
|
getFakeDns(v2rayConfig)
|
||||||
|
getDns(v2rayConfig)
|
||||||
|
|
||||||
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
||||||
|
getCustomLocalDns(v2rayConfig)
|
||||||
|
}
|
||||||
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) {
|
||||||
|
v2rayConfig.stats = null
|
||||||
|
v2rayConfig.policy = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve and add to DNS Hosts
|
||||||
|
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") {
|
||||||
|
resolveOutboundDomainsToHosts(v2rayConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v2rayConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes non-essential sections for speedtest use.
|
||||||
|
*/
|
||||||
|
private fun postProcessForSpeedtest(v2rayConfig: V2rayConfig) {
|
||||||
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
||||||
v2rayConfig.inbounds.clear()
|
v2rayConfig.inbounds.clear()
|
||||||
v2rayConfig.routing.rules.clear()
|
v2rayConfig.routing.rules.clear()
|
||||||
@@ -309,15 +302,16 @@ object CoreConfigManager {
|
|||||||
v2rayConfig.fakedns = null
|
v2rayConfig.fakedns = null
|
||||||
v2rayConfig.stats = null
|
v2rayConfig.stats = null
|
||||||
v2rayConfig.policy = null
|
v2rayConfig.policy = null
|
||||||
|
v2rayConfig.outbounds.forEach { key -> key.mux = null }
|
||||||
|
}
|
||||||
|
|
||||||
v2rayConfig.outbounds.forEach { key ->
|
/** Converts a built config object into a unified result payload. */
|
||||||
key.mux = null
|
private fun toConfigResult(guid: String, v2rayConfig: V2rayConfig): ConfigResult {
|
||||||
}
|
return ConfigResult(
|
||||||
|
status = true,
|
||||||
result.status = true
|
guid = guid,
|
||||||
result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
|
content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
|
||||||
result.guid = guid
|
)
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -801,60 +795,6 @@ object CoreConfigManager {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures additional outbound connections for proxy chaining.
|
|
||||||
*
|
|
||||||
* Sets up previous and next proxies in a subscription for advanced routing capabilities.
|
|
||||||
*
|
|
||||||
* @param v2rayConfig The V2ray configuration object to be modified
|
|
||||||
* @param subscriptionId The subscription ID to look up related proxies
|
|
||||||
* @return true if additional outbounds were configured successfully, false otherwise
|
|
||||||
*/
|
|
||||||
private fun getMoreOutbounds(v2rayConfig: V2rayConfig, subscriptionId: String): Boolean {
|
|
||||||
//fragment proxy
|
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == true) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subscriptionId.isEmpty()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
val subItem = MmkvManager.decodeSubscription(subscriptionId) ?: return false
|
|
||||||
|
|
||||||
//current proxy
|
|
||||||
val outbound = v2rayConfig.outbounds[0]
|
|
||||||
|
|
||||||
//Previous proxy
|
|
||||||
val prevNode = SettingsManager.getServerViaRemarks(subItem.prevProfile)
|
|
||||||
if (prevNode != null) {
|
|
||||||
val prevOutbound = convertProfile2Outbound(prevNode)
|
|
||||||
if (prevOutbound != null) {
|
|
||||||
prevOutbound.tag = AppConfig.TAG_PROXY + "2"
|
|
||||||
v2rayConfig.outbounds.add(prevOutbound)
|
|
||||||
outbound.ensureSockopt().dialerProxy = prevOutbound.tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Next proxy
|
|
||||||
val nextNode = SettingsManager.getServerViaRemarks(subItem.nextProfile)
|
|
||||||
if (nextNode != null) {
|
|
||||||
val nextOutbound = convertProfile2Outbound(nextNode)
|
|
||||||
if (nextOutbound != null) {
|
|
||||||
nextOutbound.tag = AppConfig.TAG_PROXY
|
|
||||||
v2rayConfig.outbounds.add(0, nextOutbound)
|
|
||||||
outbound.tag = AppConfig.TAG_PROXY + "1"
|
|
||||||
nextOutbound.ensureSockopt().dialerProxy = outbound.tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
LogUtil.e(AppConfig.TAG, "Failed to configure more outbounds", e)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures load balancing settings for the V2ray configuration.
|
* Configures load balancing settings for the V2ray configuration.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -17,17 +17,12 @@ import com.v2ray.ang.util.LogUtil
|
|||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Centralises all ProfileItem → OutboundBean conversion logic.
|
* Centralizes ProfileItem -> OutboundBean conversion.
|
||||||
*
|
* Most protocol builders mirror the previous *Fmt.toOutbound behavior.
|
||||||
* keeping every build method implementation identical to the original
|
|
||||||
* toOutbound implementations in each *Fmt class.
|
|
||||||
*/
|
*/
|
||||||
object CoreOutboundBuilder {
|
object CoreOutboundBuilder {
|
||||||
|
|
||||||
/**
|
/** Dispatches a profile to protocol-specific outbound builder. */
|
||||||
* Dispatches [profileItem] to the appropriate protocol builder
|
|
||||||
* and returns the resulting [OutboundBean].
|
|
||||||
*/
|
|
||||||
fun convert(profileItem: ProfileItem): OutboundBean? {
|
fun convert(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outbound = when (profileItem.configType) {
|
val outbound = when (profileItem.configType) {
|
||||||
EConfigType.VMESS -> toOutboundVmess(profileItem)
|
EConfigType.VMESS -> toOutboundVmess(profileItem)
|
||||||
@@ -49,11 +44,7 @@ object CoreOutboundBuilder {
|
|||||||
return outbound
|
return outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Applies global outbound options (mux, protocol-specific tweaks, etc.). */
|
||||||
* Updates outbound settings based on global preferences.
|
|
||||||
*
|
|
||||||
* Applies multiplexing and protocol-specific settings to an outbound connection.
|
|
||||||
*/
|
|
||||||
private fun updateOutboundWithGlobalSettings(outbound: OutboundBean): Boolean {
|
private fun updateOutboundWithGlobalSettings(outbound: OutboundBean): Boolean {
|
||||||
try {
|
try {
|
||||||
var muxEnabled = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)
|
var muxEnabled = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)
|
||||||
@@ -126,14 +117,7 @@ object CoreOutboundBuilder {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Creates an initial outbound template for a protocol type. */
|
||||||
* Creates an initial outbound configuration for a specific protocol type.
|
|
||||||
*
|
|
||||||
* Provides a template configuration for different protocol types.
|
|
||||||
*
|
|
||||||
* @param configType The type of configuration to create
|
|
||||||
* @return An initial OutboundBean for the specified configuration type, or null for custom types
|
|
||||||
*/
|
|
||||||
fun createInitOutbound(configType: EConfigType): OutboundBean? {
|
fun createInitOutbound(configType: EConfigType): OutboundBean? {
|
||||||
return when (configType) {
|
return when (configType) {
|
||||||
EConfigType.VMESS,
|
EConfigType.VMESS,
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.v2ray.ang.enums.CoreResolvedType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime context produced by the builder and consumed by CoreConfigManager.
|
||||||
|
*/
|
||||||
|
data class CoreConfigContext(
|
||||||
|
val context: Context,
|
||||||
|
val guid: String,
|
||||||
|
val selectedProfile: ProfileItem,
|
||||||
|
val resolvedProfiles: List<ProfileItem>,
|
||||||
|
val resolvedType: CoreResolvedType,
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.v2ray.ang.enums
|
||||||
|
|
||||||
|
/** Runtime type used during config assembly only. */
|
||||||
|
enum class CoreResolvedType {
|
||||||
|
NORMAL,
|
||||||
|
POLICYGROUP,
|
||||||
|
PROXYCHAIN,
|
||||||
|
CUSTOM,
|
||||||
|
}
|
||||||
Reference in new issue
Block a user