Repository: 2dust/v2rayN Branch: master Commit: eb0ef90ed254 Files: 357 Total size: 2.7 MB Directory structure: gitextract_8wam50gz/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── 01_bug_report.yml │ │ ├── 02_feature_request.yml │ │ └── config.yml │ ├── dependabot.yml │ └── workflows/ │ ├── build-all.yml │ ├── build-linux.yml │ ├── build-osx.yml │ ├── build-windows-desktop.yml │ ├── build-windows.yml │ └── winget-publish.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── package-debian.sh ├── package-osx.sh ├── package-release-zip.sh ├── package-rhel.sh └── v2rayN/ ├── AmazTool/ │ ├── AmazTool.csproj │ ├── Program.cs │ ├── Resx/ │ │ ├── Resource.Designer.cs │ │ ├── Resource.resx │ │ ├── Resource.zh-Hans.resx │ │ └── Resource.zh-Hant.resx │ ├── UpgradeApp.cs │ └── Utils.cs ├── Directory.Build.props ├── Directory.Packages.props ├── ServiceLib/ │ ├── Base/ │ │ └── MyReactiveObject.cs │ ├── Common/ │ │ ├── EmbedUtils.cs │ │ ├── Extension.cs │ │ ├── FileUtils.cs │ │ ├── JsonUtils.cs │ │ ├── Logging.cs │ │ ├── ProcUtils.cs │ │ ├── QRCodeUtils.cs │ │ ├── Utils.cs │ │ ├── WindowsUtils.cs │ │ └── YamlUtils.cs │ ├── Enums/ │ │ ├── EConfigType.cs │ │ ├── ECoreType.cs │ │ ├── EGirdOrientation.cs │ │ ├── EGlobalHotkey.cs │ │ ├── EInboundProtocol.cs │ │ ├── EMove.cs │ │ ├── EMultipleLoad.cs │ │ ├── EPresetType.cs │ │ ├── ERuleMode.cs │ │ ├── ERuleType.cs │ │ ├── EServerColName.cs │ │ ├── ESpeedActionType.cs │ │ ├── ESysProxyType.cs │ │ ├── ETheme.cs │ │ ├── ETransport.cs │ │ └── EViewAction.cs │ ├── Events/ │ │ ├── AppEvents.cs │ │ └── EventChannel.cs │ ├── FodyWeavers.xml │ ├── Global.cs │ ├── GlobalUsings.cs │ ├── Handler/ │ │ ├── AutoStartupHandler.cs │ │ ├── Builder/ │ │ │ ├── CoreConfigContextBuilder.cs │ │ │ └── NodeValidator.cs │ │ ├── ConfigHandler.cs │ │ ├── ConnectionHandler.cs │ │ ├── CoreConfigHandler.cs │ │ ├── Fmt/ │ │ │ ├── AnytlsFmt.cs │ │ │ ├── BaseFmt.cs │ │ │ ├── ClashFmt.cs │ │ │ ├── FmtHandler.cs │ │ │ ├── HtmlPageFmt.cs │ │ │ ├── Hysteria2Fmt.cs │ │ │ ├── ShadowsocksFmt.cs │ │ │ ├── SingboxFmt.cs │ │ │ ├── SocksFmt.cs │ │ │ ├── TrojanFmt.cs │ │ │ ├── TuicFmt.cs │ │ │ ├── V2rayFmt.cs │ │ │ ├── VLESSFmt.cs │ │ │ ├── VmessFmt.cs │ │ │ └── WireguardFmt.cs │ │ ├── SubscriptionHandler.cs │ │ └── SysProxy/ │ │ ├── ProxySettingLinux.cs │ │ ├── ProxySettingOSX.cs │ │ ├── ProxySettingWindows.cs │ │ └── SysProxyHandler.cs │ ├── Helper/ │ │ ├── DownloaderHelper.cs │ │ ├── HttpClientHelper.cs │ │ └── SqliteHelper.cs │ ├── Manager/ │ │ ├── AppManager.cs │ │ ├── CertPemManager.cs │ │ ├── ClashApiManager.cs │ │ ├── CoreAdminManager.cs │ │ ├── CoreInfoManager.cs │ │ ├── CoreManager.cs │ │ ├── GroupProfileManager.cs │ │ ├── NoticeManager.cs │ │ ├── PacManager.cs │ │ ├── ProfileExManager.cs │ │ ├── StatisticsManager.cs │ │ ├── TaskManager.cs │ │ └── WebDavManager.cs │ ├── Models/ │ │ ├── CheckUpdateModel.cs │ │ ├── ClashConnectionModel.cs │ │ ├── ClashConnections.cs │ │ ├── ClashProviders.cs │ │ ├── ClashProxies.cs │ │ ├── ClashProxyModel.cs │ │ ├── CmdItem.cs │ │ ├── ComboItem.cs │ │ ├── Config.cs │ │ ├── ConfigItems.cs │ │ ├── CoreConfigContext.cs │ │ ├── CoreInfo.cs │ │ ├── DNSItem.cs │ │ ├── FullConfigTemplateItem.cs │ │ ├── GitHubRelease.cs │ │ ├── IPAPIInfo.cs │ │ ├── ProfileExItem.cs │ │ ├── ProfileGroupItem.cs │ │ ├── ProfileItem.cs │ │ ├── ProfileItemModel.cs │ │ ├── ProtocolExtraItem.cs │ │ ├── RetResult.cs │ │ ├── RoutingItem.cs │ │ ├── RoutingItemModel.cs │ │ ├── RoutingTemplate.cs │ │ ├── RulesItem.cs │ │ ├── RulesItemModel.cs │ │ ├── SemanticVersion.cs │ │ ├── ServerSpeedItem.cs │ │ ├── ServerStatItem.cs │ │ ├── ServerTestItem.cs │ │ ├── SingboxConfig.cs │ │ ├── SpeedTestResult.cs │ │ ├── SsSIP008.cs │ │ ├── SubItem.cs │ │ ├── UpdateResult.cs │ │ ├── V2rayConfig.cs │ │ ├── V2rayMetricsVars.cs │ │ ├── V2rayTcpRequest.cs │ │ └── VmessQRCode.cs │ ├── Resx/ │ │ ├── ResUI.Designer.cs │ │ ├── ResUI.fa-Ir.resx │ │ ├── ResUI.fr.resx │ │ ├── ResUI.hu.resx │ │ ├── ResUI.resx │ │ ├── ResUI.ru.resx │ │ ├── ResUI.zh-Hans.resx │ │ └── ResUI.zh-Hant.resx │ ├── Sample/ │ │ ├── SampleClientConfig │ │ ├── SampleHttpRequest │ │ ├── SampleHttpResponse │ │ ├── SampleInbound │ │ ├── SampleOutbound │ │ ├── SingboxSampleClientConfig │ │ ├── SingboxSampleOutbound │ │ ├── clash_mixin_yaml │ │ ├── clash_tun_yaml │ │ ├── custom_routing_black │ │ ├── custom_routing_global │ │ ├── custom_routing_white │ │ ├── dns_singbox_normal │ │ ├── dns_v2ray_normal │ │ ├── kill_as_sudo_linux_sh │ │ ├── kill_as_sudo_osx_sh │ │ ├── linux_autostart_config │ │ ├── pac │ │ ├── proxy_set_linux_sh │ │ ├── proxy_set_osx_sh │ │ ├── singbox_fakeip_filter │ │ ├── tun_singbox_dns │ │ ├── tun_singbox_inbound │ │ └── tun_singbox_rules │ ├── ServiceLib.csproj │ ├── Services/ │ │ ├── CoreConfig/ │ │ │ ├── CoreConfigClashService.cs │ │ │ ├── Singbox/ │ │ │ │ ├── CoreConfigSingboxService.cs │ │ │ │ ├── SingboxConfigTemplateService.cs │ │ │ │ ├── SingboxDnsService.cs │ │ │ │ ├── SingboxInboundService.cs │ │ │ │ ├── SingboxLogService.cs │ │ │ │ ├── SingboxOutboundService.cs │ │ │ │ ├── SingboxRoutingService.cs │ │ │ │ ├── SingboxRulesetService.cs │ │ │ │ └── SingboxStatisticService.cs │ │ │ └── V2ray/ │ │ │ ├── CoreConfigV2rayService.cs │ │ │ ├── V2rayBalancerService.cs │ │ │ ├── V2rayConfigTemplateService.cs │ │ │ ├── V2rayDnsService.cs │ │ │ ├── V2rayInboundService.cs │ │ │ ├── V2rayLogService.cs │ │ │ ├── V2rayOutboundService.cs │ │ │ ├── V2rayRoutingService.cs │ │ │ └── V2rayStatisticService.cs │ │ ├── DownloadService.cs │ │ ├── ProcessService.cs │ │ ├── SpeedtestService.cs │ │ ├── Statistics/ │ │ │ ├── StatisticsSingboxService.cs │ │ │ └── StatisticsXrayService.cs │ │ ├── UpdateService.cs │ │ └── WindowsJobService.cs │ └── ViewModels/ │ ├── AddGroupServerViewModel.cs │ ├── AddServer2ViewModel.cs │ ├── AddServerViewModel.cs │ ├── BackupAndRestoreViewModel.cs │ ├── CheckUpdateViewModel.cs │ ├── ClashConnectionsViewModel.cs │ ├── ClashProxiesViewModel.cs │ ├── DNSSettingViewModel.cs │ ├── FullConfigTemplateViewModel.cs │ ├── GlobalHotkeySettingViewModel.cs │ ├── MainWindowViewModel.cs │ ├── MsgViewModel.cs │ ├── OptionSettingViewModel.cs │ ├── ProfilesSelectViewModel.cs │ ├── ProfilesViewModel.cs │ ├── RoutingRuleDetailsViewModel.cs │ ├── RoutingRuleSettingViewModel.cs │ ├── RoutingSettingViewModel.cs │ ├── StatusBarViewModel.cs │ ├── SubEditViewModel.cs │ └── SubSettingViewModel.cs ├── v2rayN/ │ ├── App.xaml │ ├── App.xaml.cs │ ├── AssemblyInfo.cs │ ├── Base/ │ │ ├── MyDGTextColumn.cs │ │ └── WindowBase.cs │ ├── Common/ │ │ ├── QRCodeWindowsUtils.cs │ │ ├── UI.cs │ │ └── WindowsUtils.cs │ ├── Converters/ │ │ ├── DelayColorConverter.cs │ │ ├── InverseBooleanConverter.cs │ │ └── MaterialDesignFonts.cs │ ├── FodyWeavers.xml │ ├── GlobalUsings.cs │ ├── Manager/ │ │ ├── HotkeyManager.cs │ │ └── WindowsManager.cs │ ├── Properties/ │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── ViewModels/ │ │ └── ThemeSettingViewModel.cs │ ├── Views/ │ │ ├── AddGroupServerWindow.xaml │ │ ├── AddGroupServerWindow.xaml.cs │ │ ├── AddServer2Window.xaml │ │ ├── AddServer2Window.xaml.cs │ │ ├── AddServerWindow.xaml │ │ ├── AddServerWindow.xaml.cs │ │ ├── BackupAndRestoreView.xaml │ │ ├── BackupAndRestoreView.xaml.cs │ │ ├── CheckUpdateView.xaml │ │ ├── CheckUpdateView.xaml.cs │ │ ├── ClashConnectionsView.xaml │ │ ├── ClashConnectionsView.xaml.cs │ │ ├── ClashProxiesView.xaml │ │ ├── ClashProxiesView.xaml.cs │ │ ├── DNSSettingWindow.xaml │ │ ├── DNSSettingWindow.xaml.cs │ │ ├── FullConfigTemplateWindow.xaml │ │ ├── FullConfigTemplateWindow.xaml.cs │ │ ├── GlobalHotkeySettingWindow.xaml │ │ ├── GlobalHotkeySettingWindow.xaml.cs │ │ ├── MainWindow.xaml │ │ ├── MainWindow.xaml.cs │ │ ├── MsgView.xaml │ │ ├── MsgView.xaml.cs │ │ ├── OptionSettingWindow.xaml │ │ ├── OptionSettingWindow.xaml.cs │ │ ├── ProfilesSelectWindow.xaml │ │ ├── ProfilesSelectWindow.xaml.cs │ │ ├── ProfilesView.xaml │ │ ├── ProfilesView.xaml.cs │ │ ├── QrcodeView.xaml │ │ ├── QrcodeView.xaml.cs │ │ ├── RoutingRuleDetailsWindow.xaml │ │ ├── RoutingRuleDetailsWindow.xaml.cs │ │ ├── RoutingRuleSettingWindow.xaml │ │ ├── RoutingRuleSettingWindow.xaml.cs │ │ ├── RoutingSettingWindow.xaml │ │ ├── RoutingSettingWindow.xaml.cs │ │ ├── StatusBarView.xaml │ │ ├── StatusBarView.xaml.cs │ │ ├── SubEditWindow.xaml │ │ ├── SubEditWindow.xaml.cs │ │ ├── SubSettingWindow.xaml │ │ ├── SubSettingWindow.xaml.cs │ │ ├── ThemeSettingView.xaml │ │ └── ThemeSettingView.xaml.cs │ ├── app.manifest │ └── v2rayN.csproj ├── v2rayN.Desktop/ │ ├── App.axaml │ ├── App.axaml.cs │ ├── Assets/ │ │ ├── GlobalResources.axaml │ │ └── GlobalStyles.axaml │ ├── Base/ │ │ └── WindowBase.cs │ ├── Common/ │ │ ├── AppBuilderExtension.cs │ │ ├── AvaUtils.cs │ │ ├── TextEditorKeywordHighlighter.cs │ │ └── UI.cs │ ├── Converters/ │ │ └── DelayColorConverter.cs │ ├── FodyWeavers.xml │ ├── GlobalUsings.cs │ ├── Manager/ │ │ └── HotkeyManager.cs │ ├── Program.cs │ ├── ViewModels/ │ │ └── ThemeSettingViewModel.cs │ ├── Views/ │ │ ├── AddGroupServerWindow.axaml │ │ ├── AddGroupServerWindow.axaml.cs │ │ ├── AddServer2Window.axaml │ │ ├── AddServer2Window.axaml.cs │ │ ├── AddServerWindow.axaml │ │ ├── AddServerWindow.axaml.cs │ │ ├── BackupAndRestoreView.axaml │ │ ├── BackupAndRestoreView.axaml.cs │ │ ├── CheckUpdateView.axaml │ │ ├── CheckUpdateView.axaml.cs │ │ ├── ClashConnectionsView.axaml │ │ ├── ClashConnectionsView.axaml.cs │ │ ├── ClashProxiesView.axaml │ │ ├── ClashProxiesView.axaml.cs │ │ ├── DNSSettingWindow.axaml │ │ ├── DNSSettingWindow.axaml.cs │ │ ├── FullConfigTemplateWindow.axaml │ │ ├── FullConfigTemplateWindow.axaml.cs │ │ ├── GlobalHotkeySettingWindow.axaml │ │ ├── GlobalHotkeySettingWindow.axaml.cs │ │ ├── MainWindow.axaml │ │ ├── MainWindow.axaml.cs │ │ ├── MsgView.axaml │ │ ├── MsgView.axaml.cs │ │ ├── OptionSettingWindow.axaml │ │ ├── OptionSettingWindow.axaml.cs │ │ ├── ProfilesSelectWindow.axaml │ │ ├── ProfilesSelectWindow.axaml.cs │ │ ├── ProfilesView.axaml │ │ ├── ProfilesView.axaml.cs │ │ ├── QrcodeView.axaml │ │ ├── QrcodeView.axaml.cs │ │ ├── RoutingRuleDetailsWindow.axaml │ │ ├── RoutingRuleDetailsWindow.axaml.cs │ │ ├── RoutingRuleSettingWindow.axaml │ │ ├── RoutingRuleSettingWindow.axaml.cs │ │ ├── RoutingSettingWindow.axaml │ │ ├── RoutingSettingWindow.axaml.cs │ │ ├── StatusBarView.axaml │ │ ├── StatusBarView.axaml.cs │ │ ├── SubEditWindow.axaml │ │ ├── SubEditWindow.axaml.cs │ │ ├── SubSettingWindow.axaml │ │ ├── SubSettingWindow.axaml.cs │ │ ├── SudoPasswordInputView.axaml │ │ ├── SudoPasswordInputView.axaml.cs │ │ ├── ThemeSettingView.axaml │ │ └── ThemeSettingView.axaml.cs │ ├── v2rayN.Desktop.csproj │ └── v2rayN.icns ├── v2rayN.sln └── v2rayN.slnx ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 indent_style = space tab_width = 4 indent_size = 4 end_of_line = crlf trim_trailing_whitespace = true insert_final_newline = true [*.{yml,yaml}] indent_style = space indent_size = 2 [*.cs] dotnet_hide_advanced_members = true dotnet_member_insertion_location = with_other_members_of_the_same_kind dotnet_property_generation_behavior = prefer_throwing_properties dotnet_search_reference_assemblies = true dotnet_separate_import_directive_groups = false:warning dotnet_sort_system_directives_first = true:warning file_header_template = unset dotnet_style_qualification_for_event = false:warning dotnet_style_qualification_for_field = false:warning dotnet_style_qualification_for_method = false:warning dotnet_style_qualification_for_property = false:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_operators = always_for_clarity:warning dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning dotnet_style_require_accessibility_modifiers = always:warning dotnet_prefer_system_hash_code = true:warning dotnet_style_coalesce_expression = true:warning dotnet_style_collection_initializer = false:warning dotnet_style_explicit_tuple_names = true:warning dotnet_style_namespace_match_folder = true:warning dotnet_style_null_propagation = true:warning dotnet_style_object_initializer = true:warning dotnet_style_operator_placement_when_wrapping = beginning_of_line dotnet_style_prefer_auto_properties = true:warning dotnet_style_prefer_collection_expression = false:warning dotnet_style_prefer_compound_assignment = true:warning dotnet_style_prefer_conditional_expression_over_assignment = false:warning dotnet_style_prefer_conditional_expression_over_return = false:warning dotnet_style_prefer_foreach_explicit_cast_in_source = always:warning dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning dotnet_style_prefer_inferred_tuple_names = true:warning dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning dotnet_style_prefer_simplified_boolean_expressions = true:warning dotnet_style_prefer_simplified_interpolation = true:warning dotnet_style_readonly_field = true:warning dotnet_code_quality_unused_parameters = all:warning dotnet_remove_unnecessary_suppression_exclusions = none dotnet_style_allow_multiple_blank_lines_experimental = false:warning dotnet_style_allow_statement_immediately_after_block_experimental = true:warning csharp_style_var_elsewhere = true:warning csharp_style_var_for_built_in_types = true:warning csharp_style_var_when_type_is_apparent = true:warning csharp_style_expression_bodied_accessors = when_on_single_line:warning csharp_style_expression_bodied_constructors = false:warning csharp_style_expression_bodied_indexers = when_on_single_line:warning csharp_style_expression_bodied_lambdas = when_on_single_line:warning csharp_style_expression_bodied_local_functions = false:warning csharp_style_expression_bodied_methods = false:warning csharp_style_expression_bodied_operators = false:warning csharp_style_expression_bodied_properties = when_on_single_line:warning csharp_style_pattern_matching_over_as_with_null_check = true:warning csharp_style_pattern_matching_over_is_with_cast_check = true:warning csharp_style_prefer_extended_property_pattern = true:warning csharp_style_prefer_not_pattern = true:warning csharp_style_prefer_pattern_matching = true:warning csharp_style_prefer_switch_expression = false:warning csharp_style_conditional_delegate_call = true:warning csharp_prefer_static_anonymous_function = true:warning csharp_prefer_static_local_function = true:warning csharp_preferred_modifier_order = public,internal,private,protected,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:warning csharp_style_prefer_readonly_struct = true:warning csharp_style_prefer_readonly_struct_member = true:warning csharp_prefer_braces = true:warning csharp_prefer_simple_using_statement = true:warning csharp_prefer_system_threading_lock = true:warning csharp_style_namespace_declarations = file_scoped:warning csharp_style_prefer_method_group_conversion = true:warning csharp_style_prefer_primary_constructors = true:warning csharp_style_prefer_top_level_statements = false:warning csharp_prefer_simple_default_expression = true:warning csharp_style_deconstructed_variable_declaration = true:warning csharp_style_implicit_object_creation_when_type_is_apparent = true:warning csharp_style_inlined_variable_declaration = true:warning csharp_style_prefer_index_operator = false:warning csharp_style_prefer_local_over_anonymous_function = true:warning csharp_style_prefer_null_check_over_type_check = true:warning csharp_style_prefer_range_operator = false:warning csharp_style_prefer_tuple_swap = true:warning csharp_style_prefer_utf8_string_literals = true:warning csharp_style_throw_expression = true:warning csharp_style_unused_value_assignment_preference = discard_variable:warning csharp_style_unused_value_expression_statement_preference = discard_variable:warning csharp_using_directive_placement = outside_namespace:warning csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:warning csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:warning csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false:warning csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning csharp_new_line_before_catch = true csharp_new_line_before_else = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_open_brace = all csharp_new_line_between_query_expression_clauses = true csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_case_contents_when_block = true csharp_indent_labels = no_change csharp_indent_switch_labels = true csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_comma = true csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after csharp_space_around_declaration_statements = false:warning csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = false dotnet_naming_rule.interface_should_be_pascal.severity = warning dotnet_naming_rule.interface_should_be_pascal.symbols = interface dotnet_naming_rule.interface_should_be_pascal.style = pascal dotnet_naming_rule.types_should_be_pascal.severity = warning dotnet_naming_rule.types_should_be_pascal.symbols = types dotnet_naming_rule.types_should_be_pascal.style = pascal dotnet_naming_rule.non_field_members_should_be_pascal.severity = warning dotnet_naming_rule.non_field_members_should_be_pascal.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascal.style = pascal dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = * dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = * dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = * dotnet_naming_symbols.non_field_members.required_modifiers = dotnet_naming_style.pascal.required_prefix = dotnet_naming_style.pascal.required_suffix = dotnet_naming_style.pascal.word_separator = dotnet_naming_style.pascal.capitalization = pascal_case ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .github/ISSUE_TEMPLATE/01_bug_report.yml ================================================ name: Bug 报告 description: 在提出问题前请先自行排除服务器端问题和升级到最新客户端,同时也请通过搜索确认是否有人提出过相同问题。 title: "[Bug]: " labels: ["bug"] body: - type: markdown attributes: value: | ### 报告 Bug 前请务必确认以下事项: > ** ** > **✓ 已自行排除服务器端问题。** > **✓ 已升级到最新客户端版本。** > **✓ 已通过搜索确认没有人提出过相同问题。** > **✓ 已确认自己的电脑系统环境是受支持的。** --- - type: input id: "os-version" attributes: label: "操作系统和版本" description: "操作系统和版本" validations: required: true - type: input id: "expectation" attributes: label: "预期情况" description: "描述你认为应该发生什么" validations: required: true - type: textarea id: "describe-the-bug" attributes: label: "实际情况" description: "描述实际发生了什么" validations: required: true - type: textarea id: "reproduction-method" attributes: label: "复现方法" description: "在BUG出现前执行了哪些操作" placeholder: "标序号" validations: required: true - type: textarea id: "gui-log" attributes: label: "软件日志" description: "位置在软件当前目录下的guiLogs" placeholder: "在日志开始和结束位置粘贴冒号后的内容到这:" validations: required: true - type: textarea id: "core-log" attributes: label: "内核日志" description: "位置在软件主界面的信息框内" placeholder: "在信息框内鼠标右键复制全部信息粘贴在这:" validations: required: true - type: textarea id: "more" attributes: label: "额外信息" description: "可选" validations: required: false - type: checkboxes id: "latest-version" attributes: label: "我确认已更新至最新版本" description: "否则请更新后尝试" options: - label: 是 required: true - type: checkboxes id: "issues" attributes: label: "我确认已查询历史issues" description: "否则请查询后提出" options: - label: 是 required: true - type: checkboxes id: "system-version" attributes: label: "我确认系统版本是受支持的" description: "否则请切换后尝试" options: - label: 是 required: true ================================================ FILE: .github/ISSUE_TEMPLATE/02_feature_request.yml ================================================ name: Feature 请求 description: "为这个项目提出一个建议" title: "[Feature request]: " labels: ['enhancement'] body: - type: input id: problem attributes: label: 相关问题 description: "清楚而简洁地描述问题是什么。" placeholder: "当我想要……时,软件不能……" validations: required: true - type: input id: way-to-solve attributes: label: 描述你希望的解决方案 description: "你希望发生什么" validations: required: true - type: input id: instead attributes: label: 描述你所考虑的替代方案 validations: required: false - type: checkboxes id: "issues" attributes: label: "我确认已查询历史issues" description: "否则请查询后提出" options: - label: 是 required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Discussions / 讨论区 url: https://github.com/2dust/v2rayN/discussions about: 使用问题或需要帮助请前往 Discussions。 - name: Wiki / 使用说明 url: https://github.com/2dust/v2rayN/wiki about: 查看常见问题和使用文档。 ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" - package-ecosystem: "nuget" directory: "/" schedule: interval: "daily" ================================================ FILE: .github/workflows/build-all.yml ================================================ name: release all platforms on: workflow_dispatch: inputs: release_tag: required: false type: string jobs: update: runs-on: ubuntu-latest steps: - name: Trigger build windows if: github.event.inputs.release_tag != '' run: | curl -X POST \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ https://api.github.com/repos/${{ github.repository }}/actions/workflows/build-windows.yml/dispatches \ -d "{ \"ref\": \"master\", \"inputs\": { \"release_tag\": \"${{ github.event.inputs.release_tag }}\" } }" - name: Trigger build linux if: github.event.inputs.release_tag != '' run: | curl -X POST \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ https://api.github.com/repos/${{ github.repository }}/actions/workflows/build-linux.yml/dispatches \ -d "{ \"ref\": \"master\", \"inputs\": { \"release_tag\": \"${{ github.event.inputs.release_tag }}\" } }" - name: Trigger build osx if: github.event.inputs.release_tag != '' run: | curl -X POST \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ https://api.github.com/repos/${{ github.repository }}/actions/workflows/build-osx.yml/dispatches \ -d "{ \"ref\": \"master\", \"inputs\": { \"release_tag\": \"${{ github.event.inputs.release_tag }}\" } }" - name: Trigger build windows desktop if: github.event.inputs.release_tag != '' run: | curl -X POST \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ https://api.github.com/repos/${{ github.repository }}/actions/workflows/build-windows-desktop.yml/dispatches \ -d "{ \"ref\": \"master\", \"inputs\": { \"release_tag\": \"${{ github.event.inputs.release_tag }}\" } }" ================================================ FILE: .github/workflows/build-linux.yml ================================================ name: release Linux on: workflow_dispatch: inputs: release_tag: required: false type: string push: branches: - master tags: - 'v*' - 'V*' permissions: contents: write env: OutputArch: "linux-64" OutputArchArm: "linux-arm64" OutputPath64: "${{ github.workspace }}/v2rayN/Release/linux-64" OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/linux-arm64" jobs: build: strategy: matrix: configuration: [Release] runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v6.0.2 with: submodules: 'recursive' fetch-depth: '0' - name: Setup .NET uses: actions/setup-dotnet@v5.2.0 with: dotnet-version: '8.0.x' - name: Build run: | cd v2rayN dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-x64 -p:SelfContained=true -o "$OutputPath64" dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-arm64 -p:SelfContained=true -o "$OutputPathArm64" dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-x64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPath64" dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64" - name: Upload build artifacts uses: actions/upload-artifact@v7.0.0 with: name: v2rayN-linux path: | ${{ github.workspace }}/v2rayN/Release/linux* # release debian package - name: Package debian if: github.event.inputs.release_tag != '' run: | chmod 755 package-debian.sh ./package-debian.sh "$OutputArch" "$OutputPath64" "${{ github.event.inputs.release_tag }}" ./package-debian.sh "$OutputArchArm" "$OutputPathArm64" "${{ github.event.inputs.release_tag }}" - name: Upload deb to release uses: svenstaro/upload-release-action@v2 if: github.event.inputs.release_tag != '' with: file: ${{ github.workspace }}/v2rayN*.deb tag: ${{ github.event.inputs.release_tag }} file_glob: true prerelease: true # release zip archive - name: Package release zip archive if: github.event.inputs.release_tag != '' run: | chmod 755 package-release-zip.sh ./package-release-zip.sh "$OutputArch" "$OutputPath64" ./package-release-zip.sh "$OutputArchArm" "$OutputPathArm64" - name: Upload zip archive to release uses: svenstaro/upload-release-action@v2 if: github.event.inputs.release_tag != '' with: file: ${{ github.workspace }}/v2rayN*.zip tag: ${{ github.event.inputs.release_tag }} file_glob: true prerelease: true rpm: needs: build if: | (github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag != '') || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) runs-on: ubuntu-24.04 container: image: registry.access.redhat.com/ubi10/ubi env: RELEASE_TAG: ${{ github.event.inputs.release_tag != '' && github.event.inputs.release_tag || github.ref_name }} steps: - name: Prepare tools (Red Hat) shell: bash run: | set -euo pipefail . /etc/os-release EL_MAJOR="${VERSION_ID%%.*}" echo "EL_MAJOR=${EL_MAJOR}" dnf -y makecache || true command -v curl >/dev/null || dnf -y install curl ca-certificates ARCH="$(uname -m)" case "$ARCH" in x86_64|aarch64) ;; *) echo "Unsupported arch: $ARCH"; exit 1 ;; esac install_epel_from_dir() { local base="$1" rpm echo "Try: $base" rpm="$( { curl -fsSL "$base/Packages/" 2>/dev/null curl -fsSL "$base/Packages/e/" 2>/dev/null | sed 's|href="|href="e/|' } | sed -n 's/.*href="\([^"]*epel-release-[^"]*\.noarch\.rpm\)".*/\1/p' | sort -V | tail -n1 )" || true if [[ -n "$rpm" ]]; then dnf -y install "$base/Packages/$rpm" return 0 fi return 1 } FEDORA="https://dl.fedoraproject.org/pub/epel/epel-release-latest-${EL_MAJOR}.noarch.rpm" echo "Try Fedora: $FEDORA" if curl -fsSLI "$FEDORA" >/dev/null; then dnf -y install "$FEDORA" else ROCKY="https://dl.rockylinux.org/pub/rocky/${EL_MAJOR}/extras/${ARCH}/os" if install_epel_from_dir "$ROCKY"; then : else ALMA="https://repo.almalinux.org/almalinux/${EL_MAJOR}/extras/${ARCH}/os" if install_epel_from_dir "$ALMA"; then : else echo "EPEL bootstrap failed (Fedora/Rocky/Alma)" exit 1 fi fi fi dnf -y install sudo git rpm-build rpmdevtools dnf-plugins-core \ rsync findutils tar gzip unzip which dnf repolist | grep -i epel || true - name: Checkout repo (for scripts) uses: actions/checkout@v6.0.2 with: submodules: 'recursive' fetch-depth: '0' - name: Restore build artifacts uses: actions/download-artifact@v8 with: name: v2rayN-linux path: ${{ github.workspace }}/v2rayN/Release - name: Ensure script permissions run: chmod 755 package-rhel.sh - name: Package RPM (RHEL-family) run: ./package-rhel.sh "${RELEASE_TAG}" --arch all - name: Collect RPMs into workspace run: | mkdir -p "$GITHUB_WORKSPACE/dist/rpm" rsync -av "$HOME/rpmbuild/RPMS/" "$GITHUB_WORKSPACE/dist/rpm/" || true find "$GITHUB_WORKSPACE/dist/rpm" -name "v2rayN-*-1*.x86_64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm/v2rayN-linux-rhel-64.rpm" \; || true find "$GITHUB_WORKSPACE/dist/rpm" -name "v2rayN-*-1*.aarch64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true echo "==== Dist tree ====" ls -R "$GITHUB_WORKSPACE/dist/rpm" || true - name: Upload RPM artifacts uses: actions/upload-artifact@v7.0.0 with: name: v2rayN-rpm path: dist/rpm/**/*.rpm - name: Upload RPMs to release uses: svenstaro/upload-release-action@v2 with: file: dist/rpm/**/*.rpm tag: ${{ env.RELEASE_TAG }} file_glob: true prerelease: true ================================================ FILE: .github/workflows/build-osx.yml ================================================ name: release macOS on: workflow_dispatch: inputs: release_tag: required: false type: string push: branches: - master env: OutputArch: "macos-64" OutputArchArm: "macos-arm64" OutputPath64: "${{ github.workspace }}/v2rayN/Release/macos-64" OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/macos-arm64" jobs: build: strategy: matrix: configuration: [Release] runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v6.0.2 with: submodules: 'recursive' fetch-depth: '0' - name: Setup uses: actions/setup-dotnet@v5.2.0 with: dotnet-version: '8.0.x' - name: Build run: | cd v2rayN dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-x64 -p:SelfContained=true -o $OutputPath64 dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-arm64 -p:SelfContained=true -o $OutputPathArm64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-x64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPath64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64 - name: Upload build artifacts uses: actions/upload-artifact@v7.0.0 with: name: v2rayN-macos path: | ${{ github.workspace }}/v2rayN/Release/macos* # release osx package - name: Package osx if: github.event.inputs.release_tag != '' run: | brew install create-dmg chmod 755 package-osx.sh ./package-osx.sh $OutputArch $OutputPath64 ${{ github.event.inputs.release_tag }} ./package-osx.sh $OutputArchArm $OutputPathArm64 ${{ github.event.inputs.release_tag }} - name: Upload dmg to release uses: svenstaro/upload-release-action@v2 if: github.event.inputs.release_tag != '' with: file: ${{ github.workspace }}/v2rayN*.dmg tag: ${{ github.event.inputs.release_tag }} file_glob: true prerelease: true # release zip archive - name: Package release zip archive if: github.event.inputs.release_tag != '' run: | chmod 755 package-release-zip.sh ./package-release-zip.sh $OutputArch $OutputPath64 ./package-release-zip.sh $OutputArchArm $OutputPathArm64 - name: Upload zip archive to release uses: svenstaro/upload-release-action@v2 if: github.event.inputs.release_tag != '' with: file: ${{ github.workspace }}/v2rayN*.zip tag: ${{ github.event.inputs.release_tag }} file_glob: true prerelease: true ================================================ FILE: .github/workflows/build-windows-desktop.yml ================================================ name: release Windows desktop (Avalonia UI) on: workflow_dispatch: inputs: release_tag: required: false type: string push: branches: - master env: OutputArch: "windows-64" OutputArchArm: "windows-arm64" OutputPath64: "${{ github.workspace }}/v2rayN/Release/windows-64" OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/windows-arm64" jobs: build: strategy: matrix: configuration: [Release] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6.0.2 with: submodules: 'recursive' fetch-depth: '0' - name: Setup uses: actions/setup-dotnet@v5.2.0 with: dotnet-version: '8.0.x' - name: Build run: | cd v2rayN dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64 dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64 - name: Upload build artifacts uses: actions/upload-artifact@v7.0.0 with: name: v2rayN-windows-desktop path: | ${{ github.workspace }}/v2rayN/Release/windows* # release zip archive - name: Package release zip archive if: github.event.inputs.release_tag != '' run: | chmod 755 package-release-zip.sh ./package-release-zip.sh $OutputArch $OutputPath64 mv "v2rayN-${OutputArch}.zip" "v2rayN-${OutputArch}-desktop.zip" ./package-release-zip.sh $OutputArchArm $OutputPathArm64 mv "v2rayN-${OutputArchArm}.zip" "v2rayN-${OutputArchArm}-desktop.zip" - name: Upload zip archive to release uses: svenstaro/upload-release-action@v2 if: github.event.inputs.release_tag != '' with: file: ${{ github.workspace }}/v2rayN*.zip tag: ${{ github.event.inputs.release_tag }} file_glob: true prerelease: true ================================================ FILE: .github/workflows/build-windows.yml ================================================ name: release Windows on: workflow_dispatch: inputs: release_tag: required: false type: string push: branches: - master env: OutputArch: "windows-64" OutputArchArm: "windows-arm64" OutputPath64: "${{ github.workspace }}/v2rayN/Release/windows-64" OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/windows-arm64" jobs: build: strategy: matrix: configuration: [Release] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6.0.2 - name: Setup uses: actions/setup-dotnet@v5.2.0 with: dotnet-version: '8.0.x' - name: Build run: | cd v2rayN dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64 dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64 - name: Upload build artifacts uses: actions/upload-artifact@v7.0.0 with: name: v2rayN-windows path: | ${{ github.workspace }}/v2rayN/Release/windows* # release zip archive - name: Package release zip archive if: github.event.inputs.release_tag != '' run: | chmod 755 package-release-zip.sh ./package-release-zip.sh $OutputArch $OutputPath64 ./package-release-zip.sh $OutputArchArm $OutputPathArm64 - name: Upload zip archive to release uses: svenstaro/upload-release-action@v2 if: github.event.inputs.release_tag != '' with: file: ${{ github.workspace }}/v2rayN*.zip tag: ${{ github.event.inputs.release_tag }} file_glob: true prerelease: true ================================================ FILE: .github/workflows/winget-publish.yml ================================================ name: WinGet submission on release # based off of https://github.com/nushell/nushell/blob/main/.github/workflows/winget-submission.yml # inspired by https://github.com/microsoft/PowerToys/blob/main/.github/workflows/package-submissions.yml # Modified by @MerrickZ https://github.com/anpho on: workflow_dispatch: release: types: [released] jobs: winget: name: Publish winget package runs-on: windows-latest steps: - name: Submit v2ray package to Windows Package Manager Community Repository run: | $wingetPackage = "2dust.v2rayN" $gitToken = "${{ secrets.PT_WINGET }}" $github = Invoke-RestMethod -uri "https://api.github.com/repos/2dust/v2rayN/releases" $targetRelease = $github | Where-Object -Property prerelease -match 'False' | Select -First 1 $x64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip' | Select -ExpandProperty browser_download_url $arm64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-arm64\.zip' | Select -ExpandProperty browser_download_url $ver = $targetRelease.tag_name # getting latest wingetcreate file iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe Write-Host "Updating with both x64 and arm64 installers" Write-Host "Version: $ver" Write-Host "x64 URL: $x64InstallerUrl" Write-Host "arm64 URL: $arm64InstallerUrl" .\wingetcreate.exe update $wingetPackage -s -v $ver -u "$x64InstallerUrl|x64" "$arm64InstallerUrl|arm64" -t $gitToken ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # ASP.NET Scaffolding ScaffoldingReadMe.txt # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp # but not Directory.Build.rsp, as it configures directory-level build defaults !Directory.Build.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.tlog *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio 6 auto-generated project file (contains which files were open etc.) *.vbp # Visual Studio 6 workspace and project file (working project files containing files to include in project) *.dsw *.dsp # Visual Studio 6 technical files *.ncb *.aps # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # Visual Studio History (VSHistory) files .vshistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd # VS Code files for those working on multiple tools .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json *.code-workspace # Local History for Visual Studio Code .history/ # Windows Installer files from build outputs *.cab *.msi *.msix *.msm *.msp # JetBrains Rider .idea/ *.sln.iml ================================================ FILE: .gitmodules ================================================ [submodule "v2rayN/GlobalHotKeys"] path = v2rayN/GlobalHotKeys url = https://github.com/2dust/GlobalHotKeys ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 2019-Present 2dust This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: v2rayN Copyright (C) 2019-Present 2dust This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # v2rayN A GUI client for Windows, Linux and macOS, support [Xray](https://github.com/XTLS/Xray-core) and [sing-box](https://github.com/SagerNet/sing-box) and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayN)](https://github.com/2dust/v2rayN/commits/master) [![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayn/badge)](https://www.codefactor.io/repository/github/2dust/v2rayn) [![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayN/latest/total?logo=github)](https://github.com/2dust/v2rayN/releases) [![Chat on Telegram](https://img.shields.io/badge/Chat%20on-Telegram-brightgreen.svg)](https://t.me/v2rayn) ## How to use Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details. ## Telegram Channel [github_2dust](https://t.me/github_2dust) ================================================ FILE: package-debian.sh ================================================ #!/bin/bash Arch="$1" OutputPath="$2" Version="$3" FileName="v2rayN-${Arch}.zip" wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/$FileName" 7z x $FileName cp -rf v2rayN-${Arch}/* $OutputPath PackagePath="v2rayN-Package-${Arch}" mkdir -p "${PackagePath}/DEBIAN" mkdir -p "${PackagePath}/opt" cp -rf $OutputPath "${PackagePath}/opt/v2rayN" echo "When this file exists, app will not store configs under this folder" > "${PackagePath}/opt/v2rayN/NotStoreConfigHere.txt" if [ $Arch = "linux-64" ]; then Arch2="amd64" else Arch2="arm64" fi echo $Arch2 # basic cat >"${PackagePath}/DEBIAN/control" <<-EOF Package: v2rayN Version: $Version Architecture: $Arch2 Maintainer: https://github.com/2dust/v2rayN Depends: libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1), libfreetype6 (>= 2.11) Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others EOF mkdir -p "${PackagePath}/usr/share/applications" cat >"${PackagePath}/usr/share/applications/v2rayN.desktop" <<-EOF [Desktop Entry] Name=v2rayN Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others Exec=/opt/v2rayN/v2rayN Icon=/opt/v2rayN/v2rayN.png Terminal=false Type=Application Categories=Network;Application; EOF cat >"${PackagePath}/DEBIAN/postinst" <<-'EOF' set -e update-desktop-database || true exit 0 EOF sudo chmod 0755 "${PackagePath}/DEBIAN/postinst" sudo chmod 0755 "${PackagePath}/opt/v2rayN/v2rayN" sudo chmod 0755 "${PackagePath}/opt/v2rayN/AmazTool" # Patch # set owner to root:root sudo chown -R root:root "${PackagePath}" # set all directories to 755 (readable & traversable by all users) sudo find "${PackagePath}/opt/v2rayN" -type d -exec chmod 755 {} + # set all regular files to 644 (readable by all users) sudo find "${PackagePath}/opt/v2rayN" -type f -exec chmod 644 {} + # ensure main binaries are 755 (executable by all users) sudo chmod 755 "${PackagePath}/opt/v2rayN/v2rayN" 2>/dev/null || true sudo chmod 755 "${PackagePath}/opt/v2rayN/AmazTool" 2>/dev/null || true # build deb package sudo dpkg-deb -Zxz --build $PackagePath sudo mv "${PackagePath}.deb" "v2rayN-${Arch}.deb" ================================================ FILE: package-osx.sh ================================================ #!/bin/bash Arch="$1" OutputPath="$2" Version="$3" FileName="v2rayN-${Arch}.zip" wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/$FileName" 7z x $FileName cp -rf v2rayN-${Arch}/* $OutputPath PackagePath="v2rayN-Package-${Arch}" mkdir -p "$PackagePath/v2rayN.app/Contents/Resources" cp -rf "$OutputPath" "$PackagePath/v2rayN.app/Contents/MacOS" cp -f "$PackagePath/v2rayN.app/Contents/MacOS/v2rayN.icns" "$PackagePath/v2rayN.app/Contents/Resources/AppIcon.icns" echo "When this file exists, app will not store configs under this folder" > "$PackagePath/v2rayN.app/Contents/MacOS/NotStoreConfigHere.txt" chmod +x "$PackagePath/v2rayN.app/Contents/MacOS/v2rayN" cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF CFBundleDevelopmentRegion English CFBundleDisplayName v2rayN CFBundleExecutable v2rayN CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier 2dust.v2rayN CFBundleName v2rayN CFBundlePackageType APPL CFBundleShortVersionString ${Version} CSResourcesFileMapped NSHighResolutionCapable LSMinimumSystemVersion 12.7 EOF create-dmg \ --volname "v2rayN Installer" \ --window-size 700 420 \ --icon-size 100 \ --icon "v2rayN.app" 160 185 \ --hide-extension "v2rayN.app" \ --app-drop-link 500 185 \ "v2rayN-${Arch}.dmg" \ "$PackagePath/v2rayN.app" ================================================ FILE: package-release-zip.sh ================================================ #!/bin/bash Arch="$1" OutputPath="$2" OutputArch="v2rayN-${Arch}" FileName="v2rayN-${Arch}.zip" wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/$FileName" ZipPath64="./$OutputArch" mkdir $ZipPath64 cp -rf $OutputPath "$ZipPath64/$OutputArch" 7z a -tZip $FileName "$ZipPath64/$OutputArch" -mx1 ================================================ FILE: package-rhel.sh ================================================ #!/usr/bin/env bash set -euo pipefail # Require Red Hat base branch . /etc/os-release case "${ID:-}" in rhel|rocky|almalinux|fedora|centos) echo "Detected supported system: ${NAME:-$ID} ${VERSION_ID:-}" ;; *) echo "Unsupported system: ${NAME:-unknown} (${ID:-unknown})." echo "This script only supports: RHEL / Rocky / AlmaLinux / Fedora / CentOS." exit 1 ;; esac # Kernel version MIN_KERNEL="6.11" CURRENT_KERNEL="$(uname -r)" lowest="$(printf '%s\n%s\n' "$MIN_KERNEL" "$CURRENT_KERNEL" | sort -V | head -n1)" if [[ "$lowest" != "$MIN_KERNEL" ]]; then echo "Kernel $CURRENT_KERNEL is below $MIN_KERNEL" exit 1 fi echo "[OK] Kernel $CURRENT_KERNEL verified." # Config & Parse arguments VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty WITH_CORE="both" # Default: bundle both xray+sing-box FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads ARCH_OVERRIDE="" # --arch x64|arm64|all (optional compile target) BUILD_FROM="" # --buildfrom 1|2|3 to select channel non-interactively # If the first argument starts with --, do not treat it as a version number if [[ "${VERSION_ARG:-}" == --* ]]; then VERSION_ARG="" fi # Take the first non --* argument as version, discard it if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi # Parse remaining optional arguments while [[ $# -gt 0 ]]; do case "$1" in --with-core) WITH_CORE="${2:-both}"; shift 2;; --xray-ver) XRAY_VER="${2:-}"; shift 2;; --singbox-ver) SING_VER="${2:-}"; shift 2;; --netcore) FORCE_NETCORE=1; shift;; --arch) ARCH_OVERRIDE="${2:-}"; shift 2;; --buildfrom) BUILD_FROM="${2:-}"; shift 2;; *) if [[ -z "${VERSION_ARG:-}" ]]; then VERSION_ARG="$1"; fi shift;; esac done # Conflict: version number AND --buildfrom cannot be used together if [[ -n "${VERSION_ARG:-}" && -n "${BUILD_FROM:-}" ]]; then echo "You cannot specify both an explicit version and --buildfrom at the same time." echo " Provide either a version (e.g. 7.14.0) OR --buildfrom 1|2|3." exit 1 fi # Check and install dependencies host_arch="$(uname -m)" [[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; } install_ok=0 if command -v dnf >/dev/null 2>&1; then sudo dnf -y install rpm-build rpmdevtools curl unzip tar jq rsync dotnet-sdk-8.0 \ && install_ok=1 fi if [[ "$install_ok" -ne 1 ]]; then echo "Could not auto-install dependencies for '$ID'. Make sure these are available:" echo "dotnet-sdk 8.x, curl, unzip, tar, rsync, rpm, rpmdevtools, rpm-build (on Red Hat branch)" fi # Root directory SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # Git submodules (best effort) if [[ -f .gitmodules ]]; then git submodule sync --recursive || true git submodule update --init --recursive || true fi # Locate project PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj" if [[ ! -f "$PROJECT" ]]; then PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)" fi [[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; } choose_channel() { # If --buildfrom provided, map it directly and skip interaction. if [[ -n "${BUILD_FROM:-}" ]]; then case "$BUILD_FROM" in 1) echo "latest"; return 0;; 2) echo "prerelease"; return 0;; 3) echo "keep"; return 0;; *) echo "[ERROR] Invalid --buildfrom value: ${BUILD_FROM}. Use 1|2|3." >&2; exit 1;; esac fi # Print menu to stderr and read from /dev/tty so stdout only carries the token. local ch="latest" sel="" if [[ -t 0 ]]; then echo "[?] Choose v2rayN release channel:" >&2 echo " 1) Latest (stable) [default]" >&2 echo " 2) Pre-release (preview)" >&2 echo " 3) Keep current (do nothing)" >&2 printf "Enter 1, 2 or 3 [default 1]: " >&2 if read -r sel /dev/null 2>&1; then git fetch --tags --force --prune --depth=1 || true if git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then ref="${want}" fi if [[ -n "$ref" ]]; then echo "[OK] Found ref '${ref}', checking out..." git checkout -f "${ref}" if [[ -f .gitmodules ]]; then git submodule sync --recursive || true git submodule update --init --recursive || true fi return 0 fi fi return 1 } apply_channel_or_keep() { local ch="$1" tag if [[ "$ch" == "keep" ]]; then echo "[*] Keep current repository state (no checkout)." VERSION="$(git describe --tags --abbrev=0 2>/dev/null || echo '0.0.0+git')" VERSION="${VERSION#v}" return 0 fi echo "[*] Resolving ${ch} tag from GitHub releases..." if [[ "$ch" == "prerelease" ]]; then tag="$(get_latest_tag_prerelease || true)" else tag="$(get_latest_tag_latest || true)" fi [[ -n "$tag" ]] || { echo "Failed to resolve latest tag for channel '${ch}'."; exit 1; } echo "[*] Latest tag for '${ch}': ${tag}" git_try_checkout "$tag" || { echo "Failed to checkout '${tag}'."; exit 1; } VERSION="${tag#v}" } if git rev-parse --git-dir >/dev/null 2>&1; then if [[ -n "${VERSION_ARG:-}" ]]; then clean_ver="${VERSION_ARG#v}" if git_try_checkout "$clean_ver"; then VERSION="$clean_ver" else echo "[WARN] Tag '${VERSION_ARG}' not found." ch="$(choose_channel)" apply_channel_or_keep "$ch" fi else ch="$(choose_channel)" apply_channel_or_keep "$ch" fi else echo "Current directory is not a git repo; proceeding on current tree." VERSION="${VERSION_ARG:-0.0.0}" fi VERSION="${VERSION#v}" echo "[*] GUI version resolved as: ${VERSION}" # Helpers for core download_xray() { # Download Xray core local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip" mkdir -p "$outdir" if [[ -z "$ver" ]]; then ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \ | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true fi [[ -n "$ver" ]] || { echo "[xray] Failed to get version"; return 1; } if [[ "$RID_DIR" == "linux-arm64" ]]; then url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip" else url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip" fi echo "[+] Download xray: $url" tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN curl -fL "$url" -o "$tmp/$zipname" unzip -q "$tmp/$zipname" -d "$tmp" install -Dm755 "$tmp/xray" "$outdir/xray" } download_singbox() { # Download sing-box local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin mkdir -p "$outdir" if [[ -z "$ver" ]]; then ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \ | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true fi [[ -n "$ver" ]] || { echo "[sing-box] Failed to get version"; return 1; } if [[ "$RID_DIR" == "linux-arm64" ]]; then url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz" else url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz" fi echo "[+] Download sing-box: $url" tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN curl -fL "$url" -o "$tmp/$tarname" tar -C "$tmp" -xzf "$tmp/$tarname" bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)" [[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; return 1; } install -Dm755 "$bin" "$outdir/sing-box" } # Move geo files to outroot/bin unify_geo_layout() { local outroot="$1" mkdir -p "$outroot/bin" local names=( \ "geosite.dat" \ "geoip.dat" \ "geoip-only-cn-private.dat" \ "Country.mmdb" \ "geoip.metadb" \ ) for n in "${names[@]}"; do if [[ -f "$outroot/bin/xray/$n" ]]; then mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n" fi done } # Download geo/rule assets download_geo_assets() { local outroot="$1" local bin_dir="$outroot/bin" local srss_dir="$bin_dir/srss" mkdir -p "$bin_dir" "$srss_dir" echo "[+] Download Xray Geo to ${bin_dir}" curl -fsSL -o "$bin_dir/geosite.dat" \ "https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat" curl -fsSL -o "$bin_dir/geoip.dat" \ "https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat" curl -fsSL -o "$bin_dir/geoip-only-cn-private.dat" \ "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat" curl -fsSL -o "$bin_dir/Country.mmdb" \ "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb" echo "[+] Download sing-box rule DB & rule-sets" curl -fsSL -o "$bin_dir/geoip.metadb" \ "https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb" || true for f in \ geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs \ geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do curl -fsSL -o "$srss_dir/$f" \ "https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true done for f in \ geosite-cn.srs geosite-gfw.srs geosite-google.srs geosite-greatfire.srs \ geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do curl -fsSL -o "$srss_dir/$f" \ "https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true done # Unify to bin unify_geo_layout "$outroot" } # Prefer the prebuilt v2rayN core bundle; then unify geo layout download_v2rayn_bundle() { local outroot="$1" local url="" if [[ "$RID_DIR" == "linux-arm64" ]]; then url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64.zip" else url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64.zip" fi echo "[+] Try v2rayN bundle archive: $url" local tmp zipname tmp="$(mktemp -d)"; zipname="$tmp/v2rayn.zip" curl -fL "$url" -o "$zipname" || { echo "[!] Bundle download failed"; return 1; } unzip -q "$zipname" -d "$tmp" || { echo "[!] Bundle unzip failed"; return 1; } if [[ -d "$tmp/bin" ]]; then mkdir -p "$outroot/bin" rsync -a "$tmp/bin/" "$outroot/bin/" else rsync -a "$tmp/" "$outroot/" fi rm -f "$outroot/v2rayn.zip" 2>/dev/null || true find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true local nested_dir nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)" if [[ -n "$nested_dir" && -d "$nested_dir/bin" ]]; then mkdir -p "$outroot/bin" rsync -a "$nested_dir/bin/" "$outroot/bin/" rm -rf "$nested_dir" fi # Unify to bin/ unify_geo_layout "$outroot" echo "[+] Bundle extracted to $outroot" } # ===== Build results collection for --arch all ======================================== BUILT_RPMS=() # Will collect absolute paths of built RPMs BUILT_ALL=0 # Flag to know if we should print the final summary # ===== Build (single-arch) function ==================================================== build_for_arch() { # $1: target short arch: x64 | arm64 local short="$1" local rid rpm_target archdir case "$short" in x64) rid="linux-x64"; rpm_target="x86_64"; archdir="x86_64" ;; arm64) rid="linux-arm64"; rpm_target="aarch64"; archdir="aarch64" ;; *) echo "Unknown arch '$short' (use x64|arm64)"; return 1;; esac echo "[*] Building for target: $short (RID=$rid, RPM --target $rpm_target)" # .NET publish (self-contained) for this RID dotnet clean "$PROJECT" -c Release rm -rf "$(dirname "$PROJECT")/bin/Release/net8.0" || true dotnet restore "$PROJECT" dotnet publish "$PROJECT" \ -c Release -r "$rid" \ -p:PublishSingleFile=false \ -p:SelfContained=true # Per-arch variables (scoped) local RID_DIR="$rid" local PUBDIR PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish" [[ -d "$PUBDIR" ]] # Make RID_DIR visible to download helpers (they read this var) export RID_DIR # Per-arch working area local PKGROOT="v2rayN-publish" local WORKDIR WORKDIR="$(mktemp -d)" trap '[[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR"' RETURN # rpmbuild topdir selection local TOPDIR SPECDIR SOURCEDIR rpmdev-setuptree TOPDIR="${HOME}/rpmbuild" SPECDIR="${TOPDIR}/SPECS" SOURCEDIR="${TOPDIR}/SOURCES" # Stage publish content mkdir -p "$WORKDIR/$PKGROOT" cp -a "$PUBDIR/." "$WORKDIR/$PKGROOT/" # Optional icon local ICON_CANDIDATE ICON_CANDIDATE="$(dirname "$PROJECT")/../v2rayN.Desktop/v2rayN.png" [[ -f "$ICON_CANDIDATE" ]] && cp "$ICON_CANDIDATE" "$WORKDIR/$PKGROOT/v2rayn.png" || true # Prepare bin structure mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box" # Bundle / cores per-arch fetch_separate_cores_and_rules() { local outroot="$1" if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then download_xray "$outroot/bin/xray" || echo "[!] xray download failed (skipped)" fi if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then download_singbox "$outroot/bin/sing_box" || echo "[!] sing-box download failed (skipped)" fi download_geo_assets "$outroot" || echo "[!] Geo rules download failed (skipped)" } if [[ "$FORCE_NETCORE" -eq 0 ]]; then if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then echo "[*] Using v2rayN bundle archive." else echo "[*] Bundle failed, fallback to separate core + rules." fetch_separate_cores_and_rules "$WORKDIR/$PKGROOT" fi else echo "[*] --netcore specified: use separate core + rules." fetch_separate_cores_and_rules "$WORKDIR/$PKGROOT" fi # Tarball mkdir -p "$SOURCEDIR" tar -C "$WORKDIR" -czf "$SOURCEDIR/$PKGROOT.tar.gz" "$PKGROOT" # SPEC local SPECFILE="$SPECDIR/v2rayN.spec" mkdir -p "$SPECDIR" cat > "$SPECFILE" <<'SPEC' %global debug_package %{nil} %undefine _debuginfo_subpackages %undefine _debugsource_packages # Ignore outdated LTTng dependencies incorrectly reported by the .NET runtime (to avoid installation failures) %global __requires_exclude ^liblttng-ust\.so\..*$ Name: v2rayN Version: __VERSION__ Release: 1%{?dist} Summary: v2rayN (Avalonia) GUI client for Linux (x86_64/aarch64) License: GPL-3.0-only URL: https://github.com/2dust/v2rayN BugURL: https://github.com/2dust/v2rayN/issues ExclusiveArch: aarch64 x86_64 Source0: __PKGROOT__.tar.gz # Runtime dependencies (Avalonia / X11 / Fonts / GL) Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL Requires: glibc >= 2.34 Requires: fontconfig >= 2.13.1 Requires: desktop-file-utils >= 0.26 Requires: xdg-utils >= 1.1.3 Requires: coreutils >= 8.32 Requires: bash >= 5.1 Requires: freetype >= 2.10 %description v2rayN Linux for Red Hat Enterprise Linux Support vless / vmess / Trojan / http / socks / Anytls / Hysteria2 / Shadowsocks / tuic / WireGuard Support Red Hat Enterprise Linux / Fedora Linux / Rocky Linux / AlmaLinux / CentOS For more information, Please visit our website https://github.com/2dust/v2rayN %prep %setup -q -n __PKGROOT__ %build # no build %install install -dm0755 %{buildroot}/opt/v2rayN cp -a * %{buildroot}/opt/v2rayN/ # Launcher (prefer native ELF first, then DLL fallback) install -dm0755 %{buildroot}%{_bindir} cat > %{buildroot}%{_bindir}/v2rayn << 'EOF' #!/usr/bin/bash set -euo pipefail DIR="/opt/v2rayN" # Prefer native apphost if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi # DLL fallback for dll in v2rayN.Desktop.dll v2rayN.dll; do if [[ -f "$DIR/$dll" ]]; then exec /usr/bin/dotnet "$DIR/$dll" "$@"; fi done echo "v2rayN launcher: no executable found in $DIR" >&2 ls -l "$DIR" >&2 || true exit 1 EOF chmod 0755 %{buildroot}%{_bindir}/v2rayn # Desktop file install -dm0755 %{buildroot}%{_datadir}/applications cat > %{buildroot}%{_datadir}/applications/v2rayn.desktop << 'EOF' [Desktop Entry] Type=Application Name=v2rayN Comment=v2rayN for Red Hat Enterprise Linux Exec=v2rayn Icon=v2rayn Terminal=false Categories=Network; EOF # Icon if [ -f "%{_builddir}/__PKGROOT__/v2rayn.png" ]; then install -dm0755 %{buildroot}%{_datadir}/icons/hicolor/256x256/apps install -m0644 %{_builddir}/__PKGROOT__/v2rayn.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png fi %post /usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true /usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true %postun /usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true /usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true %files %{_bindir}/v2rayn /opt/v2rayN %{_datadir}/applications/v2rayn.desktop %{_datadir}/icons/hicolor/256x256/apps/v2rayn.png SPEC # Replace placeholders sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE" sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE" # Build RPM for this arch rpmbuild -ba "$SPECFILE" --target "$rpm_target" echo "Build done for $short. RPM at:" local f for f in "${TOPDIR}/RPMS/${archdir}/v2rayN-${VERSION}-1"*.rpm; do [[ -e "$f" ]] || continue echo " $f" BUILT_RPMS+=("$f") done } # ===== Arch selection and build orchestration ========================================= case "${ARCH_OVERRIDE:-}" in all) targets=(x64 arm64); BUILT_ALL=1 ;; x64|amd64) targets=(x64) ;; arm64|aarch64) targets=(arm64) ;; "") targets=($([[ "$host_arch" == "aarch64" ]] && echo arm64 || echo x64)) ;; *) echo "Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all."; exit 1 ;; esac for arch in "${targets[@]}"; do build_for_arch "$arch" done # Print Both arches information if [[ "$BUILT_ALL" -eq 1 ]]; then echo "" echo "================ Build Summary (both architectures) ================" if [[ "${#BUILT_RPMS[@]}" -gt 0 ]]; then for rp in "${BUILT_RPMS[@]}"; do echo "$rp" done else echo "No RPMs detected in summary (check build logs above)." fi echo "====================================================================" fi ================================================ FILE: v2rayN/AmazTool/AmazTool.csproj ================================================  Exe ResXFileCodeGenerator Resource.Designer.cs True True Resource.resx ================================================ FILE: v2rayN/AmazTool/Program.cs ================================================ namespace AmazTool; internal static class Program { [STAThread] private static void Main(string[] args) { try { // If no arguments are provided, display usage guidelines and exit if (args.Length == 0) { ShowHelp(); return; } // Log all arguments for debugging purposes foreach (var arg in args) { Console.WriteLine(arg); } // Parse command based on first argument switch (args[0].ToLowerInvariant()) { case "rebootas": // Handle application restart HandleRebootAsync(); break; case "help": case "--help": case "-h": case "/?": // Display help information ShowHelp(); break; default: // Default behavior: handle as upgrade data // Maintain backward compatibility with existing usage pattern var argData = Uri.UnescapeDataString(string.Join(" ", args)); HandleUpgrade(argData); break; } } catch (Exception ex) { // Global exception handling Console.WriteLine($"An error occurred: {ex.Message}"); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } } /// /// Display help information and usage guidelines /// private static void ShowHelp() { Console.WriteLine(Resx.Resource.Guidelines); Console.WriteLine("Available commands:"); Console.WriteLine(" rebootas - Restart the application"); Console.WriteLine(" help - Display this help information"); Thread.Sleep(5000); } /// /// Handle application restart /// private static void HandleRebootAsync() { Console.WriteLine("Restarting application..."); Thread.Sleep(1000); Utils.StartV2RayN(); } /// /// Handle application upgrade with the provided data /// /// Data for the upgrade process private static void HandleUpgrade(string upgradeData) { Console.WriteLine("Upgrading application..."); UpgradeApp.Upgrade(upgradeData); } } ================================================ FILE: v2rayN/AmazTool/Resx/Resource.Designer.cs ================================================ //------------------------------------------------------------------------------ // // 此代码由工具生成。 // 运行时版本:4.0.30319.42000 // // 对此文件的更改可能会导致不正确的行为,并且如果 // 重新生成代码,这些更改将会丢失。 // //------------------------------------------------------------------------------ namespace AmazTool.Resx { using System; /// /// 一个强类型的资源类,用于查找本地化的字符串等。 /// // 此类是由 StronglyTypedResourceBuilder // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen // (以 /str 作为命令选项),或重新生成 VS 项目。 [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resource { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resource() { } /// /// 返回此类使用的缓存的 ResourceManager 实例。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AmazTool.Resx.Resource", typeof(Resource).Assembly); resourceMan = temp; } return resourceMan; } } /// /// 重写当前线程的 CurrentUICulture 属性,对 /// 使用此强类型资源类的所有资源查找执行重写。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// 查找类似 Failed to terminate the v2rayN. Close it manually, or the upgrade may fail. 的本地化字符串。 /// internal static string FailedTerminateProcess { get { return ResourceManager.GetString("FailedTerminateProcess", resourceCulture); } } /// /// 查找类似 Failed to extract the update package. 的本地化字符串。 /// internal static string FailedUnzipping { get { return ResourceManager.GetString("FailedUnzipping", resourceCulture); } } /// /// 查找类似 Upgrade failed. 的本地化字符串。 /// internal static string FailedUpgrade { get { return ResourceManager.GetString("FailedUpgrade", resourceCulture); } } /// /// 查找类似 Please run it from the main application. 的本地化字符串。 /// internal static string Guidelines { get { return ResourceManager.GetString("Guidelines", resourceCulture); } } /// /// 查找类似 Information 的本地化字符串。 /// internal static string Information { get { return ResourceManager.GetString("Information", resourceCulture); } } /// /// 查找类似 In progress, please wait... 的本地化字符串。 /// internal static string InProgress { get { return ResourceManager.GetString("InProgress", resourceCulture); } } /// /// 查找类似 Start v2rayN, please wait... 的本地化字符串。 /// internal static string Restartv2rayN { get { return ResourceManager.GetString("Restartv2rayN", resourceCulture); } } /// /// 查找类似 Start extracting the update package... 的本地化字符串。 /// internal static string StartUnzipping { get { return ResourceManager.GetString("StartUnzipping", resourceCulture); } } /// /// 查找类似 Successfully extracted the update package. 的本地化字符串。 /// internal static string SuccessUnzipping { get { return ResourceManager.GetString("SuccessUnzipping", resourceCulture); } } /// /// 查找类似 Upgrade success. 的本地化字符串。 /// internal static string SuccessUpgrade { get { return ResourceManager.GetString("SuccessUpgrade", resourceCulture); } } /// /// 查找类似 Try to terminate the v2rayN process... 的本地化字符串。 /// internal static string TryTerminateProcess { get { return ResourceManager.GetString("TryTerminateProcess", resourceCulture); } } /// /// 查找类似 Upgrade failed, file not found. 的本地化字符串。 /// internal static string UpgradeFileNotFound { get { return ResourceManager.GetString("UpgradeFileNotFound", resourceCulture); } } } } ================================================ FILE: v2rayN/AmazTool/Resx/Resource.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Start v2rayN, please wait... Please run it from the main application. Upgrade failed, file not found. In progress, please wait... Try to terminate the v2rayN process... Failed to terminate the v2rayN. Close it manually, or the upgrade may fail. Start extracting the update package... Successfully extracted the update package. Failed to extract the update package. Upgrade failed. Upgrade success. Information ================================================ FILE: v2rayN/AmazTool/Resx/Resource.zh-Hans.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 正在重启,请等待... 请从主应用运行。 升级失败,文件不存在。 正在进行中,请等待... 尝试结束 v2rayN 进程... 请手动关闭正在运行的 v2rayN,否则可能升级失败。 开始解压缩更新包... 解压缩更新包成功。 解压缩更新包失败。 升级失败。 升级成功。 提示 ================================================ FILE: v2rayN/AmazTool/Resx/Resource.zh-Hant.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 正在重啟,請等待... 請從主應用程式運行。 升級失敗,檔案不存在。 正在進行中,請等待... 嘗試結束 v2rayN 進程... 請手動關閉正在執行的 v2rayN,否則可能會升級失敗。 開始解壓縮更新包... 解壓縮更新包成功。 解壓縮更新包失敗。 升級失敗。 升級成功。 提示 ================================================ FILE: v2rayN/AmazTool/UpgradeApp.cs ================================================ using System.Diagnostics; using System.IO.Compression; using System.Text; namespace AmazTool; internal class UpgradeApp { public static void Upgrade(string fileName) { Console.WriteLine($"{Resx.Resource.StartUnzipping}\n{fileName}"); Utils.Waiting(5); if (!File.Exists(fileName)) { Console.WriteLine(Resx.Resource.UpgradeFileNotFound); return; } Console.WriteLine(Resx.Resource.TryTerminateProcess); try { var existing = Process.GetProcessesByName(Utils.V2rayN); foreach (var pp in existing) { var path = pp.MainModule?.FileName ?? ""; if (path.StartsWith(Utils.GetPath(Utils.V2rayN))) { pp?.Kill(); pp?.WaitForExit(1000); } } } catch (Exception ex) { // Access may be denied without admin right. The user may not be an administrator. Console.WriteLine(Resx.Resource.FailedTerminateProcess + ex.StackTrace); } Console.WriteLine(Resx.Resource.StartUnzipping); StringBuilder sb = new(); try { var thisAppOldFile = $"{Utils.GetExePath()}.tmp"; File.Delete(thisAppOldFile); var splitKey = "/"; using var archive = ZipFile.OpenRead(fileName); foreach (var entry in archive.Entries) { try { if (entry.Length == 0) { continue; } Console.WriteLine(entry.FullName); var lst = entry.FullName.Split(splitKey); if (lst.Length == 1) { continue; } var fullName = string.Join(splitKey, lst[1..lst.Length]); if (string.Equals(Utils.GetExePath(), Utils.GetPath(fullName), StringComparison.OrdinalIgnoreCase)) { File.Move(Utils.GetExePath(), thisAppOldFile); } var entryOutputPath = Utils.GetPath(fullName); Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!); //In the bin folder, if the file already exists, it will be skipped if (fullName.StartsWith("bin") && File.Exists(entryOutputPath)) { continue; } TryExtractToFile(entry, entryOutputPath); Console.WriteLine(entryOutputPath); } catch (Exception ex) { sb.Append(ex.StackTrace); } } } catch (Exception ex) { Console.WriteLine(Resx.Resource.FailedUpgrade + ex.StackTrace); //return; } if (sb.Length > 0) { Console.WriteLine(Resx.Resource.FailedUpgrade + sb.ToString()); //return; } Console.WriteLine(Resx.Resource.Restartv2rayN); Utils.Waiting(2); Utils.StartV2RayN(); } private static bool TryExtractToFile(ZipArchiveEntry entry, string outputPath) { var retryCount = 5; var delayMs = 1000; for (var i = 1; i <= retryCount; i++) { try { entry.ExtractToFile(outputPath, true); return true; } catch { Thread.Sleep(delayMs * i); } } return false; } } ================================================ FILE: v2rayN/AmazTool/Utils.cs ================================================ using System.Diagnostics; namespace AmazTool; internal class Utils { public static string GetExePath() { return Environment.ProcessPath ?? Process.GetCurrentProcess().MainModule?.FileName ?? string.Empty; } public static string StartupPath() { return AppDomain.CurrentDomain.BaseDirectory; } public static string GetPath(string fileName) { var startupPath = StartupPath(); if (string.IsNullOrEmpty(fileName)) { return startupPath; } return Path.Combine(startupPath, fileName); } public static string V2rayN => "v2rayN"; public static void StartV2RayN() { Process process = new() { StartInfo = new() { UseShellExecute = true, FileName = V2rayN, WorkingDirectory = StartupPath() } }; process.Start(); } public static void Waiting(int second) { for (var i = second; i > 0; i--) { Console.WriteLine(i); Thread.Sleep(1000); } } } ================================================ FILE: v2rayN/Directory.Build.props ================================================ 7.19.4 net8.0 true true CA1031;CS1591;NU1507;CA1416;IDE0058;IDE0053;IDE0200 annotations enable 2dust GPL-3.0 Copyright © 2017-$([System.DateTime]::UtcNow.Year) $(Authors) false embedded false false false false false false true true false ================================================ FILE: v2rayN/Directory.Packages.props ================================================ true true false ================================================ FILE: v2rayN/ServiceLib/Base/MyReactiveObject.cs ================================================ namespace ServiceLib.Base; public class MyReactiveObject : ReactiveObject { protected static Config? _config; protected Func>? _updateView; } ================================================ FILE: v2rayN/ServiceLib/Common/EmbedUtils.cs ================================================ namespace ServiceLib.Common; public static class EmbedUtils { private static readonly string _tag = "EmbedUtils"; private static readonly ConcurrentDictionary _dicEmbedCache = new(); /// /// Get embedded text resources /// /// /// public static string GetEmbedText(string res) { if (_dicEmbedCache.TryGetValue(res, out var value)) { return value; } var result = string.Empty; try { var assembly = Assembly.GetExecutingAssembly(); using var stream = assembly.GetManifestResourceStream(res); ArgumentNullException.ThrowIfNull(stream); using StreamReader reader = new(stream); result = reader.ReadToEnd(); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } _dicEmbedCache.TryAdd(res, result); return result; } /// /// Get local storage resources /// /// public static string? LoadResource(string? res) { try { if (File.Exists(res)) { return File.ReadAllText(res); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return null; } } ================================================ FILE: v2rayN/ServiceLib/Common/Extension.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace ServiceLib.Common; public static class Extension { public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) { return string.IsNullOrWhiteSpace(value) || string.IsNullOrEmpty(value); } public static bool IsNotEmpty([NotNullWhen(false)] this string? value) { return !string.IsNullOrWhiteSpace(value); } public static string? NullIfEmpty(this string? value) { return string.IsNullOrWhiteSpace(value) ? null : value; } public static bool BeginWithAny(this string s, IEnumerable chars) { if (s.IsNullOrEmpty()) { return false; } return chars.Contains(s.First()); } private static bool IsWhiteSpace(this string value) { return value.All(char.IsWhiteSpace); } public static IEnumerable NonWhiteSpaceLines(this TextReader reader) { while (reader.ReadLine() is { } line) { if (line.IsWhiteSpace()) { continue; } yield return line; } } public static string TrimEx(this string? value) { return value == null ? string.Empty : value.Trim(); } public static string RemovePrefix(this string value, char prefix) { return value.StartsWith(prefix) ? value[1..] : value; } public static string RemovePrefix(this string value, string prefix) { return value.StartsWith(prefix) ? value[prefix.Length..] : value; } public static string UpperFirstChar(this string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } return char.ToUpper(value.First()) + value[1..]; } public static string AppendQuotes(this string value) { return string.IsNullOrEmpty(value) ? string.Empty : $"\"{value}\""; } public static int ToInt(this string? value, int defaultValue = 0) { return int.TryParse(value, out var result) ? result : defaultValue; } public static List AppendEmpty(this IEnumerable source) { return source.Concat(new[] { string.Empty }).ToList(); } public static bool IsGroupType(this EConfigType configType) { return configType is EConfigType.PolicyGroup or EConfigType.ProxyChain; } public static bool IsComplexType(this EConfigType configType) { return configType is EConfigType.Custom or EConfigType.PolicyGroup or EConfigType.ProxyChain; } /// /// Safely adds elements from a collection to the list. Does nothing if the source is null. /// public static void AddRangeSafe(this ICollection destination, IEnumerable? source) { ArgumentNullException.ThrowIfNull(destination); if (source is null) { return; } if (destination is List list) { list.AddRange(source); return; } foreach (var item in source) { destination.Add(item); } } } ================================================ FILE: v2rayN/ServiceLib/Common/FileUtils.cs ================================================ using System.Formats.Tar; using System.IO.Compression; namespace ServiceLib.Common; public static class FileUtils { private static readonly string _tag = "FileManager"; public static bool ByteArrayToFile(string fileName, byte[] content) { try { File.WriteAllBytes(fileName, content); return true; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return false; } public static void DecompressFile(string fileName, byte[] content) { try { using var fs = File.Create(fileName); using GZipStream input = new(new MemoryStream(content), CompressionMode.Decompress, false); input.CopyTo(fs); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } public static void DecompressFile(string fileName, string toPath, string? toName) { try { FileInfo fileInfo = new(fileName); using var originalFileStream = fileInfo.OpenRead(); using var decompressedFileStream = File.Create(toName != null ? Path.Combine(toPath, toName) : toPath); using GZipStream decompressionStream = new(originalFileStream, CompressionMode.Decompress); decompressionStream.CopyTo(decompressedFileStream); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } public static void DecompressTarFile(string fileName, string toPath) { try { using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); using var gz = new GZipStream(fs, CompressionMode.Decompress, leaveOpen: true); TarFile.ExtractToDirectory(gz, toPath, overwriteFiles: true); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } public static string NonExclusiveReadAllText(string path) { return NonExclusiveReadAllText(path, Encoding.Default); } private static string NonExclusiveReadAllText(string path, Encoding encoding) { try { using FileStream fs = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using StreamReader sr = new(fs, encoding); return sr.ReadToEnd(); } catch (Exception ex) { Logging.SaveLog(_tag, ex); throw; } } public static bool ZipExtractToFile(string fileName, string toPath, string ignoredName) { try { using var archive = ZipFile.OpenRead(fileName); foreach (var entry in archive.Entries) { if (entry.Length == 0) { continue; } try { if (ignoredName.IsNotEmpty() && entry.Name.Contains(ignoredName)) { continue; } entry.ExtractToFile(Path.Combine(toPath, entry.Name), true); } catch (IOException ex) { Logging.SaveLog(_tag, ex); } } } catch (Exception ex) { Logging.SaveLog(_tag, ex); return false; } return true; } public static List? GetFilesFromZip(string fileName) { if (!File.Exists(fileName)) { return null; } try { using var archive = ZipFile.OpenRead(fileName); return archive.Entries.Select(entry => entry.FullName).ToList(); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return null; } } public static bool CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName) { try { if (File.Exists(destinationArchiveFileName)) { File.Delete(destinationArchiveFileName); } ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, CompressionLevel.SmallestSize, true); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return false; } return true; } public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive, bool overwrite, string? ignoredName = null) { // Get information about the source directory var dir = new DirectoryInfo(sourceDir); // Check if the source directory exists if (!dir.Exists) { throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); } // Cache directories before we start copying var dirs = dir.GetDirectories(); // Create the destination directory _ = Directory.CreateDirectory(destinationDir); // Get the files in the source directory and copy to the destination directory foreach (var file in dir.GetFiles()) { if (ignoredName.IsNotEmpty() && file.Name.Contains(ignoredName)) { continue; } if (file.Extension == file.Name) { continue; } var targetFilePath = Path.Combine(destinationDir, file.Name); if (!overwrite && File.Exists(targetFilePath)) { continue; } _ = file.CopyTo(targetFilePath, overwrite); } // If recursive and copying subdirectories, recursively call this method if (recursive) { foreach (var subDir in dirs) { var newDestinationDir = Path.Combine(destinationDir, subDir.Name); CopyDirectory(subDir.FullName, newDestinationDir, true, overwrite, ignoredName); } } } public static void DeleteExpiredFiles(string sourceDir, DateTime dtLine) { try { var files = Directory.GetFiles(sourceDir, "*.*"); foreach (var filePath in files) { var file = new FileInfo(filePath); if (file.CreationTime >= dtLine) { continue; } file.Delete(); } } catch { // ignored } } /// /// Creates a Linux shell file with the specified contents. /// /// /// /// /// public static async Task CreateLinuxShellFile(string fileName, string contents, bool overwrite) { var shFilePath = Utils.GetBinConfigPath(fileName); // Check if the file already exists and if we should overwrite it if (!overwrite && File.Exists(shFilePath)) { return shFilePath; } File.Delete(shFilePath); await File.WriteAllTextAsync(shFilePath, contents); await Utils.SetLinuxChmod(shFilePath); return shFilePath; } } ================================================ FILE: v2rayN/ServiceLib/Common/JsonUtils.cs ================================================ namespace ServiceLib.Common; public class JsonUtils { private static readonly string _tag = "JsonUtils"; private static readonly JsonSerializerOptions _defaultDeserializeOptions = new() { PropertyNameCaseInsensitive = true, ReadCommentHandling = JsonCommentHandling.Skip }; private static readonly JsonSerializerOptions _defaultSerializeOptions = new() { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; private static readonly JsonSerializerOptions _nullValueSerializeOptions = new() { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.Never, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; private static readonly JsonDocumentOptions _defaultDocumentOptions = new() { CommentHandling = JsonCommentHandling.Skip }; /// /// DeepCopy /// /// /// /// public static T? DeepCopy(T? obj) { if (obj is null) { return default; } return Deserialize(Serialize(obj, false)); } /// /// Deserialize to object /// /// /// /// public static T? Deserialize(string? strJson) { try { if (string.IsNullOrWhiteSpace(strJson)) { return default; } return JsonSerializer.Deserialize(strJson, _defaultDeserializeOptions); } catch { return default; } } /// /// parse /// /// /// public static JsonNode? ParseJson(string? strJson) { try { if (string.IsNullOrWhiteSpace(strJson)) { return null; } return JsonNode.Parse(strJson, nodeOptions: null, _defaultDocumentOptions); } catch { //SaveLog(ex.Message, ex); return null; } } /// /// Serialize Object to Json string /// /// /// /// /// public static string Serialize(object? obj, bool indented = true, bool nullValue = false) { var result = string.Empty; try { if (obj == null) { return result; } var options = nullValue ? _nullValueSerializeOptions : _defaultSerializeOptions; result = JsonSerializer.Serialize(obj, options); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return result; } /// /// Serialize Object to Json string /// /// /// /// public static string Serialize(object? obj, JsonSerializerOptions? options) { var result = string.Empty; try { if (obj == null) { return result; } result = JsonSerializer.Serialize(obj, options ?? _defaultSerializeOptions); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return result; } /// /// SerializeToNode /// /// /// public static JsonNode? SerializeToNode(object? obj, JsonSerializerOptions? options = null) { return JsonSerializer.SerializeToNode(obj, options); } } ================================================ FILE: v2rayN/ServiceLib/Common/Logging.cs ================================================ using NLog; using NLog.Config; using NLog.Targets; namespace ServiceLib.Common; public class Logging { private static readonly Logger _logger1 = LogManager.GetLogger("Log1"); private static readonly Logger _logger2 = LogManager.GetLogger("Log2"); public static void Setup() { LoggingConfiguration config = new(); FileTarget fileTarget = new(); config.AddTarget("file", fileTarget); fileTarget.Layout = "${longdate}-${level:uppercase=true} ${message}"; fileTarget.FileName = Utils.GetLogPath("${shortdate}.txt"); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget)); LogManager.Configuration = config; } public static void LoggingEnabled(bool enable) { if (!enable) { LogManager.SuspendLogging(); } } public static void SaveLog(string strContent) { if (!LogManager.IsLoggingEnabled()) { return; } _logger1.Info(strContent); } public static void SaveLog(string strTitle, Exception ex) { if (!LogManager.IsLoggingEnabled()) { return; } _logger2.Debug($"{strTitle},{ex.Message}"); _logger2.Debug(ex.StackTrace); if (ex?.InnerException != null) { _logger2.Error(ex.InnerException); } } } ================================================ FILE: v2rayN/ServiceLib/Common/ProcUtils.cs ================================================ namespace ServiceLib.Common; public static class ProcUtils { private static readonly string _tag = "ProcUtils"; public static void ProcessStart(string? fileName, string arguments = "") { _ = ProcessStart(fileName, arguments, null); } public static int? ProcessStart(string? fileName, string arguments, string? dir) { if (fileName.IsNullOrEmpty()) { return null; } try { if (fileName.Contains(' ')) { fileName = fileName.AppendQuotes(); } if (arguments.Contains(' ')) { arguments = arguments.AppendQuotes(); } Process proc = new() { StartInfo = new ProcessStartInfo { UseShellExecute = true, FileName = fileName, Arguments = arguments, WorkingDirectory = dir ?? string.Empty } }; _ = proc.Start(); return dir is null ? null : proc.Id; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return null; } public static void RebootAsAdmin(bool blAdmin = true) { try { ProcessStartInfo startInfo = new() { UseShellExecute = true, Arguments = Global.RebootAs, WorkingDirectory = Utils.StartupPath(), FileName = Utils.GetExePath().AppendQuotes(), Verb = blAdmin ? "runas" : null, }; _ = Process.Start(startInfo); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } } ================================================ FILE: v2rayN/ServiceLib/Common/QRCodeUtils.cs ================================================ using QRCoder; using QRCoder.Exceptions; using SkiaSharp; using ZXing.SkiaSharp; namespace ServiceLib.Common; public class QRCodeUtils { public static byte[]? GenQRCode(string? url) { if (url.IsNullOrEmpty()) { return null; } using QRCodeGenerator qrGenerator = new(); DataTooLongException? lastDtle = null; var levels = new[] { QRCodeGenerator.ECCLevel.H, QRCodeGenerator.ECCLevel.Q, QRCodeGenerator.ECCLevel.M, QRCodeGenerator.ECCLevel.L }; foreach (var level in levels) { try { using var qrCodeData = qrGenerator.CreateQrCode(url, level); using PngByteQRCode qrCode = new(qrCodeData); return qrCode.GetGraphic(20); } catch (DataTooLongException ex) { lastDtle = ex; continue; } catch { throw; } } if (lastDtle != null) { throw lastDtle; } return null; } public static string? ParseBarcode(string? fileName) { if (fileName == null || !File.Exists(fileName)) { return null; } try { var image = SKImage.FromEncodedData(fileName); var bitmap = SKBitmap.FromImage(image); return ReaderBarcode(bitmap); } catch { // ignored } return null; } public static string? ParseBarcode(byte[]? bytes) { try { var bitmap = SKBitmap.Decode(bytes); //using var stream = new FileStream("test2.png", FileMode.Create, FileAccess.Write); //using var image = SKImage.FromBitmap(bitmap); //using var encodedImage = image.Encode(); //encodedImage.SaveTo(stream); return ReaderBarcode(bitmap); } catch { // ignored } return null; } private static string? ReaderBarcode(SKBitmap? bitmap) { var reader = new BarcodeReader(); var result = reader.Decode(bitmap); if (result != null && result.Text.IsNotEmpty()) { return result.Text; } //FlipBitmap var result2 = reader.Decode(FlipBitmap(bitmap)); return result2?.Text; } private static SKBitmap FlipBitmap(SKBitmap bmp) { // Create a bitmap (to return) var flipped = new SKBitmap(bmp.Width, bmp.Height, bmp.Info.ColorType, bmp.Info.AlphaType); // Create a canvas to draw into the bitmap using var canvas = new SKCanvas(flipped); // Set a transform matrix which moves the bitmap to the right, // and then "scales" it by -1, which just flips the pixels // horizontally canvas.Translate(bmp.Width, 0); canvas.Scale(-1, 1); canvas.DrawBitmap(bmp, 0, 0); return flipped; } } ================================================ FILE: v2rayN/ServiceLib/Common/Utils.cs ================================================ using System.Collections.Specialized; using System.Security.Principal; using CliWrap; using CliWrap.Buffered; namespace ServiceLib.Common; public class Utils { private static readonly string _tag = "Utils"; #region Conversion Functions /// /// Convert to comma-separated string /// /// /// /// public static string List2String(List? lst, bool wrap = false) { if (lst == null || lst.Count == 0) { return string.Empty; } var separator = wrap ? "," + Environment.NewLine : ","; try { return string.Join(separator, lst); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return string.Empty; } } /// /// Comma-separated string /// /// /// public static List? String2List(string? str) { if (string.IsNullOrWhiteSpace(str)) { return null; } try { str = str.Replace(Environment.NewLine, string.Empty); return new List(str.Split(',', StringSplitOptions.RemoveEmptyEntries)); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return null; } } /// /// Comma-separated string, sorted and then converted to List /// /// /// public static List? String2ListSorted(string str) { var lst = String2List(str); lst?.Sort(); return lst; } /// /// Base64 Encode /// /// /// /// public static string Base64Encode(string plainText, bool removePadding = false) { try { var plainTextBytes = Encoding.UTF8.GetBytes(plainText); var base64 = Convert.ToBase64String(plainTextBytes); if (removePadding) { base64 = base64.TrimEnd('='); } return base64; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return string.Empty; } /// /// Base64 Decode /// /// /// public static string Base64Decode(string? plainText) { try { if (plainText.IsNullOrEmpty()) { return string.Empty; } plainText = plainText.Trim() .Replace(Environment.NewLine, "") .Replace("\n", "") .Replace("\r", "") .Replace('_', '/') .Replace('-', '+') .Replace(" ", ""); if (plainText.Length % 4 > 0) { plainText = plainText.PadRight(plainText.Length + 4 - (plainText.Length % 4), '='); } var data = Convert.FromBase64String(plainText); return Encoding.UTF8.GetString(data); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return string.Empty; } public static bool ToBool(object obj) { try { return Convert.ToBoolean(obj); } catch { return false; } } public static string ToString(object? obj) { try { return obj?.ToString() ?? string.Empty; } catch { return string.Empty; } } public static string HumanFy(long amount) { if (amount <= 0) { return $"{amount:f1} B"; } string[] units = ["KB", "MB", "GB", "TB", "PB"]; var unitIndex = 0; double size = amount; // Loop and divide by 1024 until a suitable unit is found while (size >= 1024 && unitIndex < units.Length - 1) { size /= 1024; unitIndex++; } return $"{size:f1} {units[unitIndex]}"; } public static string UrlEncode(string url) { return Uri.EscapeDataString(url); } public static string UrlDecode(string url) { return Uri.UnescapeDataString(url); } public static NameValueCollection ParseQueryString(string query) { var result = new NameValueCollection(StringComparer.OrdinalIgnoreCase); if (query.IsNullOrEmpty()) { return result; } var parts = query[1..].Split('&', StringSplitOptions.RemoveEmptyEntries); foreach (var part in parts) { var keyValue = part.Split('='); if (keyValue.Length != 2) { continue; } var key = Uri.UnescapeDataString(keyValue.First()); var val = Uri.UnescapeDataString(keyValue.Last()); if (result[key] is null) { result.Add(key, val); } } return result; } public static string GetMd5(string str) { if (string.IsNullOrEmpty(str)) { return string.Empty; } try { var byteOld = Encoding.UTF8.GetBytes(str); var byteNew = MD5.HashData(byteOld); StringBuilder sb = new(32); foreach (var b in byteNew) { sb.Append(b.ToString("x2")); } return sb.ToString(); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return string.Empty; } } public static string GetFileHash(string filePath) { if (string.IsNullOrEmpty(filePath)) { return string.Empty; } if (!File.Exists(filePath)) { return string.Empty; } try { using var md5 = MD5.Create(); using var stream = File.OpenRead(filePath); var hash = md5.ComputeHash(stream); return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return string.Empty; } } /// /// idn to idc /// /// /// public static string GetPunycode(string url) { if (url.IsNullOrEmpty()) { return url; } try { Uri uri = new(url); if (uri.Host == uri.IdnHost || uri.Host == $"[{uri.IdnHost}]") { return url; } else { return url.Replace(uri.Host, uri.IdnHost); } } catch { return url; } } public static bool IsBase64String(string? plainText) { if (plainText.IsNullOrEmpty()) { return false; } var buffer = new Span(new byte[plainText.Length]); return Convert.TryFromBase64String(plainText, buffer, out var _); } public static string Convert2Comma(string text) { if (text.IsNullOrEmpty()) { return text; } return text.Replace(",", ",").Replace(Environment.NewLine, ","); } public static List GetEnumNames() where TEnum : Enum { return Enum.GetValues(typeof(TEnum)) .Cast() .Select(e => e.ToString()) .ToList(); } public static Dictionary> ParseHostsToDictionary(string? hostsContent) { if (hostsContent.IsNullOrEmpty()) { return new(); } var userHostsMap = hostsContent .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) .Select(line => line.Trim()) // skip full-line comments .Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith('#')) // ensure line still contains valid parts .Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' ')) .Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries)) .Where(parts => parts.Length >= 2) .GroupBy(parts => parts[0]) .ToDictionary( group => group.Key, group => group.SelectMany(parts => parts.Skip(1)).ToList() ); return userHostsMap; } /// /// Parse a possibly non-standard URL into scheme, domain, port, and path. /// If parsing fails, the entire input is returned as domain, and others are empty or zero. /// /// Input URL or string /// (domain, scheme, port, path) public static (string domain, string scheme, int port, string path) ParseUrl(string url) { if (string.IsNullOrWhiteSpace(url)) { return ("", "", 0, ""); } // 1. First, try to parse using the standard Uri class. if (Uri.TryCreate(url, UriKind.Absolute, out var uri) && !string.IsNullOrEmpty(uri.Host)) { var scheme = uri.Scheme; var domain = uri.Host; var port = uri.IsDefaultPort ? 0 : uri.Port; var path = uri.PathAndQuery; return (domain, scheme, port, path); } // 2. Try to handle more general cases with a regular expression, including non-standard schemes. // This regex captures the scheme (optional), authority (host+port), and path (optional). var match = Regex.Match(url, @"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):/{2,})?([^/?#]+)([^?#]*)?.*$"); if (match.Success) { var scheme = match.Groups[1].Value; var authority = match.Groups[2].Value; var path = match.Groups[3].Value; // Remove userinfo from the authority part. var atIndex = authority.LastIndexOf('@'); if (atIndex > 0) { authority = authority.Substring(atIndex + 1); } var (domain, port) = ParseAuthority(authority); // If the parsed domain is empty, it means the authority part is malformed, so trigger the fallback. if (!string.IsNullOrEmpty(domain)) { return (domain, scheme, port, path); } } // 3. If all of the above fails, execute the final fallback strategy. return (url, "", 0, ""); } /// /// Helper function to parse domain and port from the authority part, with correct handling for IPv6. /// private static (string domain, int port) ParseAuthority(string authority) { if (string.IsNullOrEmpty(authority)) { return ("", 0); } var port = 0; var domain = authority; // Handle IPv6 addresses, e.g., "[2001:db8::1]:443" if (authority.StartsWith('[') && authority.Contains(']')) { var closingBracketIndex = authority.LastIndexOf(']'); if (closingBracketIndex < authority.Length - 1 && authority[closingBracketIndex + 1] == ':') { // Port exists var portStr = authority.Substring(closingBracketIndex + 2); if (int.TryParse(portStr, out var portNum)) { port = portNum; } domain = authority.Substring(0, closingBracketIndex + 1); } else { // No port domain = authority; } } else // Handle IPv4 or domain names { var lastColonIndex = authority.LastIndexOf(':'); // Ensure there are digits after the colon and that this colon is not part of an IPv6 address. if (lastColonIndex > 0 && lastColonIndex < authority.Length - 1 && authority.Substring(lastColonIndex + 1).All(char.IsDigit)) { var portStr = authority.Substring(lastColonIndex + 1); if (int.TryParse(portStr, out var portNum)) { port = portNum; domain = authority.Substring(0, lastColonIndex); } } } return (domain, port); } public static string? DomainStrategy4Sbox(string? strategy) { return strategy switch { not null when strategy.StartsWith("UseIPv4") => "prefer_ipv4", not null when strategy.StartsWith("UseIPv6") => "prefer_ipv6", not null when strategy.StartsWith("ForceIPv4") => "ipv4_only", not null when strategy.StartsWith("ForceIPv6") => "ipv6_only", _ => null }; } #endregion Conversion Functions #region Data Checks /// /// Determine if the input is a number /// /// /// public static bool IsNumeric(string oText) { return oText.All(char.IsNumber); } /// /// Validate if the domain address is valid /// /// public static bool IsDomain(string? domain) { if (domain.IsNullOrEmpty()) { return false; } var ext = Path.GetExtension(domain); if (ext.IsNotEmpty() && ext[1..].ToLowerInvariant() is "json" or "txt" or "xml" or "cfg" or "ini" or "log" or "yaml" or "yml" or "toml") { return false; } return Uri.CheckHostName(domain) == UriHostNameType.Dns; } public static bool IsIpv6(string ip) { if (IPAddress.TryParse(ip, out var address)) { return address.AddressFamily switch { AddressFamily.InterNetwork => false, AddressFamily.InterNetworkV6 => true, _ => false, }; } return false; } public static bool IsIpAddress(string? ip) { if (ip.IsNullOrEmpty()) { return false; } ip = ip.Trim(); // First, validate using built-in parser if (!IPAddress.TryParse(ip, out var address)) { return false; } // For IPv4: ensure it has exactly 3 dots (meaning 4 parts) if (address.AddressFamily == AddressFamily.InterNetwork) { return ip.Count(c => c == '.') == 3; } // For IPv6: TryParse is already strict enough return address.AddressFamily == AddressFamily.InterNetworkV6; } public static Uri? TryUri(string url) { try { return new Uri(url); } catch (UriFormatException) { return null; } } public static bool IsPrivateNetwork(string ip) { if (IPAddress.TryParse(ip, out var address)) { // Loopback address check (127.0.0.1 for IPv4, ::1 for IPv6) if (IPAddress.IsLoopback(address)) { return true; } var ipBytes = address.GetAddressBytes(); if (address.AddressFamily == AddressFamily.InterNetwork) { // IPv4 private address check if (ipBytes[0] == 10) { return true; } if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31) { return true; } if (ipBytes[0] == 192 && ipBytes[1] == 168) { return true; } } else if (address.AddressFamily == AddressFamily.InterNetworkV6) { // IPv6 private address check // Link-local address fe80::/10 if (ipBytes[0] == 0xfe && (ipBytes[1] & 0xc0) == 0x80) { return true; } // Unique local address fc00::/7 (typically fd00::/8) if ((ipBytes[0] & 0xfe) == 0xfc) { return true; } // Private portion in IPv4-mapped addresses ::ffff:0:0/96 if (address.IsIPv4MappedToIPv6) { var ipv4Bytes = ipBytes.Skip(12).ToArray(); if (ipv4Bytes[0] == 10) { return true; } if (ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31) { return true; } if (ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168) { return true; } } } } return false; } #endregion Data Checks #region Speed Test private static bool PortInUse(int port) { try { var (lstIpEndPoints, lstTcpConns) = GetActiveNetworkInfo(); if (lstIpEndPoints?.FindIndex(it => it.Port == port) >= 0) { return true; } if (lstTcpConns?.FindIndex(it => it.LocalEndPoint.Port == port) >= 0) { return true; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return false; } public static int GetFreePort(int defaultPort = 0) { try { if (!(defaultPort == 0 || Utils.PortInUse(defaultPort))) { return defaultPort; } TcpListener l = new(IPAddress.Loopback, 0); l.Start(); var port = ((IPEndPoint)l.LocalEndpoint).Port; l.Stop(); return port; } catch { } return 59090; } public static (List endpoints, List connections) GetActiveNetworkInfo() { var endpoints = new List(); var connections = new List(); try { var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); endpoints.AddRange(ipGlobalProperties.GetActiveTcpListeners()); endpoints.AddRange(ipGlobalProperties.GetActiveUdpListeners()); connections.AddRange(ipGlobalProperties.GetActiveTcpConnections()); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return (endpoints, connections); } #endregion Speed Test #region Miscellaneous public static bool UpgradeAppExists(out string upgradeFileName) { upgradeFileName = Path.Combine(GetBaseDirectory(), GetExeName("AmazTool")); return File.Exists(upgradeFileName); } /// /// Get version /// /// public static string GetVersion(bool blFull = true) { try { return blFull ? $"{Global.AppName} - V{GetVersionInfo()} - {RuntimeInformation.ProcessArchitecture}" : $"{Global.AppName}/{GetVersionInfo()}"; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return Global.AppName; } public static string GetVersionInfo() { try { return Assembly.GetExecutingAssembly()?.GetName()?.Version?.ToString(3) ?? "0.0"; } catch (Exception ex) { Logging.SaveLog(_tag, ex); return "0.0"; } } public static string GetRuntimeInfo() { return $"{Utils.GetVersion()} | {Utils.StartupPath()} | {Utils.GetExePath()} | {Environment.OSVersion}"; } /// /// GUID /// /// public static string GetGuid(bool full = true) { try { if (full) { return Guid.NewGuid().ToString("D"); } else { return BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0).ToString(); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return string.Empty; } public static bool IsGuidByParse(string strSrc) { return Guid.TryParse(strSrc, out _); } private static Dictionary GetSystemHosts(string hostFile) { var systemHosts = new Dictionary(); try { if (!File.Exists(hostFile)) { return systemHosts; } var hosts = File.ReadAllText(hostFile).Replace("\r", ""); var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (var host in hostsList) { // Trim whitespace var line = host.Trim(); // Skip comments and empty lines if (line.IsNullOrEmpty() || line.StartsWith("#")) { continue; } // Strip inline comments var commentIndex = line.IndexOf('#'); if (commentIndex >= 0) { line = line.Substring(0, commentIndex).Trim(); } if (line.IsNullOrEmpty()) { continue; } var hostItem = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); if (hostItem.Length < 2) { continue; } var ipAddress = hostItem[0]; var domain = hostItem[1]; // Validate IP address if (!IsIpAddress(ipAddress)) { continue; } // Validate domain name if (domain.IsNullOrEmpty() || domain.Length > 255) { continue; } systemHosts[domain] = ipAddress; } return systemHosts; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return systemHosts; } public static Dictionary GetSystemHosts() { var hosts = GetSystemHosts(@"C:\Windows\System32\drivers\etc\hosts"); var hostsIcs = GetSystemHosts(@"C:\Windows\System32\drivers\etc\hosts.ics"); foreach (var (key, value) in hostsIcs) { hosts[key] = value; } return hosts; } public static async Task GetCliWrapOutput(string filePath, string? arg) { return await GetCliWrapOutput(filePath, arg != null ? new List() { arg } : null); } public static async Task GetCliWrapOutput(string filePath, IEnumerable? args) { try { var cmd = Cli.Wrap(filePath); if (args != null) { if (args.Count() == 1) { cmd = cmd.WithArguments(args.First()); } else { cmd = cmd.WithArguments(args); } } var result = await cmd.ExecuteBufferedAsync(); if (result.IsSuccess) { return result.StandardOutput ?? ""; } Logging.SaveLog(result.ToString() ?? ""); } catch (Exception ex) { Logging.SaveLog("GetCliWrapOutput", ex); } return null; } #endregion Miscellaneous #region TempPath public static bool HasWritePermission() { try { var basePath = GetBaseDirectory(); //When this file exists, it is equivalent to having no permission to read and write if (File.Exists(Path.Combine(basePath, "NotStoreConfigHere.txt"))) { return false; } //Check if it is installed by Windows WinGet if (IsWindows() && basePath.Contains("Users") && basePath.Contains("WinGet")) { return false; } var tempPath = Path.Combine(basePath, "guiTemps"); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } var fileName = Path.Combine(tempPath, GetGuid()); File.Create(fileName).Close(); File.Delete(fileName); } catch (Exception) { return false; } return true; } public static string GetPath(string fileName) { var startupPath = StartupPath(); if (fileName.IsNullOrEmpty()) { return startupPath; } return Path.Combine(startupPath, fileName); } public static string GetBaseDirectory(string fileName = "") { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); } public static string GetExePath() { return Environment.ProcessPath ?? Process.GetCurrentProcess().MainModule?.FileName ?? string.Empty; } public static string StartupPath() { if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1") { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "v2rayN"); } return GetBaseDirectory(); } public static string GetTempPath(string filename = "") { var tempPath = Path.Combine(StartupPath(), "guiTemps"); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } if (filename.IsNullOrEmpty()) { return tempPath; } else { return Path.Combine(tempPath, filename); } } public static string GetBackupPath(string filename) { var tempPath = Path.Combine(StartupPath(), "guiBackups"); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } return Path.Combine(tempPath, filename); } public static string GetConfigPath(string filename = "") { var tempPath = Path.Combine(StartupPath(), "guiConfigs"); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } if (filename.IsNullOrEmpty()) { return tempPath; } else { return Path.Combine(tempPath, filename); } } public static string GetBinPath(string filename, string? coreType = null) { var tempPath = Path.Combine(StartupPath(), "bin"); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } if (coreType != null) { tempPath = Path.Combine(tempPath, coreType.ToLower().ToString()); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } } if (filename.IsNullOrEmpty()) { return tempPath; } else { return Path.Combine(tempPath, filename); } } public static string GetLogPath(string filename = "") { var tempPath = Path.Combine(StartupPath(), "guiLogs"); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } if (filename.IsNullOrEmpty()) { return tempPath; } else { return Path.Combine(tempPath, filename); } } public static string GetFontsPath(string filename = "") { var tempPath = Path.Combine(StartupPath(), "guiFonts"); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } if (filename.IsNullOrEmpty()) { return tempPath; } else { return Path.Combine(tempPath, filename); } } public static string GetBinConfigPath(string filename = "") { var tempPath = Path.Combine(StartupPath(), "binConfigs"); if (!Directory.Exists(tempPath)) { Directory.CreateDirectory(tempPath); } if (filename.IsNullOrEmpty()) { return tempPath; } else { return Path.Combine(tempPath, filename); } } #endregion TempPath #region Platform public static bool IsWindows() => OperatingSystem.IsWindows(); public static bool IsLinux() => OperatingSystem.IsLinux(); public static bool IsMacOS() => OperatingSystem.IsMacOS(); public static bool IsNonWindows() => !OperatingSystem.IsWindows(); public static string GetExeName(string name) { if (name.IsNullOrEmpty() || IsNonWindows()) { return name; } if (name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { return name; } else { return $"{name}.exe"; } } public static bool IsAdministrator() { if (IsWindows()) { return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); } return false; } public static bool IsPackagedInstall() { try { if (IsWindows() || IsMacOS()) { return false; } var exePath = GetExePath(); var baseDir = string.IsNullOrEmpty(exePath) ? StartupPath() : Path.GetDirectoryName(exePath) ?? ""; var p = baseDir.Replace('\\', '/'); if (string.IsNullOrEmpty(p)) { return false; } if (p.StartsWith("/opt/v2rayN", StringComparison.OrdinalIgnoreCase)) { return true; } if (p.StartsWith("/usr/lib/v2rayN", StringComparison.OrdinalIgnoreCase)) { return true; } if (p.StartsWith("/usr/share/v2rayN", StringComparison.OrdinalIgnoreCase)) { return true; } } catch { } return false; } private static async Task GetLinuxUserId() { var arg = new List() { "-c", "id -u" }; return await GetCliWrapOutput(Global.LinuxBash, arg); } public static async Task SetLinuxChmod(string? fileName) { if (fileName.IsNullOrEmpty()) { return null; } if (SetUnixFileMode(fileName)) { Logging.SaveLog($"Successfully set the file execution permission, {fileName}"); return string.Empty; } if (fileName.Contains(' ')) { fileName = fileName.AppendQuotes(); } var arg = new List() { "-c", $"chmod +x {fileName}" }; return await GetCliWrapOutput(Global.LinuxBash, arg); } public static bool SetUnixFileMode(string? fileName) { try { if (fileName.IsNullOrEmpty()) { return false; } if (File.Exists(fileName)) { var currentMode = File.GetUnixFileMode(fileName); File.SetUnixFileMode(fileName, currentMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute); return true; } } catch (Exception ex) { Logging.SaveLog("SetUnixFileMode", ex); } return false; } public static async Task GetLinuxFontFamily(string lang) { // var arg = new List() { "-c", $"fc-list :lang={lang} family" }; var arg = new List() { "-c", $"fc-list : family" }; return await GetCliWrapOutput(Global.LinuxBash, arg); } public static string? GetHomePath() { return IsWindows() ? Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%") : Environment.GetEnvironmentVariable("HOME"); } #endregion Platform } ================================================ FILE: v2rayN/ServiceLib/Common/WindowsUtils.cs ================================================ using Microsoft.Win32; namespace ServiceLib.Common; internal static class WindowsUtils { private static readonly string _tag = "WindowsUtils"; public static string? RegReadValue(string path, string name, string def) { RegistryKey? regKey = null; try { regKey = Registry.CurrentUser.OpenSubKey(path, false); var value = regKey?.GetValue(name) as string; return value.IsNullOrEmpty() ? def : value; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } finally { regKey?.Close(); } return def; } public static void RegWriteValue(string path, string name, object value) { RegistryKey? regKey = null; try { regKey = Registry.CurrentUser.CreateSubKey(path); if (value.ToString().IsNullOrEmpty()) { regKey?.DeleteValue(name, false); } else { regKey?.SetValue(name, value); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } finally { regKey?.Close(); } } public static async Task RemoveTunDevice() { try { var sum = MD5.HashData(Encoding.UTF8.GetBytes("wintunsingbox_tun")); var guid = new Guid(sum); var pnpUtilPath = @"C:\Windows\System32\pnputil.exe"; var arg = $$""" /remove-device "SWD\Wintun\{{{guid}}}" """; // Try to remove the device _ = await Utils.GetCliWrapOutput(pnpUtilPath, arg); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } } ================================================ FILE: v2rayN/ServiceLib/Common/YamlUtils.cs ================================================ using YamlDotNet.Core; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; namespace ServiceLib.Common; public class YamlUtils { private static readonly string _tag = "YamlUtils"; #region YAML /// /// Deserialize /// /// /// /// public static T FromYaml(string str) { var deserializer = new DeserializerBuilder() .WithNamingConvention(PascalCaseNamingConvention.Instance) .Build(); try { var obj = deserializer.Deserialize(str); return obj; } catch (Exception ex) { Logging.SaveLog(_tag, ex); return deserializer.Deserialize(""); } } /// /// Serialize /// /// /// public static string ToYaml(object? obj) { var result = string.Empty; if (obj == null) { return result; } var serializer = new SerializerBuilder() .WithNamingConvention(HyphenatedNamingConvention.Instance) .Build(); try { result = serializer.Serialize(obj); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return result; } public static string? PreprocessYaml(string str) { try { var mergingParser = new MergingParser(new Parser(new StringReader(str))); var obj = new DeserializerBuilder().Build().Deserialize(mergingParser); return ToYaml(obj); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return null; } } #endregion YAML } ================================================ FILE: v2rayN/ServiceLib/Enums/EConfigType.cs ================================================ namespace ServiceLib.Enums; public enum EConfigType { VMess = 1, Custom = 2, Shadowsocks = 3, SOCKS = 4, VLESS = 5, Trojan = 6, Hysteria2 = 7, TUIC = 8, WireGuard = 9, HTTP = 10, Anytls = 11, PolicyGroup = 101, ProxyChain = 102, } ================================================ FILE: v2rayN/ServiceLib/Enums/ECoreType.cs ================================================ namespace ServiceLib.Enums; public enum ECoreType { v2fly = 1, Xray = 2, v2fly_v5 = 4, mihomo = 13, hysteria = 21, naiveproxy = 22, tuic = 23, sing_box = 24, juicity = 25, hysteria2 = 26, brook = 27, overtls = 28, shadowquic = 29, mieru = 30, v2rayN = 99 } ================================================ FILE: v2rayN/ServiceLib/Enums/EGirdOrientation.cs ================================================ namespace ServiceLib.Enums; public enum EGirdOrientation { Horizontal, Vertical, Tab, } ================================================ FILE: v2rayN/ServiceLib/Enums/EGlobalHotkey.cs ================================================ namespace ServiceLib.Enums; public enum EGlobalHotkey { ShowForm = 0, SystemProxyClear = 1, SystemProxySet = 2, SystemProxyUnchanged = 3, SystemProxyPac = 4, } ================================================ FILE: v2rayN/ServiceLib/Enums/EInboundProtocol.cs ================================================ namespace ServiceLib.Enums; public enum EInboundProtocol { socks = 0, socks2, socks3, pac, api, api2, mixed, speedtest = 21 } ================================================ FILE: v2rayN/ServiceLib/Enums/EMove.cs ================================================ namespace ServiceLib.Enums; public enum EMove { Top = 1, Up = 2, Down = 3, Bottom = 4, Position = 5 } ================================================ FILE: v2rayN/ServiceLib/Enums/EMultipleLoad.cs ================================================ namespace ServiceLib.Enums; public enum EMultipleLoad { LeastPing, Fallback, Random, RoundRobin, LeastLoad } ================================================ FILE: v2rayN/ServiceLib/Enums/EPresetType.cs ================================================ namespace ServiceLib.Enums; public enum EPresetType { Default = 0, Russia = 1, Iran = 2, } ================================================ FILE: v2rayN/ServiceLib/Enums/ERuleMode.cs ================================================ namespace ServiceLib.Enums; public enum ERuleMode { Rule = 0, Global = 1, Direct = 2, Unchanged = 3 } ================================================ FILE: v2rayN/ServiceLib/Enums/ERuleType.cs ================================================ namespace ServiceLib.Enums; public enum ERuleType { ALL = 0, Routing = 1, DNS = 2, } ================================================ FILE: v2rayN/ServiceLib/Enums/EServerColName.cs ================================================ namespace ServiceLib.Enums; public enum EServerColName { Def = 0, ConfigType, Remarks, Address, Port, Network, StreamSecurity, SubRemarks, DelayVal, SpeedVal, TodayDown, TodayUp, TotalDown, TotalUp } ================================================ FILE: v2rayN/ServiceLib/Enums/ESpeedActionType.cs ================================================ namespace ServiceLib.Enums; public enum ESpeedActionType { Tcping, Realping, Speedtest, Mixedtest, FastRealping } ================================================ FILE: v2rayN/ServiceLib/Enums/ESysProxyType.cs ================================================ namespace ServiceLib.Enums; public enum ESysProxyType { ForcedClear = 0, ForcedChange = 1, Unchanged = 2, Pac = 3 } ================================================ FILE: v2rayN/ServiceLib/Enums/ETheme.cs ================================================ namespace ServiceLib.Enums; public enum ETheme { FollowSystem, Dark, Light, Aquatic, Desert, Dusk, NightSky } ================================================ FILE: v2rayN/ServiceLib/Enums/ETransport.cs ================================================ namespace ServiceLib.Enums; public enum ETransport { tcp, kcp, ws, httpupgrade, xhttp, h2, http, quic, grpc } ================================================ FILE: v2rayN/ServiceLib/Enums/EViewAction.cs ================================================ namespace ServiceLib.Enums; public enum EViewAction { CloseWindow, ShowYesNo, SaveFileDialog, AddBatchRoutingRulesYesNo, SetClipboardData, AddServerViaClipboard, ImportRulesFromClipboard, ProfilesFocus, ShareSub, ShareServer, ScanScreenTask, ScanImageTask, BrowseServer, ImportRulesFromFile, InitSettingFont, PasswordInput, SubEditWindow, RoutingRuleSettingWindow, RoutingRuleDetailsWindow, AddServerWindow, AddServer2Window, AddGroupServerWindow, DNSSettingWindow, RoutingSettingWindow, OptionSettingWindow, FullConfigTemplateWindow, GlobalHotkeySettingWindow, SubSettingWindow, DispatcherRefreshServersBiz, DispatcherRefreshIcon, DispatcherShowMsg, } ================================================ FILE: v2rayN/ServiceLib/Events/AppEvents.cs ================================================ namespace ServiceLib.Events; public static class AppEvents { public static readonly EventChannel ReloadRequested = new(); public static readonly EventChannel ShowHideWindowRequested = new(); public static readonly EventChannel AddServerViaScanRequested = new(); public static readonly EventChannel AddServerViaClipboardRequested = new(); public static readonly EventChannel SubscriptionsUpdateRequested = new(); public static readonly EventChannel ProfilesRefreshRequested = new(); public static readonly EventChannel SubscriptionsRefreshRequested = new(); public static readonly EventChannel ProxiesReloadRequested = new(); public static readonly EventChannel DispatcherStatisticsRequested = new(); public static readonly EventChannel SendSnackMsgRequested = new(); public static readonly EventChannel SendMsgViewRequested = new(); public static readonly EventChannel AppExitRequested = new(); public static readonly EventChannel ShutdownRequested = new(); public static readonly EventChannel AdjustMainLvColWidthRequested = new(); public static readonly EventChannel SetDefaultServerRequested = new(); public static readonly EventChannel RoutingsMenuRefreshRequested = new(); public static readonly EventChannel TestServerRequested = new(); public static readonly EventChannel InboundDisplayRequested = new(); public static readonly EventChannel SysProxyChangeRequested = new(); } ================================================ FILE: v2rayN/ServiceLib/Events/EventChannel.cs ================================================ using System.Reactive.Subjects; namespace ServiceLib.Events; public sealed class EventChannel { private readonly ISubject _subject = Subject.Synchronize(new Subject()); public IObservable AsObservable() { return _subject.AsObservable(); } public void Publish(T value) { _subject.OnNext(value); } public void Publish() { if (typeof(T) != typeof(Unit)) { throw new InvalidOperationException("Publish() without value is only valid for EventChannel."); } _subject.OnNext((T)(object)Unit.Default); } } ================================================ FILE: v2rayN/ServiceLib/FodyWeavers.xml ================================================  ================================================ FILE: v2rayN/ServiceLib/Global.cs ================================================ namespace ServiceLib; public class Global { #region const public const string AppName = "v2rayN"; public const string GithubUrl = "https://github.com"; public const string GithubApiUrl = "https://api.github.com/repos"; public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat"; public const string SingboxRulesetUrl = @"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-{0}/{1}.srs"; public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="; public const string ConfigFileName = "guiNConfig.json"; public const string CoreConfigFileName = "config.json"; public const string CorePreConfigFileName = "configPre.json"; public const string CoreSpeedtestConfigFileName = "configTest{0}.json"; public const string ClashMixinConfigFileName = "Mixin.yaml"; public const string NamespaceSample = "ServiceLib.Sample."; public const string V2raySampleClient = NamespaceSample + "SampleClientConfig"; public const string SingboxSampleClient = NamespaceSample + "SingboxSampleClientConfig"; public const string V2raySampleHttpRequestFileName = NamespaceSample + "SampleHttpRequest"; public const string V2raySampleHttpResponseFileName = NamespaceSample + "SampleHttpResponse"; public const string V2raySampleInbound = NamespaceSample + "SampleInbound"; public const string V2raySampleOutbound = NamespaceSample + "SampleOutbound"; public const string SingboxSampleOutbound = NamespaceSample + "SingboxSampleOutbound"; public const string CustomRoutingFileName = NamespaceSample + "custom_routing_"; public const string TunSingboxDNSFileName = NamespaceSample + "tun_singbox_dns"; public const string TunSingboxInboundFileName = NamespaceSample + "tun_singbox_inbound"; public const string TunSingboxRulesFileName = NamespaceSample + "tun_singbox_rules"; public const string DNSV2rayNormalFileName = NamespaceSample + "dns_v2ray_normal"; public const string DNSSingboxNormalFileName = NamespaceSample + "dns_singbox_normal"; public const string ClashMixinYaml = NamespaceSample + "clash_mixin_yaml"; public const string ClashTunYaml = NamespaceSample + "clash_tun_yaml"; public const string LinuxAutostartConfig = NamespaceSample + "linux_autostart_config"; public const string PacFileName = NamespaceSample + "pac"; public const string ProxySetOSXShellFileName = NamespaceSample + "proxy_set_osx_sh"; public const string ProxySetLinuxShellFileName = NamespaceSample + "proxy_set_linux_sh"; public const string KillAsSudoOSXShellFileName = NamespaceSample + "kill_as_sudo_osx_sh"; public const string KillAsSudoLinuxShellFileName = NamespaceSample + "kill_as_sudo_linux_sh"; public const string SingboxFakeIPFilterFileName = NamespaceSample + "singbox_fakeip_filter"; public const string DefaultSecurity = "auto"; public const string DefaultNetwork = "tcp"; public const string TcpHeaderHttp = "http"; public const string None = "none"; public const string ProxyTag = "proxy"; public const string DirectTag = "direct"; public const string BlockTag = "block"; public const string DnsTag = "dns-module"; public const string DirectDnsTag = "direct-dns"; public const string BalancerTagSuffix = "-round"; public const string StreamSecurity = "tls"; public const string StreamSecurityReality = "reality"; public const string Loopback = "127.0.0.1"; public const string InboundAPIProtocol = "dokodemo-door"; public const string HttpProtocol = "http://"; public const string HttpsProtocol = "https://"; public const string SocksProtocol = "socks://"; public const string Socks5Protocol = "socks5://"; public const string AsIs = "AsIs"; public const string IPIfNonMatch = "IPIfNonMatch"; public const string IPOnDemand = "IPOnDemand"; public const string UserEMail = "t@t.tt"; public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run"; public const string AutoRunName = "v2rayNAutoRun"; public const string SystemProxyExceptionsWindows = "localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*"; public const string SystemProxyExceptionsLinux = "localhost,127.0.0.0/8,::1"; public const string RoutingRuleComma = ""; public const string GrpcGunMode = "gun"; public const string GrpcMultiMode = "multi"; public const int MaxPort = 65536; public const int MinFontSize = 8; public const int MinFontSizeCount = 13; public const string RebootAs = "rebootas"; public const string AvaAssets = "avares://v2rayN/Assets/"; public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2"; public const string V2RayLocalAsset = "V2RAY_LOCATION_ASSET"; public const string XrayLocalAsset = "XRAY_LOCATION_ASSET"; public const string XrayLocalCert = "XRAY_LOCATION_CERT"; public const int SpeedTestPageSize = 1000; public const string LinuxBash = "/bin/bash"; public const string SingboxDirectDNSTag = "direct_dns"; public const string SingboxRemoteDNSTag = "remote_dns"; public const string SingboxLocalDNSTag = "local_local"; public const string SingboxHostsDNSTag = "hosts_dns"; public const string SingboxFakeDNSTag = "fake_dns"; public const int Hysteria2DefaultHopInt = 10; public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset"; public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$"; public static readonly List PolicyGroupDefaultFilterList = [ // All nodes (exclude traffic/expiry info) PolicyGroupDefaultAllFilter, // Low multiplier nodes, e.g. ×0.1, 0.5x, 0.1倍 @"^.*(?:[×xX✕*]\s*0\.[0-9]+|0\.[0-9]+\s*[×xX✕*倍]).*$", // Dedicated line nodes, e.g. IPLC, IEPL $@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:专线|IPLC|IEPL|中转).*$", // Japan nodes $@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan).*$", ]; public static readonly List IEProxyProtocols = [ "{ip}:{http_port}", "socks={ip}:{socks_port}", "http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}", "http=http://{ip}:{http_port};https=http://{ip}:{http_port}", "" ]; public static readonly List SubConvertUrls = [ @"https://sub.xeton.dev/sub?url={0}", @"https://api.dler.io/sub?url={0}", @"http://127.0.0.1:25500/sub?url={0}", "" ]; public static readonly List SubConvertConfig = [ @"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini" ]; public static readonly List SubConvertTargets = [ "", "mixed", "v2ray", "clash", "ss" ]; public static readonly List SpeedTestUrls = [ @"https://cachefly.cachefly.net/50mb.test", @"https://speed.cloudflare.com/__down?bytes=10000000", @"https://speed.cloudflare.com/__down?bytes=50000000", @"https://speed.cloudflare.com/__down?bytes=100000000", ]; public static readonly List SpeedPingTestUrls = [ @"https://www.google.com/generate_204", @"https://www.gstatic.com/generate_204", @"https://www.apple.com/library/test/success.html", @"http://www.msftconnecttest.com/connecttest.txt" ]; public static readonly List GeoFilesSources = [ "", @"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/{0}.dat", @"https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/{0}.dat" ]; public static readonly List SingboxRulesetSources = [ "", @"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs", @"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs" ]; public static readonly List RoutingRulesSources = [ "", @"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/template.json", @"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/template.json" ]; public static readonly List DNSTemplateSources = [ "", @"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/", @"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/" ]; public static readonly Dictionary UserAgentTexts = new() { {"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" }, {"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" }, {"safari","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" }, {"edge","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70" }, {"none",""} }; public const string Hysteria2ProtocolShare = "hy2://"; public static readonly Dictionary ProtocolShares = new() { { EConfigType.VMess, "vmess://" }, { EConfigType.Shadowsocks, "ss://" }, { EConfigType.SOCKS, "socks://" }, { EConfigType.VLESS, "vless://" }, { EConfigType.Trojan, "trojan://" }, { EConfigType.Hysteria2, "hysteria2://" }, { EConfigType.TUIC, "tuic://" }, { EConfigType.WireGuard, "wireguard://" }, { EConfigType.Anytls, "anytls://" } }; public static readonly Dictionary ProtocolTypes = new() { { EConfigType.VMess, "vmess" }, { EConfigType.Shadowsocks, "shadowsocks" }, { EConfigType.SOCKS, "socks" }, { EConfigType.HTTP, "http" }, { EConfigType.VLESS, "vless" }, { EConfigType.Trojan, "trojan" }, { EConfigType.Hysteria2, "hysteria2" }, { EConfigType.TUIC, "tuic" }, { EConfigType.WireGuard, "wireguard" }, { EConfigType.Anytls, "anytls" } }; public static readonly List VmessSecurities = [ "aes-128-gcm", "chacha20-poly1305", "auto", "none", "zero" ]; public static readonly List SsSecurities = [ "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "none", "plain" ]; public static readonly List SsSecuritiesInXray = [ "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "none", "plain", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" ]; public static readonly List SsSecuritiesInSingbox = [ "aes-256-gcm", "aes-192-gcm", "aes-128-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "none", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20" ]; public static readonly List Flows = [ "", "xtls-rprx-vision", "xtls-rprx-vision-udp443" ]; public static readonly List Networks = [ "tcp", "kcp", "ws", "httpupgrade", "xhttp", "h2", "quic", "grpc" ]; public static readonly List KcpHeaderTypes = [ "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns" ]; public static readonly Dictionary KcpHeaderMaskMap = new() { { "srtp", "header-srtp" }, { "utp", "header-utp" }, { "wechat-video", "header-wechat" }, { "dtls", "header-dtls" }, { "wireguard", "header-wireguard" }, { "dns", "header-dns" } }; public static readonly List CoreTypes = [ "Xray", "sing_box" ]; public static readonly HashSet XraySupportConfigType = [ EConfigType.VMess, EConfigType.VLESS, EConfigType.Shadowsocks, EConfigType.Trojan, EConfigType.Hysteria2, EConfigType.WireGuard, EConfigType.SOCKS, EConfigType.HTTP, ]; public static readonly HashSet SingboxSupportConfigType = [ EConfigType.VMess, EConfigType.VLESS, EConfigType.Shadowsocks, EConfigType.Trojan, EConfigType.Hysteria2, EConfigType.TUIC, EConfigType.Anytls, EConfigType.WireGuard, EConfigType.SOCKS, EConfigType.HTTP, ]; public static readonly HashSet SingboxOnlyConfigType = SingboxSupportConfigType.Except(XraySupportConfigType).ToHashSet(); public static readonly List DomainStrategies = [ AsIs, IPIfNonMatch, IPOnDemand ]; public static readonly List DomainStrategies4Sbox = [ "", "prefer_ipv4", "prefer_ipv6", "ipv4_only", "ipv6_only" ]; public static readonly List Fingerprints = [ "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random", "randomized", "" ]; public static readonly List UserAgent = [ "chrome", "firefox", "safari", "edge", "none" ]; public static readonly List XhttpMode = [ "auto", "packet-up", "stream-up", "stream-one" ]; public static readonly List AllowInsecure = [ "true", "false", "" ]; public static readonly List DomainStrategy = [ "AsIs", "UseIP", "UseIPv4v6", "UseIPv6v4", "UseIPv4", "UseIPv6", "" ]; public static readonly List DomainDirectDNSAddress = [ "https://dns.alidns.com/dns-query", "https://doh.pub/dns-query", "https://dns.alidns.com/dns-query,https://doh.pub/dns-query", "223.5.5.5", "119.29.29.29", "localhost" ]; public static readonly List DomainRemoteDNSAddress = [ "https://cloudflare-dns.com/dns-query", "https://dns.google/dns-query", "https://cloudflare-dns.com/dns-query,https://dns.google/dns-query,8.8.8.8", "https://dns.cloudflare.com/dns-query", "https://doh.dns.sb/dns-query", "https://doh.opendns.com/dns-query", "https://common.dot.dns.yandex.net", "8.8.8.8", "1.1.1.1", "185.222.222.222", "208.67.222.222", "77.88.8.8" ]; public static readonly List DomainPureIPDNSAddress = [ "223.5.5.5", "119.29.29.29", "localhost" ]; public static readonly List Languages = [ "zh-Hans", "zh-Hant", "en", "fa-Ir", "fr", "ru", "hu" ]; public static readonly List Alpns = [ "h3", "h2", "http/1.1", "h3,h2", "h2,http/1.1", "h3,h2,http/1.1", "" ]; public static readonly List LogLevels = [ "debug", "info", "warning", "error", "none" ]; public static readonly Dictionary LogLevelColors = new() { { "debug", "#6C757D" }, { "info", "#2ECC71" }, { "warning", "#FFA500" }, { "error", "#E74C3C" }, }; public static readonly List InboundTags = [ "socks", "socks2", "socks3" ]; public static readonly List RuleProtocols = [ "http", "tls", "bittorrent" ]; public static readonly List RuleNetworks = [ "", "tcp", "udp", "tcp,udp" ]; public static readonly List destOverrideProtocols = [ "http", "tls", "quic", "fakedns", "fakedns+others" ]; public static readonly List TunMtus = [ 1280, 1408, 1500, 4064, 9000, 65535 ]; public static readonly List TunStacks = [ "gvisor", "system", "mixed" ]; public static readonly List PresetMsgFilters = [ "proxy", "direct", "block", "" ]; public static readonly List SingboxMuxs = [ "h2mux", "smux", "yamux", "" ]; public static readonly List TuicCongestionControls = [ "cubic", "new_reno", "bbr" ]; public static readonly List allowSelectType = [ "selector", "urltest", "loadbalance", "fallback" ]; public static readonly List notAllowTestType = [ "selector", "urltest", "direct", "reject", "compatible", "pass", "loadbalance", "fallback" ]; public static readonly List proxyVehicleType = [ "file", "http" ]; public static readonly Dictionary CoreUrls = new() { { ECoreType.v2fly, "v2fly/v2ray-core" }, { ECoreType.v2fly_v5, "v2fly/v2ray-core" }, { ECoreType.Xray, "XTLS/Xray-core" }, { ECoreType.sing_box, "SagerNet/sing-box" }, { ECoreType.mihomo, "MetaCubeX/mihomo" }, { ECoreType.hysteria, "apernet/hysteria" }, { ECoreType.hysteria2, "apernet/hysteria" }, { ECoreType.naiveproxy, "klzgrad/naiveproxy" }, { ECoreType.tuic, "EAimTY/tuic" }, { ECoreType.juicity, "juicity/juicity" }, { ECoreType.brook, "txthinking/brook" }, { ECoreType.overtls, "ShadowsocksR-Live/overtls" }, { ECoreType.shadowquic, "spongebob888/shadowquic" }, { ECoreType.mieru, "enfein/mieru" }, { ECoreType.v2rayN, "2dust/v2rayN" }, }; public static readonly List OtherGeoUrls = [ @"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat", @"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb", @"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb" ]; public static readonly List IPAPIUrls = [ @"https://api.ip.sb/geoip", @"https://api-ipv4.ip.sb/geoip", @"https://api-ipv6.ip.sb/geoip", @"https://api.ipapi.is", @"" ]; public static readonly List OutboundTags = [ ProxyTag, DirectTag, BlockTag ]; public static readonly Dictionary> PredefinedHosts = new() { { "dns.google", ["8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844"] }, { "dns.alidns.com", ["223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1"] }, { "one.one.one.one", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] }, { "1dot1dot1dot1.cloudflare-dns.com", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] }, { "cloudflare-dns.com", ["104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9"] }, { "dns.cloudflare.com", ["104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5"] }, { "dot.pub", ["1.12.12.12", "120.53.53.53"] }, { "doh.pub", ["1.12.12.12", "120.53.53.53"] }, { "dns.quad9.net", ["9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9"] }, { "dns.yandex.net", ["77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff"] }, { "dns.sb", ["185.222.222.222", "2a09::"] }, { "dns.umbrella.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] }, { "dns.sse.cisco.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] }, { "engage.cloudflareclient.com", ["162.159.192.1"] } }; public static readonly List ExpectedIPs = [ "geoip:cn", "geoip:ir", "geoip:ru", "" ]; public static readonly List EchForceQuerys = [ "none", "half", "full", "" ]; #endregion const } ================================================ FILE: v2rayN/ServiceLib/GlobalUsings.cs ================================================ global using System.Collections.Concurrent; global using System.Diagnostics; global using System.Net; global using System.Net.NetworkInformation; global using System.Net.Sockets; global using System.Reactive; global using System.Reactive.Disposables; global using System.Reactive.Linq; global using System.Reflection; global using System.Runtime.InteropServices; global using System.Security.Cryptography; global using System.Text; global using System.Text.Encodings.Web; global using System.Text.Json; global using System.Text.Json.Nodes; global using System.Text.Json.Serialization; global using System.Text.RegularExpressions; global using DynamicData; global using DynamicData.Binding; global using ReactiveUI; global using ReactiveUI.Fody.Helpers; global using ServiceLib.Base; global using ServiceLib.Common; global using ServiceLib.Enums; global using ServiceLib.Events; global using ServiceLib.Handler; global using ServiceLib.Handler.Builder; global using ServiceLib.Handler.Fmt; global using ServiceLib.Handler.SysProxy; global using ServiceLib.Helper; global using ServiceLib.Manager; global using ServiceLib.Models; global using ServiceLib.Resx; global using ServiceLib.Services; global using ServiceLib.Services.CoreConfig; global using ServiceLib.Services.Statistics; global using SQLite; ================================================ FILE: v2rayN/ServiceLib/Handler/AutoStartupHandler.cs ================================================ using System.Security.Principal; namespace ServiceLib.Handler; public static class AutoStartupHandler { private static readonly string _tag = "AutoStartupHandler"; public static async Task UpdateTask(Config config) { if (Utils.IsWindows()) { await ClearTaskWindows(); if (config.GuiItem.AutoRun) { await SetTaskWindows(); } } else if (Utils.IsLinux()) { await ClearTaskLinux(); if (config.GuiItem.AutoRun) { await SetTaskLinux(); } } else if (Utils.IsMacOS()) { await ClearTaskOSX(); if (config.GuiItem.AutoRun) { await SetTaskOSX(); } } return true; } #region Windows private static async Task ClearTaskWindows() { var autoRunName = GetAutoRunNameWindows(); WindowsUtils.RegWriteValue(Global.AutoRunRegPath, autoRunName, ""); if (Utils.IsAdministrator()) { AutoStartTaskService(autoRunName, "", ""); } await Task.CompletedTask; } private static async Task SetTaskWindows() { try { var autoRunName = GetAutoRunNameWindows(); var exePath = Utils.GetExePath(); if (Utils.IsAdministrator()) { AutoStartTaskService(autoRunName, exePath, ""); } else { WindowsUtils.RegWriteValue(Global.AutoRunRegPath, autoRunName, exePath.AppendQuotes()); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } await Task.CompletedTask; } /// /// Auto Start via TaskService /// /// /// /// /// public static void AutoStartTaskService(string taskName, string fileName, string description) { if (taskName.IsNullOrEmpty()) { return; } var logonUser = WindowsIdentity.GetCurrent().Name; using var taskService = new Microsoft.Win32.TaskScheduler.TaskService(); var tasks = taskService.RootFolder.GetTasks(new Regex(taskName)); if (fileName.IsNullOrEmpty()) { foreach (var t in tasks) { taskService.RootFolder.DeleteTask(t.Name); } return; } var task = taskService.NewTask(); task.RegistrationInfo.Description = description; task.Settings.DisallowStartIfOnBatteries = false; task.Settings.StopIfGoingOnBatteries = false; task.Settings.RunOnlyIfIdle = false; task.Settings.IdleSettings.StopOnIdleEnd = false; task.Settings.ExecutionTimeLimit = TimeSpan.Zero; task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(30) }); task.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest; task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName))); taskService.RootFolder.RegisterTaskDefinition(taskName, task); } private static string GetAutoRunNameWindows() { return $"{Global.AutoRunName}_{Utils.GetMd5(Utils.StartupPath())}"; } #endregion Windows #region Linux private static async Task ClearTaskLinux() { try { File.Delete(GetHomePathLinux()); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } await Task.CompletedTask; } private static async Task SetTaskLinux() { try { var linuxConfig = EmbedUtils.GetEmbedText(Global.LinuxAutostartConfig); if (linuxConfig.IsNotEmpty()) { linuxConfig = linuxConfig.Replace("$ExecPath$", Utils.GetExePath()); Logging.SaveLog(linuxConfig); var homePath = GetHomePathLinux(); await File.WriteAllTextAsync(homePath, linuxConfig); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private static string GetHomePathLinux() { var homePath = Path.Combine(Utils.GetHomePath(), ".config", "autostart", $"{Global.AppName}.desktop"); Directory.CreateDirectory(Path.GetDirectoryName(homePath)); return homePath; } #endregion Linux #region macOS private static async Task ClearTaskOSX() { try { var launchAgentPath = GetLaunchAgentPathMacOS(); if (File.Exists(launchAgentPath)) { var args = new[] { "-c", $"launchctl unload -w \"{launchAgentPath}\"" }; await Utils.GetCliWrapOutput(Global.LinuxBash, args); File.Delete(launchAgentPath); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private static async Task SetTaskOSX() { try { var plistContent = GenerateLaunchAgentPlist(); var launchAgentPath = GetLaunchAgentPathMacOS(); await File.WriteAllTextAsync(launchAgentPath, plistContent); var args = new[] { "-c", $"launchctl load -w \"{launchAgentPath}\"" }; await Utils.GetCliWrapOutput(Global.LinuxBash, args); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private static string GetLaunchAgentPathMacOS() { var homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); var launchAgentPath = Path.Combine(homePath, "Library", "LaunchAgents", $"{Global.AppName}-LaunchAgent.plist"); Directory.CreateDirectory(Path.GetDirectoryName(launchAgentPath)); return launchAgentPath; } private static string GenerateLaunchAgentPlist() { var exePath = Utils.GetExePath(); var appName = Path.GetFileNameWithoutExtension(exePath); return $@" Label {Global.AppName}-LaunchAgent ProgramArguments /bin/sh -c if ! pgrep -x ""{appName}"" > /dev/null; then ""{exePath}""; fi RunAtLoad KeepAlive "; } #endregion macOS } ================================================ FILE: v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs ================================================ namespace ServiceLib.Handler.Builder; public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult) { public bool Success => ValidatorResult.Success; } /// /// Holds the results of a full context build, including the main context and an optional /// pre-socks context (e.g. for TUN protection or pre-socks chaining). /// public record CoreConfigContextBuilderAllResult( CoreConfigContextBuilderResult MainResult, CoreConfigContextBuilderResult? PreSocksResult) { /// True only when both the main result and (if present) the pre-socks result succeeded. public bool Success => MainResult.Success && (PreSocksResult?.Success ?? true); /// /// Merges all errors and warnings from the main result and the optional pre-socks result /// into a single for unified notification. /// public NodeValidatorResult CombinedValidatorResult => new( [.. MainResult.ValidatorResult.Errors, .. PreSocksResult?.ValidatorResult.Errors ?? []], [.. MainResult.ValidatorResult.Warnings, .. PreSocksResult?.ValidatorResult.Warnings ?? []]); /// /// The main context with TunProtectSsPort/ProxyRelaySsPort and ProtectDomainList merged in /// from the pre-socks result (if any). Pass this to the core runner. /// public CoreConfigContext ResolvedMainContext => PreSocksResult is not null ? MainResult.Context with { TunProtectSsPort = PreSocksResult.Context.TunProtectSsPort, ProxyRelaySsPort = PreSocksResult.Context.ProxyRelaySsPort, ProtectDomainList = [.. MainResult.Context.ProtectDomainList ?? [], .. PreSocksResult.Context.ProtectDomainList ?? []], } : MainResult.Context; } public class CoreConfigContextBuilder { /// /// Builds a for the given node, resolves its proxy map, /// and processes outbound nodes referenced by routing rules. /// public static async Task Build(Config config, ProfileItem node) { var runCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType); var coreType = runCoreType == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray; var context = new CoreConfigContext() { Node = node, RunCoreType = runCoreType, AllProxiesMap = [], AppConfig = config, FullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(coreType), IsTunEnabled = config.TunModeItem.EnableTun, SimpleDnsItem = config.SimpleDNSItem, ProtectDomainList = [], TunProtectSsPort = 0, ProxyRelaySsPort = 0, RawDnsItem = await AppManager.Instance.GetDNSItem(coreType), RoutingItem = await ConfigHandler.GetDefaultRouting(config), }; var validatorResult = NodeValidatorResult.Empty(); var (actNode, nodeValidatorResult) = await ResolveNodeAsync(context, node); if (!nodeValidatorResult.Success) { return new CoreConfigContextBuilderResult(context, nodeValidatorResult); } context = context with { Node = actNode }; validatorResult.Warnings.AddRange(nodeValidatorResult.Warnings); if (!(context.RoutingItem?.RuleSet.IsNullOrEmpty() ?? true)) { var rules = JsonUtils.Deserialize>(context.RoutingItem?.RuleSet) ?? []; foreach (var ruleItem in rules.Where(ruleItem => ruleItem.Enabled && !Global.OutboundTags.Contains(ruleItem.OutboundTag))) { if (ruleItem.OutboundTag.IsNullOrEmpty()) { validatorResult.Warnings.Add(string.Format(ResUI.MsgRoutingRuleEmptyOutboundTag, ruleItem.Remarks)); ruleItem.OutboundTag = Global.ProxyTag; continue; } var ruleOutboundNode = await AppManager.Instance.GetProfileItemViaRemarks(ruleItem.OutboundTag); if (ruleOutboundNode == null) { validatorResult.Warnings.Add(string.Format(ResUI.MsgRoutingRuleOutboundNodeNotFound, ruleItem.Remarks, ruleItem.OutboundTag)); ruleItem.OutboundTag = Global.ProxyTag; continue; } var (actRuleNode, ruleNodeValidatorResult) = await ResolveNodeAsync(context, ruleOutboundNode, false); validatorResult.Warnings.AddRange(ruleNodeValidatorResult.Warnings.Select(w => string.Format(ResUI.MsgRoutingRuleOutboundNodeWarning, ruleItem.Remarks, ruleItem.OutboundTag, w))); if (!ruleNodeValidatorResult.Success) { validatorResult.Warnings.AddRange(ruleNodeValidatorResult.Errors.Select(e => string.Format(ResUI.MsgRoutingRuleOutboundNodeError, ruleItem.Remarks, ruleItem.OutboundTag, e))); ruleItem.OutboundTag = Global.ProxyTag; continue; } context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode; } } return new CoreConfigContextBuilderResult(context, validatorResult); } /// /// Builds the main for and, when /// the main build succeeds, also builds the optional pre-socks context required for TUN /// protection or pre-socks proxy chaining. /// public static async Task BuildAll(Config config, ProfileItem node) { var mainResult = await Build(config, node); if (!mainResult.Success) { return new CoreConfigContextBuilderAllResult(mainResult, null); } var preResult = await BuildPreSocksIfNeeded(mainResult.Context); return new CoreConfigContextBuilderAllResult(mainResult, preResult); } /// /// Determines whether a pre-socks context is required for /// and, if so, builds and returns it. Returns null when no pre-socks core is needed. /// private static async Task BuildPreSocksIfNeeded(CoreConfigContext nodeContext) { var config = nodeContext.AppConfig; var node = nodeContext.Node; var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); var preSocksItem = ConfigHandler.GetPreSocksItem(config, node, coreType); if (preSocksItem != null) { var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem); return preSocksResult with { Context = preSocksResult.Context with { ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []], } }; } if (!nodeContext.IsTunEnabled || coreType != ECoreType.Xray || node.ConfigType == EConfigType.Custom) { return null; } var tunProtectSsPort = Utils.GetFreePort(); var proxyRelaySsPort = Utils.GetFreePort(); var preItem = new ProfileItem() { CoreType = ECoreType.sing_box, ConfigType = EConfigType.Shadowsocks, Address = Global.Loopback, Port = proxyRelaySsPort, Password = Global.None, }; preItem.SetProtocolExtra(preItem.GetProtocolExtra() with { SsMethod = Global.None, }); var preResult2 = await Build(nodeContext.AppConfig, preItem); return preResult2 with { Context = preResult2.Context with { ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preResult2.Context.ProtectDomainList ?? []], TunProtectSsPort = tunProtectSsPort, ProxyRelaySsPort = proxyRelaySsPort, } }; } /// /// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain. /// Returns the effective (possibly replaced) node and the validation result. /// public static async Task<(ProfileItem, NodeValidatorResult)> ResolveNodeAsync(CoreConfigContext context, ProfileItem node, bool includeSubChain = true) { if (node.IndexId.IsNullOrEmpty()) { return (node, NodeValidatorResult.Empty()); } if (includeSubChain) { var (virtualChainNode, chainValidatorResult) = await BuildSubscriptionChainNodeAsync(node); if (virtualChainNode != null) { context.AllProxiesMap[virtualChainNode.IndexId] = virtualChainNode; var (resolvedNode, resolvedResult) = await ResolveNodeAsync(context, virtualChainNode, false); resolvedResult.Warnings.InsertRange(0, chainValidatorResult.Warnings); return (resolvedNode, resolvedResult); } // Chain not built but warnings may still exist (e.g. missing profiles) if (chainValidatorResult.Warnings.Count > 0) { var fillResult = await RegisterNodeAsync(context, node); fillResult.Warnings.InsertRange(0, chainValidatorResult.Warnings); return (node, fillResult); } } var registerResult = await RegisterNodeAsync(context, node); return (node, registerResult); } /// /// If the node's subscription defines prev/next profiles, creates a virtual /// node that wraps them together. /// Returns null as the chain item when no chain is needed. /// Any warnings (e.g. missing prev/next profile) are returned in the validator result. /// private static async Task<(ProfileItem? ChainNode, NodeValidatorResult ValidatorResult)> BuildSubscriptionChainNodeAsync(ProfileItem node) { var result = NodeValidatorResult.Empty(); if (node.Subid.IsNullOrEmpty() || node.ConfigType == EConfigType.Custom) { return (null, result); } var subItem = await AppManager.Instance.GetSubItem(node.Subid); if (subItem == null) { return (null, result); } ProfileItem? prevNode = null; ProfileItem? nextNode = null; if (!subItem.PrevProfile.IsNullOrEmpty()) { prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); if (prevNode == null) { result.Warnings.Add(string.Format(ResUI.MsgSubscriptionPrevProfileNotFound, subItem.PrevProfile)); } } if (!subItem.NextProfile.IsNullOrEmpty()) { nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode == null) { result.Warnings.Add(string.Format(ResUI.MsgSubscriptionNextProfileNotFound, subItem.NextProfile)); } } if (prevNode is null && nextNode is null) { return (null, result); } // Build new proxy chain node var chainNode = new ProfileItem() { IndexId = $"inner-{Utils.GetGuid(false)}", ConfigType = EConfigType.ProxyChain, CoreType = AppManager.Instance.GetCoreType(node, node.ConfigType), Remarks = node.Remarks, }; List childItems = [prevNode?.IndexId, node.IndexId, nextNode?.IndexId]; var chainExtraItem = chainNode.GetProtocolExtra() with { GroupType = chainNode.ConfigType.ToString(), ChildItems = string.Join(",", childItems.Where(x => !x.IsNullOrEmpty())), }; chainNode.SetProtocolExtra(chainExtraItem); return (chainNode, result); } /// /// Dispatches registration to either or /// based on the node's config type. /// private static async Task RegisterNodeAsync(CoreConfigContext context, ProfileItem node) { if (node.ConfigType.IsGroupType()) { return await RegisterGroupNodeAsync(context, node); } else { return RegisterSingleNodeAsync(context, node); } } /// /// Validates a single (non-group) node and, on success, adds it to the proxy map /// and records any domain addresses that should bypass the proxy. /// private static NodeValidatorResult RegisterSingleNodeAsync(CoreConfigContext context, ProfileItem node) { if (node.ConfigType.IsGroupType()) { return NodeValidatorResult.Empty(); } var nodeValidatorResult = NodeValidator.Validate(node, context.RunCoreType); if (!nodeValidatorResult.Success) { return nodeValidatorResult; } context.AllProxiesMap[node.IndexId] = node; var address = node.Address; if (Utils.IsDomain(address)) { context.ProtectDomainList.Add(address); } if (!node.EchConfigList.IsNullOrEmpty()) { var echQuerySni = node.Sni; if (node.StreamSecurity == Global.StreamSecurity && node.EchConfigList?.Contains("://") == true) { var idx = node.EchConfigList.IndexOf('+'); echQuerySni = idx > 0 ? node.EchConfigList[..idx] : node.Sni; } if (Utils.IsDomain(echQuerySni)) { context.ProtectDomainList.Add(echQuerySni); } } return nodeValidatorResult; } /// /// Entry point for registering a group node. Initialises the visited/ancestor sets /// and delegates to . /// private static async Task RegisterGroupNodeAsync(CoreConfigContext context, ProfileItem node) { if (!node.ConfigType.IsGroupType()) { return NodeValidatorResult.Empty(); } HashSet ancestors = [node.IndexId]; HashSet globalVisited = [node.IndexId]; return await TraverseGroupNodeAsync(context, node, globalVisited, ancestors); } /// /// Recursively walks the children of a group node, registering valid leaf nodes /// and nested groups. Detects cycles via and /// deduplicates shared nodes via . /// private static async Task TraverseGroupNodeAsync( CoreConfigContext context, ProfileItem node, HashSet globalVisitedGroup, HashSet ancestorsGroup) { var (groupChildList, _) = await GroupProfileManager.GetChildProfileItems(node); List childIndexIdList = []; var childNodeValidatorResult = NodeValidatorResult.Empty(); foreach (var childNode in groupChildList) { if (ancestorsGroup.Contains(childNode.IndexId)) { childNodeValidatorResult.Errors.Add( string.Format(ResUI.MsgGroupCycleDependency, node.Remarks, childNode.Remarks)); continue; } if (globalVisitedGroup.Contains(childNode.IndexId)) { childIndexIdList.Add(childNode.IndexId); continue; } if (!childNode.ConfigType.IsGroupType()) { var childNodeResult = RegisterSingleNodeAsync(context, childNode); childNodeValidatorResult.Warnings.AddRange(childNodeResult.Warnings.Select(w => string.Format(ResUI.MsgGroupChildNodeWarning, node.Remarks, childNode.Remarks, w))); childNodeValidatorResult.Errors.AddRange(childNodeResult.Errors.Select(e => string.Format(ResUI.MsgGroupChildNodeError, node.Remarks, childNode.Remarks, e))); if (!childNodeResult.Success) { continue; } globalVisitedGroup.Add(childNode.IndexId); childIndexIdList.Add(childNode.IndexId); continue; } var newAncestorsGroup = new HashSet(ancestorsGroup) { childNode.IndexId }; var childGroupResult = await TraverseGroupNodeAsync(context, childNode, globalVisitedGroup, newAncestorsGroup); childNodeValidatorResult.Warnings.AddRange(childGroupResult.Warnings.Select(w => string.Format(ResUI.MsgGroupChildGroupNodeWarning, node.Remarks, childNode.Remarks, w))); childNodeValidatorResult.Errors.AddRange(childGroupResult.Errors.Select(e => string.Format(ResUI.MsgGroupChildGroupNodeError, node.Remarks, childNode.Remarks, e))); if (!childGroupResult.Success) { continue; } globalVisitedGroup.Add(childNode.IndexId); childIndexIdList.Add(childNode.IndexId); } if (childIndexIdList.Count == 0) { childNodeValidatorResult.Errors.Add(string.Format(ResUI.MsgGroupNoValidChildNode, node.Remarks)); return childNodeValidatorResult; } else { childNodeValidatorResult.Warnings.AddRange(childNodeValidatorResult.Errors); childNodeValidatorResult.Errors.Clear(); } node.SetProtocolExtra(node.GetProtocolExtra() with { ChildItems = Utils.List2String(childIndexIdList), }); context.AllProxiesMap[node.IndexId] = node; return childNodeValidatorResult; } } ================================================ FILE: v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs ================================================ namespace ServiceLib.Handler.Builder; public record NodeValidatorResult(List Errors, List Warnings) { public bool Success => Errors.Count == 0; public static NodeValidatorResult Empty() { return new NodeValidatorResult([], []); } } public class NodeValidator { // Static validator rules private static readonly HashSet SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)]; private static readonly HashSet SingboxTransportSupportedProtocols = [EConfigType.VMess, EConfigType.VLESS, EConfigType.Trojan, EConfigType.Shadowsocks]; private static readonly HashSet SingboxShadowsocksAllowedTransports = [nameof(ETransport.tcp), nameof(ETransport.ws), nameof(ETransport.quic)]; public static NodeValidatorResult Validate(ProfileItem item, ECoreType coreType) { var v = new ValidationContext(); ValidateNodeAndCoreSupport(item, coreType, v); return v.ToResult(); } private class ValidationContext { public List Errors { get; } = []; public List Warnings { get; } = []; public void Error(string message) { Errors.Add(message); } public void Warning(string message) { Warnings.Add(message); } public void Assert(bool condition, string errorMsg) { if (!condition) { Error(errorMsg); } } public NodeValidatorResult ToResult() { return new NodeValidatorResult(Errors, Warnings); } } private static void ValidateNodeAndCoreSupport(ProfileItem item, ECoreType coreType, ValidationContext v) { if (item.ConfigType is EConfigType.Custom) { return; } if (item.ConfigType.IsGroupType()) { // Group logic is handled in ValidateGroupNode return; } // Basic Property Validation v.Assert(!item.Address.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "Address")); v.Assert(item.Port is > 0 and <= 65535, string.Format(ResUI.MsgInvalidProperty, "Port")); // Network & Core Logic var net = item.GetNetwork(); if (coreType == ECoreType.sing_box) { var transportError = ValidateSingboxTransport(item.ConfigType, net); if (transportError != null) { v.Error(transportError); } if (!Global.SingboxSupportConfigType.Contains(item.ConfigType)) { v.Error(string.Format(ResUI.MsgCoreNotSupportProtocol, nameof(ECoreType.sing_box), item.ConfigType)); } } else if (coreType is ECoreType.Xray) { if (!Global.XraySupportConfigType.Contains(item.ConfigType)) { v.Error(string.Format(ResUI.MsgCoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType)); } } // Protocol Specifics var protocolExtra = item.GetProtocolExtra(); switch (item.ConfigType) { case EConfigType.VMess: v.Assert(!item.Password.IsNullOrEmpty() && Utils.IsGuidByParse(item.Password), string.Format(ResUI.MsgInvalidProperty, "Password")); break; case EConfigType.VLESS: v.Assert( !item.Password.IsNullOrEmpty() && (Utils.IsGuidByParse(item.Password) || item.Password.Length <= 30), string.Format(ResUI.MsgInvalidProperty, "Password") ); v.Assert(Global.Flows.Contains(protocolExtra.Flow ?? string.Empty), string.Format(ResUI.MsgInvalidProperty, "Flow")); break; case EConfigType.Shadowsocks: v.Assert(!item.Password.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "Password")); v.Assert( !string.IsNullOrEmpty(protocolExtra.SsMethod) && Global.SsSecuritiesInSingbox.Contains(protocolExtra.SsMethod), string.Format(ResUI.MsgInvalidProperty, "SsMethod")); break; } // TLS & Security if (item.StreamSecurity == Global.StreamSecurity) { if (!item.Cert.IsNullOrEmpty() && CertPemManager.ParsePemChain(item.Cert).Count == 0 && !item.CertSha.IsNullOrEmpty()) { v.Error(string.Format(ResUI.MsgInvalidProperty, "TLS Certificate")); } } if (item.StreamSecurity == Global.StreamSecurityReality) { v.Assert(!item.PublicKey.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "PublicKey")); } if (item.Network == nameof(ETransport.xhttp) && !item.Extra.IsNullOrEmpty()) { if (JsonUtils.ParseJson(item.Extra) is null) { v.Error(string.Format(ResUI.MsgInvalidProperty, "XHTTP Extra")); } } } private static string? ValidateSingboxTransport(EConfigType configType, string net) { // sing-box does not support xhttp / kcp transports if (SingboxUnsupportedTransports.Contains(net)) { return string.Format(ResUI.MsgCoreNotSupportNetwork, nameof(ECoreType.sing_box), net); } // sing-box does not support non-tcp transports for protocols other than vmess/trojan/vless/shadowsocks if (!SingboxTransportSupportedProtocols.Contains(configType) && net != nameof(ETransport.tcp)) { return string.Format(ResUI.MsgCoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), configType.ToString(), net); } // sing-box shadowsocks only supports tcp/ws/quic transports if (configType == EConfigType.Shadowsocks && !SingboxShadowsocksAllowedTransports.Contains(net)) { return string.Format(ResUI.MsgCoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), configType.ToString(), net); } return null; } } ================================================ FILE: v2rayN/ServiceLib/Handler/ConfigHandler.cs ================================================ using System.Data; namespace ServiceLib.Handler; public static class ConfigHandler { private static readonly string _configRes = Global.ConfigFileName; private static readonly string _tag = "ConfigHandler"; #region ConfigHandler /// /// Load the application configuration file /// If the file exists, deserialize it from JSON /// If not found, create a new Config object with default settings /// Initialize default values for missing configuration sections /// /// Config object containing application settings or null if there's an error public static Config? LoadConfig() { Config? config = null; var result = EmbedUtils.LoadResource(Utils.GetConfigPath(_configRes)); if (result.IsNotEmpty()) { config = JsonUtils.Deserialize(result); } else { if (File.Exists(Utils.GetConfigPath(_configRes))) { Logging.SaveLog("LoadConfig Exception"); return null; } } config ??= new Config(); config.CoreBasicItem ??= new() { LogEnabled = false, Loglevel = "warning", MuxEnabled = false, }; if (config.Inbound == null) { config.Inbound = new List(); InItem inItem = new() { Protocol = EInboundProtocol.socks.ToString(), LocalPort = 10808, UdpEnabled = true, SniffingEnabled = true, RouteOnly = false, }; config.Inbound.Add(inItem); } else { if (config.Inbound.Count > 0) { config.Inbound.First().Protocol = EInboundProtocol.socks.ToString(); } } config.RoutingBasicItem ??= new(); if (config.RoutingBasicItem.DomainStrategy.IsNullOrEmpty()) { config.RoutingBasicItem.DomainStrategy = Global.DomainStrategies.First(); } config.KcpItem ??= new KcpItem { Mtu = 1350, Tti = 50, UplinkCapacity = 12, DownlinkCapacity = 100, ReadBufferSize = 2, WriteBufferSize = 2, Congestion = false }; config.GrpcItem ??= new GrpcItem { IdleTimeout = 60, HealthCheckTimeout = 20, PermitWithoutStream = false, InitialWindowsSize = 0, }; config.TunModeItem ??= new TunModeItem { EnableTun = false, Mtu = 9000, }; config.GuiItem ??= new(); config.MsgUIItem ??= new(); config.UiItem ??= new(); config.UiItem.MainColumnItem ??= new(); config.UiItem.WindowSizeItem ??= new(); if (config.UiItem.CurrentLanguage.IsNullOrEmpty()) { config.UiItem.CurrentLanguage = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName.Equals("zh", StringComparison.CurrentCultureIgnoreCase) ? Global.Languages.First() : Global.Languages[2]; } config.ConstItem ??= new ConstItem(); config.SimpleDNSItem ??= InitBuiltinSimpleDNS(); config.SimpleDNSItem.GlobalFakeIp ??= true; config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault(); config.SimpleDNSItem.ServeStale ??= false; config.SimpleDNSItem.ParallelQuery ??= false; config.SpeedTestItem ??= new(); if (config.SpeedTestItem.SpeedTestTimeout < 10) { config.SpeedTestItem.SpeedTestTimeout = 10; } if (config.SpeedTestItem.SpeedTestUrl.IsNullOrEmpty()) { config.SpeedTestItem.SpeedTestUrl = Global.SpeedTestUrls.First(); } if (config.SpeedTestItem.SpeedPingTestUrl.IsNullOrEmpty()) { config.SpeedTestItem.SpeedPingTestUrl = Global.SpeedPingTestUrls.First(); } if (config.SpeedTestItem.MixedConcurrencyCount < 1) { config.SpeedTestItem.MixedConcurrencyCount = 5; } config.Mux4RayItem ??= new() { Concurrency = 8, XudpConcurrency = 16, XudpProxyUDP443 = "reject" }; config.Mux4SboxItem ??= new() { Protocol = Global.SingboxMuxs.First(), MaxConnections = 8 }; config.HysteriaItem ??= new() { UpMbps = 100, DownMbps = 100 }; config.ClashUIItem ??= new(); config.ClashUIItem.ConnectionsColumnItem ??= new(); config.SystemProxyItem ??= new(); config.WebDavItem ??= new(); config.CheckUpdateItem ??= new(); config.Fragment4RayItem ??= new() { Packets = "tlshello", Length = "100-200", Interval = "10-20" }; config.GlobalHotkeys ??= new(); if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty()) { config.SystemProxyItem.SystemProxyExceptions = Utils.IsWindows() ? Global.SystemProxyExceptionsWindows : Global.SystemProxyExceptionsLinux; } return config; } /// /// Save the configuration to a file /// First writes to a temporary file, then replaces the original file /// /// Configuration object to be saved /// 0 if successful, -1 if failed public static async Task SaveConfig(Config config) { try { //save temp file var resPath = Utils.GetConfigPath(_configRes); var tempPath = $"{resPath}_temp"; var content = JsonUtils.Serialize(config, true, true); if (content.IsNullOrEmpty()) { return -1; } await File.WriteAllTextAsync(tempPath, content); //rename File.Move(tempPath, resPath, true); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return -1; } return 0; } #endregion ConfigHandler #region Server /// /// Add a server profile to the configuration /// Dispatches the request to the appropriate method based on the config type /// /// Current configuration /// Server profile to add /// Result of the operation (0 if successful, -1 if failed) public static async Task AddServer(Config config, ProfileItem profileItem) { var item = await AppManager.Instance.GetProfileItem(profileItem.IndexId); if (item is null) { item = profileItem; } else { item.CoreType = profileItem.CoreType; item.Remarks = profileItem.Remarks; item.Address = profileItem.Address; item.Port = profileItem.Port; item.Username = profileItem.Username; item.Password = profileItem.Password; item.Network = profileItem.Network; item.HeaderType = profileItem.HeaderType; item.RequestHost = profileItem.RequestHost; item.Path = profileItem.Path; item.StreamSecurity = profileItem.StreamSecurity; item.Sni = profileItem.Sni; item.AllowInsecure = profileItem.AllowInsecure; item.Fingerprint = profileItem.Fingerprint; item.Alpn = profileItem.Alpn; item.PublicKey = profileItem.PublicKey; item.ShortId = profileItem.ShortId; item.SpiderX = profileItem.SpiderX; item.Mldsa65Verify = profileItem.Mldsa65Verify; item.Extra = profileItem.Extra; item.MuxEnabled = profileItem.MuxEnabled; item.Cert = profileItem.Cert; item.CertSha = profileItem.CertSha; item.EchConfigList = profileItem.EchConfigList; item.EchForceQuery = profileItem.EchForceQuery; item.Finalmask = profileItem.Finalmask; item.ProtoExtra = profileItem.ProtoExtra; } var ret = item.ConfigType switch { EConfigType.VMess => await AddVMessServer(config, item), EConfigType.Shadowsocks => await AddShadowsocksServer(config, item), EConfigType.SOCKS => await AddSocksServer(config, item), EConfigType.HTTP => await AddHttpServer(config, item), EConfigType.Trojan => await AddTrojanServer(config, item), EConfigType.VLESS => await AddVlessServer(config, item), EConfigType.Hysteria2 => await AddHysteria2Server(config, item), EConfigType.TUIC => await AddTuicServer(config, item), EConfigType.WireGuard => await AddWireguardServer(config, item), EConfigType.Anytls => await AddAnytlsServer(config, item), _ => -1, }; return ret; } /// /// Add or edit a VMess server /// Validates and processes VMess-specific settings /// /// Current configuration /// VMess profile to add /// Whether to save to file /// 0 if successful, -1 if failed public static async Task AddVMessServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.VMess; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Password = profileItem.Password.TrimEx(); profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with { VmessSecurity = profileItem.GetProtocolExtra().VmessSecurity?.TrimEx() }); profileItem.Network = profileItem.Network.TrimEx(); profileItem.HeaderType = profileItem.HeaderType.TrimEx(); profileItem.RequestHost = profileItem.RequestHost.TrimEx(); profileItem.Path = profileItem.Path.TrimEx(); profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx(); if (!Global.VmessSecurities.Contains(profileItem.GetProtocolExtra().VmessSecurity)) { return -1; } if (profileItem.Password.IsNullOrEmpty()) { return -1; } await AddServerCommon(config, profileItem, toFile); return 0; } /// /// Remove multiple servers from the configuration /// /// Current configuration /// List of server profiles to remove /// 0 if successful public static async Task RemoveServers(Config config, List indexes) { var subid = "TempRemoveSubId"; foreach (var item in indexes) { item.Subid = subid; } await SQLiteHelper.Instance.UpdateAllAsync(indexes); await RemoveServersViaSubid(config, subid, false); return 0; } /// /// Clone server profiles /// Creates copies of the specified server profiles with "-clone" appended to the remarks /// /// Current configuration /// List of server profiles to clone /// 0 if successful public static async Task CopyServer(Config config, List indexes) { foreach (var it in indexes) { var item = await AppManager.Instance.GetProfileItem(it.IndexId); if (item is null) { continue; } var profileItem = JsonUtils.DeepCopy(item); profileItem.IndexId = string.Empty; profileItem.Remarks = $"{item.Remarks}-clone"; if (profileItem.ConfigType == EConfigType.Custom) { profileItem.Address = Utils.GetConfigPath(profileItem.Address); if (await AddCustomServer(config, profileItem, false) == 0) { } } else { await AddServerCommon(config, profileItem, true); } } return 0; } /// /// Set the default server by its index ID /// Updates the configuration to use the specified server as default /// /// Current configuration /// Index ID of the server to set as default /// 0 if successful, -1 if failed public static async Task SetDefaultServerIndex(Config config, string? indexId) { if (indexId.IsNullOrEmpty()) { return -1; } config.IndexId = indexId; await SaveConfig(config); return 0; } /// /// Set a default server from the provided list of profiles /// Ensures there's always a valid default server selected /// /// Current configuration /// List of profile models to choose from /// Result of SetDefaultServerIndex operation public static async Task SetDefaultServer(Config config, List lstProfile) { if (lstProfile.Exists(t => t.IndexId == config.IndexId)) { return 0; } if (await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(t => t.IndexId == config.IndexId) != null) { return 0; } if (lstProfile.Count > 0) { return await SetDefaultServerIndex(config, lstProfile.FirstOrDefault(t => t.Port > 0)?.IndexId); } var item = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(t => t.Port > 0); return await SetDefaultServerIndex(config, item?.IndexId); } /// /// Get the current default server profile /// If the current default is invalid, selects a new default /// /// Current configuration /// The default profile item or null if none exists public static async Task GetDefaultServer(Config config) { var item = await AppManager.Instance.GetProfileItem(config.IndexId); if (item is null) { var item2 = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(); await SetDefaultServerIndex(config, item2?.IndexId); return item2; } return item; } /// /// Move a server in the list to a different position /// Supports moving to top, up, down, bottom or specific position /// /// Current configuration /// List of server profiles /// Index of the server to move /// Direction to move the server /// Target position when using EMove.Position /// 0 if successful, -1 if failed public static async Task MoveServer(Config config, List lstProfile, int index, EMove eMove, int pos = -1) { var count = lstProfile.Count; if (index < 0 || index > lstProfile.Count - 1) { return -1; } for (var i = 0; i < lstProfile.Count; i++) { ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10); } var sort = 0; switch (eMove) { case EMove.Top: { if (index == 0) { return 0; } sort = ProfileExManager.Instance.GetSort(lstProfile.First().IndexId) - 1; break; } case EMove.Up: { if (index == 0) { return 0; } sort = ProfileExManager.Instance.GetSort(lstProfile[index - 1].IndexId) - 1; break; } case EMove.Down: { if (index == count - 1) { return 0; } sort = ProfileExManager.Instance.GetSort(lstProfile[index + 1].IndexId) + 1; break; } case EMove.Bottom: { if (index == count - 1) { return 0; } sort = ProfileExManager.Instance.GetSort(lstProfile[^1].IndexId) + 1; break; } case EMove.Position: sort = (pos * 10) + 1; break; } ProfileExManager.Instance.SetSort(lstProfile[index].IndexId, sort); return await Task.FromResult(0); } /// /// Add a custom server configuration from a file /// Copies the configuration file to the app's config directory /// /// Current configuration /// Profile item with the file path in Address /// Whether to delete the source file after copying /// 0 if successful, -1 if failed public static async Task AddCustomServer(Config config, ProfileItem profileItem, bool blDelete) { var fileName = profileItem.Address; if (!File.Exists(fileName)) { return -1; } var ext = Path.GetExtension(fileName); var newFileName = $"{Utils.GetGuid()}{ext}"; //newFileName = Path.Combine(Utile.GetTempPath(), newFileName); try { File.Copy(fileName, Utils.GetConfigPath(newFileName)); if (blDelete) { File.Delete(fileName); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); return -1; } profileItem.Address = newFileName; profileItem.ConfigType = EConfigType.Custom; if (profileItem.Remarks.IsNullOrEmpty()) { profileItem.Remarks = $"import custom@{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}"; } await AddServerCommon(config, profileItem, true); return 0; } /// /// Edit an existing custom server configuration /// Updates the server's properties without changing the file /// /// Current configuration /// Profile item with updated properties /// 0 if successful, -1 if failed public static async Task EditCustomServer(Config config, ProfileItem profileItem) { var item = await AppManager.Instance.GetProfileItem(profileItem.IndexId); if (item is null) { item = profileItem; } else { item.Remarks = profileItem.Remarks; item.Address = profileItem.Address; item.CoreType = profileItem.CoreType; item.DisplayLog = profileItem.DisplayLog; item.PreSocksPort = profileItem.PreSocksPort; } if (await SQLiteHelper.Instance.UpdateAsync(item) > 0) { return 0; } else { return -1; } //ToJsonFile(config); } /// /// Add or edit a Shadowsocks server /// Validates and processes Shadowsocks-specific settings /// /// Current configuration /// Shadowsocks profile to add /// Whether to save to file /// 0 if successful, -1 if failed public static async Task AddShadowsocksServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.Shadowsocks; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Password = profileItem.Password.TrimEx(); profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with { SsMethod = profileItem.GetProtocolExtra().SsMethod?.TrimEx() }); if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.GetProtocolExtra().SsMethod)) { return -1; } if (profileItem.Password.IsNullOrEmpty()) { return -1; } await AddServerCommon(config, profileItem, toFile); return 0; } /// /// Add or edit a SOCKS server /// Processes SOCKS-specific settings /// /// Current configuration /// SOCKS profile to add /// Whether to save to file /// 0 if successful, -1 if failed public static async Task AddSocksServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.SOCKS; profileItem.Address = profileItem.Address.TrimEx(); await AddServerCommon(config, profileItem, toFile); return 0; } /// /// Add or edit an HTTP server /// Processes HTTP-specific settings /// /// Current configuration /// HTTP profile to add /// Whether to save to file /// 0 if successful, -1 if failed public static async Task AddHttpServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.HTTP; profileItem.Address = profileItem.Address.TrimEx(); await AddServerCommon(config, profileItem, toFile); return 0; } /// /// Add or edit a Trojan server /// Validates and processes Trojan-specific settings /// /// Current configuration /// Trojan profile to add /// Whether to save to file /// 0 if successful, -1 if failed public static async Task AddTrojanServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.Trojan; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Password = profileItem.Password.TrimEx(); if (profileItem.StreamSecurity.IsNullOrEmpty()) { profileItem.StreamSecurity = Global.StreamSecurity; } if (profileItem.Password.IsNullOrEmpty()) { return -1; } await AddServerCommon(config, profileItem, toFile); return 0; } /// /// Add or edit a Hysteria2 server /// Validates and processes Hysteria2-specific settings /// Sets the core type to sing_box as required by Hysteria2 /// /// Current configuration /// Hysteria2 profile to add /// Whether to save to file /// 0 if successful, -1 if failed public static async Task AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.Hysteria2; //profileItem.CoreType = ECoreType.sing_box; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Password = profileItem.Password.TrimEx(); profileItem.Network = string.Empty; if (profileItem.StreamSecurity.IsNullOrEmpty()) { profileItem.StreamSecurity = Global.StreamSecurity; } if (profileItem.Password.IsNullOrEmpty()) { return -1; } profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with { SalamanderPass = profileItem.GetProtocolExtra().SalamanderPass?.TrimEx(), HopInterval = profileItem.GetProtocolExtra().HopInterval?.TrimEx(), }); await AddServerCommon(config, profileItem, toFile); return 0; } /// /// Add or edit a TUIC server /// Validates and processes TUIC-specific settings /// Sets the core type to sing_box as required by TUIC /// /// Current configuration /// TUIC profile to add /// Whether to save to file /// 0 if successful, -1 if failed public static async Task AddTuicServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.TUIC; profileItem.CoreType = ECoreType.sing_box; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Username = profileItem.Username.TrimEx(); profileItem.Password = profileItem.Password.TrimEx(); profileItem.Network = string.Empty; if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType)) { profileItem.HeaderType = Global.TuicCongestionControls.FirstOrDefault()!; } if (profileItem.StreamSecurity.IsNullOrEmpty()) { profileItem.StreamSecurity = Global.StreamSecurity; } if (profileItem.Alpn.IsNullOrEmpty()) { profileItem.Alpn = "h3"; } if (profileItem.Password.IsNullOrEmpty()) { return -1; } await AddServerCommon(config, profileItem, toFile); return 0; } /// /// Add or edit a WireGuard server /// Validates and processes WireGuard-specific settings /// /// Current configuration /// WireGuard profile to add /// Whether to save to file /// 0 if successful, -1 if failed public static async Task AddWireguardServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.WireGuard; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Password = profileItem.Password.TrimEx(); profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with { WgPublicKey = profileItem.GetProtocolExtra().WgPublicKey?.TrimEx(), WgPresharedKey = profileItem.GetProtocolExtra().WgPresharedKey?.TrimEx(), WgInterfaceAddress = profileItem.GetProtocolExtra().WgInterfaceAddress?.TrimEx(), WgReserved = profileItem.GetProtocolExtra().WgReserved?.TrimEx(), WgMtu = profileItem.GetProtocolExtra().WgMtu is null or <= 0 ? Global.TunMtus.First() : profileItem.GetProtocolExtra().WgMtu, }); if (profileItem.Password.IsNullOrEmpty()) { return -1; } await AddServerCommon(config, profileItem, toFile); return 0; } /// /// Add or edit a Anytls server /// Validates and processes Anytls-specific settings /// /// Current configuration /// Anytls profile to add /// Whether to save to file /// 0 if successful, -1 if failed public static async Task AddAnytlsServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.Anytls; profileItem.CoreType = ECoreType.sing_box; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Password = profileItem.Password.TrimEx(); profileItem.Network = string.Empty; if (profileItem.StreamSecurity.IsNullOrEmpty()) { profileItem.StreamSecurity = Global.StreamSecurity; } if (profileItem.Password.IsNullOrEmpty()) { return -1; } await AddServerCommon(config, profileItem, toFile); return 0; } /// /// Sort the server list by the specified column /// Updates the sort order in the profile extension data /// /// Current configuration /// Subscription ID to filter servers /// Column name to sort by /// Sort in ascending order if true, descending if false /// 0 if successful, -1 if failed public static async Task SortServers(Config config, string subId, string colName, bool asc) { var lstModel = await AppManager.Instance.ProfileModels(subId, ""); if (lstModel.Count <= 0) { return -1; } var lstServerStat = (config.GuiItem.EnableStatistics ? StatisticsManager.Instance.ServerStat : null) ?? []; var lstProfileExs = await ProfileExManager.Instance.GetProfileExs(); var lstProfile = (from t in lstModel join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b from t22 in t2b.DefaultIfEmpty() join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b from t33 in t3b.DefaultIfEmpty() select new ProfileItemModel { IndexId = t.IndexId, ConfigType = t.ConfigType, Remarks = t.Remarks, Address = t.Address, Port = t.Port, //Security = t.Security, Network = t.Network, StreamSecurity = t.StreamSecurity, Delay = t33?.Delay ?? 0, Speed = t33?.Speed ?? 0, Sort = t33?.Sort ?? 0, TodayDown = (t22?.TodayDown ?? 0).ToString("D16"), TodayUp = (t22?.TodayUp ?? 0).ToString("D16"), TotalDown = (t22?.TotalDown ?? 0).ToString("D16"), TotalUp = (t22?.TotalUp ?? 0).ToString("D16"), }).ToList(); Enum.TryParse(colName, true, out EServerColName name); if (asc) { lstProfile = name switch { EServerColName.ConfigType => lstProfile.OrderBy(t => t.ConfigType).ToList(), EServerColName.Remarks => lstProfile.OrderBy(t => t.Remarks).ToList(), EServerColName.Address => lstProfile.OrderBy(t => t.Address).ToList(), EServerColName.Port => lstProfile.OrderBy(t => t.Port).ToList(), EServerColName.Network => lstProfile.OrderBy(t => t.Network).ToList(), EServerColName.StreamSecurity => lstProfile.OrderBy(t => t.StreamSecurity).ToList(), EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(), EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).ToList(), EServerColName.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(), EServerColName.TodayDown => lstProfile.OrderBy(t => t.TodayDown).ToList(), EServerColName.TodayUp => lstProfile.OrderBy(t => t.TodayUp).ToList(), EServerColName.TotalDown => lstProfile.OrderBy(t => t.TotalDown).ToList(), EServerColName.TotalUp => lstProfile.OrderBy(t => t.TotalUp).ToList(), _ => lstProfile }; } else { lstProfile = name switch { EServerColName.ConfigType => lstProfile.OrderByDescending(t => t.ConfigType).ToList(), EServerColName.Remarks => lstProfile.OrderByDescending(t => t.Remarks).ToList(), EServerColName.Address => lstProfile.OrderByDescending(t => t.Address).ToList(), EServerColName.Port => lstProfile.OrderByDescending(t => t.Port).ToList(), EServerColName.Network => lstProfile.OrderByDescending(t => t.Network).ToList(), EServerColName.StreamSecurity => lstProfile.OrderByDescending(t => t.StreamSecurity).ToList(), EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(), EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).ToList(), EServerColName.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(), EServerColName.TodayDown => lstProfile.OrderByDescending(t => t.TodayDown).ToList(), EServerColName.TodayUp => lstProfile.OrderByDescending(t => t.TodayUp).ToList(), EServerColName.TotalDown => lstProfile.OrderByDescending(t => t.TotalDown).ToList(), EServerColName.TotalUp => lstProfile.OrderByDescending(t => t.TotalUp).ToList(), _ => lstProfile }; } for (var i = 0; i < lstProfile.Count; i++) { ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10); } switch (name) { case EServerColName.DelayVal: { var maxSort = lstProfile.Max(t => t.Sort) + 10; foreach (var item in lstProfile.Where(item => item.Delay <= 0)) { ProfileExManager.Instance.SetSort(item.IndexId, maxSort); } break; } case EServerColName.SpeedVal: { var maxSort = lstProfile.Max(t => t.Sort) + 10; foreach (var item in lstProfile.Where(item => item.Speed <= 0)) { ProfileExManager.Instance.SetSort(item.IndexId, maxSort); } break; } } return 0; } /// /// Add or edit a VLESS server /// Validates and processes VLESS-specific settings /// /// Current configuration /// VLESS profile to add /// Whether to save to file /// 0 if successful, -1 if failed public static async Task AddVlessServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.VLESS; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Password = profileItem.Password.TrimEx(); profileItem.Network = profileItem.Network.TrimEx(); profileItem.HeaderType = profileItem.HeaderType.TrimEx(); profileItem.RequestHost = profileItem.RequestHost.TrimEx(); profileItem.Path = profileItem.Path.TrimEx(); profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx(); var vlessEncryption = profileItem.GetProtocolExtra().VlessEncryption?.TrimEx(); var flow = profileItem.GetProtocolExtra().Flow?.TrimEx() ?? string.Empty; profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with { VlessEncryption = vlessEncryption.IsNullOrEmpty() ? Global.None : vlessEncryption, Flow = Global.Flows.Contains(flow) ? flow : Global.Flows.First(), }); if (profileItem.Password.IsNullOrEmpty()) { return -1; } await AddServerCommon(config, profileItem, toFile); return 0; } /// /// Remove duplicate servers from a subscription /// Compares servers based on their properties rather than just names /// /// Current configuration /// Subscription ID to deduplicate /// Tuple with total count and remaining count after deduplication public static async Task> DedupServerList(Config config, string subId) { var lstProfile = await AppManager.Instance.ProfileItems(subId); if (lstProfile == null) { return new Tuple(0, 0); } List lstKeep = new(); List lstRemove = new(); if (!config.GuiItem.KeepOlderDedupl) { lstProfile.Reverse(); } foreach (var item in lstProfile) { if (!lstKeep.Exists(i => CompareProfileItem(i, item, false))) { lstKeep.Add(item); } else { lstRemove.Add(item); } } await RemoveServers(config, lstRemove); return new Tuple(lstProfile.Count, lstKeep.Count); } /// /// Common server addition logic used by all server types /// Sets common properties and handles sorting and persistence /// /// Current configuration /// Profile item to add /// Whether to save to database /// 0 if successful public static async Task AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigVersion = 3; if (profileItem.StreamSecurity.IsNotEmpty()) { if (profileItem.StreamSecurity is not Global.StreamSecurity and not Global.StreamSecurityReality) { profileItem.StreamSecurity = string.Empty; } else { if (profileItem.AllowInsecure.IsNullOrEmpty()) { profileItem.AllowInsecure = config.CoreBasicItem.DefAllowInsecure.ToString().ToLower(); } if (profileItem.Fingerprint.IsNullOrEmpty() && profileItem.StreamSecurity == Global.StreamSecurityReality) { profileItem.Fingerprint = config.CoreBasicItem.DefFingerprint; } } } if (profileItem.Network.IsNotEmpty() && !Global.Networks.Contains(profileItem.Network)) { profileItem.Network = Global.DefaultNetwork; } var maxSort = -1; if (profileItem.IndexId.IsNullOrEmpty()) { profileItem.IndexId = Utils.GetGuid(false); maxSort = ProfileExManager.Instance.GetMaxSort(); } if (!toFile && maxSort < 0) { maxSort = ProfileExManager.Instance.GetMaxSort(); } if (maxSort > 0) { ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1); } if (toFile) { profileItem.SetProtocolExtra(); await SQLiteHelper.Instance.ReplaceAsync(profileItem); } return 0; } /// /// Compare two profile items to determine if they represent the same server /// Used for deduplication and server matching /// /// First profile item /// Second profile item /// Whether to compare remarks /// True if the profiles match, false otherwise private static bool CompareProfileItem(ProfileItem? o, ProfileItem? n, bool remarks) { if (o == null || n == null) { return false; } var oProtocolExtra = o.GetProtocolExtra(); var nProtocolExtra = n.GetProtocolExtra(); return o.ConfigType == n.ConfigType && AreEqual(o.Address, n.Address) && o.Port == n.Port && AreEqual(o.Password, n.Password) && AreEqual(oProtocolExtra.VlessEncryption, nProtocolExtra.VlessEncryption) && AreEqual(oProtocolExtra.SsMethod, nProtocolExtra.SsMethod) && AreEqual(oProtocolExtra.VmessSecurity, nProtocolExtra.VmessSecurity) && AreEqual(o.Network, n.Network) && AreEqual(o.HeaderType, n.HeaderType) && AreEqual(o.RequestHost, n.RequestHost) && AreEqual(o.Path, n.Path) && (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity) && AreEqual(oProtocolExtra.Flow, nProtocolExtra.Flow) && AreEqual(oProtocolExtra.SalamanderPass, nProtocolExtra.SalamanderPass) && AreEqual(o.Sni, n.Sni) && AreEqual(o.Alpn, n.Alpn) && AreEqual(o.Fingerprint, n.Fingerprint) && AreEqual(o.PublicKey, n.PublicKey) && AreEqual(o.ShortId, n.ShortId) && AreEqual(o.Finalmask, n.Finalmask) && (!remarks || o.Remarks == n.Remarks); static bool AreEqual(string? a, string? b) { return string.Equals(a, b) || (string.IsNullOrEmpty(a) && string.IsNullOrEmpty(b)); } } /// /// Searches the specified collection for a profile item that matches the target profile item based on a series of /// criteria. /// /// The method attempts to find a match by comparing the target's remarks, address, port, and /// password in various combinations. The search is performed in order of specificity, starting with the most /// detailed comparison. If no match is found at any stage, the method returns null. /// An enumerable collection of profile items to search. This parameter can be null. /// The profile item to match against items in the source collection. This parameter can be null. /// A profile item from the source collection that matches the target item according to defined criteria; otherwise, /// null if no match is found or if either parameter is null. private static ProfileItem? FindMatchedProfileItem(IEnumerable? source, ProfileItem? target) { if (source == null || target == null) { return null; } var matchedItem = source.FirstOrDefault(t => CompareProfileItem(t, target, true)); if (matchedItem != null) { return matchedItem; } if (target.Remarks.IsNotEmpty()) { matchedItem = source.FirstOrDefault(t => t.Remarks == target.Remarks); if (matchedItem != null) { return matchedItem; } } if (target.Address.IsNotEmpty() && target.Port > 0 && target.Password.IsNotEmpty()) { matchedItem = source.FirstOrDefault(t => IsSameText(t.Address, target.Address) && t.Port == target.Port && IsSameText(t.Password, target.Password)); if (matchedItem != null) { return matchedItem; } } if (target.Address.IsNotEmpty() && target.Port > 0) { matchedItem = source.FirstOrDefault(t => IsSameText(t.Address, target.Address) && t.Port == target.Port); if (matchedItem != null) { return matchedItem; } } if (target.Address.IsNotEmpty()) { matchedItem = source.FirstOrDefault(t => IsSameText(t.Address, target.Address)); if (matchedItem != null) { return matchedItem; } } return null; static bool IsSameText(string? left, string? right) { if (left.IsNullOrEmpty() || right.IsNullOrEmpty()) { return false; } return string.Equals(left.TrimEx(), right.TrimEx(), StringComparison.OrdinalIgnoreCase); } } /// /// Remove a single server profile by its index ID /// Deletes the configuration file if it's a custom config /// /// Current configuration /// Index ID of the profile to remove /// 0 if successful private static async Task RemoveProfileItem(Config config, string indexId) { try { var item = await AppManager.Instance.GetProfileItem(indexId); if (item == null) { return 0; } if (item.ConfigType == EConfigType.Custom) { File.Delete(Utils.GetConfigPath(item.Address)); } await SQLiteHelper.Instance.DeleteAsync(item); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return 0; } /// /// Create a group server that combines multiple servers for load balancing /// Generates a PolicyGroup profile with references to the sub-items /// /// Current configuration /// Sub-item for grouping /// Result object with success state and data public static async Task AddGroupAllServer(Config config, SubItem? subItem) { var result = new RetResult(); var subId = subItem?.Id; if (subId.IsNullOrEmpty()) { result.Success = false; return result; } var indexId = Utils.GetGuid(false); var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup}"; var profile = new ProfileItem { IndexId = indexId, CoreType = ECoreType.Xray, ConfigType = EConfigType.PolicyGroup, Remarks = remark, IsSub = false }; if (!subId.IsNullOrEmpty()) { profile.Subid = subId; } var extraItem = new ProtocolExtraItem { MultipleLoad = EMultipleLoad.LeastPing, GroupType = profile.ConfigType.ToString(), SubChildItems = subId, Filter = Global.PolicyGroupDefaultAllFilter, }; profile.SetProtocolExtra(extraItem); var ret = await AddServerCommon(config, profile, true); result.Success = ret == 0; result.Data = indexId; return result; } private static string CombineWithDefaultAllFilter(string regionPattern) { return $"^(?!.*(?:{Global.PolicyGroupExcludeKeywords})).*(?:{regionPattern}).*$"; } private static readonly Dictionary PolicyGroupRegionFilters = new() { { "JP", "日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan" }, { "US", "美国|\\b[Uu][Ss]\\b|🇺🇸|[Uu]nited [Ss]tates|\\b[Uu][Ss][Aa]\\b" }, { "HK", "香港|\\b[Hh][Kk]\\b|🇭🇰|[Hh]ong ?[Kk]ong" }, { "TW", "台湾|台灣|\\b[Tt][Ww]\\b|🇹🇼|[Tt]aiwan" }, { "KR", "韩国|\\b[Kk][Rr]\\b|🇰🇷|[Kk]orea" }, { "SG", "新加坡|\\b[Ss][Gg]\\b|🇸🇬|[Ss]ingapore" }, { "DE", "德国|\\b[Dd][Ee]\\b|🇩🇪|[Gg]ermany" }, { "FR", "法国|\\b[Ff][Rr]\\b|🇫🇷|[Ff]rance" }, { "GB", "英国|\\b[Gg][Bb]\\b|🇬🇧|[Uu]nited [Kk]ingdom|[Bb]ritain" }, { "CA", "加拿大|🇨🇦|[Cc]anada" }, { "AU", "澳大利亚|\\b[Aa][Uu]\\b|🇦🇺|[Aa]ustralia" }, { "RU", "俄罗斯|\\b[Rr][Uu]\\b|🇷🇺|[Rr]ussia" }, { "BR", "巴西|\\b[Bb][Rr]\\b|🇧🇷|[Bb]razil" }, { "IN", "印度|🇮🇳|[Ii]ndia" }, { "VN", "越南|\\b[Vv][Nn]\\b|🇻🇳|[Vv]ietnam" }, { "ID", "印度尼西亚|\\b[Ii][Dd]\\b|🇮🇩|[Ii]ndonesia" }, { "MX", "墨西哥|\\b[Mm][Xx]\\b|🇲🇽|[Mm]exico" } }; public static async Task AddGroupRegionServer(Config config, SubItem? subItem) { var result = new RetResult(); var subId = subItem?.Id; if (subId.IsNullOrEmpty()) { result.Success = false; return result; } var childProfiles = await AppManager.Instance.ProfileItems(subId); List indexIdList = []; foreach (var regionFilter in PolicyGroupRegionFilters) { var indexId = Utils.GetGuid(false); var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup} - {regionFilter.Key}"; var profile = new ProfileItem { IndexId = indexId, CoreType = ECoreType.Xray, ConfigType = EConfigType.PolicyGroup, Remarks = remark, IsSub = false }; if (!subId.IsNullOrEmpty()) { profile.Subid = subId; } var extraItem = new ProtocolExtraItem { MultipleLoad = EMultipleLoad.LeastPing, GroupType = profile.ConfigType.ToString(), SubChildItems = subId, Filter = CombineWithDefaultAllFilter(regionFilter.Value), }; profile.SetProtocolExtra(extraItem); var matchedChildProfiles = childProfiles?.Where(p => p != null && p.IsValid() && !p.ConfigType.IsComplexType() && (extraItem.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extraItem.Filter)) ) .ToList() ?? []; if (matchedChildProfiles.Count == 0) { continue; } var ret = await AddServerCommon(config, profile, true); if (ret == 0) { indexIdList.Add(indexId); } } result.Success = indexIdList.Count > 0; result.Data = indexIdList; return result; } /// /// Get a SOCKS server profile for pre-SOCKS functionality /// Used when TUN mode is enabled or when a custom config has a pre-SOCKS port /// /// Current configuration /// Server node that might need pre-SOCKS /// Core type being used /// A SOCKS profile item or null if not needed public static ProfileItem? GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) { if (node.ConfigType != EConfigType.Custom || !(node.PreSocksPort > 0)) { return null; } ProfileItem? itemSocks = null; var preCoreType = AppManager.Instance.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray; itemSocks = new ProfileItem() { CoreType = preCoreType, ConfigType = EConfigType.SOCKS, Address = Global.Loopback, Port = node.PreSocksPort.Value, }; return itemSocks; } /// /// Remove servers with invalid test results (timeout) /// Useful for cleaning up subscription lists /// /// Current configuration /// Subscription ID to filter servers /// Number of removed servers or -1 if failed public static async Task RemoveInvalidServerResult(Config config, string subid) { var lstModel = await AppManager.Instance.ProfileModels(subid, ""); lstModel.RemoveAll(t => t.ConfigType.IsComplexType()); if (lstModel is { Count: <= 0 }) { return -1; } var lstProfileExs = await ProfileExManager.Instance.GetProfileExs(); var lstProfile = (from t in lstModel join t2 in lstProfileExs on t.IndexId equals t2.IndexId where t2.Delay == -1 select t).ToList(); await RemoveServers(config, JsonUtils.Deserialize>(JsonUtils.Serialize(lstProfile))); return lstProfile.Count; } #endregion Server #region Batch add servers /// /// Add multiple servers from string data (common protocols) /// Parses the string data into server profiles /// /// Current configuration /// String data containing server information /// Subscription ID to associate with the servers /// Whether this is from a subscription /// Number of successfully imported servers or -1 if failed private static async Task AddBatchServersCommon(Config config, string strData, string subid, bool isSub) { if (strData.IsNullOrEmpty()) { return -1; } var subFilter = string.Empty; //remove sub items if (isSub && subid.IsNotEmpty()) { await RemoveServersViaSubid(config, subid, isSub); subFilter = (await AppManager.Instance.GetSubItem(subid))?.Filter ?? ""; } var countServers = 0; List lstAdd = new(); var arrData = strData.Split(Environment.NewLine.ToCharArray()).Where(t => !t.IsNullOrEmpty()); if (isSub) { arrData = arrData.Distinct(); } foreach (var str in arrData) { //maybe sub if (!isSub && (str.StartsWith(Global.HttpsProtocol) || str.StartsWith(Global.HttpProtocol))) { if (await AddSubItem(config, str) == 0) { countServers++; } continue; } var profileItem = FmtHandler.ResolveConfig(str, out var msg); if (profileItem is null) { continue; } //exist sub items //filter if (isSub && subid.IsNotEmpty() && subFilter.IsNotEmpty()) { if (!Regex.IsMatch(profileItem.Remarks, subFilter)) { continue; } } profileItem.Subid = subid; profileItem.IsSub = isSub; var addStatus = profileItem.ConfigType switch { EConfigType.VMess => await AddVMessServer(config, profileItem, false), EConfigType.Shadowsocks => await AddShadowsocksServer(config, profileItem, false), EConfigType.SOCKS => await AddSocksServer(config, profileItem, false), EConfigType.Trojan => await AddTrojanServer(config, profileItem, false), EConfigType.VLESS => await AddVlessServer(config, profileItem, false), EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false), EConfigType.TUIC => await AddTuicServer(config, profileItem, false), EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false), EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false), _ => -1, }; if (addStatus == 0) { countServers++; lstAdd.Add(profileItem); } } if (lstAdd.Count > 0) { await SQLiteHelper.Instance.InsertAllAsync(lstAdd); } await SaveConfig(config); return countServers; } /// /// Add servers from custom configuration formats (sing-box, v2ray, etc.) /// Handles various configuration formats and imports them as custom configs /// /// Current configuration /// String data containing server information /// Subscription ID to associate with the servers /// Whether this is from a subscription /// Number of successfully imported servers or -1 if failed private static async Task AddBatchServers4Custom(Config config, string strData, string subid, bool isSub) { if (strData.IsNullOrEmpty()) { return -1; } var subItem = await AppManager.Instance.GetSubItem(subid); var subRemarks = subItem?.Remarks; var preSocksPort = subItem?.PreSocksPort; List? lstProfiles = null; //Is sing-box array configuration if (lstProfiles is null || lstProfiles.Count <= 0) { lstProfiles = SingboxFmt.ResolveFullArray(strData, subRemarks); } //Is v2ray array configuration if (lstProfiles is null || lstProfiles.Count <= 0) { lstProfiles = V2rayFmt.ResolveFullArray(strData, subRemarks); } if (lstProfiles != null && lstProfiles.Count > 0) { if (isSub && subid.IsNotEmpty()) { await RemoveServersViaSubid(config, subid, isSub); } var count = 0; foreach (var it in lstProfiles) { it.Subid = subid; it.IsSub = isSub; it.PreSocksPort = preSocksPort; if (await AddCustomServer(config, it, true) == 0) { count++; } } if (count > 0) { return count; } } ProfileItem? profileItem = null; //Is sing-box configuration if (profileItem is null) { profileItem = SingboxFmt.ResolveFull(strData, subRemarks); } //Is v2ray configuration if (profileItem is null) { profileItem = V2rayFmt.ResolveFull(strData, subRemarks); } //Is Html Page if (profileItem is null && HtmlPageFmt.IsHtmlPage(strData)) { return -1; } //Is Clash configuration if (profileItem is null) { profileItem = ClashFmt.ResolveFull(strData, subRemarks); } //Is hysteria configuration if (profileItem is null) { profileItem = Hysteria2Fmt.ResolveFull2(strData, subRemarks); } if (profileItem is null || profileItem.Address.IsNullOrEmpty()) { return -1; } if (isSub && subid.IsNotEmpty()) { await RemoveServersViaSubid(config, subid, isSub); } profileItem.Subid = subid; profileItem.IsSub = isSub; profileItem.PreSocksPort = preSocksPort; if (await AddCustomServer(config, profileItem, true) == 0) { return 1; } else { return -1; } } /// /// Add Shadowsocks servers from SIP008 format /// SIP008 is a JSON-based format for Shadowsocks servers /// /// Current configuration /// String data in SIP008 format /// Subscription ID to associate with the servers /// Whether this is from a subscription /// Number of successfully imported servers or -1 if failed private static async Task AddBatchServers4SsSIP008(Config config, string strData, string subid, bool isSub) { if (strData.IsNullOrEmpty()) { return -1; } if (isSub && subid.IsNotEmpty()) { await RemoveServersViaSubid(config, subid, isSub); } var lstSsServer = ShadowsocksFmt.ResolveSip008(strData); if (lstSsServer?.Count > 0) { var counter = 0; foreach (var ssItem in lstSsServer) { ssItem.Subid = subid; ssItem.IsSub = isSub; if (await AddShadowsocksServer(config, ssItem) == 0) { counter++; } } await SaveConfig(config); return counter; } return -1; } /// /// Main entry point for adding batch servers from various formats /// Tries different parsing methods to import as many servers as possible /// /// Current configuration /// String data containing server information /// Subscription ID to associate with the servers /// Whether this is from a subscription /// Number of successfully imported servers or -1 if failed public static async Task AddBatchServers(Config config, string strData, string subid, bool isSub) { if (strData.IsNullOrEmpty()) { return -1; } List? lstOriSub = null; ProfileItem? activeProfile = null; if (isSub && subid.IsNotEmpty()) { lstOriSub = await AppManager.Instance.ProfileItems(subid); activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId); } var counter = 0; if (Utils.IsBase64String(strData)) { counter = await AddBatchServersCommon(config, Utils.Base64Decode(strData), subid, isSub); } if (counter < 1) { counter = await AddBatchServersCommon(config, strData, subid, isSub); } if (counter < 1) { counter = await AddBatchServersCommon(config, Utils.Base64Decode(strData), subid, isSub); } if (counter < 1) { counter = await AddBatchServers4SsSIP008(config, strData, subid, isSub); } //maybe other sub if (counter < 1) { counter = await AddBatchServers4Custom(config, strData, subid, isSub); } //Select active node if (activeProfile != null) { var lstSub = await AppManager.Instance.ProfileItems(subid); var existItem = FindMatchedProfileItem(lstSub, activeProfile); if (existItem != null) { await ConfigHandler.SetDefaultServerIndex(config, existItem.IndexId); } } //Keep the last traffic statistics if (lstOriSub != null) { var lstSub = await AppManager.Instance.ProfileItems(subid); foreach (var item in lstSub) { var existItem = FindMatchedProfileItem(lstOriSub, item); if (existItem != null) { await StatisticsManager.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId); } } } return counter; } #endregion Batch add servers #region Sub & Group /// /// Add a subscription item from URL /// Creates a new subscription with default settings /// /// Current configuration /// Subscription URL /// 0 if successful, -1 if failed public static async Task AddSubItem(Config config, string url) { //already exists var count = await SQLiteHelper.Instance.TableAsync().CountAsync(e => e.Url == url); if (count > 0) { return 0; } SubItem subItem = new() { Id = string.Empty, Url = url }; var uri = Utils.TryUri(url); if (uri == null) { return -1; } //Do not allow http protocol if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost)) { //TODO Temporary reminder to be removed later NoticeManager.Instance.Enqueue(ResUI.InsecureUrlProtocol); //return -1; } var queryVars = Utils.ParseQueryString(uri.Query); subItem.Remarks = queryVars["remarks"] ?? "import_sub"; return await AddSubItem(config, subItem); } /// /// Add or update a subscription item /// /// Current configuration /// Subscription item to add or update /// 0 if successful, -1 if failed public static async Task AddSubItem(Config config, SubItem subItem) { var item = await AppManager.Instance.GetSubItem(subItem.Id); if (item is null) { item = subItem; } else { item.Remarks = subItem.Remarks; item.Url = subItem.Url; item.MoreUrl = subItem.MoreUrl; item.Enabled = subItem.Enabled; item.AutoUpdateInterval = subItem.AutoUpdateInterval; item.UserAgent = subItem.UserAgent; item.Sort = subItem.Sort; item.Filter = subItem.Filter; item.UpdateTime = subItem.UpdateTime; item.ConvertTarget = subItem.ConvertTarget; item.PrevProfile = subItem.PrevProfile; item.NextProfile = subItem.NextProfile; item.PreSocksPort = subItem.PreSocksPort; item.Memo = subItem.Memo; } if (item.Id.IsNullOrEmpty()) { item.Id = Utils.GetGuid(false); if (item.Sort <= 0) { var maxSort = 0; if (await SQLiteHelper.Instance.TableAsync().CountAsync() > 0) { var lstSubs = await AppManager.Instance.SubItems(); maxSort = lstSubs.LastOrDefault()?.Sort ?? 0; } item.Sort = maxSort + 1; } } if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) { return 0; } else { return -1; } } /// /// Remove servers associated with a subscription ID /// /// Current configuration /// Subscription ID /// Whether to only remove servers marked as subscription items /// 0 if successful, -1 if failed public static async Task RemoveServersViaSubid(Config config, string subid, bool isSub) { if (subid.IsNullOrEmpty()) { return -1; } var customProfile = await SQLiteHelper.Instance.TableAsync().Where(t => t.Subid == subid && t.ConfigType == EConfigType.Custom).ToListAsync(); if (isSub) { await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where isSub = 1 and subid = '{subid}'"); } else { await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where subid = '{subid}'"); } foreach (var item in customProfile) { File.Delete(Utils.GetConfigPath(item.Address)); } return 0; } /// /// Delete a subscription item and all its associated servers /// /// Current configuration /// Subscription ID to delete /// 0 if successful public static async Task DeleteSubItem(Config config, string id) { var item = await AppManager.Instance.GetSubItem(id); if (item is null) { return 0; } await SQLiteHelper.Instance.DeleteAsync(item); await RemoveServersViaSubid(config, id, false); return 0; } /// /// Move servers to a different group (subscription) /// /// Current configuration /// List of profiles to move /// Target subscription ID /// 0 if successful public static async Task MoveToGroup(Config config, List lstProfile, string subid) { foreach (var item in lstProfile) { item.Subid = subid; } await SQLiteHelper.Instance.UpdateAllAsync(lstProfile); return 0; } #endregion Sub & Group #region Routing /// /// Save a routing item to the database /// /// Current configuration /// Routing item to save /// 0 if successful, -1 if failed public static async Task SaveRoutingItem(Config config, RoutingItem item) { if (item.Id.IsNullOrEmpty()) { item.Id = Utils.GetGuid(false); } if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) { return 0; } else { return -1; } } /// /// Add multiple routing rules to a routing item /// /// Routing item to add rules to /// JSON string containing rules data /// 0 if successful, -1 if failed public static async Task AddBatchRoutingRules(RoutingItem routingItem, string strData) { if (strData.IsNullOrEmpty()) { return -1; } var lstRules = JsonUtils.Deserialize>(strData); if (lstRules == null) { return -1; } foreach (var item in lstRules) { item.Id = Utils.GetGuid(false); } routingItem.RuleNum = lstRules.Count; routingItem.RuleSet = JsonUtils.Serialize(lstRules, false); if (routingItem.Id.IsNullOrEmpty()) { routingItem.Id = Utils.GetGuid(false); } if (await SQLiteHelper.Instance.ReplaceAsync(routingItem) > 0) { return 0; } else { return -1; } } /// /// Move a routing rule within a rules list /// Supports moving to top, up, down, bottom or specific position /// /// List of routing rules /// Index of the rule to move /// Direction to move the rule /// Target position when using EMove.Position /// 0 if successful, -1 if failed public static async Task MoveRoutingRule(List rules, int index, EMove eMove, int pos = -1) { var count = rules.Count; if (index < 0 || index > rules.Count - 1) { return -1; } switch (eMove) { case EMove.Top: { if (index == 0) { return 0; } var item = JsonUtils.DeepCopy(rules[index]); rules.RemoveAt(index); rules.Insert(0, item); break; } case EMove.Up: { if (index == 0) { return 0; } var item = JsonUtils.DeepCopy(rules[index]); rules.RemoveAt(index); rules.Insert(index - 1, item); break; } case EMove.Down: { if (index == count - 1) { return 0; } var item = JsonUtils.DeepCopy(rules[index]); rules.RemoveAt(index); rules.Insert(index + 1, item); break; } case EMove.Bottom: { if (index == count - 1) { return 0; } var item = JsonUtils.DeepCopy(rules[index]); rules.RemoveAt(index); rules.Add(item); break; } case EMove.Position: { var removeItem = rules[index]; var item = JsonUtils.DeepCopy(rules[index]); rules.Insert(pos, item); rules.Remove(removeItem); break; } } return await Task.FromResult(0); } /// /// Set the default routing configuration /// /// Current configuration /// Routing item to set as default /// 0 if successful public static async Task SetDefaultRouting(Config config, RoutingItem routingItem) { var items = await AppManager.Instance.RoutingItems(); if (items.Any(t => t.Id == routingItem.Id && t.IsActive == true)) { return -1; } foreach (var item in items) { if (item.Id == routingItem.Id) { item.IsActive = true; } else { item.IsActive = false; } } await SQLiteHelper.Instance.UpdateAllAsync(items); return 0; } /// /// Get the current default routing configuration /// If no default is set, selects the first available routing item /// /// Current configuration /// The default routing item public static async Task GetDefaultRouting(Config config) { var item = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.IsActive == true); if (item is null) { var item2 = await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(); await SetDefaultRouting(config, item2); return item2; } return item; } /// /// Initialize routing rules from built-in or external templates /// /// Current configuration /// Whether to import advanced rules /// 0 if successful public static async Task InitRouting(Config config, bool blImportAdvancedRules = false) { if (config.ConstItem.RouteRulesTemplateSourceUrl.IsNullOrEmpty()) { await InitBuiltinRouting(config, blImportAdvancedRules); } else { await InitExternalRouting(config, blImportAdvancedRules); } return 0; } /// /// Initialize routing rules from external templates /// Downloads and processes routing templates from a URL /// /// Current configuration /// Whether to import advanced rules /// 0 if successful public static async Task InitExternalRouting(Config config, bool blImportAdvancedRules = false) { var downloadHandle = new DownloadService(); var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, ""); if (templateContent.IsNullOrEmpty()) { return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback } var template = JsonUtils.Deserialize(templateContent); if (template == null) { return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback } var items = await AppManager.Instance.RoutingItems(); var maxSort = items.Count; if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0) { return 0; } for (var i = 0; i < template.RoutingItems.Length; i++) { var item = template.RoutingItems[i]; if (item.Url.IsNullOrEmpty() && item.RuleSet.IsNullOrEmpty()) { continue; } var ruleSetsString = !item.RuleSet.IsNullOrEmpty() ? item.RuleSet : await downloadHandle.TryDownloadString(item.Url, true, ""); if (ruleSetsString.IsNullOrEmpty()) { continue; } item.Remarks = $"{template.Version}-{item.Remarks}"; item.Enabled = true; item.Sort = ++maxSort; item.Url = string.Empty; await AddBatchRoutingRules(item, ruleSetsString); //first rule as default at first startup if (!blImportAdvancedRules && i == 0) { await SetDefaultRouting(config, item); } } return 0; } /// /// Initialize built-in routing rules /// Creates default routing configurations (whitelist, blacklist, global) /// /// Current configuration /// Whether to import advanced rules /// 0 if successful public static async Task InitBuiltinRouting(Config config, bool blImportAdvancedRules = false) { var ver = "V4-"; var items = await AppManager.Instance.RoutingItems(); //TODO Temporary code to be removed later var lockItem = items?.FirstOrDefault(t => t.Locked == true); if (lockItem != null) { await ConfigHandler.RemoveRoutingItem(lockItem); items = await AppManager.Instance.RoutingItems(); } if (!blImportAdvancedRules && items.Count(u => u.Remarks.StartsWith(ver)) > 0) { //migrate //TODO Temporary code to be removed later if (config.RoutingBasicItem.RoutingIndexId.IsNotEmpty()) { var item = items.FirstOrDefault(t => t.Id == config.RoutingBasicItem.RoutingIndexId); if (item != null) { await SetDefaultRouting(config, item); } config.RoutingBasicItem.RoutingIndexId = string.Empty; } return 0; } var maxSort = items.Count; //Bypass the mainland var item2 = new RoutingItem() { Remarks = $"{ver}绕过大陆(Whitelist)", Url = string.Empty, Sort = maxSort + 1, }; await AddBatchRoutingRules(item2, EmbedUtils.GetEmbedText(Global.CustomRoutingFileName + "white")); //Blacklist var item3 = new RoutingItem() { Remarks = $"{ver}黑名单(Blacklist)", Url = string.Empty, Sort = maxSort + 2, }; await AddBatchRoutingRules(item3, EmbedUtils.GetEmbedText(Global.CustomRoutingFileName + "black")); //Global var item1 = new RoutingItem() { Remarks = $"{ver}全局(Global)", Url = string.Empty, Sort = maxSort + 3, }; await AddBatchRoutingRules(item1, EmbedUtils.GetEmbedText(Global.CustomRoutingFileName + "global")); if (!blImportAdvancedRules) { await SetDefaultRouting(config, item2); } return 0; } /// /// Remove a routing item from the database /// /// Routing item to remove public static async Task RemoveRoutingItem(RoutingItem routingItem) { await SQLiteHelper.Instance.DeleteAsync(routingItem); } #endregion Routing #region DNS /// /// Initialize built-in DNS configurations /// Creates default DNS items for V2Ray and sing-box /// Also checks existing DNS items and disables those with empty NormalDNS /// /// Current configuration /// 0 if successful public static async Task InitBuiltinDNS(Config config) { var items = await AppManager.Instance.DNSItems(); // Check existing DNS items and disable those with empty NormalDNS var needsUpdate = false; foreach (var existingItem in items) { if (existingItem.NormalDNS.IsNullOrEmpty() && existingItem.Enabled) { existingItem.Enabled = false; needsUpdate = true; } } // Update items if any changes were made if (needsUpdate) { await SQLiteHelper.Instance.UpdateAllAsync(items); } if (items.Count <= 0) { var item = new DNSItem() { Remarks = "V2ray", CoreType = ECoreType.Xray, Enabled = false, }; await SaveDNSItems(config, item); var item2 = new DNSItem() { Remarks = "sing-box", CoreType = ECoreType.sing_box, Enabled = false, }; await SaveDNSItems(config, item2); } return 0; } /// /// Save a DNS item to the database /// /// Current configuration /// DNS item to save /// 0 if successful, -1 if failed public static async Task SaveDNSItems(Config config, DNSItem item) { if (item == null) { return -1; } if (item.Id.IsNullOrEmpty()) { item.Id = Utils.GetGuid(false); } if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) { return 0; } else { return -1; } } /// /// Get an external DNS configuration from URL /// Downloads and processes DNS templates /// /// Core type (Xray or sing-box) /// URL of the DNS template /// DNS item with configuration from the URL public static async Task GetExternalDNSItem(ECoreType type, string url) { var currentItem = await AppManager.Instance.GetDNSItem(type); var downloadHandle = new DownloadService(); var templateContent = await downloadHandle.TryDownloadString(url, true, ""); if (templateContent.IsNullOrEmpty()) { return currentItem; } var template = JsonUtils.Deserialize(templateContent); if (template == null) { return currentItem; } if (!template.NormalDNS.IsNullOrEmpty()) { template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, ""); } if (!template.TunDNS.IsNullOrEmpty()) { template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, ""); } template.Id = currentItem.Id; template.Enabled = currentItem.Enabled; template.Remarks = currentItem.Remarks; template.CoreType = type; return template; } #endregion DNS #region Simple DNS public static SimpleDNSItem InitBuiltinSimpleDNS() { return new SimpleDNSItem() { UseSystemHosts = false, AddCommonHosts = true, FakeIP = false, GlobalFakeIp = true, BlockBindingQuery = true, DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(), RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(), BootstrapDNS = Global.DomainPureIPDNSAddress.FirstOrDefault(), }; } public static async Task GetExternalSimpleDNSItem(string url) { var downloadHandle = new DownloadService(); var templateContent = await downloadHandle.TryDownloadString(url, true, ""); if (templateContent.IsNullOrEmpty()) { return null; } var template = JsonUtils.Deserialize(templateContent); if (template == null) { return null; } return template; } #endregion Simple DNS #region Custom Config public static async Task InitBuiltinFullConfigTemplate(Config config) { var items = await AppManager.Instance.FullConfigTemplateItem(); if (items.Count <= 0) { var item = new FullConfigTemplateItem() { Remarks = "V2ray", CoreType = ECoreType.Xray, }; await SaveFullConfigTemplate(config, item); var item2 = new FullConfigTemplateItem() { Remarks = "sing-box", CoreType = ECoreType.sing_box, }; await SaveFullConfigTemplate(config, item2); } return 0; } public static async Task SaveFullConfigTemplate(Config config, FullConfigTemplateItem item) { if (item == null) { return -1; } if (item.Id.IsNullOrEmpty()) { item.Id = Utils.GetGuid(false); } if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) { return 0; } else { return -1; } } #endregion Custom Config #region Regional Presets /// /// Apply regional presets for geo-specific configurations /// Sets up geo files, routing rules, and DNS for specific regions /// /// Current configuration /// Type of preset (Default, Russia, Iran) /// True if successful public static async Task ApplyRegionalPreset(Config config, EPresetType type) { switch (type) { case EPresetType.Default: config.ConstItem.GeoSourceUrl = ""; config.ConstItem.SrsSourceUrl = ""; config.ConstItem.RouteRulesTemplateSourceUrl = ""; await SQLiteHelper.Instance.DeleteAllAsync(); await InitBuiltinDNS(config); config.SimpleDNSItem = InitBuiltinSimpleDNS(); break; case EPresetType.Russia: config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[1]; config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1]; config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1]; var xrayDnsRussia = await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json"); var singboxDnsRussia = await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json"); var simpleDnsRussia = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[1] + "simple_dns.json"); if (simpleDnsRussia == null) { xrayDnsRussia.Enabled = true; singboxDnsRussia.Enabled = true; config.SimpleDNSItem = InitBuiltinSimpleDNS(); } else { config.SimpleDNSItem = simpleDnsRussia; } await SaveDNSItems(config, xrayDnsRussia); await SaveDNSItems(config, singboxDnsRussia); break; case EPresetType.Iran: config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[2]; config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2]; config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2]; var xrayDnsIran = await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json"); var singboxDnsIran = await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json"); var simpleDnsIran = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[2] + "simple_dns.json"); if (simpleDnsIran == null) { xrayDnsIran.Enabled = true; singboxDnsIran.Enabled = true; config.SimpleDNSItem = InitBuiltinSimpleDNS(); } else { config.SimpleDNSItem = simpleDnsIran; } await SaveDNSItems(config, xrayDnsIran); await SaveDNSItems(config, singboxDnsIran); break; } return true; } #endregion Regional Presets #region UIItem public static WindowSizeItem? GetWindowSizeItem(Config config, string typeName) { var sizeItem = config?.UiItem?.WindowSizeItem?.FirstOrDefault(t => t.TypeName == typeName); if (sizeItem == null || sizeItem.Width <= 0 || sizeItem.Height <= 0) { return null; } return sizeItem; } public static int SaveWindowSizeItem(Config config, string typeName, double width, double height) { var sizeItem = config?.UiItem?.WindowSizeItem?.FirstOrDefault(t => t.TypeName == typeName); if (sizeItem == null) { sizeItem = new WindowSizeItem { TypeName = typeName }; config.UiItem.WindowSizeItem.Add(sizeItem); } sizeItem.Width = (int)width; sizeItem.Height = (int)height; return 0; } public static int SaveMainGirdHeight(Config config, double height1, double height2) { var uiItem = config.UiItem ?? new(); uiItem.MainGirdHeight1 = (int)(height1 + 0.1); uiItem.MainGirdHeight2 = (int)(height2 + 0.1); return 0; } #endregion UIItem } ================================================ FILE: v2rayN/ServiceLib/Handler/ConnectionHandler.cs ================================================ namespace ServiceLib.Handler; public static class ConnectionHandler { private static readonly string _tag = "ConnectionHandler"; public static async Task RunAvailabilityCheck() { var time = await GetRealPingTimeInfo(); var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None; return string.Format(ResUI.TestMeOutput, time, ip); } private static async Task GetIPInfo() { var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl; if (url.IsNullOrEmpty()) { return null; } var downloadHandle = new DownloadService(); var result = await downloadHandle.TryDownloadString(url, true, ""); if (result == null) { return null; } var ipInfo = JsonUtils.Deserialize(result); if (ipInfo == null) { return null; } var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query; var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code; return $"({country ?? "unknown"}) {ip}"; } private static async Task GetRealPingTimeInfo() { var responseTime = -1; try { var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); var webProxy = new WebProxy($"socks5://{Global.Loopback}:{port}"); var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl; for (var i = 0; i < 2; i++) { responseTime = await GetRealPingTime(url, webProxy, 10); if (responseTime > 0) { break; } await Task.Delay(500); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); return -1; } return responseTime; } public static async Task GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout) { var responseTime = -1; try { using var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout)); using var client = new HttpClient(new SocketsHttpHandler() { Proxy = webProxy, UseProxy = webProxy != null }); List oneTime = new(); for (var i = 0; i < 2; i++) { var timer = Stopwatch.StartNew(); await client.GetAsync(url, cts.Token).ConfigureAwait(false); timer.Stop(); oneTime.Add((int)timer.Elapsed.TotalMilliseconds); await Task.Delay(100); } responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault(); } catch { } return responseTime; } } ================================================ FILE: v2rayN/ServiceLib/Handler/CoreConfigHandler.cs ================================================ namespace ServiceLib.Handler; /// /// Core configuration file processing class /// public static class CoreConfigHandler { private static readonly string _tag = "CoreConfigHandler"; public static async Task GenerateClientConfig(CoreConfigContext context, string? fileName) { var config = AppManager.Instance.Config; var result = new RetResult(); var node = context.Node; if (node.ConfigType == EConfigType.Custom) { result = node.CoreType switch { ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName), _ => await GenerateClientCustomConfig(node, fileName) }; } else if (context.RunCoreType == ECoreType.sing_box) { result = new CoreConfigSingboxService(context).GenerateClientConfigContent(); } else { result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); } if (result.Success != true) { return result; } if (fileName.IsNotEmpty() && result.Data != null) { await File.WriteAllTextAsync(fileName, result.Data.ToString()); } return result; } private static async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) { var ret = new RetResult(); try { if (node == null || fileName is null) { ret.Msg = ResUI.CheckServerSettings; return ret; } if (File.Exists(fileName)) { File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail File.Delete(fileName); } var addressFileName = node.Address; if (!File.Exists(addressFileName)) { addressFileName = Utils.GetConfigPath(addressFileName); } if (!File.Exists(addressFileName)) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } File.Copy(addressFileName, fileName); File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file. //check again if (!File.Exists(fileName)) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; return await Task.FromResult(ret); } catch (Exception ex) { Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } } public static async Task GenerateClientSpeedtestConfig(Config config, string fileName, List selecteds, ECoreType coreType) { var result = new RetResult(); var dummyNode = new ProfileItem { CoreType = coreType }; var builderResult = await CoreConfigContextBuilder.Build(config, dummyNode); var context = builderResult.Context; foreach (var testItem in selecteds) { var node = testItem.Profile; var (actNode, _) = await CoreConfigContextBuilder.ResolveNodeAsync(context, node, true); if (node.IndexId == actNode.IndexId) { continue; } context.ServerTestItemMap[node.IndexId] = actNode.IndexId; } if (coreType == ECoreType.sing_box) { result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(selecteds); } else if (coreType == ECoreType.Xray) { result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(selecteds); } if (result.Success != true) { return result; } await File.WriteAllTextAsync(fileName, result.Data.ToString()); return result; } public static async Task GenerateClientSpeedtestConfig(Config config, CoreConfigContext context, ServerTestItem testItem, string fileName) { var result = new RetResult(); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); var port = Utils.GetFreePort(initPort + testItem.QueueNum); testItem.Port = port; if (context.RunCoreType == ECoreType.sing_box) { result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(port); } else { result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(port); } if (result.Success != true) { return result; } await File.WriteAllTextAsync(fileName, result.Data.ToString()); return result; } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class AnytlsFmt : BaseFmt { public static ProfileItem? Resolve(string str, out string msg) { msg = ResUI.ConfigurationFormatIncorrect; var parsedUrl = Utils.TryUri(str); if (parsedUrl == null) { return null; } ProfileItem item = new() { ConfigType = EConfigType.Anytls, Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), Address = parsedUrl.IdnHost, Port = parsedUrl.Port, }; var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); item.Password = rawUserInfo; var query = Utils.ParseQueryString(parsedUrl.Query); ResolveUriQuery(query, ref item); return item; } public static string? ToUri(ProfileItem? item) { if (item == null) { return null; } var remark = string.Empty; if (item.Remarks.IsNotEmpty()) { remark = "#" + Utils.UrlEncode(item.Remarks); } var pw = item.Password; var dicQuery = new Dictionary(); ToUriQuery(item, Global.None, ref dicQuery); return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark); } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs ================================================ using System.Collections.Specialized; namespace ServiceLib.Handler.Fmt; public class BaseFmt { private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure" }; protected static string GetIpv6(string address) { if (Utils.IsIpv6(address)) { // Check if the address is already surrounded by square brackets, if not, add square brackets return address.StartsWith('[') && address.EndsWith(']') ? address : $"[{address}]"; } else { return address; } } protected static int ToUriQuery(ProfileItem item, string? securityDef, ref Dictionary dicQuery) { if (item.StreamSecurity.IsNotEmpty()) { dicQuery.Add("security", item.StreamSecurity); } else { if (securityDef != null) { dicQuery.Add("security", securityDef); } } if (item.Sni.IsNotEmpty()) { dicQuery.Add("sni", Utils.UrlEncode(item.Sni)); } if (item.Fingerprint.IsNotEmpty()) { dicQuery.Add("fp", Utils.UrlEncode(item.Fingerprint)); } if (item.PublicKey.IsNotEmpty()) { dicQuery.Add("pbk", Utils.UrlEncode(item.PublicKey)); } if (item.ShortId.IsNotEmpty()) { dicQuery.Add("sid", Utils.UrlEncode(item.ShortId)); } if (item.SpiderX.IsNotEmpty()) { dicQuery.Add("spx", Utils.UrlEncode(item.SpiderX)); } if (item.Mldsa65Verify.IsNotEmpty()) { dicQuery.Add("pqv", Utils.UrlEncode(item.Mldsa65Verify)); } if (item.StreamSecurity.Equals(Global.StreamSecurity)) { if (item.Alpn.IsNotEmpty()) { dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); } ToUriQueryAllowInsecure(item, ref dicQuery); } if (item.EchConfigList.IsNotEmpty()) { dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList)); } if (item.CertSha.IsNotEmpty()) { dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha)); } if (item.Finalmask.IsNotEmpty()) { var node = JsonUtils.ParseJson(item.Finalmask); var finalmask = node != null ? JsonUtils.Serialize(node, new JsonSerializerOptions { WriteIndented = false, DefaultIgnoreCondition = JsonIgnoreCondition.Never, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }) : item.Finalmask; dicQuery.Add("fm", Utils.UrlEncode(finalmask)); } dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp)); switch (item.Network) { case nameof(ETransport.tcp): dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None); if (item.RequestHost.IsNotEmpty()) { dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); } break; case nameof(ETransport.kcp): dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None); if (item.Path.IsNotEmpty()) { dicQuery.Add("seed", Utils.UrlEncode(item.Path)); } break; case nameof(ETransport.ws): case nameof(ETransport.httpupgrade): if (item.RequestHost.IsNotEmpty()) { dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); } if (item.Path.IsNotEmpty()) { dicQuery.Add("path", Utils.UrlEncode(item.Path)); } break; case nameof(ETransport.xhttp): if (item.RequestHost.IsNotEmpty()) { dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); } if (item.Path.IsNotEmpty()) { dicQuery.Add("path", Utils.UrlEncode(item.Path)); } if (item.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(item.HeaderType)) { dicQuery.Add("mode", Utils.UrlEncode(item.HeaderType)); } if (item.Extra.IsNotEmpty()) { var node = JsonUtils.ParseJson(item.Extra); var extra = node != null ? JsonUtils.Serialize(node, new JsonSerializerOptions { WriteIndented = false, DefaultIgnoreCondition = JsonIgnoreCondition.Never, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }) : item.Extra; dicQuery.Add("extra", Utils.UrlEncode(extra)); } break; case nameof(ETransport.http): case nameof(ETransport.h2): dicQuery["type"] = nameof(ETransport.http); if (item.RequestHost.IsNotEmpty()) { dicQuery.Add("host", Utils.UrlEncode(item.RequestHost)); } if (item.Path.IsNotEmpty()) { dicQuery.Add("path", Utils.UrlEncode(item.Path)); } break; case nameof(ETransport.quic): dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None); dicQuery.Add("quicSecurity", Utils.UrlEncode(item.RequestHost)); dicQuery.Add("key", Utils.UrlEncode(item.Path)); break; case nameof(ETransport.grpc): if (item.Path.IsNotEmpty()) { dicQuery.Add("authority", Utils.UrlEncode(item.RequestHost)); dicQuery.Add("serviceName", Utils.UrlEncode(item.Path)); if (item.HeaderType is Global.GrpcGunMode or Global.GrpcMultiMode) { dicQuery.Add("mode", Utils.UrlEncode(item.HeaderType)); } } break; } return 0; } protected static int ToUriQueryLite(ProfileItem item, ref Dictionary dicQuery) { if (item.Sni.IsNotEmpty()) { dicQuery.Add("sni", Utils.UrlEncode(item.Sni)); } if (item.Alpn.IsNotEmpty()) { dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); } ToUriQueryAllowInsecure(item, ref dicQuery); return 0; } private static int ToUriQueryAllowInsecure(ProfileItem item, ref Dictionary dicQuery) { if (item.AllowInsecure.Equals(Global.AllowInsecure.First())) { // Add two for compatibility dicQuery.Add("insecure", "1"); dicQuery.Add("allowInsecure", "1"); } else { dicQuery.Add("insecure", "0"); dicQuery.Add("allowInsecure", "0"); } return 0; } protected static int ResolveUriQuery(NameValueCollection query, ref ProfileItem item) { item.StreamSecurity = GetQueryValue(query, "security"); item.Sni = GetQueryValue(query, "sni"); item.Alpn = GetQueryDecoded(query, "alpn"); item.Fingerprint = GetQueryDecoded(query, "fp"); item.PublicKey = GetQueryDecoded(query, "pbk"); item.ShortId = GetQueryDecoded(query, "sid"); item.SpiderX = GetQueryDecoded(query, "spx"); item.Mldsa65Verify = GetQueryDecoded(query, "pqv"); item.EchConfigList = GetQueryDecoded(query, "ech"); item.CertSha = GetQueryDecoded(query, "pcs"); var finalmaskDecoded = GetQueryDecoded(query, "fm"); if (finalmaskDecoded.IsNotEmpty()) { var node = JsonUtils.ParseJson(finalmaskDecoded); item.Finalmask = node != null ? JsonUtils.Serialize(node, new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.Never, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }) : finalmaskDecoded; } else { item.Finalmask = string.Empty; } if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1")) { item.AllowInsecure = Global.AllowInsecure.First(); } else if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "0")) { item.AllowInsecure = Global.AllowInsecure.Skip(1).First(); } else { item.AllowInsecure = string.Empty; } item.Network = GetQueryValue(query, "type", nameof(ETransport.tcp)); switch (item.Network) { case nameof(ETransport.tcp): item.HeaderType = GetQueryValue(query, "headerType", Global.None); item.RequestHost = GetQueryDecoded(query, "host"); break; case nameof(ETransport.kcp): item.HeaderType = GetQueryValue(query, "headerType", Global.None); item.Path = GetQueryDecoded(query, "seed"); break; case nameof(ETransport.ws): case nameof(ETransport.httpupgrade): item.RequestHost = GetQueryDecoded(query, "host"); item.Path = GetQueryDecoded(query, "path", "/"); break; case nameof(ETransport.xhttp): item.RequestHost = GetQueryDecoded(query, "host"); item.Path = GetQueryDecoded(query, "path", "/"); item.HeaderType = GetQueryDecoded(query, "mode"); var extraDecoded = GetQueryDecoded(query, "extra"); if (extraDecoded.IsNotEmpty()) { var node = JsonUtils.ParseJson(extraDecoded); if (node != null) { extraDecoded = JsonUtils.Serialize(node, new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.Never, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); } } item.Extra = extraDecoded; break; case nameof(ETransport.http): case nameof(ETransport.h2): item.Network = nameof(ETransport.h2); item.RequestHost = GetQueryDecoded(query, "host"); item.Path = GetQueryDecoded(query, "path", "/"); break; case nameof(ETransport.quic): item.HeaderType = GetQueryValue(query, "headerType", Global.None); item.RequestHost = GetQueryValue(query, "quicSecurity", Global.None); item.Path = GetQueryDecoded(query, "key"); break; case nameof(ETransport.grpc): item.RequestHost = GetQueryDecoded(query, "authority"); item.Path = GetQueryDecoded(query, "serviceName"); item.HeaderType = GetQueryDecoded(query, "mode", Global.GrpcGunMode); break; default: break; } return 0; } protected static bool Contains(string str, params string[] s) { return s.All(item => str.Contains(item, StringComparison.OrdinalIgnoreCase)); } protected static string WriteAllText(string strData, string ext = "json") { var fileName = Utils.GetTempPath($"{Utils.GetGuid(false)}.{ext}"); File.WriteAllText(fileName, strData); return fileName; } protected static string ToUri(EConfigType eConfigType, string address, object port, string userInfo, Dictionary? dicQuery, string? remark) { var query = dicQuery != null ? ("?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray())) : string.Empty; var url = $"{Utils.UrlEncode(userInfo)}@{GetIpv6(address)}:{port}"; return $"{Global.ProtocolShares[eConfigType]}{url}{query}{remark}"; } protected static string GetQueryValue(NameValueCollection query, string key, string defaultValue = "") { return query[key] ?? defaultValue; } protected static string GetQueryDecoded(NameValueCollection query, string key, string defaultValue = "") { return Utils.UrlDecode(GetQueryValue(query, key, defaultValue)); } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/ClashFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class ClashFmt : BaseFmt { public static ProfileItem? ResolveFull(string strData, string? subRemarks) { if (Contains(strData, "rules", "-port", "proxies")) { var fileName = WriteAllText(strData, "yaml"); var profileItem = new ProfileItem { CoreType = ECoreType.mihomo, Address = fileName, Remarks = subRemarks ?? "clash_custom" }; return profileItem; } return null; } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs ================================================ namespace ServiceLib.Handler.Fmt; public class FmtHandler { private static readonly string _tag = "FmtHandler"; public static string? GetShareUri(ProfileItem item) { try { var url = item.ConfigType switch { EConfigType.VMess => VmessFmt.ToUri(item), EConfigType.Shadowsocks => ShadowsocksFmt.ToUri(item), EConfigType.SOCKS => SocksFmt.ToUri(item), EConfigType.Trojan => TrojanFmt.ToUri(item), EConfigType.VLESS => VLESSFmt.ToUri(item), EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item), EConfigType.TUIC => TuicFmt.ToUri(item), EConfigType.WireGuard => WireguardFmt.ToUri(item), EConfigType.Anytls => AnytlsFmt.ToUri(item), _ => null, }; return url; } catch (Exception ex) { Logging.SaveLog(_tag, ex); return string.Empty; } } public static ProfileItem? ResolveConfig(string config, out string msg) { msg = ResUI.ConfigurationFormatIncorrect; try { var str = config.TrimEx(); if (str.IsNullOrEmpty()) { msg = ResUI.FailedReadConfiguration; return null; } if (str.StartsWith(Global.ProtocolShares[EConfigType.VMess])) { return VmessFmt.Resolve(str, out msg); } else if (str.StartsWith(Global.ProtocolShares[EConfigType.Shadowsocks])) { return ShadowsocksFmt.Resolve(str, out msg); } else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS])) { return SocksFmt.Resolve(str, out msg); } else if (str.StartsWith(Global.ProtocolShares[EConfigType.Trojan])) { return TrojanFmt.Resolve(str, out msg); } else if (str.StartsWith(Global.ProtocolShares[EConfigType.VLESS])) { return VLESSFmt.Resolve(str, out msg); } else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2]) || str.StartsWith(Global.Hysteria2ProtocolShare)) { return Hysteria2Fmt.Resolve(str, out msg); } else if (str.StartsWith(Global.ProtocolShares[EConfigType.TUIC])) { return TuicFmt.Resolve(str, out msg); } else if (str.StartsWith(Global.ProtocolShares[EConfigType.WireGuard])) { return WireguardFmt.Resolve(str, out msg); } else if (str.StartsWith(Global.ProtocolShares[EConfigType.Anytls])) { return AnytlsFmt.Resolve(str, out msg); } else { msg = ResUI.NonvmessOrssProtocol; return null; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); msg = ResUI.Incorrectconfiguration; return null; } } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/HtmlPageFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class HtmlPageFmt : BaseFmt { public static bool IsHtmlPage(string strData) { return Contains(strData, "(); ToUriQueryLite(item, ref dicQuery); var protocolExtraItem = item.GetProtocolExtra(); if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty()) { dicQuery.Add("obfs", "salamander"); dicQuery.Add("obfs-password", Utils.UrlEncode(protocolExtraItem.SalamanderPass)); } if (!protocolExtraItem.Ports.IsNullOrEmpty()) { dicQuery.Add("mport", Utils.UrlEncode(protocolExtraItem.Ports.Replace(':', '-'))); } if (!item.CertSha.IsNullOrEmpty()) { var sha = item.CertSha; var idx = sha.IndexOf(','); if (idx > 0) { sha = sha[..idx]; } dicQuery.Add("pinSHA256", Utils.UrlEncode(sha)); } return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Password, dicQuery, remark); } public static ProfileItem? ResolveFull2(string strData, string? subRemarks) { if (Contains(strData, "server", "auth", "up", "down", "listen")) { var fileName = WriteAllText(strData); var profileItem = new ProfileItem { CoreType = ECoreType.hysteria2, Address = fileName, Remarks = subRemarks ?? "hysteria2_custom" }; return profileItem; } return null; } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class ShadowsocksFmt : BaseFmt { public static ProfileItem? Resolve(string str, out string msg) { msg = ResUI.ConfigurationFormatIncorrect; ProfileItem? item; item = ResolveSSLegacy(str) ?? ResolveSip002(str); if (item == null) { return null; } if (item.Address.Length == 0 || item.Port == 0 || item.GetProtocolExtra().SsMethod.IsNullOrEmpty() || item.Password.Length == 0) { return null; } item.ConfigType = EConfigType.Shadowsocks; return item; } public static string? ToUri(ProfileItem? item) { if (item == null) { return null; } var remark = string.Empty; if (item.Remarks.IsNotEmpty()) { remark = "#" + Utils.UrlEncode(item.Remarks); } //url = string.Format("{0}:{1}@{2}:{3}", // item.security, // item.id, // item.address, // item.port); //url = Utile.Base64Encode(url); //new Sip002 var pw = Utils.Base64Encode($"{item.GetProtocolExtra().SsMethod}:{item.Password}", true); // plugin var plugin = string.Empty; var pluginArgs = string.Empty; if (item.Network == nameof(ETransport.tcp) && item.HeaderType == Global.TcpHeaderHttp) { plugin = "obfs-local"; pluginArgs = $"obfs=http;obfs-host={item.RequestHost};"; } else { if (item.Network == nameof(ETransport.ws)) { pluginArgs += "mode=websocket;"; pluginArgs += $"host={item.RequestHost};"; // https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172 // Equal signs and commas [and backslashes] must be escaped with a backslash. var path = item.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,"); pluginArgs += $"path={path};"; } else if (item.Network == nameof(ETransport.quic)) { pluginArgs += "mode=quic;"; } if (item.StreamSecurity == Global.StreamSecurity) { pluginArgs += "tls;"; var certs = CertPemManager.ParsePemChain(item.Cert); if (certs.Count > 0) { var cert = certs.First(); const string beginMarker = "-----BEGIN CERTIFICATE-----\n"; const string endMarker = "\n-----END CERTIFICATE-----"; var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim(); base64Content = base64Content.Replace("=", "\\="); pluginArgs += $"certRaw={base64Content};"; } } if (pluginArgs.Length > 0) { plugin = "v2ray-plugin"; pluginArgs += "mux=0;"; } } var dicQuery = new Dictionary(); if (plugin.IsNotEmpty()) { var pluginStr = plugin + ";" + pluginArgs; // pluginStr remove last ';' and url encode if (pluginStr.EndsWith(';')) { pluginStr = pluginStr[..^1]; } dicQuery["plugin"] = Utils.UrlEncode(pluginStr); } return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, dicQuery, remark); } private static readonly Regex UrlFinder = new(@"ss://(?[A-Za-z0-9+-/=_]+)(?:#(?\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex DetailsParser = new(@"^((?.+?):(?.*)@(?.+?):(?\d+?))$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static ProfileItem? ResolveSSLegacy(string result) { var match = UrlFinder.Match(result); if (!match.Success) { return null; } ProfileItem item = new(); var base64 = match.Groups["base64"].Value.TrimEnd('/'); var tag = match.Groups["tag"].Value; if (tag.IsNotEmpty()) { item.Remarks = Utils.UrlDecode(tag); } Match details; try { details = DetailsParser.Match(Utils.Base64Decode(base64)); } catch (FormatException) { return null; } if (!details.Success) { return null; } item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = details.Groups["method"].Value }); item.Password = details.Groups["password"].Value; item.Address = details.Groups["hostname"].Value; item.Port = details.Groups["port"].Value.ToInt(); return item; } private static ProfileItem? ResolveSip002(string result) { var parsedUrl = Utils.TryUri(result); if (parsedUrl == null) { return null; } ProfileItem item = new() { Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), Address = parsedUrl.IdnHost, Port = parsedUrl.Port, }; var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); //2022-blake3 if (rawUserInfo.Contains(':')) { var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); if (userInfoParts.Length != 2) { return null; } item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() }); item.Password = Utils.UrlDecode(userInfoParts.Last()); } else { // parse base64 UserInfo var userInfo = Utils.Base64Decode(rawUserInfo); var userInfoParts = userInfo.Split(new[] { ':' }, 2); if (userInfoParts.Length != 2) { return null; } item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() }); item.Password = userInfoParts.Last(); } var queryParameters = Utils.ParseQueryString(parsedUrl.Query); if (queryParameters["plugin"] != null) { var pluginStr = queryParameters["plugin"]; var pluginParts = pluginStr.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); if (pluginParts.Length == 0) { return null; } var pluginName = pluginParts[0]; // A typo in https://github.com/shadowsocks/shadowsocks-org/blob/6b1c064db4129de99c516294960e731934841c94/docs/doc/sip002.md?plain=1#L15 // "simple-obfs" should be "obfs-local" if (pluginName == "simple-obfs") { pluginName = "obfs-local"; } // Parse obfs-local plugin if (pluginName == "obfs-local") { var obfsMode = pluginParts.FirstOrDefault(t => t.StartsWith("obfs=")); var obfsHost = pluginParts.FirstOrDefault(t => t.StartsWith("obfs-host=")); if ((!obfsMode.IsNullOrEmpty()) && obfsMode.Contains("obfs=http") && obfsHost.IsNotEmpty()) { obfsHost = obfsHost.Replace("obfs-host=", ""); item.Network = Global.DefaultNetwork; item.HeaderType = Global.TcpHeaderHttp; item.RequestHost = obfsHost; } } // Parse v2ray-plugin else if (pluginName == "v2ray-plugin") { var mode = pluginParts.FirstOrDefault(t => t.StartsWith("mode="), "websocket"); var host = pluginParts.FirstOrDefault(t => t.StartsWith("host=")); var path = pluginParts.FirstOrDefault(t => t.StartsWith("path=")); var hasTls = pluginParts.Any(t => t == "tls"); var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw=")); var mux = pluginParts.FirstOrDefault(t => t.StartsWith("mux=")); var modeValue = mode.Replace("mode=", ""); if (modeValue == "websocket") { item.Network = nameof(ETransport.ws); if (!host.IsNullOrEmpty()) { item.RequestHost = host.Replace("host=", ""); item.Sni = item.RequestHost; } if (!path.IsNullOrEmpty()) { var pathValue = path.Replace("path=", ""); pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\"); item.Path = pathValue; } } else if (modeValue == "quic") { item.Network = nameof(ETransport.quic); } if (hasTls) { item.StreamSecurity = Global.StreamSecurity; if (!certRaw.IsNullOrEmpty()) { var certBase64 = certRaw.Replace("certRaw=", ""); certBase64 = certBase64.Replace("\\=", "="); const string beginMarker = "-----BEGIN CERTIFICATE-----\n"; const string endMarker = "\n-----END CERTIFICATE-----"; var certPem = beginMarker + certBase64 + endMarker; item.Cert = certPem; } } if (!mux.IsNullOrEmpty()) { var muxValue = mux.Replace("mux=", ""); var muxCount = muxValue.ToInt(); if (muxCount > 0) { return null; } } } } return item; } public static List? ResolveSip008(string result) { //SsSIP008 var lstSsServer = JsonUtils.Deserialize>(result); if (lstSsServer?.Count <= 0) { var ssSIP008 = JsonUtils.Deserialize(result); if (ssSIP008?.servers?.Count > 0) { lstSsServer = ssSIP008.servers; } } if (lstSsServer?.Count > 0) { List lst = []; foreach (var it in lstSsServer) { var ssItem = new ProfileItem() { Remarks = it.remarks, Password = it.password, Address = it.server, Port = it.server_port.ToInt() }; ssItem.SetProtocolExtra(new ProtocolExtraItem() { SsMethod = it.method }); lst.Add(ssItem); } return lst; } return null; } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/SingboxFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class SingboxFmt : BaseFmt { public static List? ResolveFullArray(string strData, string? subRemarks) { var configObjects = JsonUtils.Deserialize(strData); if (configObjects is not { Length: > 0 }) { return null; } List lstResult = []; foreach (var configObject in configObjects) { var objectString = JsonUtils.Serialize(configObject); var profileIt = ResolveFull(objectString, subRemarks); if (profileIt != null) { lstResult.Add(profileIt); } } return lstResult; } public static ProfileItem? ResolveFull(string strData, string? subRemarks) { var config = JsonUtils.ParseJson(strData); if (config?["inbounds"] == null || config["outbounds"] == null || config["route"] == null || config["dns"] == null) { return null; } var fileName = WriteAllText(strData); var profileItem = new ProfileItem { CoreType = ECoreType.sing_box, Address = fileName, Remarks = subRemarks ?? "singbox_custom" }; return profileItem; } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class SocksFmt : BaseFmt { public static ProfileItem? Resolve(string str, out string msg) { msg = ResUI.ConfigurationFormatIncorrect; var item = ResolveSocksNew(str) ?? ResolveSocks(str); if (item == null) { return null; } if (item.Address.Length == 0 || item.Port == 0) { return null; } item.ConfigType = EConfigType.SOCKS; return item; } public static string? ToUri(ProfileItem? item) { if (item == null) { return null; } var remark = string.Empty; if (item.Remarks.IsNotEmpty()) { remark = "#" + Utils.UrlEncode(item.Remarks); } //new var pw = Utils.Base64Encode($"{item.Username}:{item.Password}", true); return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark); } private static ProfileItem? ResolveSocks(string result) { ProfileItem item = new() { ConfigType = EConfigType.SOCKS }; result = result[Global.ProtocolShares[EConfigType.SOCKS].Length..]; //remark var indexRemark = result.IndexOf('#'); if (indexRemark > 0) { try { item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1)); } catch { } result = result[..indexRemark]; } //part decode var indexS = result.IndexOf('@'); if (indexS > 0) { } else { result = Utils.Base64Decode(result); } var arr1 = result.Split('@'); if (arr1.Length != 2) { return null; } var arr21 = arr1.First().Split(':'); var indexPort = arr1.Last().LastIndexOf(":"); if (arr21.Length != 2 || indexPort < 0) { return null; } item.Address = arr1[1][..indexPort]; item.Port = arr1[1][(indexPort + 1)..].ToInt(); item.Username = arr21.First(); item.Password = arr21[1]; return item; } private static ProfileItem? ResolveSocksNew(string result) { var parsedUrl = Utils.TryUri(result); if (parsedUrl == null) { return null; } ProfileItem item = new() { Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), Address = parsedUrl.IdnHost, Port = parsedUrl.Port, }; // parse base64 UserInfo var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); var userInfo = Utils.Base64Decode(rawUserInfo); var userInfoParts = userInfo.Split([':'], 2); if (userInfoParts.Length == 2) { item.Username = userInfoParts.First(); item.Password = userInfoParts[1]; } return item; } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class TrojanFmt : BaseFmt { public static ProfileItem? Resolve(string str, out string msg) { msg = ResUI.ConfigurationFormatIncorrect; ProfileItem item = new() { ConfigType = EConfigType.Trojan }; var url = Utils.TryUri(str); if (url == null) { return null; } item.Address = url.IdnHost; item.Port = url.Port; item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); item.Password = Utils.UrlDecode(url.UserInfo); var query = Utils.ParseQueryString(url.Query); item.SetProtocolExtra(item.GetProtocolExtra() with { Flow = GetQueryValue(query, "flow") }); ResolveUriQuery(query, ref item); return item; } public static string? ToUri(ProfileItem? item) { if (item == null) { return null; } var remark = string.Empty; if (item.Remarks.IsNotEmpty()) { remark = "#" + Utils.UrlEncode(item.Remarks); } var dicQuery = new Dictionary(); if (!item.GetProtocolExtra().Flow.IsNullOrEmpty()) { dicQuery.Add("flow", item.GetProtocolExtra().Flow); } ToUriQuery(item, null, ref dicQuery); return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Password, dicQuery, remark); } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class TuicFmt : BaseFmt { public static ProfileItem? Resolve(string str, out string msg) { msg = ResUI.ConfigurationFormatIncorrect; ProfileItem item = new() { ConfigType = EConfigType.TUIC }; var url = Utils.TryUri(str); if (url == null) { return null; } item.Address = url.IdnHost; item.Port = url.Port; item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); var rawUserInfo = Utils.UrlDecode(url.UserInfo); var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); if (userInfoParts.Length == 2) { item.Username = userInfoParts.First(); item.Password = userInfoParts.Last(); } var query = Utils.ParseQueryString(url.Query); ResolveUriQuery(query, ref item); item.HeaderType = GetQueryValue(query, "congestion_control"); return item; } public static string? ToUri(ProfileItem? item) { if (item == null) { return null; } var remark = string.Empty; if (item.Remarks.IsNotEmpty()) { remark = "#" + Utils.UrlEncode(item.Remarks); } var dicQuery = new Dictionary(); ToUriQueryLite(item, ref dicQuery); dicQuery.Add("congestion_control", item.HeaderType); return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Username ?? ""}:{item.Password}", dicQuery, remark); } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/V2rayFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class V2rayFmt : BaseFmt { public static List? ResolveFullArray(string strData, string? subRemarks) { var configObjects = JsonUtils.Deserialize(strData); if (configObjects is not { Length: > 0 }) { return null; } List lstResult = []; foreach (var configObject in configObjects) { var objectString = JsonUtils.Serialize(configObject); var profileIt = ResolveFull(objectString, subRemarks); if (profileIt != null) { lstResult.Add(profileIt); } } return lstResult; } public static ProfileItem? ResolveFull(string strData, string? subRemarks) { var config = JsonUtils.ParseJson(strData); if (config?["inbounds"] == null || config["outbounds"] == null || config["routing"] == null) { return null; } var fileName = WriteAllText(strData); var profileItem = new ProfileItem { CoreType = ECoreType.Xray, Address = fileName, Remarks = config?["remarks"]?.ToString() ?? subRemarks ?? "v2ray_custom" }; return profileItem; } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class VLESSFmt : BaseFmt { public static ProfileItem? Resolve(string str, out string msg) { msg = ResUI.ConfigurationFormatIncorrect; ProfileItem item = new() { ConfigType = EConfigType.VLESS, }; var url = Utils.TryUri(str); if (url == null) { return null; } item.Address = url.IdnHost; item.Port = url.Port; item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); item.Password = Utils.UrlDecode(url.UserInfo); var query = Utils.ParseQueryString(url.Query); item.SetProtocolExtra(item.GetProtocolExtra() with { VlessEncryption = GetQueryValue(query, "encryption", Global.None), Flow = GetQueryValue(query, "flow") }); item.StreamSecurity = GetQueryValue(query, "security"); ResolveUriQuery(query, ref item); return item; } public static string? ToUri(ProfileItem? item) { if (item == null) { return null; } var remark = string.Empty; if (item.Remarks.IsNotEmpty()) { remark = "#" + Utils.UrlEncode(item.Remarks); } var dicQuery = new Dictionary(); dicQuery.Add("encryption", !item.GetProtocolExtra().VlessEncryption.IsNullOrEmpty() ? item.GetProtocolExtra().VlessEncryption : Global.None); if (!item.GetProtocolExtra().Flow.IsNullOrEmpty()) { dicQuery.Add("flow", item.GetProtocolExtra().Flow); } ToUriQuery(item, Global.None, ref dicQuery); return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Password, dicQuery, remark); } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class VmessFmt : BaseFmt { public static ProfileItem? Resolve(string str, out string msg) { msg = ResUI.ConfigurationFormatIncorrect; ProfileItem? item; if (str.IndexOf('@') > 0) { item = ResolveStdVmess(str) ?? ResolveVmess(str, out msg); } else { item = ResolveVmess(str, out msg); } return item; } public static string? ToUri(ProfileItem? item) { if (item == null) { return null; } var vmessQRCode = new VmessQRCode { v = 2, ps = item.Remarks.TrimEx(), add = item.Address, port = item.Port, id = item.Password, aid = int.TryParse(item.GetProtocolExtra()?.AlterId, out var result) ? result : 0, scy = item.GetProtocolExtra().VmessSecurity ?? "", net = item.Network, type = item.HeaderType, host = item.RequestHost, path = item.Path, tls = item.StreamSecurity, sni = item.Sni, alpn = item.Alpn, fp = item.Fingerprint, insecure = item.AllowInsecure.Equals(Global.AllowInsecure.First()) ? "1" : "0" }; var url = JsonUtils.Serialize(vmessQRCode); url = Utils.Base64Encode(url); url = $"{Global.ProtocolShares[EConfigType.VMess]}{url}"; return url; } private static ProfileItem? ResolveVmess(string result, out string msg) { msg = string.Empty; var item = new ProfileItem { ConfigType = EConfigType.VMess }; result = result[Global.ProtocolShares[EConfigType.VMess].Length..]; result = Utils.Base64Decode(result); var vmessQRCode = JsonUtils.Deserialize(result); if (vmessQRCode == null) { msg = ResUI.FailedConversionConfiguration; return null; } item.Network = Global.DefaultNetwork; item.HeaderType = Global.None; //item.ConfigVersion = vmessQRCode.v; item.Remarks = Utils.ToString(vmessQRCode.ps); item.Address = Utils.ToString(vmessQRCode.add); item.Port = vmessQRCode.port; item.Password = Utils.ToString(vmessQRCode.id); item.SetProtocolExtra(new ProtocolExtraItem { AlterId = vmessQRCode.aid.ToString(), VmessSecurity = vmessQRCode.scy.IsNullOrEmpty() ? Global.DefaultSecurity : vmessQRCode.scy, }); if (vmessQRCode.net.IsNotEmpty()) { item.Network = vmessQRCode.net; } if (vmessQRCode.type.IsNotEmpty()) { item.HeaderType = vmessQRCode.type; } item.RequestHost = Utils.ToString(vmessQRCode.host); item.Path = Utils.ToString(vmessQRCode.path); item.StreamSecurity = Utils.ToString(vmessQRCode.tls); item.Sni = Utils.ToString(vmessQRCode.sni); item.Alpn = Utils.ToString(vmessQRCode.alpn); item.Fingerprint = Utils.ToString(vmessQRCode.fp); item.AllowInsecure = vmessQRCode.insecure == "1" ? Global.AllowInsecure.First() : string.Empty; return item; } public static ProfileItem? ResolveStdVmess(string str) { var item = new ProfileItem { ConfigType = EConfigType.VMess, }; var url = Utils.TryUri(str); if (url == null) { return null; } item.Address = url.IdnHost; item.Port = url.Port; item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); item.Password = Utils.UrlDecode(url.UserInfo); item.SetProtocolExtra(new ProtocolExtraItem { VmessSecurity = "auto", }); var query = Utils.ParseQueryString(url.Query); ResolveUriQuery(query, ref item); return item; } } ================================================ FILE: v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs ================================================ namespace ServiceLib.Handler.Fmt; public class WireguardFmt : BaseFmt { public static ProfileItem? Resolve(string str, out string msg) { msg = ResUI.ConfigurationFormatIncorrect; ProfileItem item = new() { ConfigType = EConfigType.WireGuard }; var url = Utils.TryUri(str); if (url == null) { return null; } item.Address = url.IdnHost; item.Port = url.Port; item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); item.Password = Utils.UrlDecode(url.UserInfo); var query = Utils.ParseQueryString(url.Query); item.SetProtocolExtra(item.GetProtocolExtra() with { WgPublicKey = GetQueryDecoded(query, "publickey"), WgReserved = GetQueryDecoded(query, "reserved"), WgInterfaceAddress = GetQueryDecoded(query, "address"), WgMtu = int.TryParse(GetQueryDecoded(query, "mtu"), out var mtuVal) ? mtuVal : 1280, }); return item; } public static string? ToUri(ProfileItem? item) { if (item == null) { return null; } var remark = string.Empty; if (item.Remarks.IsNotEmpty()) { remark = "#" + Utils.UrlEncode(item.Remarks); } var dicQuery = new Dictionary(); if (!item.GetProtocolExtra().WgPublicKey.IsNullOrEmpty()) { dicQuery.Add("publickey", Utils.UrlEncode(item.GetProtocolExtra().WgPublicKey)); } if (!item.GetProtocolExtra().WgReserved.IsNullOrEmpty()) { dicQuery.Add("reserved", Utils.UrlEncode(item.GetProtocolExtra().WgReserved)); } if (!item.GetProtocolExtra().WgInterfaceAddress.IsNullOrEmpty()) { dicQuery.Add("address", Utils.UrlEncode(item.GetProtocolExtra().WgInterfaceAddress)); } dicQuery.Add("mtu", Utils.UrlEncode(item.GetProtocolExtra().WgMtu > 0 ? item.GetProtocolExtra().WgMtu.ToString() : "1280")); return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Password, dicQuery, remark); } } ================================================ FILE: v2rayN/ServiceLib/Handler/SubscriptionHandler.cs ================================================ namespace ServiceLib.Handler; public static class SubscriptionHandler { public static async Task UpdateProcess(Config config, string subId, bool blProxy, Func updateFunc) { await updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart); var subItem = await AppManager.Instance.SubItems(); if (subItem is not { Count: > 0 }) { await updateFunc?.Invoke(false, ResUI.MsgNoValidSubscription); return; } var successCount = 0; foreach (var item in subItem) { try { if (!IsValidSubscription(item, subId)) { continue; } var hashCode = $"{item.Remarks}->"; if (item.Enabled == false) { await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}"); continue; } // Create download handler var downloadHandle = CreateDownloadHandler(hashCode, updateFunc); await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}"); // Get all subscription content (main subscription + additional subscriptions) var result = await DownloadAllSubscriptions(config, item, blProxy, downloadHandle); // Process download result if (await ProcessDownloadResult(config, item.Id, result, hashCode, updateFunc)) { successCount++; } await updateFunc?.Invoke(false, "-------------------------------------------------------"); } catch (Exception ex) { var hashCode = $"{item.Remarks}->"; Logging.SaveLog("UpdateSubscription", ex); await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgFailedImportSubscription}: {ex.Message}"); await updateFunc?.Invoke(false, "-------------------------------------------------------"); } } await updateFunc?.Invoke(successCount > 0, $"{ResUI.MsgUpdateSubscriptionEnd}"); } private static bool IsValidSubscription(SubItem item, string subId) { var id = item.Id.TrimEx(); var url = item.Url.TrimEx(); if (id.IsNullOrEmpty() || url.IsNullOrEmpty()) { return false; } if (subId.IsNotEmpty() && item.Id != subId) { return false; } if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol)) { return false; } return true; } private static DownloadService CreateDownloadHandler(string hashCode, Func updateFunc) { var downloadHandle = new DownloadService(); downloadHandle.Error += (sender2, args) => { updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}"); }; return downloadHandle; } private static async Task DownloadSubscriptionContent(DownloadService downloadHandle, string url, bool blProxy, string userAgent) { var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent); // If download with proxy fails, try direct connection if (blProxy && result.IsNullOrEmpty()) { result = await downloadHandle.TryDownloadString(url, false, userAgent); } return result ?? string.Empty; } private static async Task DownloadAllSubscriptions(Config config, SubItem item, bool blProxy, DownloadService downloadHandle) { // Download main subscription content var result = await DownloadMainSubscription(config, item, blProxy, downloadHandle); // Process additional subscription links (if any) if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty()) { result = await DownloadAdditionalSubscriptions(item, result, blProxy, downloadHandle); } return result; } private static async Task DownloadMainSubscription(Config config, SubItem item, bool blProxy, DownloadService downloadHandle) { // Prepare subscription URL and download directly var url = Utils.GetPunycode(item.Url.TrimEx()); // If conversion is needed if (item.ConvertTarget.IsNotEmpty()) { var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty() ? Global.SubConvertUrls.FirstOrDefault() : config.ConstItem.SubConvertUrl; url = string.Format(subConvertUrl!, Utils.UrlEncode(url)); if (!url.Contains("target=")) { url += string.Format("&target={0}", item.ConvertTarget); } if (!url.Contains("config=")) { url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault()); } } // Download and return result directly return await DownloadSubscriptionContent(downloadHandle, url, blProxy, item.UserAgent); } private static async Task DownloadAdditionalSubscriptions(SubItem item, string mainResult, bool blProxy, DownloadService downloadHandle) { var result = mainResult; // If main subscription result is Base64 encoded, decode it first if (result.IsNotEmpty() && Utils.IsBase64String(result)) { result = Utils.Base64Decode(result); } // Process additional URL list var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? []; foreach (var it in lstUrl) { var url2 = Utils.GetPunycode(it); if (url2.IsNullOrEmpty()) { continue; } var additionalResult = await DownloadSubscriptionContent(downloadHandle, url2, blProxy, item.UserAgent); if (additionalResult.IsNotEmpty()) { // Process additional subscription results, add to main result if (Utils.IsBase64String(additionalResult)) { result += Environment.NewLine + Utils.Base64Decode(additionalResult); } else { result += Environment.NewLine + additionalResult; } } } return result; } private static async Task ProcessDownloadResult(Config config, string id, string result, string hashCode, Func updateFunc) { if (result.IsNullOrEmpty()) { await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}"); return false; } await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}"); // If result is too short, display content directly if (result.Length < 99) { await updateFunc?.Invoke(false, $"{hashCode}{result}"); } await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartParsingSubscription}"); // Add servers to configuration var ret = await ConfigHandler.AddBatchServers(config, result, id, true); if (ret <= 0) { Logging.SaveLog("FailedImportSubscription"); Logging.SaveLog(result); } // Update completion message await updateFunc?.Invoke(false, ret > 0 ? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}" : $"{hashCode}{ResUI.MsgFailedImportSubscription}"); return ret > 0; } } ================================================ FILE: v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs ================================================ namespace ServiceLib.Handler.SysProxy; public static class ProxySettingLinux { private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh"; public static async Task SetProxy(string host, int port, string exceptions) { List args = ["manual", host, port.ToString(), exceptions]; await ExecCmd(args); } public static async Task UnsetProxy() { List args = ["none"]; await ExecCmd(args); } private static async Task ExecCmd(List args) { var customSystemProxyScriptPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyScriptPath; var fileName = (customSystemProxyScriptPath.IsNotEmpty() && File.Exists(customSystemProxyScriptPath)) ? customSystemProxyScriptPath : await FileUtils.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false); // TODO: temporarily notify which script is being used NoticeManager.Instance.SendMessage(fileName); await Utils.GetCliWrapOutput(fileName, args); } } ================================================ FILE: v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs ================================================ namespace ServiceLib.Handler.SysProxy; public static class ProxySettingOSX { private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh"; public static async Task SetProxy(string host, int port, string exceptions) { List args = ["set", host, port.ToString()]; if (exceptions.IsNotEmpty()) { args.AddRange(exceptions.Split(',')); } await ExecCmd(args); } public static async Task UnsetProxy() { List args = ["clear"]; await ExecCmd(args); } private static async Task ExecCmd(List args) { var customSystemProxyScriptPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyScriptPath; var fileName = (customSystemProxyScriptPath.IsNotEmpty() && File.Exists(customSystemProxyScriptPath)) ? customSystemProxyScriptPath : await FileUtils.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName), false); // TODO: temporarily notify which script is being used NoticeManager.Instance.SendMessage(fileName); await Utils.GetCliWrapOutput(fileName, args); } } ================================================ FILE: v2rayN/ServiceLib/Handler/SysProxy/ProxySettingWindows.cs ================================================ using static ServiceLib.Handler.SysProxy.ProxySettingWindows.InternetConnectionOption; namespace ServiceLib.Handler.SysProxy; public static class ProxySettingWindows { private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings"; private static bool SetProxyFallback(string? strProxy, string? exceptions, int type) { if (type == 1) { WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 0); WindowsUtils.RegWriteValue(_regPath, "ProxyServer", string.Empty); WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", string.Empty); WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", string.Empty); } if (type == 2) { WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 1); WindowsUtils.RegWriteValue(_regPath, "ProxyServer", strProxy ?? string.Empty); WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", exceptions ?? string.Empty); WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", string.Empty); } else if (type == 4) { WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 0); WindowsUtils.RegWriteValue(_regPath, "ProxyServer", string.Empty); WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", string.Empty); WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", strProxy ?? string.Empty); } return true; } /// // set to use no proxy /// /// Error message with win32 error code public static bool UnsetProxy() { return SetProxy(null, null, 1); } /// /// Set system proxy settings /// /// proxy address /// exception addresses that do not use proxy /// type of proxy defined in PerConnFlags /// PROXY_TYPE_DIRECT = 0x00000001, // direct connection (no proxy) /// PROXY_TYPE_PROXY = 0x00000002, // via named proxy /// PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy script URL /// PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection /// /// Error message with win32 error code /// true: one of connection is successfully updated proxy settings public static bool SetProxy(string? strProxy, string? exceptions, int type) { try { // set proxy for LAN var result = SetConnectionProxy(null, strProxy, exceptions, type); // set proxy for dial up connections var connections = EnumerateRasEntries(); foreach (var connection in connections) { result |= SetConnectionProxy(connection, strProxy, exceptions, type); } return result; } catch { _ = SetProxyFallback(strProxy, exceptions, type); return false; } } private static bool SetConnectionProxy(string? connectionName, string? strProxy, string? exceptions, int type) { var list = new InternetPerConnOptionList(); var optionCount = 1; if (type == 1) // No proxy { optionCount = 1; } else if (type is 2 or 4) // named proxy or autoproxy script URL { optionCount = exceptions.IsNullOrEmpty() ? 2 : 3; } var m_Int = (int)PerConnFlags.PROXY_TYPE_DIRECT; var m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS; if (type == 2) // named proxy { m_Int = (int)(PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_PROXY); m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_SERVER; } else if (type == 4) // autoproxy script url { m_Int = (int)(PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_AUTO_PROXY_URL); m_Option = PerConnOption.INTERNET_PER_CONN_AUTOCONFIG_URL; } var options = new InternetConnectionOption[optionCount]; // USE a proxy server ... options[0].m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS; options[0].m_Value.m_Int = m_Int; // use THIS proxy server if (optionCount > 1) { options[1].m_Option = m_Option; options[1].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(strProxy); // !! remember to deallocate memory 1 // except for these addresses ... if (optionCount > 2) { options[2].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_BYPASS; options[2].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(exceptions); // !! remember to deallocate memory 2 } } // default stuff list.dwSize = Marshal.SizeOf(list); if (connectionName != null) { list.szConnection = Marshal.StringToHGlobalAuto(connectionName); // !! remember to deallocate memory 3 } else { list.szConnection = nint.Zero; } list.dwOptionCount = options.Length; list.dwOptionError = 0; var optSize = Marshal.SizeOf(typeof(InternetConnectionOption)); // make a pointer out of all that ... var optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length); // !! remember to deallocate memory 4 // copy the array over into that spot in memory ... for (var i = 0; i < options.Length; ++i) { if (Environment.Is64BitOperatingSystem) { var opt = new nint(optionsPtr.ToInt64() + (i * optSize)); Marshal.StructureToPtr(options[i], opt, false); } else { var opt = new nint(optionsPtr.ToInt32() + (i * optSize)); Marshal.StructureToPtr(options[i], opt, false); } } list.options = optionsPtr; // and then make a pointer out of the whole list var ipcoListPtr = Marshal.AllocCoTaskMem(list.dwSize); // !! remember to deallocate memory 5 Marshal.StructureToPtr(list, ipcoListPtr, false); // and finally, call the API method! var isSuccess = NativeMethods.InternetSetOption(nint.Zero, InternetOption.INTERNET_OPTION_PER_CONNECTION_OPTION, ipcoListPtr, list.dwSize); var returnvalue = 0; // ERROR_SUCCESS if (!isSuccess) { // get the error codes, they might be helpful returnvalue = Marshal.GetLastPInvokeError(); } else { // Notify the system that the registry settings have been changed and cause them to be refreshed _ = NativeMethods.InternetSetOption(nint.Zero, InternetOption.INTERNET_OPTION_SETTINGS_CHANGED, nint.Zero, 0); _ = NativeMethods.InternetSetOption(nint.Zero, InternetOption.INTERNET_OPTION_REFRESH, nint.Zero, 0); } // FREE the data ASAP if (list.szConnection != nint.Zero) { Marshal.FreeHGlobal(list.szConnection); // release mem 3 } if (optionCount > 1) { Marshal.FreeHGlobal(options[1].m_Value.m_StringPtr); // release mem 1 if (optionCount > 2) { Marshal.FreeHGlobal(options[2].m_Value.m_StringPtr); // release mem 2 } } Marshal.FreeCoTaskMem(optionsPtr); // release mem 4 Marshal.FreeCoTaskMem(ipcoListPtr); // release mem 5 if (returnvalue != 0) { // throw the error codes, they might be helpful throw new ApplicationException($"Set Internet Proxy failed with error code: {Marshal.GetLastWin32Error()}"); } return true; } /// /// Retrieve list of connections including LAN and WAN to support PPPoE connection /// /// A list of RAS connection names. May be empty list if no dial up connection. /// Error message with win32 error code private static IEnumerable EnumerateRasEntries() { var entries = 0; // attempt to query with 1 entry buffer var rasEntryNames = new RASENTRYNAME[1]; var bufferSize = Marshal.SizeOf(typeof(RASENTRYNAME)); rasEntryNames[0].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME)); var result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries); // increase buffer if the buffer is not large enough if (result == (uint)ErrorCode.ERROR_BUFFER_TOO_SMALL) { rasEntryNames = new RASENTRYNAME[bufferSize / Marshal.SizeOf(typeof(RASENTRYNAME))]; for (var i = 0; i < rasEntryNames.Length; i++) { rasEntryNames[i].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME)); } result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries); } if (result == 0) { var entryNames = new List(); for (var i = 0; i < entries; i++) { entryNames.Add(rasEntryNames[i].szEntryName); } return entryNames; } throw new ApplicationException($"RasEnumEntries failed with error code: {result}"); } #region WinInet structures [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct InternetPerConnOptionList { public int dwSize; // size of the INTERNET_PER_CONN_OPTION_LIST struct public nint szConnection; // connection name to set/query options public int dwOptionCount; // number of options to set/query public int dwOptionError; // on error, which option failed //[MarshalAs(UnmanagedType.)] public nint options; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct InternetConnectionOption { private static readonly int Size; public PerConnOption m_Option; public InternetConnectionOptionValue m_Value; static InternetConnectionOption() { Size = Marshal.SizeOf(typeof(InternetConnectionOption)); } // Nested Types [StructLayout(LayoutKind.Explicit)] public struct InternetConnectionOptionValue { // Fields [FieldOffset(0)] public System.Runtime.InteropServices.ComTypes.FILETIME m_FileTime; [FieldOffset(0)] public int m_Int; [FieldOffset(0)] public nint m_StringPtr; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct RASENTRYNAME { public int dwSize; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS_MaxEntryName + 1)] public string szEntryName; public int dwFlags; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 1)] public string szPhonebookPath; } // Constants public const int RAS_MaxEntryName = 256; public const int MAX_PATH = 260; // Standard MAX_PATH value in Windows } #endregion WinInet structures #region WinInet enums // // options manifests for Internet{Query|Set}Option // public enum InternetOption : uint { INTERNET_OPTION_PER_CONNECTION_OPTION = 75, INTERNET_OPTION_REFRESH = 37, INTERNET_OPTION_SETTINGS_CHANGED = 39 } // // Options used in INTERNET_PER_CONN_OPTON struct // public enum PerConnOption { INTERNET_PER_CONN_FLAGS = 1, // Sets or retrieves the connection type. The Value member will contain one or more of the values from PerConnFlags INTERNET_PER_CONN_PROXY_SERVER = 2, // Sets or retrieves a string containing the proxy servers. INTERNET_PER_CONN_PROXY_BYPASS = 3, // Sets or retrieves a string containing the URLs that do not use the proxy server. INTERNET_PER_CONN_AUTOCONFIG_URL = 4//, // Sets or retrieves a string containing the URL to the automatic configuration script. } // // PER_CONN_FLAGS // [Flags] public enum PerConnFlags { PROXY_TYPE_DIRECT = 0x00000001, // direct to net PROXY_TYPE_PROXY = 0x00000002, // via named proxy PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy URL PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection } public enum ErrorCode : uint { ERROR_BUFFER_TOO_SMALL = 603, ERROR_INVALID_SIZE = 632 } #endregion WinInet enums internal static class NativeMethods { [DllImport("WinInet.dll", SetLastError = true, CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool InternetSetOption(nint hInternet, InternetOption dwOption, nint lpBuffer, int dwBufferLength); [DllImport("Rasapi32.dll", CharSet = CharSet.Auto)] public static extern uint RasEnumEntries( string? reserved, // Reserved, must be null string? lpszPhonebook, // Pointer to full path and filename of phone-book file. If this parameter is NULL, the entries are enumerated from all the remote access phone-book files [In, Out] RASENTRYNAME[]? lprasentryname, // Buffer to receive RAS entry names ref int lpcb, // Size of the buffer ref int lpcEntries // Number of entries written to the buffer ); } } ================================================ FILE: v2rayN/ServiceLib/Handler/SysProxy/SysProxyHandler.cs ================================================ namespace ServiceLib.Handler.SysProxy; public static class SysProxyHandler { private static readonly string _tag = "SysProxyHandler"; public static async Task UpdateSysProxy(Config config, bool forceDisable) { var type = config.SystemProxyItem.SysProxyType; if (forceDisable && type != ESysProxyType.Unchanged) { type = ESysProxyType.ForcedClear; } try { var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", ""); if (port <= 0) { return false; } switch (type) { case ESysProxyType.ForcedChange when Utils.IsWindows(): { GetWindowsProxyString(config, port, out var strProxy, out var strExceptions); ProxySettingWindows.SetProxy(strProxy, strExceptions, 2); break; } case ESysProxyType.ForcedChange when Utils.IsLinux(): await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions); break; case ESysProxyType.ForcedChange when Utils.IsMacOS(): await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions); break; case ESysProxyType.ForcedClear when Utils.IsWindows(): ProxySettingWindows.UnsetProxy(); break; case ESysProxyType.ForcedClear when Utils.IsLinux(): await ProxySettingLinux.UnsetProxy(); break; case ESysProxyType.ForcedClear when Utils.IsMacOS(): await ProxySettingOSX.UnsetProxy(); break; case ESysProxyType.Pac when Utils.IsWindows(): await SetWindowsProxyPac(port); break; } if (type != ESysProxyType.Pac && Utils.IsWindows()) { PacManager.Instance.Stop(); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return true; } private static void GetWindowsProxyString(Config config, int port, out string strProxy, out string strExceptions) { strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", ""); if (config.SystemProxyItem.NotProxyLocalAddress) { strExceptions = $";{strExceptions}"; } strProxy = string.Empty; if (config.SystemProxyItem.SystemProxyAdvancedProtocol.IsNullOrEmpty()) { strProxy = $"{Global.Loopback}:{port}"; } else { strProxy = config.SystemProxyItem.SystemProxyAdvancedProtocol .Replace("{ip}", Global.Loopback) .Replace("{http_port}", port.ToString()) .Replace("{socks_port}", port.ToString()); } } private static async Task SetWindowsProxyPac(int port) { var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac); await PacManager.Instance.StartAsync(port, portPac); var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}"; ProxySettingWindows.SetProxy(strProxy, "", 4); } } ================================================ FILE: v2rayN/ServiceLib/Helper/DownloaderHelper.cs ================================================ using Downloader; namespace ServiceLib.Helper; public class DownloaderHelper { private static readonly Lazy _instance = new(() => new()); public static DownloaderHelper Instance => _instance.Value; public async Task DownloadStringAsync(IWebProxy? webProxy, string url, string? userAgent, int timeout) { if (url.IsNullOrEmpty()) { return null; } Uri uri = new(url); //Authorization Header var headers = new WebHeaderCollection(); if (uri.UserInfo.IsNotEmpty()) { headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo)); } var downloadOpt = new DownloadConfiguration() { BlockTimeout = timeout * 1000, MaxTryAgainOnFailure = 2, RequestConfiguration = { Headers = headers, UserAgent = userAgent, ConnectTimeout = timeout * 1000, Proxy = webProxy } }; await using var downloader = new Downloader.DownloadService(downloadOpt); downloader.DownloadFileCompleted += (sender, value) => { if (value.Error != null) { throw value.Error; } }; using var cts = new CancellationTokenSource(); await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token); using StreamReader reader = new(stream); downloadOpt = null; return await reader.ReadToEndAsync(cts.Token); } public async Task DownloadDataAsync4Speed(IWebProxy webProxy, string url, IProgress progress, int timeout) { if (url.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(url)); } var downloadOpt = new DownloadConfiguration() { BlockTimeout = timeout * 1000, MaxTryAgainOnFailure = 2, RequestConfiguration = { ConnectTimeout= timeout * 1000, Proxy = webProxy } }; var lastUpdateTime = DateTime.Now; var hasValue = false; double maxSpeed = 0; await using var downloader = new Downloader.DownloadService(downloadOpt); downloader.DownloadProgressChanged += (sender, value) => { if (progress != null && value.BytesPerSecondSpeed > 0) { hasValue = true; if (value.BytesPerSecondSpeed > maxSpeed) { maxSpeed = value.BytesPerSecondSpeed; } var ts = DateTime.Now - lastUpdateTime; if (ts.TotalMilliseconds >= 1000) { lastUpdateTime = DateTime.Now; var speed = (maxSpeed / 1000 / 1000).ToString("#0.0"); progress.Report(speed); } } }; downloader.DownloadFileCompleted += (sender, value) => { if (progress != null) { if (hasValue && maxSpeed > 0) { var finalSpeed = (maxSpeed / 1000 / 1000).ToString("#0.0"); progress.Report(finalSpeed); } else if (value.Error != null) { progress.Report(value.Error?.Message); } else { progress.Report("0"); } } }; //progress.Report("......"); using var cts = new CancellationTokenSource(); cts.CancelAfter(timeout * 1000); await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token); downloadOpt = null; } public async Task DownloadFileAsync(IWebProxy? webProxy, string url, string fileName, IProgress progress, int timeout) { if (url.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(url)); } if (fileName.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(fileName)); } if (File.Exists(fileName)) { File.Delete(fileName); } var downloadOpt = new DownloadConfiguration() { BlockTimeout = timeout * 1000, MaxTryAgainOnFailure = 2, RequestConfiguration = { ConnectTimeout= timeout * 1000, Proxy = webProxy } }; var progressPercentage = 0; var hasValue = false; await using var downloader = new Downloader.DownloadService(downloadOpt); downloader.DownloadStarted += (sender, value) => progress?.Report(0); downloader.DownloadProgressChanged += (sender, value) => { hasValue = true; var percent = (int)value.ProgressPercentage;// Convert.ToInt32((totalRead * 1d) / (total * 1d) * 100); if (progressPercentage != percent && percent % 10 == 0) { progressPercentage = percent; progress.Report(percent); } }; downloader.DownloadFileCompleted += (sender, value) => { if (progress != null) { if (hasValue && value.Error == null) { progress.Report(101); } else if (value.Error != null) { throw value.Error; } } }; using var cts = new CancellationTokenSource(); await downloader.DownloadFileTaskAsync(url, fileName, cts.Token); downloadOpt = null; } } ================================================ FILE: v2rayN/ServiceLib/Helper/HttpClientHelper.cs ================================================ using System.Net.Http.Headers; using System.Net.Mime; namespace ServiceLib.Helper; /// /// public class HttpClientHelper { private static readonly Lazy _instance = new(() => { SocketsHttpHandler handler = new() { UseCookies = false }; HttpClientHelper helper = new(new HttpClient(handler)); return helper; }); public static HttpClientHelper Instance => _instance.Value; private readonly HttpClient httpClient; private HttpClientHelper(HttpClient httpClient) { this.httpClient = httpClient; } public async Task TryGetAsync(string url) { if (url.IsNullOrEmpty()) { return null; } try { var response = await httpClient.GetAsync(url); return await response.Content.ReadAsStringAsync(); } catch { return null; } } public async Task GetAsync(string url) { if (url.IsNullOrEmpty()) { return null; } return await httpClient.GetStringAsync(url); } public async Task PutAsync(string url, Dictionary headers) { var jsonContent = JsonUtils.Serialize(headers); var content = new StringContent(jsonContent, Encoding.UTF8, MediaTypeNames.Application.Json); await httpClient.PutAsync(url, content); } public async Task PatchAsync(string url, Dictionary headers) { var myContent = JsonUtils.Serialize(headers); var buffer = Encoding.UTF8.GetBytes(myContent); var byteContent = new ByteArrayContent(buffer); byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); await httpClient.PatchAsync(url, byteContent); } public async Task DeleteAsync(string url) { await httpClient.DeleteAsync(url); } } ================================================ FILE: v2rayN/ServiceLib/Helper/SqliteHelper.cs ================================================ using System.Collections; namespace ServiceLib.Helper; public sealed class SQLiteHelper { private static readonly Lazy _instance = new(() => new()); public static SQLiteHelper Instance => _instance.Value; private readonly string _connstr; private SQLiteConnection _db; private SQLiteAsyncConnection _dbAsync; private readonly string _configDB = "guiNDB.db"; public SQLiteHelper() { _connstr = Utils.GetConfigPath(_configDB); _db = new SQLiteConnection(_connstr, false); _dbAsync = new SQLiteAsyncConnection(_connstr, false); } public CreateTableResult CreateTable() { return _db.CreateTable(); } public async Task InsertAllAsync(IEnumerable models) { return await _dbAsync.InsertAllAsync(models, runInTransaction: true).ConfigureAwait(false); } public async Task InsertAsync(object model) { return await _dbAsync.InsertAsync(model); } public async Task ReplaceAsync(object model) { return await _dbAsync.InsertOrReplaceAsync(model); } public async Task UpdateAsync(object model) { return await _dbAsync.UpdateAsync(model); } public async Task UpdateAllAsync(IEnumerable models) { return await _dbAsync.UpdateAllAsync(models, runInTransaction: true).ConfigureAwait(false); } public async Task DeleteAsync(object model) { return await _dbAsync.DeleteAsync(model); } public async Task DeleteAllAsync() { return await _dbAsync.DeleteAllAsync(); } public async Task ExecuteAsync(string sql) { return await _dbAsync.ExecuteAsync(sql); } public async Task> QueryAsync(string sql) where T : new() { return await _dbAsync.QueryAsync(sql); } public AsyncTableQuery TableAsync() where T : new() { return _dbAsync.Table(); } public async Task DisposeDbConnectionAsync() { await Task.Factory.StartNew(() => { _db?.Close(); _db?.Dispose(); _db = null; _dbAsync?.GetConnection()?.Close(); _dbAsync?.GetConnection()?.Dispose(); _dbAsync = null; }); } } ================================================ FILE: v2rayN/ServiceLib/Manager/AppManager.cs ================================================ namespace ServiceLib.Manager; public sealed class AppManager { #region Property private static readonly Lazy _instance = new(() => new()); private Config _config; private int? _statePort; private int? _statePort2; public static AppManager Instance => _instance.Value; public Config Config => _config; public int StatePort { get { _statePort ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api)); return _statePort.Value; } } public int StatePort2 { get { _statePort2 ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api2)); return _statePort2.Value + (_config.TunModeItem.EnableTun ? 1 : 0); } } public string LinuxSudoPwd { get; set; } public bool ShowInTaskbar { get; set; } public ECoreType RunningCoreType { get; set; } public bool IsRunningCore(ECoreType type) { switch (type) { case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5: case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo: return true; default: return false; } } #endregion Property #region App public bool InitApp() { if (Utils.HasWritePermission() == false) { Environment.SetEnvironmentVariable(Global.LocalAppData, "1", EnvironmentVariableTarget.Process); } Logging.Setup(); var config = ConfigHandler.LoadConfig(); if (config == null) { return false; } _config = config; Thread.CurrentThread.CurrentUICulture = new(_config.UiItem.CurrentLanguage); //Under Win10 if (Utils.IsWindows() && Environment.OSVersion.Version.Major < 10) { Environment.SetEnvironmentVariable("DOTNET_EnableWriteXorExecute", "0", EnvironmentVariableTarget.User); } SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); #pragma warning disable CS0618 SQLiteHelper.Instance.CreateTable(); #pragma warning restore CS0618 return true; } public bool InitComponents() { Logging.SaveLog($"v2rayN start up | {Utils.GetRuntimeInfo()}"); Logging.LoggingEnabled(_config.GuiItem.EnableLog); //First determine the port value _ = StatePort; _ = StatePort2; Task.Run(async () => { await MigrateProfileExtra(); }).Wait(); return true; } public bool Reset() { _statePort = null; _statePort2 = null; return true; } public async Task AppExitAsync(bool needShutdown) { try { Logging.SaveLog("AppExitAsync Begin"); await SysProxyHandler.UpdateSysProxy(_config, true); AppEvents.AppExitRequested.Publish(); await Task.Delay(50); //Wait for AppExitRequested to be processed await ConfigHandler.SaveConfig(_config); await ProfileExManager.Instance.SaveTo(); await StatisticsManager.Instance.SaveTo(); await CoreManager.Instance.CoreStop(); StatisticsManager.Instance.Close(); Logging.SaveLog("AppExitAsync End"); } catch { } finally { if (needShutdown) { Shutdown(false); } } } public void Shutdown(bool byUser) { AppEvents.ShutdownRequested.Publish(byUser); } public async Task RebootAsAdmin() { ProcUtils.RebootAsAdmin(); await AppManager.Instance.AppExitAsync(true); } #endregion App #region Config public int GetLocalPort(EInboundProtocol protocol) { var localPort = _config.Inbound.FirstOrDefault(t => t.Protocol == nameof(EInboundProtocol.socks))?.LocalPort ?? 10808; return localPort + (int)protocol; } #endregion Config #region SqliteHelper public async Task?> SubItems() { return await SQLiteHelper.Instance.TableAsync().OrderBy(t => t.Sort).ToListAsync(); } public async Task GetSubItem(string? subid) { return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(t => t.Id == subid); } public async Task?> ProfileItems(string subid) { if (subid.IsNullOrEmpty()) { return await SQLiteHelper.Instance.TableAsync().ToListAsync(); } else { return await SQLiteHelper.Instance.TableAsync().Where(t => t.Subid == subid).ToListAsync(); } } public async Task?> ProfileItemIndexes(string subid) { return (await ProfileItems(subid))?.Select(t => t.IndexId)?.ToList(); } public async Task?> ProfileModels(string subid, string filter) { var sql = @$"select a.IndexId ,a.ConfigType ,a.Remarks ,a.Address ,a.Port ,a.Network ,a.StreamSecurity ,a.Subid ,b.remarks as subRemarks from ProfileItem a left join SubItem b on a.subid = b.id where 1=1 "; if (subid.IsNotEmpty()) { sql += $" and a.subid = '{subid}'"; } if (filter.IsNotEmpty()) { if (filter.Contains('\'')) { filter = filter.Replace("'", ""); } sql += string.Format(" and (a.remarks like '%{0}%' or a.address like '%{0}%') ", filter); } return await SQLiteHelper.Instance.QueryAsync(sql); } public async Task GetProfileItem(string indexId) { if (indexId.IsNullOrEmpty()) { return null; } return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.IndexId == indexId); } public async Task> GetProfileItemsByIndexIds(IEnumerable indexIds) { var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList(); if (ids.Count == 0) { return []; } return await SQLiteHelper.Instance.TableAsync() .Where(it => ids.Contains(it.IndexId)) .ToListAsync(); } public async Task> GetProfileItemsByIndexIdsAsMap(IEnumerable indexIds) { var items = await GetProfileItemsByIndexIds(indexIds); return items.ToDictionary(it => it.IndexId); } public async Task> GetProfileItemsOrderedByIndexIds(IEnumerable indexIds) { var idList = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList(); if (idList.Count == 0) { return []; } var items = await SQLiteHelper.Instance.TableAsync() .Where(it => idList.Contains(it.IndexId)) .ToListAsync(); var itemMap = items.ToDictionary(it => it.IndexId); return idList.Select(id => itemMap.GetValueOrDefault(id)) .Where(item => item != null) .ToList(); } public async Task GetProfileItemViaRemarks(string? remarks) { if (remarks.IsNullOrEmpty()) { return null; } return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.Remarks == remarks); } public async Task?> RoutingItems() { return await SQLiteHelper.Instance.TableAsync().OrderBy(t => t.Sort).ToListAsync(); } public async Task GetRoutingItem(string id) { return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.Id == id); } public async Task?> DNSItems() { return await SQLiteHelper.Instance.TableAsync().ToListAsync(); } public async Task GetDNSItem(ECoreType eCoreType) { return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.CoreType == eCoreType); } public async Task?> FullConfigTemplateItem() { return await SQLiteHelper.Instance.TableAsync().ToListAsync(); } public async Task GetFullConfigTemplateItem(ECoreType eCoreType) { return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.CoreType == eCoreType); } public async Task MigrateProfileExtra() { await MigrateProfileExtraGroup(); #pragma warning disable CS0618 const int pageSize = 100; var offset = 0; while (true) { var sql = $"SELECT * FROM ProfileItem " + $"WHERE ConfigVersion < 3 " + $"AND ConfigType NOT IN ({(int)EConfigType.PolicyGroup}, {(int)EConfigType.ProxyChain}) " + $"LIMIT {pageSize} OFFSET {offset}"; var batch = await SQLiteHelper.Instance.QueryAsync(sql); if (batch is null || batch.Count == 0) { break; } var batchSuccessCount = await MigrateProfileExtraSub(batch); // Only increment offset by the number of failed items that remain in the result set // Successfully updated items are automatically excluded from future queries due to ConfigVersion = 3 offset += batch.Count - batchSuccessCount; } //await ProfileGroupItemManager.Instance.ClearAll(); #pragma warning restore CS0618 } private async Task MigrateProfileExtraSub(List batch) { var updateProfileItems = new List(); foreach (var item in batch) { try { var extra = item.GetProtocolExtra(); switch (item.ConfigType) { case EConfigType.Shadowsocks: extra = extra with { SsMethod = item.Security.NullIfEmpty() }; break; case EConfigType.VMess: extra = extra with { AlterId = item.AlterId.ToString(), VmessSecurity = item.Security.NullIfEmpty(), }; break; case EConfigType.VLESS: extra = extra with { Flow = item.Flow.NullIfEmpty(), VlessEncryption = item.Security, }; break; case EConfigType.Hysteria2: extra = extra with { SalamanderPass = item.Path.NullIfEmpty(), Ports = item.Ports.NullIfEmpty(), UpMbps = _config.HysteriaItem.UpMbps, DownMbps = _config.HysteriaItem.DownMbps, HopInterval = _config.HysteriaItem.HopInterval.ToString(), }; break; case EConfigType.TUIC: item.Username = item.Id; item.Id = item.Security; item.Password = item.Security; break; case EConfigType.HTTP: case EConfigType.SOCKS: item.Username = item.Security; break; case EConfigType.WireGuard: extra = extra with { WgPublicKey = item.PublicKey.NullIfEmpty(), WgInterfaceAddress = item.RequestHost.NullIfEmpty(), WgReserved = item.Path.NullIfEmpty(), WgMtu = int.TryParse(item.ShortId, out var mtu) ? mtu : 1280 }; break; } item.SetProtocolExtra(extra); item.Password = item.Id; item.ConfigVersion = 3; updateProfileItems.Add(item); } catch (Exception ex) { Logging.SaveLog($"MigrateProfileExtra Error: {ex}"); } } if (updateProfileItems.Count > 0) { try { var count = await SQLiteHelper.Instance.UpdateAllAsync(updateProfileItems); return count; } catch (Exception ex) { Logging.SaveLog($"MigrateProfileExtraGroup update error: {ex}"); return 0; } } else { return 0; } } private async Task MigrateProfileExtraGroup() { #pragma warning disable CS0618 var list = await SQLiteHelper.Instance.TableAsync().ToListAsync(); var groupItems = new ConcurrentDictionary(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!)); var sql = $"SELECT * FROM ProfileItem WHERE ConfigVersion < 3 AND ConfigType IN ({(int)EConfigType.PolicyGroup}, {(int)EConfigType.ProxyChain})"; var items = await SQLiteHelper.Instance.QueryAsync(sql); if (items is null || items.Count == 0) { Logging.SaveLog("MigrateProfileExtraGroup: No items to migrate."); return true; } Logging.SaveLog($"MigrateProfileExtraGroup: Found {items.Count} group items to migrate."); var updateProfileItems = new List(); foreach (var item in items) { try { var extra = item.GetProtocolExtra(); extra = extra with { GroupType = nameof(item.ConfigType) }; groupItems.TryGetValue(item.IndexId, out var groupItem); if (groupItem != null && !groupItem.NotHasChild()) { extra = extra with { ChildItems = groupItem.ChildItems, SubChildItems = groupItem.SubChildItems, Filter = groupItem.Filter, MultipleLoad = groupItem.MultipleLoad, }; } item.SetProtocolExtra(extra); item.ConfigVersion = 3; updateProfileItems.Add(item); } catch (Exception ex) { Logging.SaveLog($"MigrateProfileExtraGroup item error [{item.IndexId}]: {ex}"); } } if (updateProfileItems.Count > 0) { try { var count = await SQLiteHelper.Instance.UpdateAllAsync(updateProfileItems); Logging.SaveLog($"MigrateProfileExtraGroup: Successfully updated {updateProfileItems.Count} items."); return updateProfileItems.Count == count; } catch (Exception ex) { Logging.SaveLog($"MigrateProfileExtraGroup update error: {ex}"); return false; } } return true; //await ProfileGroupItemManager.Instance.ClearAll(); #pragma warning restore CS0618 } #endregion SqliteHelper #region Core Type public List GetShadowsocksSecurities(ProfileItem profileItem) { var coreType = GetCoreType(profileItem, EConfigType.Shadowsocks); switch (coreType) { case ECoreType.v2fly: return Global.SsSecurities; case ECoreType.Xray: return Global.SsSecuritiesInXray; case ECoreType.sing_box: return Global.SsSecuritiesInSingbox; } return Global.SsSecuritiesInSingbox; } public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType) { if (profileItem?.CoreType != null) { return (ECoreType)profileItem.CoreType; } var item = _config.CoreTypeItem?.FirstOrDefault(it => it.ConfigType == eConfigType); return item?.CoreType ?? ECoreType.Xray; } #endregion Core Type } ================================================ FILE: v2rayN/ServiceLib/Manager/CertPemManager.cs ================================================ using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace ServiceLib.Manager; /// /// Manager for certificate operations with CA pinning to prevent MITM attacks /// public class CertPemManager { private static readonly string _tag = "CertPemManager"; private static readonly Lazy _instance = new(() => new()); public static CertPemManager Instance => _instance.Value; /// /// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks /// private static readonly HashSet TrustedCaThumbprints = new(StringComparer.OrdinalIgnoreCase) { "EBD41040E4BB3EC742C9E381D31EF2A41A48B6685C96E7CEF3C1DF6CD4331C99", // GlobalSign Root CA "6DC47172E01CBCB0BF62580D895FE2B8AC9AD4F873801E0C10B9C837D21EB177", // Entrust.net Premium 2048 Secure Server CA "73C176434F1BC6D5ADF45B0E76E727287C8DE57616C1E6E6141A2B2CBC7D8E4C", // Entrust Root Certification Authority "D8E0FEBC1DB2E38D00940F37D27D41344D993E734B99D5656D9778D4D8143624", // Certum Root CA "D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4", // Comodo AAA Services root "85A0DD7DD720ADB7FF05F83D542B209DC7FF4528F7D677B18389FEA5E5C49E86", // QuoVadis Root CA 2 "18F1FC7F205DF8ADDDEB7FE007DD57E3AF375A9C4D8D73546BF4F1FED1E18D35", // QuoVadis Root CA 3 "CECDDC905099D8DADFC5B1D209B737CBE2C18CFB2C10C0FF0BCF0D3286FC1AA2", // XRamp Global CA Root "C3846BF24B9E93CA64274C0EC67C1ECC5E024FFCACD2D74019350E81FE546AE4", // Go Daddy Class 2 CA "1465FA205397B876FAA6F0A9958E5590E40FCC7FAA4FB7C2C8677521FB5FB658", // Starfield Class 2 CA "3E9099B5015E8F486C00BCEA9D111EE721FABA355A89BCF1DF69561E3DC6325C", // DigiCert Assured ID Root CA "4348A0E9444C78CB265E058D5E8944B4D84F9662BD26DB257F8934A443C70161", // DigiCert Global Root CA "7431E5F4C3C1CE4690774F0B61E05440883BA9A01ED00BA6ABD7806ED3B118CF", // DigiCert High Assurance EV Root CA "62DD0BE9B9F50A163EA0F8E75C053B1ECA57EA55C8688F647C6881F2C8357B95", // SwissSign Gold CA - G2 "F1C1B50AE5A20DD8030EC9F6BC24823DD367B5255759B4E71B61FCE9F7375D73", // SecureTrust CA "4200F5043AC8590EBB527D209ED1503029FBCBD41CA1B506EC27F15ADE7DAC69", // Secure Global CA "0C2CD63DF7806FA399EDE809116B575BF87989F06518F9808C860503178BAF66", // COMODO Certification Authority "1793927A0614549789ADCE2F8F34F7F0B66D0F3AE3A3B84D21EC15DBBA4FADC7", // COMODO ECC Certification Authority "41C923866AB4CAD6B7AD578081582E020797A6CBDF4FFF78CE8396B38937D7F5", // OISTE WISeKey Global Root GA CA "E3B6A2DB2ED7CE48842F7AC53241C7B71D54144BFB40C11F3F1D0B42F5EEA12D", // Certigna "C0A6F4DC63A24BFDCF54EF2A6A082A0A72DE35803E2FF5FF527AE5D87206DFD5", // ePKI Root Certification Authority "EAA962C4FA4A6BAFEBE415196D351CCD888D4F53F3FA8AE6D7C466A94E6042BB", // certSIGN ROOT CA "6C61DAC3A2DEF031506BE036D2A6FE401994FBD13DF9C8D466599274C446EC98", // NetLock Arany (Class Gold) Főtanúsítvány "3C5F81FEA5FAB82C64BFA2EAECAFCDE8E077FC8620A7CAE537163DF36EDBF378", // Microsec e-Szigno Root CA 2009 "CBB522D7B7F127AD6A0113865BDF1CD4102E7D0759AF635A7CF4720DC963C53B", // GlobalSign Root CA - R3 "2530CC8E98321502BAD96F9B1FBA1B099E2D299E0F4548BB914F363BC0D4531F", // Izenpe.com "45140B3247EB9CC8C5B4F0D7B53091F73292089E6E5A63E2749DD3ACA9198EDA", // Go Daddy Root Certificate Authority - G2 "2CE1CB0BF9D2F9E102993FBE215152C3B2DD0CABDE1C68E5319B839154DBB7F5", // Starfield Root Certificate Authority - G2 "568D6905A2C88708A4B3025190EDCFEDB1974A606A13C6E5290FCB2AE63EDAB5", // Starfield Services Root Certificate Authority - G2 "0376AB1D54C5F9803CE4B2E201A0EE7EEF7B57B636E8A93C9B8D4860C96F5FA7", // AffirmTrust Commercial "0A81EC5A929777F145904AF38D5D509F66B5E2C58FCDB531058B0E17F3F0B41B", // AffirmTrust Networking "70A73F7F376B60074248904534B11482D5BF0E698ECC498DF52577EBF2E93B9A", // AffirmTrust Premium "BD71FDF6DA97E4CF62D1647ADD2581B07D79ADF8397EB4ECBA9C5E8488821423", // AffirmTrust Premium ECC "5C58468D55F58E497E743982D2B50010B6D165374ACF83A7D4A32DB768C4408E", // Certum Trusted Network CA "BFD88FE1101C41AE3E801BF8BE56350EE9BAD1A6B9BD515EDC5C6D5B8711AC44", // TWCA Root Certification Authority "513B2CECB810D4CDE5DD85391ADFC6C2DD60D87BB736D2B521484AA47A0EBEF6", // Security Communication RootCA2 "55926084EC963A64B96E2ABE01CE0BA86A64FBFEBCC7AAB5AFC155B37FD76066", // Actalis Authentication Root CA "9A114025197C5BB95D94E63D55CD43790847B646B23CDF11ADA4A00EFF15FB48", // Buypass Class 2 Root CA "EDF7EBBCA27A2A384D387B7D4010C666E2EDB4843E4C29B4AE1D5B9332E6B24D", // Buypass Class 3 Root CA "FD73DAD31C644FF1B43BEF0CCDDA96710B9CD9875ECA7E31707AF3E96D522BBD", // T-TeleSec GlobalRoot Class 3 "49E7A442ACF0EA6287050054B52564B650E4F49E42E348D6AA38E039E957B1C1", // D-TRUST Root Class 3 CA 2 2009 "EEC5496B988CE98625B934092EEC2908BED0B0F316C2D4730C84EAF1F3D34881", // D-TRUST Root Class 3 CA 2 EV 2009 "E23D4A036D7B70E9F595B1422079D2B91EDFBB1FB651A0633EAA8A9DC5F80703", // CA Disig Root R2 "9A6EC012E1A7DA9DBE34194D478AD7C0DB1822FB071DF12981496ED104384113", // ACCVRAIZ1 "59769007F7685D0FCD50872F9F95D5755A5B2B457D81F3692B610A98672F0E1B", // TWCA Global Root CA "DD6936FE21F8F077C123A1A521C12224F72255B73E03A7260693E8A24B0FA389", // TeliaSonera Root CA v1 "91E2F5788D5810EBA7BA58737DE1548A8ECACD014598BC0B143E041B17052552", // T-TeleSec GlobalRoot Class 2 "F356BEA244B7A91EB35D53CA9AD7864ACE018E2D35D5F8F96DDF68A6F41AA474", // Atos TrustedRoot 2011 "8A866FD1B276B57E578E921C65828A2BED58E9F2F288054134B7F1F4BFC9CC74", // QuoVadis Root CA 1 G3 "8FE4FB0AF93A4D0D67DB0BEBB23E37C71BF325DCBCDD240EA04DAF58B47E1840", // QuoVadis Root CA 2 G3 "88EF81DE202EB018452E43F864725CEA5FBD1FC2D9D205730709C5D8B8690F46", // QuoVadis Root CA 3 G3 "7D05EBB682339F8C9451EE094EEBFEFA7953A114EDB2F44949452FAB7D2FC185", // DigiCert Assured ID Root G2 "7E37CB8B4C47090CAB36551BA6F45DB840680FBA166A952DB100717F43053FC2", // DigiCert Assured ID Root G3 "CB3CCBB76031E5E0138F8DD39A23F9DE47FFC35E43C1144CEA27D46A5AB1CB5F", // DigiCert Global Root G2 "31AD6648F8104138C738F39EA4320133393E3A18CC02296EF97C2AC9EF6731D0", // DigiCert Global Root G3 "552F7BDCF1A7AF9E6CE672017F4F12ABF77240C78E761AC203D1D9D20AC89988", // DigiCert Trusted Root G4 "52F0E1C4E58EC629291B60317F074671B85D7EA80D5B07273463534B32B40234", // COMODO RSA Certification Authority "E793C9B02FD8AA13E21C31228ACCB08119643B749C898964B1746D46C3D4CBD2", // USERTrust RSA Certification Authority "4FF460D54B9C86DABFBCFC5712E0400D2BED3FBC4D4FBDAA86E06ADCD2A9AD7A", // USERTrust ECC Certification Authority "179FBC148A3DD00FD24EA13458CC43BFA7F59C8182D783A513F6EBEC100C8924", // GlobalSign ECC Root CA - R5 "3C4FB0B95AB8B30032F432B86F535FE172C185D0FD39865837CF36187FA6F428", // Staat der Nederlanden Root CA - G3 "5D56499BE4D2E08BCFCAD08A3E38723D50503BDE706948E42F55603019E528AE", // IdenTrust Commercial Root CA 1 "30D0895A9A448A262091635522D1F52010B5867ACAE12C78EF958FD4F4389F2F", // IdenTrust Public Sector Root CA 1 "43DF5774B03E7FEF5FE40D931A7BEDF1BB2E6B42738C4E6D3841103D3AA7F339", // Entrust Root Certification Authority - G2 "02ED0EB28C14DA45165C566791700D6451D7FB56F0B2AB1D3B8EB070E56EDFF5", // Entrust Root Certification Authority - EC1 "5CC3D78E4E1D5E45547A04E6873E64F90CF9536D1CCC2EF800F355C4C5FD70FD", // CFCA EV ROOT "6B9C08E86EB0F767CFAD65CD98B62149E5494A67F5845E7BD1ED019F27B86BD6", // OISTE WISeKey Global Root GB CA "A1339D33281A0B56E557D3D32B1CE7F9367EB094BD5FA72A7E5004C8DED7CAFE", // SZAFIR ROOT CA2 "B676F2EDDAE8775CD36CB0F63CD1D4603961F49E6265BA013A2F0307B6D0B804", // Certum Trusted Network CA 2 "A040929A02CE53B4ACF4F2FFC6981CE4496F755E6D45FE0B2A692BCD52523F36", // Hellenic Academic and Research Institutions RootCA 2015 "44B545AA8A25E65A73CA15DC27FC36D24C1CB9953A066539B11582DC487B4833", // Hellenic Academic and Research Institutions ECC RootCA 2015 "96BCEC06264976F37460779ACF28C5A7CFE8A3C0AAE11A8FFCEE05C0BDDF08C6", // ISRG Root X1 "EBC5570C29018C4D67B1AA127BAF12F703B4611EBC17B7DAB5573894179B93FA", // AC RAIZ FNMT-RCM "8ECDE6884F3D87B1125BA31AC3FCB13D7016DE7F57CC904FE1CB97C6AE98196E", // Amazon Root CA 1 "1BA5B2AA8C65401A82960118F80BEC4F62304D83CEC4713A19C39C011EA46DB4", // Amazon Root CA 2 "18CE6CFE7BF14E60B2E347B8DFE868CB31D02EBB3ADA271569F50343B46DB3A4", // Amazon Root CA 3 "E35D28419ED02025CFA69038CD623962458DA5C695FBDEA3C22B0BFB25897092", // Amazon Root CA 4 "A1A86D04121EB87F027C66F53303C28E5739F943FC84B38AD6AF009035DD9457", // D-TRUST Root CA 3 2013 "46EDC3689046D53A453FB3104AB80DCAEC658B2660EA1629DD7E867990648716", // TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 "BFFF8FD04433487D6A8AA60C1A29767A9FC2BBB05E420F713A13B992891D3893", // GDCA TrustAUTH R5 ROOT "85666A562EE0BE5CE925C1D8890A6F76A87EC16D4D7D5F29EA7419CF20123B69", // SSL.com Root Certification Authority RSA "3417BB06CC6007DA1B961C920B8AB4CE3FAD820E4AA30B9ACBC4A74EBDCEBC65", // SSL.com Root Certification Authority ECC "2E7BF16CC22485A7BBE2AA8696750761B0AE39BE3B2FE9D0CC6D4EF73491425C", // SSL.com EV Root Certification Authority RSA R2 "22A2C1F7BDED704CC1E701B5F408C310880FE956B5DE2A4A44F99C873A25A7C8", // SSL.com EV Root Certification Authority ECC "2CABEAFE37D06CA22ABA7391C0033D25982952C453647349763A3AB5AD6CCF69", // GlobalSign Root CA - R6 "8560F91C3624DABA9570B5FEA0DBE36FF11A8323BE9486854FB3F34A5571198D", // OISTE WISeKey Global Root GC CA "9BEA11C976FE014764C1BE56A6F914B5A560317ABD9988393382E5161AA0493C", // UCA Global G2 Root "D43AF9B35473755C9684FC06D7D8CB70EE5C28E773FB294EB41EE71722924D24", // UCA Extended Validation Root "D48D3D23EEDB50A459E55197601C27774B9D7B18C94D5A059511A10250B93168", // Certigna Root CA "40F6AF0346A99AA1CD1D555A4E9CCE62C7F9634603EE406615833DC8C8D00367", // emSign Root CA - G1 "86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B", // emSign ECC Root CA - G3 "125609AA301DA0A249B97A8239CB6A34216F44DCAC9F3954B14292F2E8C8608F", // emSign Root CA - C1 "BC4D809B15189D78DB3E1D8CF4F9726A795DA1643CA5F1358E1DDB0EDC0D7EB3", // emSign ECC Root CA - C3 "5A2FC03F0C83B090BBFA40604B0988446C7636183DF9846E17101A447FB8EFD6", // Hongkong Post Root CA 3 "DB3517D1F6732A2D5AB97C533EC70779EE3270A62FB4AC4238372460E6F01E88", // Entrust Root Certification Authority - G4 "358DF39D764AF9E1B766E9C972DF352EE15CFAC227AF6AD1D70E8E4A6EDCBA02", // Microsoft ECC Root Certificate Authority 2017 "C741F70F4B2A8D88BF2E71C14122EF53EF10EBA0CFA5E64CFA20F418853073E0", // Microsoft RSA Root Certificate Authority 2017 "BEB00B30839B9BC32C32E4447905950641F26421B15ED089198B518AE2EA1B99", // e-Szigno Root CA 2017 "657CFE2FA73FAA38462571F332A2363A46FCE7020951710702CDFBB6EEDA3305", // certSIGN Root CA G2 "97552015F5DDFC3C8788C006944555408894450084F100867086BC1A2BB58DC8", // Trustwave Global Certification Authority "945BBC825EA554F489D1FD51A73DDF2EA624AC7019A05205225C22A78CCFA8B4", // Trustwave Global ECC P256 Certification Authority "55903859C8C0C3EBB8759ECE4E2557225FF5758BBD38EBD48276601E1BD58097", // Trustwave Global ECC P384 Certification Authority "88F438DCF8FFD1FA8F429115FFE5F82AE1E06E0C70C375FAAD717B34A49E7265", // NAVER Global Root Certification Authority "554153B13D2CF9DDB753BFBE1A4E0AE08D0AA4187058FE60A2B862B2E4B87BCB", // AC RAIZ FNMT-RCM SERVIDORES SEGUROS "319AF0A7729E6F89269C131EA6A3A16FCD86389FDCAB3C47A4A675C161A3F974", // GlobalSign Secure Mail Root R45 "5CBF6FB81FD417EA4128CD6F8172A3C9402094F74AB2ED3A06B4405D04F30B19", // GlobalSign Secure Mail Root E45 "4FA3126D8D3A11D1C4855A4F807CBAD6CF919D3A5A88B03BEA2C6372D93C40C9", // GlobalSign Root R46 "CBB9C44D84B8043E1050EA31A69F514955D7BFD2E2C6B49301019AD61D9F5058", // GlobalSign Root E46 "9A296A5182D1D451A2E37F439B74DAAFA267523329F90F9A0D2007C334E23C9A", // GLOBALTRUST 2020 "FB8FEC759169B9106B1E511644C618C51304373F6C0643088D8BEFFD1B997599", // ANF Secure Server Root CA "6B328085625318AA50D173C98D8BDA09D57E27413D114CF787A0F5D06C030CF6", // Certum EC-384 CA "FE7696573855773E37A95E7AD4D9CC96C30157C15D31765BA9B15704E1AE78FD", // Certum Trusted Root CA "2E44102AB58CB85419451C8E19D9ACF3662CAFBC614B6A53960A30F7D0E2EB41", // TunTrust Root CA "D95D0E8EDA79525BF9BEB11B14D2100D3294985F0C62D9FABD9CD999ECCB7B1D", // HARICA TLS RSA Root CA 2021 "3F99CC474ACFCE4DFED58794665E478D1547739F2E780F1BB4CA9B133097D401", // HARICA TLS ECC Root CA 2021 "1BE7ABE30686B16348AFD1C61B6866A0EA7F4821E67D5E8AF937CF8011BC750D", // HARICA Client RSA Root CA 2021 "8DD4B5373CB0DE36769C12339280D82746B3AA6CD426E797A31BABE4279CF00B", // HARICA Client ECC Root CA 2021 "57DE0583EFD2B26E0361DA99DA9DF4648DEF7EE8441C3B728AFA9BCDE0F9B26A", // Autoridad de Certificacion Firmaprofesional CIF A62634068 "30FBBA2C32238E2A98547AF97931E550428B9B3F1C8EEB6633DCFA86C5B27DD3", // vTrus ECC Root CA "8A71DE6559336F426C26E53880D00D88A18DA4C6A91F0DCB6194E206C5C96387", // vTrus Root CA "69729B8E15A86EFC177A57AFB7171DFC64ADD28C2FCA8CF1507E34453CCB1470", // ISRG Root X2 "F015CE3CC239BFEF064BE9F1D2C417E1A0264A0A94BE1F0C8D121864EB6949CC", // HiPKI Root CA - G1 "B085D70B964F191A73E4AF0D54AE7A0E07AAFDAF9B71DD0862138AB7325A24A2", // GlobalSign ECC Root CA - R4 "D947432ABDE7B7FA90FC2E6B59101B1280E0E1C7E4E40FA3C6887FFF57A7F4CF", // GTS Root R1 "8D25CD97229DBF70356BDA4EB3CC734031E24CF00FAFCFD32DC76EB5841C7EA8", // GTS Root R2 "34D8A73EE208D9BCDB0D956520934B4E40E69482596E8B6F73C8426B010A6F48", // GTS Root R3 "349DFA4058C5E263123B398AE795573C4E1313C83FE68F93556CD5E8031B3C7D", // GTS Root R4 "242B69742FCB1E5B2ABF98898B94572187544E5B4D9911786573621F6A74B82C", // Telia Root CA v2 "E59AAA816009C22BFF5B25BAD37DF306F049797C1F81D85AB089E657BD8F0044", // D-TRUST BR Root CA 1 2020 "08170D1AA36453901A2F959245E347DB0C8D37ABAABC56B81AA100DC958970DB", // D-TRUST EV Root CA 1 2020 "018E13F0772532CF809BD1B17281867283FC48C6E13BE9C69812854A490C1B05", // DigiCert TLS ECC P384 Root G5 "371A00DC0533B3721A7EEB40E8419E70799D2B0A0F2C1D80693165F7CEC4AD75", // DigiCert TLS RSA4096 Root G5 "E8E8176536A60CC2C4E10187C3BEFCA20EF263497018F566D5BEA0F94D0C111B", // DigiCert SMIME ECC P384 Root G5 "90370D3EFA88BF58C30105BA25104A358460A7FA52DFC2011DF233A0F417912A", // DigiCert SMIME RSA4096 Root G5 "77B82CD8644C4305F7ACC5CB156B45675004033D51C60C6202A8E0C33467D3A0", // Certainly Root R1 "B4585F22E4AC756A4E8612A1361C5D9D031A93FD84FEBB778FA3068B0FC42DC2", // Certainly Root E1 "82BD5D851ACF7F6E1BA7BFCBC53030D0E7BC3C21DF772D858CAB41D199BDF595", // DIGITALSIGN GLOBAL ROOT RSA CA "261D7114AE5F8FF2D8C7209A9DE4289E6AFC9D717023D85450909199F1857CFE", // DIGITALSIGN GLOBAL ROOT ECDSA CA "E74FBDA55BD564C473A36B441AA799C8A68E077440E8288B9FA1E50E4BBACA11", // Security Communication ECC RootCA1 "F3896F88FE7C0A882766A7FA6AD2749FB57A7F3E98FB769C1FA7B09C2C44D5AE", // BJCA Global Root CA1 "574DF6931E278039667B720AFDC1600FC27EB66DD3092979FB73856487212882", // BJCA Global Root CA2 "48E1CF9E43B688A51044160F46D773B8277FE45BEAAD0E4DF90D1974382FEA99", // LAWtrust Root CA2 (4096) "22D9599234D60F1D4BC7C7E96F43FA555B07301FD475175089DAFB8C25E477B3", // Sectigo Public Email Protection Root E46 "D5917A7791EB7CF20A2E57EB98284A67B28A57E89182DA53D546678C9FDE2B4F", // Sectigo Public Email Protection Root R46 "C90F26F0FB1B4018B22227519B5CA2B53E2CA5B3BE5CF18EFE1BEF47380C5383", // Sectigo Public Server Authentication Root E46 "7BB647A62AEEAC88BF257AA522D01FFEA395E0AB45C73F93F65654EC38F25A06", // Sectigo Public Server Authentication Root R46 "8FAF7D2E2CB4709BB8E0B33666BF75A5DD45B5DE480F8EA8D4BFE6BEBC17F2ED", // SSL.com TLS RSA Root CA 2022 "C32FFD9F46F936D16C3673990959434B9AD60AAFBB9E7CF33654F144CC1BA143", // SSL.com TLS ECC Root CA 2022 "AD7DD58D03AEDB22A30B5084394920CE12230C2D8017AD9B81AB04079BDD026B", // SSL.com Client ECC Root CA 2022 "1D4CA4A2AB21D0093659804FC0EB2175A617279B56A2475245C9517AFEB59153", // SSL.com Client RSA Root CA 2022 "E38655F4B0190C84D3B3893D840A687E190A256D98052F159E6D4A39F589A6EB", // Atos TrustedRoot Root CA ECC G2 2020 "78833A783BB2986C254B9370D3C20E5EBA8FA7840CBF63FE17297A0B0119685E", // Atos TrustedRoot Root CA RSA G2 2020 "B2FAE53E14CCD7AB9212064701AE279C1D8988FACB775FA8A008914E663988A8", // Atos TrustedRoot Root CA ECC TLS 2021 "81A9088EA59FB364C548A6F85559099B6F0405EFBF18E5324EC9F457BA00112F", // Atos TrustedRoot Root CA RSA TLS 2021 "E0D3226AEB1163C2E48FF9BE3B50B4C6431BE7BB1EACC5C36B5D5EC509039A08", // TrustAsia Global Root CA G3 "BE4B56CB5056C0136A526DF444508DAA36A0B54F42E4AC38F72AF470E479654C", // TrustAsia Global Root CA G4 "D92C171F5CF890BA428019292927FE22F3207FD2B54449CB6F675AF4922146E2", // D-Trust SBR Root CA 1 2022 "DBA84DD7EF622D485463A90137EA4D574DF8550928F6AFA03B4D8B1141E636CC", // D-Trust SBR Root CA 2 2022 "3AE6DF7E0D637A65A8C81612EC6F9A142F85A16834C10280D88E707028518755", // Telekom Security SMIME ECC Root 2021 "578AF4DED0853F4E5998DB4AEAF9CBEA8D945F60B620A38D1A3C13B2BC7BA8E1", // Telekom Security TLS ECC Root 2020 "78A656344F947E9CC0F734D9053D32F6742086B6B9CD2CAE4FAE1A2E4EFDE048", // Telekom Security SMIME RSA Root 2023 "EFC65CADBB59ADB6EFE84DA22311B35624B71B3B1EA0DA8B6655174EC8978646", // Telekom Security TLS RSA Root 2023 "BEF256DAF26E9C69BDEC1602359798F3CAF71821A03E018257C53C65617F3D4A", // FIRMAPROFESIONAL CA ROOT-A WEB "3F63BB2814BE174EC8B6439CF08D6D56F0B7C405883A5648A334424D6B3EC558", // TWCA CYBER Root CA "3A0072D49FFC04E996C59AEB75991D3C340F3615D6FD4DCE90AC0B3D88EAD4F4", // TWCA Global Root CA G2 "3F034BB5704D44B2D08545A02057DE93EBF3905FCE721ACBC730C06DDAEE904E", // SecureSign Root CA12 "4B009C1034494F9AB56BBA3BA1D62731FC4D20D8955ADCEC10A925607261E338", // SecureSign Root CA14 "E778F0F095FE843729CD1A0082179E5314A9C291442805E1FB1D8FB6B8886C3A", // SecureSign Root CA15 "0552E6F83FDF65E8FA9670E666DF28A4E21340B510CBE52566F97C4FB94B2BD1", // D-TRUST BR Root CA 2 2023 "436472C1009A325C54F1A5BBB5468A7BAEECCBE05DE5F099CB70D3FE41E13C16", // TrustAsia SMIME ECC Root CA "C7796BEB62C101BB143D262A7C96A0C6168183223EF50D699632D86E03B8CC9B", // TrustAsia SMIME RSA Root CA "C0076B9EF0531FB1A656D67C4EBE97CD5DBAA41EF44598ACC2489878C92D8711", // TrustAsia TLS ECC Root CA "06C08D7DAFD876971EB1124FE67F847EC0C7A158D3EA53CBE940E2EA9791F4C3", // TrustAsia TLS RSA Root CA "8E8221B2E7D4007836A1672F0DCC299C33BC07D316F132FA1A206D587150F1CE", // D-TRUST EV Root CA 2 2023 "9A12C392BFE57891A0C545309D4D9FD567E480CB613D6342278B195C79A7931F", // SwissSign RSA SMIME Root CA 2022 - 1 "193144F431E0FDDB740717D4DE926A571133884B4360D30E272913CBE660CE41", // SwissSign RSA TLS Root CA 2022 - 1 "D9A32485A8CCA85539CEF12FFFFF711378A17851D73DA2732AB4302D763BD62B", // OISTE Client Root ECC G1 "D02A0F994A868C66395F2E7A880DF509BD0C29C96DE16015A0FD501EDA4F96A9", // OISTE Client Root RSA G1 "EEC997C0C30F216F7E3B8B307D2BAE42412D753FC8219DAFD1520B2572850F49", // OISTE Server Root ECC G1 "9AE36232A5189FFDDB353DFD26520C015395D22777DAC59DB57B98C089A651E6", // OISTE Server Root RSA G1 "B49141502D00663D740F2E7EC340C52800962666121A36D09CF7DD2B90384FB4", // e-Szigno TLS Root CA 2023 }; /// /// Get certificate in PEM format from a server with CA pinning validation /// public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 4) { try { var (domain, _, port, _) = Utils.ParseUrl(target); using var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(timeout)); using var client = new TcpClient(); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); var sslOptions = new SslClientAuthenticationOptions { TargetHost = serverName, RemoteCertificateValidationCallback = ValidateServerCertificate }; await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token); var remote = ssl.RemoteCertificate; if (remote == null) { return (null, null); } var leaf = new X509Certificate2(remote); return (ExportCertToPem(leaf), null); } catch (OperationCanceledException) { Logging.SaveLog(_tag, new TimeoutException($"Connection timeout after {timeout} seconds")); return (null, $"Connection timeout after {timeout} seconds"); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return (null, ex.Message); } } /// /// Get certificate chain in PEM format from a server with CA pinning validation /// public async Task<(List, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 4) { var pemList = new List(); try { var (domain, _, port, _) = Utils.ParseUrl(target); using var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(timeout)); using var client = new TcpClient(); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); var sslOptions = new SslClientAuthenticationOptions { TargetHost = serverName, RemoteCertificateValidationCallback = ValidateServerCertificate }; await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token); if (ssl.RemoteCertificate is not X509Certificate2 certChain) { return (pemList, null); } var chain = new X509Chain(); chain.Build(certChain); pemList.AddRange(chain.ChainElements.Select(element => ExportCertToPem(element.Certificate))); return (pemList, null); } catch (OperationCanceledException) { Logging.SaveLog(_tag, new TimeoutException($"Connection timeout after {timeout} seconds")); return (pemList, $"Connection timeout after {timeout} seconds"); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return (pemList, ex.Message); } } /// /// Validate server certificate with CA pinning /// private bool ValidateServerCertificate( object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) { if (certificate == null) { return false; } // Check certificate name mismatch if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch)) { return false; } // Build certificate chain var cert2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate); var certChain = chain ?? new X509Chain(); certChain.ChainPolicy.RevocationMode = X509RevocationMode.Online; certChain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; certChain.ChainPolicy.VerificationTime = DateTime.Now; certChain.Build(cert2); // Find root CA if (certChain.ChainElements.Count == 0) { return false; } var rootCert = certChain.ChainElements[certChain.ChainElements.Count - 1].Certificate; var rootThumbprint = rootCert.GetCertHashString(HashAlgorithmName.SHA256); return TrustedCaThumbprints.Contains(rootThumbprint); } public static string ExportCertToPem(X509Certificate2 cert) { var der = cert.Export(X509ContentType.Cert); var b64 = Convert.ToBase64String(der); return $"-----BEGIN CERTIFICATE-----\n{b64}\n-----END CERTIFICATE-----\n"; } /// /// Parse concatenated PEM certificates string into a list of individual certificates /// Normalizes format: removes line breaks from base64 content for better compatibility /// /// Concatenated PEM certificates string (supports both \r\n and \n line endings) /// List of individual PEM certificate strings with normalized format public static List ParsePemChain(string pemChain) { var certs = new List(); if (string.IsNullOrWhiteSpace(pemChain)) { return certs; } // Normalize line endings (CRLF -> LF) at the beginning pemChain = pemChain.Replace("\r\n", "\n").Replace("\r", "\n"); const string beginMarker = "-----BEGIN CERTIFICATE-----"; const string endMarker = "-----END CERTIFICATE-----"; var index = 0; while (index < pemChain.Length) { var beginIndex = pemChain.IndexOf(beginMarker, index, StringComparison.Ordinal); if (beginIndex == -1) { break; } var endIndex = pemChain.IndexOf(endMarker, beginIndex, StringComparison.Ordinal); if (endIndex == -1) { break; } // Extract certificate content var base64Start = beginIndex + beginMarker.Length; var base64Content = pemChain.Substring(base64Start, endIndex - base64Start); // Remove all whitespace from base64 content base64Content = new string(base64Content.Where(c => !char.IsWhiteSpace(c)).ToArray()); // Reconstruct with clean format: BEGIN marker + base64 (no line breaks) + END marker var normalizedCert = $"{beginMarker}\n{base64Content}\n{endMarker}\n"; certs.Add(normalizedCert); // Move to next certificate index = endIndex + endMarker.Length; } return certs; } /// /// Concatenate a list of PEM certificates into a single string /// /// List of individual PEM certificate strings /// Concatenated PEM certificates string public static string ConcatenatePemChain(IEnumerable pemList) { if (pemList == null) { return string.Empty; } return string.Concat(pemList); } public static string GetCertSha256Thumbprint(string pemCert, bool includeColon = false) { try { var cert = X509Certificate2.CreateFromPem(pemCert); var thumbprint = cert.GetCertHashString(HashAlgorithmName.SHA256); if (includeColon) { return string.Join(":", thumbprint.Chunk(2).Select(c => new string(c))); } return thumbprint; } catch { return string.Empty; } } } ================================================ FILE: v2rayN/ServiceLib/Manager/ClashApiManager.cs ================================================ using static ServiceLib.Models.ClashProxies; namespace ServiceLib.Manager; public sealed class ClashApiManager { private static readonly Lazy instance = new(() => new()); public static ClashApiManager Instance => instance.Value; private static readonly string _tag = "ClashApiHandler"; private Dictionary? _proxies; public Dictionary ProfileContent { get; set; } public async Task?> GetClashProxiesAsync() { for (var i = 0; i < 3; i++) { var url = $"{GetApiUrl()}/proxies"; var result = await HttpClientHelper.Instance.TryGetAsync(url); var clashProxies = JsonUtils.Deserialize(result); var url2 = $"{GetApiUrl()}/providers/proxies"; var result2 = await HttpClientHelper.Instance.TryGetAsync(url2); var clashProviders = JsonUtils.Deserialize(result2); if (clashProxies != null || clashProviders != null) { _proxies = clashProxies?.proxies; return new Tuple(clashProxies, clashProviders); } await Task.Delay(2000); } return null; } public void ClashProxiesDelayTest(bool blAll, List lstProxy, Func updateFunc) { Task.Run(async () => { if (blAll) { if (_proxies == null) { await GetClashProxiesAsync(); } lstProxy = new List(); foreach (var kv in _proxies ?? []) { if (Global.notAllowTestType.Contains(kv.Value.type?.ToLower())) { continue; } lstProxy.Add(new ClashProxyModel() { Name = kv.Value.name, Type = kv.Value.type?.ToLower(), }); } } if (lstProxy is not { Count: > 0 }) { return; } var urlBase = $"{GetApiUrl()}/proxies"; urlBase += @"/{0}/delay?timeout=10000&url=" + AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl; var tasks = new List(); foreach (var it in lstProxy) { if (Global.notAllowTestType.Contains(it.Type.ToLower())) { continue; } var name = it.Name; var url = string.Format(urlBase, name); tasks.Add(Task.Run(async () => { var result = await HttpClientHelper.Instance.TryGetAsync(url); await updateFunc?.Invoke(it, result); })); } await Task.WhenAll(tasks); await Task.Delay(1000); await updateFunc?.Invoke(null, ""); }); } public List? GetClashProxyGroups() { try { var fileContent = ProfileContent; if (fileContent is null || fileContent?.ContainsKey("proxy-groups") == false) { return null; } return JsonUtils.Deserialize>(JsonUtils.Serialize(fileContent["proxy-groups"])); } catch (Exception ex) { Logging.SaveLog(_tag, ex); return null; } } public async Task ClashSetActiveProxy(string name, string nameNode) { try { var url = $"{GetApiUrl()}/proxies/{name}"; var headers = new Dictionary(); headers.Add("name", nameNode); await HttpClientHelper.Instance.PutAsync(url, headers); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } public async Task ClashConfigUpdate(Dictionary headers) { if (_proxies == null) { return; } var urlBase = $"{GetApiUrl()}/configs"; await HttpClientHelper.Instance.PatchAsync(urlBase, headers); } public async Task ClashConfigReload(string filePath) { await ClashConnectionClose(""); try { var url = $"{GetApiUrl()}/configs?force=true"; var headers = new Dictionary(); headers.Add("path", filePath); await HttpClientHelper.Instance.PutAsync(url, headers); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } public async Task GetClashConnectionsAsync() { try { var url = $"{GetApiUrl()}/connections"; var result = await HttpClientHelper.Instance.TryGetAsync(url); var clashConnections = JsonUtils.Deserialize(result); return clashConnections; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return null; } public async Task ClashConnectionClose(string id) { try { var url = $"{GetApiUrl()}/connections/{id}"; await HttpClientHelper.Instance.DeleteAsync(url); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private string GetApiUrl() { return $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort2}"; } } ================================================ FILE: v2rayN/ServiceLib/Manager/CoreAdminManager.cs ================================================ using CliWrap; using CliWrap.Buffered; namespace ServiceLib.Manager; public class CoreAdminManager { private static readonly Lazy _instance = new(() => new()); public static CoreAdminManager Instance => _instance.Value; private Config _config; private Func? _updateFunc; private int _linuxSudoPid = -1; private const string _tag = "CoreAdminHandler"; public async Task Init(Config config, Func updateFunc) { if (_config != null) { return; } _config = config; _updateFunc = updateFunc; await Task.CompletedTask; } private async Task UpdateFunc(bool notify, string msg) { await _updateFunc?.Invoke(notify, msg); } public async Task RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath) { StringBuilder sb = new(); sb.AppendLine("#!/bin/bash"); var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}"; sb.AppendLine($"exec sudo -S -- {cmdLine}"); var shFilePath = await FileUtils.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true); var procService = new ProcessService( fileName: shFilePath, arguments: "", workingDirectory: Utils.GetBinConfigPath(), displayLog: true, redirectInput: true, environmentVars: null, updateFunc: _updateFunc ); await procService.StartAsync(AppManager.Instance.LinuxSudoPwd); if (procService is null or { HasExited: true }) { throw new Exception(ResUI.FailedToRunCore); } _linuxSudoPid = procService.Id; return procService; } public async Task KillProcessAsLinuxSudo() { if (_linuxSudoPid < 0) { return; } try { var shellFileName = Utils.IsMacOS() ? Global.KillAsSudoOSXShellFileName : Global.KillAsSudoLinuxShellFileName; var shFilePath = await FileUtils.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true); if (shFilePath.Contains(' ')) { shFilePath = shFilePath.AppendQuotes(); } var arg = new List() { "-c", $"sudo -S {shFilePath} {_linuxSudoPid}" }; var result = await Cli.Wrap(Global.LinuxBash) .WithArguments(arg) .WithStandardInputPipe(PipeSource.FromString(AppManager.Instance.LinuxSudoPwd)) .ExecuteBufferedAsync(); await UpdateFunc(false, result.StandardOutput.ToString()); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } _linuxSudoPid = -1; } } ================================================ FILE: v2rayN/ServiceLib/Manager/CoreInfoManager.cs ================================================ namespace ServiceLib.Manager; public sealed class CoreInfoManager { private static readonly Lazy _instance = new(() => new()); private List? _coreInfo; public static CoreInfoManager Instance => _instance.Value; public CoreInfoManager() { InitCoreInfo(); } public CoreInfo? GetCoreInfo(ECoreType coreType) { if (_coreInfo == null) { InitCoreInfo(); } return _coreInfo?.FirstOrDefault(t => t.CoreType == coreType); } public List GetCoreInfo() { if (_coreInfo == null) { InitCoreInfo(); } return _coreInfo ?? []; } public string GetCoreExecFile(CoreInfo? coreInfo, out string msg) { var fileName = string.Empty; msg = string.Empty; foreach (var name in coreInfo?.CoreExes) { var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString()); if (File.Exists(vName)) { fileName = vName; break; } } if (fileName.IsNullOrEmpty()) { msg = string.Format(ResUI.NotFoundCore, Utils.GetBinPath("", coreInfo?.CoreType.ToString()), coreInfo?.CoreExes?.LastOrDefault(), coreInfo?.Url); Logging.SaveLog(msg); } return fileName; } private void InitCoreInfo() { var urlN = GetCoreUrl(ECoreType.v2rayN); var urlXray = GetCoreUrl(ECoreType.Xray); var urlMihomo = GetCoreUrl(ECoreType.mihomo); var urlSingbox = GetCoreUrl(ECoreType.sing_box); _coreInfo = [ new CoreInfo { CoreType = ECoreType.v2rayN, Url = GetCoreUrl(ECoreType.v2rayN), ReleaseApiUrl = urlN.Replace(Global.GithubUrl, Global.GithubApiUrl), DownloadUrlWin64 = urlN + "/download/{0}/v2rayN-windows-64.zip", DownloadUrlWinArm64 = urlN + "/download/{0}/v2rayN-windows-arm64.zip", DownloadUrlLinux64 = urlN + "/download/{0}/v2rayN-linux-64.zip", DownloadUrlLinuxArm64 = urlN + "/download/{0}/v2rayN-linux-arm64.zip", DownloadUrlOSX64 = urlN + "/download/{0}/v2rayN-macos-64.zip", DownloadUrlOSXArm64 = urlN + "/download/{0}/v2rayN-macos-arm64.zip", }, new CoreInfo { CoreType = ECoreType.v2fly, CoreExes = ["v2ray"], Arguments = "{0}", Url = GetCoreUrl(ECoreType.v2fly), Match = "V2Ray", VersionArg = "-version", Environment = new Dictionary() { { Global.V2RayLocalAsset, Utils.GetBinPath("") }, }, }, new CoreInfo { CoreType = ECoreType.v2fly_v5, CoreExes = ["v2ray"], Arguments = "run -c {0} -format jsonv5", Url = GetCoreUrl(ECoreType.v2fly_v5), Match = "V2Ray", VersionArg = "version", Environment = new Dictionary() { { Global.V2RayLocalAsset, Utils.GetBinPath("") }, }, }, new CoreInfo { CoreType = ECoreType.Xray, CoreExes = ["xray"], Arguments = "run -c {0}", Url = GetCoreUrl(ECoreType.Xray), ReleaseApiUrl = urlXray.Replace(Global.GithubUrl, Global.GithubApiUrl), DownloadUrlWin64 = urlXray + "/download/{0}/Xray-windows-64.zip", DownloadUrlWinArm64 = urlXray + "/download/{0}/Xray-windows-arm64-v8a.zip", DownloadUrlLinux64 = urlXray + "/download/{0}/Xray-linux-64.zip", DownloadUrlLinuxArm64 = urlXray + "/download/{0}/Xray-linux-arm64-v8a.zip", DownloadUrlOSX64 = urlXray + "/download/{0}/Xray-macos-64.zip", DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip", Match = "Xray", VersionArg = "-version", Environment = new Dictionary() { { Global.XrayLocalAsset, Utils.GetBinPath("") }, { Global.XrayLocalCert, Utils.GetBinPath("") }, }, }, new CoreInfo { CoreType = ECoreType.mihomo, CoreExes = GetMihomoCoreExes(), Arguments = "-f {0}" + PortableMode(), Url = GetCoreUrl(ECoreType.mihomo), ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl), DownloadUrlWin64 = urlMihomo + "/download/{0}/mihomo-windows-amd64-v1-{0}.zip", DownloadUrlWinArm64 = urlMihomo + "/download/{0}/mihomo-windows-arm64-{0}.zip", DownloadUrlLinux64 = urlMihomo + "/download/{0}/mihomo-linux-amd64-v1-{0}.gz", DownloadUrlLinuxArm64 = urlMihomo + "/download/{0}/mihomo-linux-arm64-{0}.gz", DownloadUrlOSX64 = urlMihomo + "/download/{0}/mihomo-darwin-amd64-v1-{0}.gz", DownloadUrlOSXArm64 = urlMihomo + "/download/{0}/mihomo-darwin-arm64-{0}.gz", Match = "Mihomo", VersionArg = "-v", }, new CoreInfo { CoreType = ECoreType.hysteria, CoreExes = ["hysteria"], Arguments = "", Url = GetCoreUrl(ECoreType.hysteria), }, new CoreInfo { CoreType = ECoreType.naiveproxy, CoreExes = [ "naive", "naiveproxy"], Arguments = "{0}", Url = GetCoreUrl(ECoreType.naiveproxy), }, new CoreInfo { CoreType = ECoreType.tuic, CoreExes = ["tuic-client", "tuic"], Arguments = "-c {0}", Url = GetCoreUrl(ECoreType.tuic), }, new CoreInfo { CoreType = ECoreType.sing_box, CoreExes = ["sing-box-client", "sing-box"], Arguments = "run -c {0} --disable-color", Url = GetCoreUrl(ECoreType.sing_box), ReleaseApiUrl = urlSingbox.Replace(Global.GithubUrl, Global.GithubApiUrl), DownloadUrlWin64 = urlSingbox + "/download/{0}/sing-box-{1}-windows-amd64.zip", DownloadUrlWinArm64 = urlSingbox + "/download/{0}/sing-box-{1}-windows-arm64.zip", DownloadUrlLinux64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-amd64.tar.gz", DownloadUrlLinuxArm64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-arm64.tar.gz", DownloadUrlOSX64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-amd64.tar.gz", DownloadUrlOSXArm64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-arm64.tar.gz", Match = "sing-box", VersionArg = "version", }, new CoreInfo { CoreType = ECoreType.juicity, CoreExes = ["juicity-client", "juicity"], Arguments = "run -c {0}", Url = GetCoreUrl(ECoreType.juicity) }, new CoreInfo { CoreType = ECoreType.hysteria2, CoreExes = ["hysteria-windows-amd64", "hysteria-linux-amd64", "hysteria"], Arguments = "", Url = GetCoreUrl(ECoreType.hysteria2), }, new CoreInfo { CoreType = ECoreType.brook, CoreExes = ["brook_windows_amd64", "brook_linux_amd64", "brook"], Arguments = " {0}", Url = GetCoreUrl(ECoreType.brook), AbsolutePath = true, }, new CoreInfo { CoreType = ECoreType.overtls, CoreExes = [ "overtls-bin", "overtls"], Arguments = "-r client -c {0}", Url = GetCoreUrl(ECoreType.overtls), AbsolutePath = false, }, new CoreInfo { CoreType = ECoreType.shadowquic, CoreExes = [ "shadowquic" ], Arguments = "-c {0}", Url = GetCoreUrl(ECoreType.shadowquic), AbsolutePath = false, }, new CoreInfo { CoreType = ECoreType.mieru, CoreExes = [ "mieru" ], Arguments = "run", Url = GetCoreUrl(ECoreType.mieru), AbsolutePath = false, Environment = new Dictionary() { { "MIERU_CONFIG_JSON_FILE", "{0}" }, }, }, ]; } private static string PortableMode() { return $" -d {Utils.GetBinPath("").AppendQuotes()}"; } private static string GetCoreUrl(ECoreType eCoreType) { return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases"; } private static List? GetMihomoCoreExes() { var names = new List(); if (Utils.IsWindows()) { names.Add("mihomo-windows-amd64-v1"); names.Add("mihomo-windows-amd64-compatible"); names.Add("mihomo-windows-amd64"); names.Add("mihomo-windows-arm64"); } else if (Utils.IsLinux()) { names.Add("mihomo-linux-amd64-v1"); names.Add("mihomo-linux-amd64"); names.Add("mihomo-linux-arm64"); } else if (Utils.IsMacOS()) { names.Add("mihomo-darwin-amd64-v1"); names.Add("mihomo-darwin-amd64"); names.Add("mihomo-darwin-arm64"); } names.Add("clash"); names.Add("mihomo"); return names; } } ================================================ FILE: v2rayN/ServiceLib/Manager/CoreManager.cs ================================================ namespace ServiceLib.Manager; /// /// Core process processing class /// public class CoreManager { private static readonly Lazy _instance = new(() => new()); public static CoreManager Instance => _instance.Value; private Config _config; private WindowsJobService? _processJob; private ProcessService? _processService; private ProcessService? _processPreService; private bool _linuxSudo = false; private Func? _updateFunc; private const string _tag = "CoreHandler"; public async Task Init(Config config, Func updateFunc) { _config = config; _updateFunc = updateFunc; //Copy the bin folder to the storage location (for init) if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1") { var fromPath = Utils.GetBaseDirectory("bin"); var toPath = Utils.GetBinPath(""); if (fromPath != toPath) { FileUtils.CopyDirectory(fromPath, toPath, true, false); } } if (Utils.IsNonWindows()) { var coreInfo = CoreInfoManager.Instance.GetCoreInfo(); foreach (var it in coreInfo) { if (it.CoreType == ECoreType.v2rayN) { if (Utils.UpgradeAppExists(out var upgradeFileName)) { await Utils.SetLinuxChmod(upgradeFileName); } continue; } foreach (var name in it.CoreExes) { var exe = Utils.GetBinPath(Utils.GetExeName(name), it.CoreType.ToString()); if (File.Exists(exe)) { await Utils.SetLinuxChmod(exe); } } } } } /// Resolved main context (with pre-socks ports already merged if applicable). /// Optional pre-socks context passed to . public async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext) { if (mainContext == null) { await UpdateFunc(false, ResUI.CheckServerSettings); return; } var node = mainContext.Node; var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); var result = await CoreConfigHandler.GenerateClientConfig(mainContext, fileName); if (result.Success != true) { await UpdateFunc(true, result.Msg); return; } await UpdateFunc(false, $"{node.GetSummary()}"); await UpdateFunc(false, $"{Utils.GetRuntimeInfo()}"); await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))); await CoreStop(); await Task.Delay(100); if (Utils.IsWindows() && _config.TunModeItem.EnableTun) { await Task.Delay(100); await WindowsUtils.RemoveTunDevice(); } await CoreStart(mainContext); await CoreStartPreService(preContext); if (_processService != null) { await UpdateFunc(true, $"{node.GetSummary()}"); } } public async Task LoadCoreConfigSpeedtest(List selecteds) { var coreType = selecteds.FirstOrDefault()?.CoreType == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray; var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); var configPath = Utils.GetBinConfigPath(fileName); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType); await UpdateFunc(false, result.Msg); if (result.Success != true) { return null; } await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))); await UpdateFunc(false, configPath); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType); return await RunProcess(coreInfo, fileName, true, false); } public async Task LoadCoreConfigSpeedtest(ServerTestItem testItem) { var node = await AppManager.Instance.GetProfileItem(testItem.IndexId); if (node is null) { return null; } var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); var configPath = Utils.GetBinConfigPath(fileName); var (context, _) = await CoreConfigContextBuilder.Build(_config, node); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath); if (result.Success != true) { return null; } var coreType = context.RunCoreType; var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType); return await RunProcess(coreInfo, fileName, true, false); } public async Task CoreStop() { try { if (_linuxSudo) { await CoreAdminManager.Instance.KillProcessAsLinuxSudo(); _linuxSudo = false; } if (_processService != null) { await _processService.StopAsync(); _processService.Dispose(); _processService = null; } if (_processPreService != null) { await _processPreService.StopAsync(); _processPreService.Dispose(); _processPreService = null; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } #region Private private async Task CoreStart(CoreConfigContext context) { var node = context.Node; var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType); var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog; var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true); if (proc is null) { return; } _processService = proc; } private async Task CoreStartPreService(CoreConfigContext? preContext) { if (_processService is { HasExited: false } && preContext != null) { var preCoreType = preContext?.Node?.CoreType ?? ECoreType.sing_box; var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); var result = await CoreConfigHandler.GenerateClientConfig(preContext, fileName); if (result.Success) { var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType); var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); if (proc is null) { return; } _processPreService = proc; } } } private async Task UpdateFunc(bool notify, string msg) { await _updateFunc?.Invoke(notify, msg); } #endregion Private #region Process private async Task RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo) { var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg); if (fileName.IsNullOrEmpty()) { await UpdateFunc(false, msg); return null; } try { if (mayNeedSudo && _config.TunModeItem.EnableTun && (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo) && Utils.IsNonWindows()) { _linuxSudo = true; await CoreAdminManager.Instance.Init(_config, _updateFunc); return await CoreAdminManager.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath); } return await RunProcessNormal(fileName, coreInfo, configPath, displayLog); } catch (Exception ex) { Logging.SaveLog(_tag, ex); await UpdateFunc(mayNeedSudo, ex.Message); return null; } } private async Task RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog) { var environmentVars = new Dictionary(); foreach (var kv in coreInfo.Environment) { environmentVars[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath); } var procService = new ProcessService( fileName: fileName, arguments: string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath), workingDirectory: Utils.GetBinConfigPath(), displayLog: displayLog, redirectInput: false, environmentVars: environmentVars, updateFunc: _updateFunc ); await procService.StartAsync(); await Task.Delay(100); if (procService is null or { HasExited: true }) { throw new Exception(ResUI.FailedToRunCore); } AddProcessJob(procService.Handle); return procService; } private void AddProcessJob(nint processHandle) { if (Utils.IsWindows()) { _processJob ??= new(); try { _processJob?.AddProcess(processHandle); } catch { } } } #endregion Process } ================================================ FILE: v2rayN/ServiceLib/Manager/GroupProfileManager.cs ================================================ namespace ServiceLib.Manager; public class GroupProfileManager { public static async Task HasCycle(ProfileItem item) { return await HasCycle(item.IndexId, item.GetProtocolExtra()); } public static async Task HasCycle(string? indexId, ProtocolExtraItem? extraInfo) { return await HasCycle(indexId, extraInfo, new HashSet(), new HashSet()); } private static async Task HasCycle(string? indexId, ProtocolExtraItem? extraInfo, HashSet visited, HashSet stack) { if (indexId.IsNullOrEmpty() || extraInfo == null) { return false; } if (stack.Contains(indexId)) { return true; } if (visited.Contains(indexId)) { return false; } visited.Add(indexId); stack.Add(indexId); try { if (extraInfo.GroupType.IsNullOrEmpty()) { return false; } if (extraInfo.ChildItems.IsNullOrEmpty()) { return false; } var childIds = Utils.String2List(extraInfo.ChildItems) ?.Where(p => !string.IsNullOrEmpty(p)) .ToList(); if (childIds == null) { return false; } var childItems = await AppManager.Instance.GetProfileItemsByIndexIds(childIds); foreach (var childItem in childItems) { if (await HasCycle(childItem.IndexId, childItem?.GetProtocolExtra(), visited, stack)) { return true; } } return false; } finally { stack.Remove(indexId); } } public static async Task<(List Items, ProtocolExtraItem? Extra)> GetChildProfileItems(ProfileItem profileItem) { var protocolExtra = profileItem?.GetProtocolExtra(); return (await GetChildProfileItemsByProtocolExtra(protocolExtra), protocolExtra); } public static async Task> GetChildProfileItemsByProtocolExtra(ProtocolExtraItem? protocolExtra) { if (protocolExtra == null) { return []; } var items = new List(); items.AddRange(await GetSubChildProfileItems(protocolExtra)); items.AddRange(await GetSelectedChildProfileItems(protocolExtra)); return items; } private static async Task> GetSelectedChildProfileItems(ProtocolExtraItem? extra) { if (extra == null || extra.ChildItems.IsNullOrEmpty()) { return []; } var childProfileIds = Utils.String2List(extra.ChildItems) ?.Where(p => !string.IsNullOrEmpty(p)) .ToList() ?? []; if (childProfileIds.Count == 0) { return []; } var ordered = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childProfileIds); return ordered; } private static async Task> GetSubChildProfileItems(ProtocolExtraItem? extra) { if (extra == null || extra.SubChildItems.IsNullOrEmpty()) { return []; } var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty); return childProfiles?.Where(p => p != null && p.IsValid() && !p.ConfigType.IsComplexType() && (extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter)) ) .ToList() ?? []; } public static async Task> GetAllChildProfileItems(ProfileItem profileItem) { var itemMap = new Dictionary(); var visited = new HashSet(); await CollectChildItems(profileItem, itemMap, visited); return itemMap; } private static async Task CollectChildItems(ProfileItem profileItem, Dictionary itemMap, HashSet visited) { var (childItems, _) = await GetChildProfileItems(profileItem); foreach (var child in childItems.Where(child => visited.Add(child.IndexId))) { itemMap[child.IndexId] = child; if (child.ConfigType.IsGroupType()) { await CollectChildItems(child, itemMap, visited); } } } } ================================================ FILE: v2rayN/ServiceLib/Manager/NoticeManager.cs ================================================ namespace ServiceLib.Manager; public class NoticeManager { private static readonly Lazy _instance = new(() => new()); public static NoticeManager Instance => _instance.Value; public void Enqueue(string? content) { if (content.IsNullOrEmpty()) { return; } AppEvents.SendSnackMsgRequested.Publish(content); } public void SendMessage(string? content) { if (content.IsNullOrEmpty()) { return; } AppEvents.SendMsgViewRequested.Publish(content); } public void SendMessageEx(string? content) { if (content.IsNullOrEmpty()) { return; } content = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss} {content}"; SendMessage(content); } public void SendMessageAndEnqueue(string? msg) { Enqueue(msg); SendMessage(msg); } /// /// Sends each error and warning in to the message panel /// and enqueues a summary snack notification (capped at 10 messages). /// Returns true when there were any messages so the caller can decide on early-return /// based on . /// public bool NotifyValidatorResult(NodeValidatorResult validatorResult) { var msgs = new List([.. validatorResult.Errors, .. validatorResult.Warnings]); if (msgs.Count == 0) { return false; } foreach (var msg in msgs) { SendMessage(msg); } Enqueue(Utils.List2String(msgs.Take(10).ToList(), true)); return true; } } ================================================ FILE: v2rayN/ServiceLib/Manager/PacManager.cs ================================================ namespace ServiceLib.Manager; public class PacManager { private static readonly Lazy _instance = new(() => new PacManager()); public static PacManager Instance => _instance.Value; private int _httpPort; private int _pacPort; private TcpListener? _tcpListener; private byte[] _writeContent; private bool _isRunning; private bool _needRestart = true; public async Task StartAsync(int httpPort, int pacPort) { _needRestart = httpPort != _httpPort || pacPort != _pacPort || !_isRunning; _httpPort = httpPort; _pacPort = pacPort; await InitText(); if (_needRestart) { Stop(); RunListener(); } } private async Task InitText() { var customSystemProxyPacPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyPacPath; var fileName = (customSystemProxyPacPath.IsNotEmpty() && File.Exists(customSystemProxyPacPath)) ? customSystemProxyPacPath : Path.Combine(Utils.GetConfigPath(), "pac.txt"); // TODO: temporarily notify which script is being used NoticeManager.Instance.SendMessage(fileName); if (!File.Exists(fileName)) { var pac = EmbedUtils.GetEmbedText(Global.PacFileName); await File.AppendAllTextAsync(fileName, pac); } var pacText = await File.ReadAllTextAsync(fileName); pacText = pacText.Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;"); var sb = new StringBuilder(); sb.AppendLine("HTTP/1.0 200 OK"); sb.AppendLine("Content-type:application/x-ns-proxy-autoconfig"); sb.AppendLine("Connection:close"); sb.AppendLine("Content-Length:" + Encoding.UTF8.GetByteCount(pacText)); sb.AppendLine(); sb.Append(pacText); _writeContent = Encoding.UTF8.GetBytes(sb.ToString()); } private void RunListener() { _tcpListener = TcpListener.Create(_pacPort); _isRunning = true; _tcpListener.Start(); Task.Factory.StartNew(async () => { while (_isRunning) { try { if (!_tcpListener.Pending()) { await Task.Delay(10); continue; } var client = await _tcpListener.AcceptTcpClientAsync(); await Task.Run(() => WriteContent(client)); } catch { // ignored } } }, TaskCreationOptions.LongRunning); } private void WriteContent(TcpClient client) { var stream = client.GetStream(); stream.Write(_writeContent, 0, _writeContent.Length); stream.Flush(); } public void Stop() { if (_tcpListener == null) { return; } try { _isRunning = false; _tcpListener.Stop(); _tcpListener = null; } catch { // ignored } } } ================================================ FILE: v2rayN/ServiceLib/Manager/ProfileExManager.cs ================================================ //using System.Reactive.Linq; namespace ServiceLib.Manager; public class ProfileExManager { private static readonly Lazy _instance = new(() => new()); private ConcurrentBag _lstProfileEx = []; private readonly Queue _queIndexIds = new(); public static ProfileExManager Instance => _instance.Value; private static readonly string _tag = "ProfileExHandler"; public ProfileExManager() { //Init(); } public async Task Init() { await InitData(); } public async Task> GetProfileExs() { return await Task.FromResult(_lstProfileEx); } private async Task InitData() { await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem where indexId not in ( select indexId from ProfileItem )"); _lstProfileEx = new(await SQLiteHelper.Instance.TableAsync().ToListAsync()); } private void IndexIdEnqueue(string indexId) { if (indexId.IsNotEmpty() && !_queIndexIds.Contains(indexId)) { _queIndexIds.Enqueue(indexId); } } private async Task SaveQueueIndexIds() { var cnt = _queIndexIds.Count; if (cnt > 0) { var lstExists = await SQLiteHelper.Instance.TableAsync().ToListAsync(); List lstInserts = []; List lstUpdates = []; for (var i = 0; i < cnt; i++) { var id = _queIndexIds.Dequeue(); var item = lstExists.FirstOrDefault(t => t.IndexId == id); var itemNew = _lstProfileEx?.FirstOrDefault(t => t.IndexId == id); if (itemNew is null) { continue; } if (item is not null) { lstUpdates.Add(itemNew); } else { lstInserts.Add(itemNew); } } try { if (lstInserts.Count > 0) { await SQLiteHelper.Instance.InsertAllAsync(lstInserts); } if (lstUpdates.Count > 0) { await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } } private ProfileExItem AddProfileEx(string indexId) { var profileEx = new ProfileExItem() { IndexId = indexId, Delay = 0, Speed = 0, Sort = 0, Message = string.Empty }; _lstProfileEx.Add(profileEx); IndexIdEnqueue(indexId); return profileEx; } private ProfileExItem GetProfileExItem(string? indexId) { return _lstProfileEx.FirstOrDefault(t => t.IndexId == indexId) ?? AddProfileEx(indexId); } public async Task ClearAll() { await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem "); _lstProfileEx = new(); } public async Task SaveTo() { try { await SaveQueueIndexIds(); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } public void SetTestDelay(string indexId, int delay) { var profileEx = GetProfileExItem(indexId); profileEx.Delay = delay; IndexIdEnqueue(indexId); } public void SetTestSpeed(string indexId, decimal speed) { var profileEx = GetProfileExItem(indexId); profileEx.Speed = speed; IndexIdEnqueue(indexId); } public void SetTestMessage(string indexId, string message) { var profileEx = GetProfileExItem(indexId); profileEx.Message = message; IndexIdEnqueue(indexId); } public void SetSort(string indexId, int sort) { var profileEx = GetProfileExItem(indexId); profileEx.Sort = sort; IndexIdEnqueue(indexId); } public int GetSort(string indexId) { var profileEx = _lstProfileEx.FirstOrDefault(t => t.IndexId == indexId); if (profileEx == null) { return 0; } return profileEx.Sort; } public int GetMaxSort() { if (_lstProfileEx.Count <= 0) { return 0; } return _lstProfileEx.Max(t => t == null ? 0 : t.Sort); } } ================================================ FILE: v2rayN/ServiceLib/Manager/StatisticsManager.cs ================================================ namespace ServiceLib.Manager; public class StatisticsManager { private static readonly Lazy instance = new(() => new()); public static StatisticsManager Instance => instance.Value; private Config _config; private ServerStatItem? _serverStatItem; private List _lstServerStat; private Func? _updateFunc; private StatisticsXrayService? _statisticsXray; private StatisticsSingboxService? _statisticsSingbox; private static readonly string _tag = "StatisticsHandler"; public List ServerStat => _lstServerStat; public async Task Init(Config config, Func updateFunc) { _config = config; _updateFunc = updateFunc; if (config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) { await InitData(); _statisticsXray = new StatisticsXrayService(config, UpdateServerStatHandler); _statisticsSingbox = new StatisticsSingboxService(config, UpdateServerStatHandler); } } public void Close() { try { _statisticsXray?.Close(); _statisticsSingbox?.Close(); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } public async Task ClearAllServerStatistics() { await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem "); _serverStatItem = null; _lstServerStat = new(); } public async Task SaveTo() { try { if (_lstServerStat != null) { await SQLiteHelper.Instance.UpdateAllAsync(_lstServerStat); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } public async Task CloneServerStatItem(string indexId, string toIndexId) { if (_lstServerStat == null) { return; } if (indexId == toIndexId) { return; } var stat = _lstServerStat.FirstOrDefault(t => t.IndexId == indexId); if (stat == null) { return; } var toStat = JsonUtils.DeepCopy(stat); toStat.IndexId = toIndexId; await SQLiteHelper.Instance.ReplaceAsync(toStat); _lstServerStat.Add(toStat); } private async Task InitData() { await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )"); var ticks = DateTime.Now.Date.Ticks; await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}"); _lstServerStat = await SQLiteHelper.Instance.TableAsync().ToListAsync(); } private async Task UpdateServerStatHandler(ServerSpeedItem server) { await UpdateServerStat(server); } private async Task UpdateServerStat(ServerSpeedItem server) { await GetServerStatItem(_config.IndexId); if (_serverStatItem is null) { return; } if (server.ProxyUp != 0 || server.ProxyDown != 0) { _serverStatItem.TodayUp += server.ProxyUp; _serverStatItem.TodayDown += server.ProxyDown; _serverStatItem.TotalUp += server.ProxyUp; _serverStatItem.TotalDown += server.ProxyDown; } server.IndexId = _config.IndexId; server.TodayUp = _serverStatItem.TodayUp; server.TodayDown = _serverStatItem.TodayDown; server.TotalUp = _serverStatItem.TotalUp; server.TotalDown = _serverStatItem.TotalDown; await _updateFunc?.Invoke(server); } private async Task GetServerStatItem(string indexId) { var ticks = DateTime.Now.Date.Ticks; if (_serverStatItem != null && _serverStatItem.IndexId != indexId) { _serverStatItem = null; } if (_serverStatItem == null) { _serverStatItem = _lstServerStat.FirstOrDefault(t => t.IndexId == indexId); if (_serverStatItem == null) { _serverStatItem = new ServerStatItem { IndexId = indexId, TotalUp = 0, TotalDown = 0, TodayUp = 0, TodayDown = 0, DateNow = ticks }; await SQLiteHelper.Instance.ReplaceAsync(_serverStatItem); _lstServerStat.Add(_serverStatItem); } } if (_serverStatItem.DateNow != ticks) { _serverStatItem.TodayUp = 0; _serverStatItem.TodayDown = 0; _serverStatItem.DateNow = ticks; } } } ================================================ FILE: v2rayN/ServiceLib/Manager/TaskManager.cs ================================================ namespace ServiceLib.Manager; public class TaskManager { private static readonly Lazy _instance = new(() => new()); public static TaskManager Instance => _instance.Value; private Config _config; private Func? _updateFunc; public void RegUpdateTask(Config config, Func updateFunc) { _config = config; _updateFunc = updateFunc; Task.Run(ScheduledTasks); } private async Task ScheduledTasks() { Logging.SaveLog("Setup Scheduled Tasks"); var numOfExecuted = 1; while (true) { //1 minute await Task.Delay(1000 * 60); //Execute once 1 minute try { await UpdateTaskRunSubscription(); } catch (Exception ex) { Logging.SaveLog("ScheduledTasks - UpdateTaskRunSubscription", ex); } //Execute once 20 minute if (numOfExecuted % 20 == 0) { //Logging.SaveLog("Execute save config"); try { await ConfigHandler.SaveConfig(_config); await ProfileExManager.Instance.SaveTo(); } catch (Exception ex) { Logging.SaveLog("ScheduledTasks - SaveConfig", ex); } } //Execute once 1 hour if (numOfExecuted % 60 == 0) { //Logging.SaveLog("Execute delete expired files"); FileUtils.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1)); FileUtils.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1)); FileUtils.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1)); try { await UpdateTaskRunGeo(numOfExecuted / 60); } catch (Exception ex) { Logging.SaveLog("ScheduledTasks - UpdateTaskRunGeo", ex); } } numOfExecuted++; } } private async Task UpdateTaskRunSubscription() { var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds(); var lstSubs = (await AppManager.Instance.SubItems())? .Where(t => t.AutoUpdateInterval > 0) .Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60) .ToList(); if (lstSubs is not { Count: > 0 }) { return; } Logging.SaveLog("Execute update subscription"); foreach (var item in lstSubs) { await SubscriptionHandler.UpdateProcess(_config, item.Id, true, async (success, msg) => { await _updateFunc?.Invoke(success, msg); if (success) { Logging.SaveLog($"Update subscription end. {msg}"); } }); item.UpdateTime = updateTime; await ConfigHandler.AddSubItem(_config, item); await Task.Delay(1000); } } private async Task UpdateTaskRunGeo(int hours) { if (_config.GuiItem.AutoUpdateInterval > 0 && hours > 0 && hours % _config.GuiItem.AutoUpdateInterval == 0) { Logging.SaveLog("Execute update geo files"); await new UpdateService(_config, async (success, msg) => { await _updateFunc?.Invoke(false, msg); }).UpdateGeoFileAll(); } } } ================================================ FILE: v2rayN/ServiceLib/Manager/WebDavManager.cs ================================================ using WebDav; namespace ServiceLib.Manager; public sealed class WebDavManager { private static readonly Lazy _instance = new(() => new()); public static WebDavManager Instance => _instance.Value; private readonly Config? _config; private WebDavClient? _client; private string? _lastDescription; private string _webDir = Global.AppName + "_backup"; private readonly string _webFileName = "backup.zip"; private readonly string _tag = "WebDav--"; public WebDavManager() { _config = AppManager.Instance.Config; } private async Task GetClient() { try { if (_config.WebDavItem.Url.IsNullOrEmpty() || _config.WebDavItem.UserName.IsNullOrEmpty() || _config.WebDavItem.Password.IsNullOrEmpty()) { throw new ArgumentException("webdav parameter error or null"); } if (_client != null) { _client?.Dispose(); _client = null; } if (_config.WebDavItem.DirName.IsNullOrEmpty()) { _webDir = Global.AppName + "_backup"; } else { _webDir = _config.WebDavItem.DirName.TrimEx(); } // Ensure BaseAddress URL ends with a trailing slash var baseUrl = _config.WebDavItem.Url.Trim().TrimEnd('/') + "/"; var clientParams = new WebDavClientParams { BaseAddress = new Uri(baseUrl), Credentials = new NetworkCredential(_config.WebDavItem.UserName, _config.WebDavItem.Password) }; _client = new WebDavClient(clientParams); } catch (Exception ex) { SaveLog(ex); return false; } return await Task.FromResult(true); } private async Task TryCreateDir() { if (_client is null) { return false; } try { var result2 = await _client.Mkcol(_webDir); if (result2.IsSuccessful) { return true; } SaveLog(result2.Description); } catch (Exception ex) { SaveLog(ex); } return false; } private void SaveLog(string desc) { _lastDescription = desc; Logging.SaveLog(_tag + desc); } private void SaveLog(Exception ex) { _lastDescription = ex.Message; Logging.SaveLog(_tag, ex); } public async Task CheckConnection() { if (await GetClient() == false) { return false; } await TryCreateDir(); try { var testName = "readme_test"; var myContent = new StringContent(testName); var result = await _client.PutFile($"{_webDir}/{testName}", myContent); if (result.IsSuccessful) { await _client.Delete($"{_webDir}/{testName}"); return true; } else { SaveLog(result.Description); } } catch (Exception ex) { SaveLog(ex); } return false; } public async Task PutFile(string fileName) { if (await GetClient() == false) { return false; } await TryCreateDir(); try { await using var fs = File.OpenRead(fileName); var result = await _client.PutFile($"{_webDir}/{_webFileName}", fs); // upload a resource if (result.IsSuccessful) { return true; } SaveLog(result.Description); } catch (Exception ex) { SaveLog(ex); } return false; } public async Task GetRawFile(string fileName) { if (await GetClient() == false) { return false; } await TryCreateDir(); try { var response = await _client.GetRawFile($"{_webDir}/{_webFileName}"); if (!response.IsSuccessful) { SaveLog(response.Description); return false; } await using var outputFileStream = new FileStream(fileName, FileMode.Create); await response.Stream.CopyToAsync(outputFileStream); return true; } catch (Exception ex) { SaveLog(ex); } return false; } public string GetLastError() => _lastDescription ?? string.Empty; } ================================================ FILE: v2rayN/ServiceLib/Models/CheckUpdateModel.cs ================================================ namespace ServiceLib.Models; public class CheckUpdateModel : ReactiveObject { public bool? IsSelected { get; set; } public string? CoreType { get; set; } [Reactive] public string? Remarks { get; set; } public string? FileName { get; set; } public bool? IsFinished { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/ClashConnectionModel.cs ================================================ namespace ServiceLib.Models; public class ClashConnectionModel { public string? Id { get; set; } public string? Network { get; set; } public string? Type { get; set; } public string? Host { get; set; } public ulong Upload { get; set; } public ulong Download { get; set; } public string? UploadTraffic { get; set; } public string? DownloadTraffic { get; set; } public double Time { get; set; } public string? Elapsed { get; set; } public string? Chain { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/ClashConnections.cs ================================================ namespace ServiceLib.Models; public class ClashConnections { public ulong downloadTotal { get; set; } public ulong uploadTotal { get; set; } public List? connections { get; set; } } public class ConnectionItem { public string? id { get; set; } public MetadataItem? metadata { get; set; } public ulong upload { get; set; } public ulong download { get; set; } public DateTime start { get; set; } public List? chains { get; set; } public string? rule { get; set; } public string? rulePayload { get; set; } } public class MetadataItem { public string? network { get; set; } public string? type { get; set; } public string? sourceIP { get; set; } public string? destinationIP { get; set; } public string? sourcePort { get; set; } public string? destinationPort { get; set; } public string? host { get; set; } public string? nsMode { get; set; } public object? uid { get; set; } public string? process { get; set; } public string? processPath { get; set; } public string? remoteDestination { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/ClashProviders.cs ================================================ using static ServiceLib.Models.ClashProxies; namespace ServiceLib.Models; public class ClashProviders { public Dictionary? providers { get; set; } public class ProvidersItem { public string? name { get; set; } public List? proxies { get; set; } public string? type { get; set; } public string? vehicleType { get; set; } } } ================================================ FILE: v2rayN/ServiceLib/Models/ClashProxies.cs ================================================ namespace ServiceLib.Models; public class ClashProxies { public Dictionary? proxies { get; set; } public class ProxiesItem { public List? all { get; set; } public List? history { get; set; } public string? name { get; set; } public string? type { get; set; } public bool udp { get; set; } public string? now { get; set; } public int delay { get; set; } } public class HistoryItem { public string? time { get; set; } public int delay { get; set; } } } ================================================ FILE: v2rayN/ServiceLib/Models/ClashProxyModel.cs ================================================ namespace ServiceLib.Models; [Serializable] public class ClashProxyModel : ReactiveObject { public string? Name { get; set; } public string? Type { get; set; } public string? Now { get; set; } [Reactive] public int Delay { get; set; } [Reactive] public string? DelayName { get; set; } public bool IsActive { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/CmdItem.cs ================================================ namespace ServiceLib.Models; public class CmdItem { public string? Cmd { get; set; } public List? Arguments { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/ComboItem.cs ================================================ namespace ServiceLib.Models; public class ComboItem { public string? ID { get; set; } public string? Text { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/Config.cs ================================================ namespace ServiceLib.Models; [Serializable] public class Config { #region property public string IndexId { get; set; } public string SubIndexId { get; set; } #endregion property #region other entities public CoreBasicItem CoreBasicItem { get; set; } public TunModeItem TunModeItem { get; set; } public KcpItem KcpItem { get; set; } public GrpcItem GrpcItem { get; set; } public RoutingBasicItem RoutingBasicItem { get; set; } public GUIItem GuiItem { get; set; } public MsgUIItem MsgUIItem { get; set; } public UIItem UiItem { get; set; } public ConstItem ConstItem { get; set; } public SpeedTestItem SpeedTestItem { get; set; } public Mux4RayItem Mux4RayItem { get; set; } public Mux4SboxItem Mux4SboxItem { get; set; } public HysteriaItem HysteriaItem { get; set; } public ClashUIItem ClashUIItem { get; set; } public SystemProxyItem SystemProxyItem { get; set; } public WebDavItem WebDavItem { get; set; } public CheckUpdateItem CheckUpdateItem { get; set; } public Fragment4RayItem? Fragment4RayItem { get; set; } public List Inbound { get; set; } public List GlobalHotkeys { get; set; } public List CoreTypeItem { get; set; } public SimpleDNSItem SimpleDNSItem { get; set; } #endregion other entities } ================================================ FILE: v2rayN/ServiceLib/Models/ConfigItems.cs ================================================ namespace ServiceLib.Models; [Serializable] public class CoreBasicItem { public bool LogEnabled { get; set; } public string Loglevel { get; set; } public bool MuxEnabled { get; set; } public bool DefAllowInsecure { get; set; } public string DefFingerprint { get; set; } public string DefUserAgent { get; set; } public bool EnableFragment { get; set; } public bool EnableCacheFile4Sbox { get; set; } = true; } [Serializable] public class InItem { public int LocalPort { get; set; } public string Protocol { get; set; } public bool UdpEnabled { get; set; } public bool SniffingEnabled { get; set; } = true; public List? DestOverride { get; set; } = ["http", "tls"]; public bool RouteOnly { get; set; } public bool AllowLANConn { get; set; } public bool NewPort4LAN { get; set; } public string User { get; set; } public string Pass { get; set; } public bool SecondLocalPortEnabled { get; set; } } [Serializable] public class KcpItem { public int Mtu { get; set; } public int Tti { get; set; } public int UplinkCapacity { get; set; } public int DownlinkCapacity { get; set; } public bool Congestion { get; set; } public int ReadBufferSize { get; set; } public int WriteBufferSize { get; set; } } [Serializable] public class GrpcItem { public int? IdleTimeout { get; set; } public int? HealthCheckTimeout { get; set; } public bool? PermitWithoutStream { get; set; } public int? InitialWindowsSize { get; set; } } [Serializable] public class GUIItem { public bool AutoRun { get; set; } public bool EnableStatistics { get; set; } public bool DisplayRealTimeSpeed { get; set; } public bool KeepOlderDedupl { get; set; } public int AutoUpdateInterval { get; set; } public int TrayMenuServersLimit { get; set; } = 20; public bool EnableHWA { get; set; } = false; public bool EnableLog { get; set; } = true; } [Serializable] public class MsgUIItem { public string? MainMsgFilter { get; set; } public bool? AutoRefresh { get; set; } } [Serializable] public class UIItem { public bool EnableAutoAdjustMainLvColWidth { get; set; } public int MainGirdHeight1 { get; set; } public int MainGirdHeight2 { get; set; } public EGirdOrientation MainGirdOrientation { get; set; } = EGirdOrientation.Vertical; public string? ColorPrimaryName { get; set; } public string? CurrentTheme { get; set; } public string CurrentLanguage { get; set; } public string CurrentFontFamily { get; set; } public int CurrentFontSize { get; set; } public bool EnableDragDropSort { get; set; } public bool DoubleClick2Activate { get; set; } public bool AutoHideStartup { get; set; } public bool Hide2TrayWhenClose { get; set; } public bool MacOSShowInDock { get; set; } public List MainColumnItem { get; set; } public List WindowSizeItem { get; set; } } [Serializable] public class ConstItem { public string? SubConvertUrl { get; set; } public string? GeoSourceUrl { get; set; } public string? SrsSourceUrl { get; set; } public string? RouteRulesTemplateSourceUrl { get; set; } } [Serializable] public class KeyEventItem { public EGlobalHotkey EGlobalHotkey { get; set; } public bool Alt { get; set; } public bool Control { get; set; } public bool Shift { get; set; } public int? KeyCode { get; set; } } [Serializable] public class CoreTypeItem { public EConfigType ConfigType { get; set; } public ECoreType CoreType { get; set; } } [Serializable] public class TunModeItem { public bool EnableTun { get; set; } public bool AutoRoute { get; set; } = true; public bool StrictRoute { get; set; } = true; public string Stack { get; set; } public int Mtu { get; set; } public bool EnableIPv6Address { get; set; } } [Serializable] public class SpeedTestItem { public int SpeedTestTimeout { get; set; } public string SpeedTestUrl { get; set; } public string SpeedPingTestUrl { get; set; } public int MixedConcurrencyCount { get; set; } public string IPAPIUrl { get; set; } } [Serializable] public class RoutingBasicItem { public string DomainStrategy { get; set; } public string DomainStrategy4Singbox { get; set; } public string RoutingIndexId { get; set; } } [Serializable] public class ColumnItem { public string Name { get; set; } public int Width { get; set; } public int Index { get; set; } } [Serializable] public class Mux4RayItem { public int? Concurrency { get; set; } public int? XudpConcurrency { get; set; } public string? XudpProxyUDP443 { get; set; } } [Serializable] public class Mux4SboxItem { public string Protocol { get; set; } public int MaxConnections { get; set; } public bool? Padding { get; set; } } [Serializable] public class HysteriaItem { public int UpMbps { get; set; } public int DownMbps { get; set; } public int HopInterval { get; set; } = 30; } [Serializable] public class ClashUIItem { public ERuleMode RuleMode { get; set; } public bool EnableIPv6 { get; set; } public bool EnableMixinContent { get; set; } public int ProxiesSorting { get; set; } public bool ProxiesAutoRefresh { get; set; } public int ProxiesAutoDelayTestInterval { get; set; } = 10; public bool ConnectionsAutoRefresh { get; set; } public int ConnectionsRefreshInterval { get; set; } = 2; public List ConnectionsColumnItem { get; set; } } [Serializable] public class SystemProxyItem { public ESysProxyType SysProxyType { get; set; } public string SystemProxyExceptions { get; set; } public bool NotProxyLocalAddress { get; set; } = true; public string SystemProxyAdvancedProtocol { get; set; } public string? CustomSystemProxyPacPath { get; set; } public string? CustomSystemProxyScriptPath { get; set; } } [Serializable] public class WebDavItem { public string? Url { get; set; } public string? UserName { get; set; } public string? Password { get; set; } public string? DirName { get; set; } } [Serializable] public class CheckUpdateItem { public bool CheckPreReleaseUpdate { get; set; } public List? SelectedCoreTypes { get; set; } } [Serializable] public class Fragment4RayItem { public string? Packets { get; set; } public string? Length { get; set; } public string? Interval { get; set; } } [Serializable] public class WindowSizeItem { public string TypeName { get; set; } public int Width { get; set; } public int Height { get; set; } } [Serializable] public class SimpleDNSItem { public bool? UseSystemHosts { get; set; } public bool? AddCommonHosts { get; set; } public bool? FakeIP { get; set; } public bool? GlobalFakeIp { get; set; } public bool? BlockBindingQuery { get; set; } public string? DirectDNS { get; set; } public string? RemoteDNS { get; set; } public string? BootstrapDNS { get; set; } public string? Strategy4Freedom { get; set; } public string? Strategy4Proxy { get; set; } public bool? ServeStale { get; set; } public bool? ParallelQuery { get; set; } public string? Hosts { get; set; } public string? DirectExpectedIPs { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/CoreConfigContext.cs ================================================ namespace ServiceLib.Models; public record CoreConfigContext { public required ProfileItem Node { get; init; } public required ECoreType RunCoreType { get; init; } public RoutingItem? RoutingItem { get; init; } public DNSItem? RawDnsItem { get; init; } public SimpleDNSItem SimpleDnsItem { get; init; } = new(); public Dictionary AllProxiesMap { get; init; } = new(); public Config AppConfig { get; init; } = new(); public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new(); // Test ServerTestItem Map public Dictionary ServerTestItemMap { get; init; } = new(); // TUN Compatibility public bool IsTunEnabled { get; init; } = false; public HashSet ProtectDomainList { get; init; } = new(); // -> tun inbound --(if routing proxy)--> relay outbound // -> proxy core (relay inbound --> proxy outbound --(dialerProxy)--> protect outbound) // -> protect inbound -> direct proxy outbound data -> internet public int TunProtectSsPort { get; init; } = 0; public int ProxyRelaySsPort { get; init; } = 0; } ================================================ FILE: v2rayN/ServiceLib/Models/CoreInfo.cs ================================================ namespace ServiceLib.Models; [Serializable] public class CoreInfo { public ECoreType CoreType { get; set; } public List? CoreExes { get; set; } public string? Arguments { get; set; } public string? Url { get; set; } public string? ReleaseApiUrl { get; set; } public string? DownloadUrlWin64 { get; set; } public string? DownloadUrlWinArm64 { get; set; } public string? DownloadUrlLinux64 { get; set; } public string? DownloadUrlLinuxArm64 { get; set; } public string? DownloadUrlOSX64 { get; set; } public string? DownloadUrlOSXArm64 { get; set; } public string? Match { get; set; } public string? VersionArg { get; set; } public bool AbsolutePath { get; set; } public IDictionary Environment { get; set; } = new Dictionary(); } ================================================ FILE: v2rayN/ServiceLib/Models/DNSItem.cs ================================================ namespace ServiceLib.Models; [Serializable] public class DNSItem { [PrimaryKey] public string Id { get; set; } public string Remarks { get; set; } public bool Enabled { get; set; } = false; public ECoreType CoreType { get; set; } public bool UseSystemHosts { get; set; } public string? NormalDNS { get; set; } public string? TunDNS { get; set; } public string? DomainStrategy4Freedom { get; set; } public string? DomainDNSAddress { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/FullConfigTemplateItem.cs ================================================ namespace ServiceLib.Models; [Serializable] public class FullConfigTemplateItem { [PrimaryKey] public string Id { get; set; } public string Remarks { get; set; } public bool Enabled { get; set; } = false; public ECoreType CoreType { get; set; } public string? Config { get; set; } public string? TunConfig { get; set; } public bool? AddProxyOnly { get; set; } = false; public string? ProxyDetour { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/GitHubRelease.cs ================================================ namespace ServiceLib.Models; public class GitHubReleaseAsset { [JsonPropertyName("url")] public string? Url { get; set; } [JsonPropertyName("id")] public int Id { get; set; } [JsonPropertyName("node_id")] public string? NodeId { get; set; } [JsonPropertyName("name")] public string? Name { get; set; } [JsonPropertyName("label")] public object Label { get; set; } [JsonPropertyName("content_type")] public string? ContentType { get; set; } [JsonPropertyName("state")] public string? State { get; set; } [JsonPropertyName("size")] public int Size { get; set; } [JsonPropertyName("download_count")] public int DownloadCount { get; set; } [JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; } [JsonPropertyName("updated_at")] public DateTime UpdatedAt { get; set; } [JsonPropertyName("browser_download_url")] public string? BrowserDownloadUrl { get; set; } } public class GitHubRelease { [JsonPropertyName("url")] public string? Url { get; set; } [JsonPropertyName("assets_url")] public string? AssetsUrl { get; set; } [JsonPropertyName("upload_url")] public string? UploadUrl { get; set; } [JsonPropertyName("html_url")] public string? HtmlUrl { get; set; } [JsonPropertyName("id")] public int Id { get; set; } [JsonPropertyName("node_id")] public string? NodeId { get; set; } [JsonPropertyName("tag_name")] public string? TagName { get; set; } [JsonPropertyName("target_commitish")] public string? TargetCommitish { get; set; } [JsonPropertyName("name")] public string? Name { get; set; } [JsonPropertyName("draft")] public bool Draft { get; set; } [JsonPropertyName("prerelease")] public bool Prerelease { get; set; } [JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; } [JsonPropertyName("published_at")] public DateTime PublishedAt { get; set; } [JsonPropertyName("assets")] public List Assets { get; set; } [JsonPropertyName("tarball_url")] public string? TarballUrl { get; set; } [JsonPropertyName("zipball_url")] public string? ZipballUrl { get; set; } [JsonPropertyName("body")] public string? Body { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/IPAPIInfo.cs ================================================ namespace ServiceLib.Models; internal class IPAPIInfo { public string? ip { get; set; } public string? clientIp { get; set; } public string? ip_addr { get; set; } public string? query { get; set; } public string? country { get; set; } public string? country_name { get; set; } public string? country_code { get; set; } public string? countryCode { get; set; } public LocationInfo? location { get; set; } } public class LocationInfo { public string? country_code { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/ProfileExItem.cs ================================================ namespace ServiceLib.Models; [Serializable] public class ProfileExItem { [PrimaryKey] public string IndexId { get; set; } public int Delay { get; set; } public decimal Speed { get; set; } public int Sort { get; set; } public string? Message { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/ProfileGroupItem.cs ================================================ namespace ServiceLib.Models; [Obsolete("Use ProtocolExtraItem instead.")] [Serializable] public class ProfileGroupItem { [PrimaryKey] public string IndexId { get; set; } public string ChildItems { get; set; } public string? SubChildItems { get; set; } public string? Filter { get; set; } public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing; public bool NotHasChild() { return string.IsNullOrWhiteSpace(ChildItems) && string.IsNullOrWhiteSpace(SubChildItems); } } ================================================ FILE: v2rayN/ServiceLib/Models/ProfileItem.cs ================================================ namespace ServiceLib.Models; [Serializable] public class ProfileItem { private ProtocolExtraItem? _protocolExtraCache; public ProfileItem() { IndexId = string.Empty; ConfigType = EConfigType.VMess; ConfigVersion = 3; Subid = string.Empty; Address = string.Empty; Port = 0; Password = string.Empty; Username = string.Empty; Network = string.Empty; Remarks = string.Empty; HeaderType = string.Empty; RequestHost = string.Empty; Path = string.Empty; StreamSecurity = string.Empty; AllowInsecure = string.Empty; } #region function public string GetSummary() { var summary = $"[{ConfigType.ToString()}] "; if (IsComplex()) { summary += $"[{CoreType.ToString()}]{Remarks}"; } else { var arrAddr = Address.Contains(':') ? Address.Split(':') : Address.Split('.'); var addr = arrAddr.Length switch { > 2 => $"{arrAddr.First()}***{arrAddr.Last()}", > 1 => $"***{arrAddr.Last()}", _ => Address }; summary += $"{Remarks}({addr}:{Port})"; } return summary; } public List? GetAlpn() { return Alpn.IsNullOrEmpty() ? null : Utils.String2List(Alpn); } public string GetNetwork() { if (Network.IsNullOrEmpty() || !Global.Networks.Contains(Network)) { return Global.DefaultNetwork; } return Network.TrimEx(); } public bool IsComplex() { return ConfigType.IsComplexType(); } public bool IsValid() { if (IsComplex()) { return true; } if (Address.IsNullOrEmpty() || Port is <= 0 or >= 65536) { return false; } switch (ConfigType) { case EConfigType.VMess: if (Password.IsNullOrEmpty() || !Utils.IsGuidByParse(Password)) { return false; } break; case EConfigType.VLESS: if (Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(Password) && Password.Length > 30)) { return false; } if (!Global.Flows.Contains(GetProtocolExtra().Flow ?? string.Empty)) { return false; } break; case EConfigType.Shadowsocks: if (Password.IsNullOrEmpty()) { return false; } if (string.IsNullOrEmpty(GetProtocolExtra().SsMethod) || !Global.SsSecuritiesInSingbox.Contains(GetProtocolExtra().SsMethod)) { return false; } break; } if ((ConfigType is EConfigType.VLESS or EConfigType.Trojan) && StreamSecurity == Global.StreamSecurityReality && PublicKey.IsNullOrEmpty()) { return false; } return true; } public void SetProtocolExtra(ProtocolExtraItem extraItem) { _protocolExtraCache = extraItem; ProtoExtra = JsonUtils.Serialize(extraItem, false); } public void SetProtocolExtra() { ProtoExtra = JsonUtils.Serialize(_protocolExtraCache, false); } public ProtocolExtraItem GetProtocolExtra() { return _protocolExtraCache ??= JsonUtils.Deserialize(ProtoExtra) ?? new ProtocolExtraItem(); } #endregion function [PrimaryKey] public string IndexId { get; set; } public EConfigType ConfigType { get; set; } public ECoreType? CoreType { get; set; } public int ConfigVersion { get; set; } public string Subid { get; set; } public bool IsSub { get; set; } = true; public int? PreSocksPort { get; set; } public bool DisplayLog { get; set; } = true; public string Remarks { get; set; } public string Address { get; set; } public int Port { get; set; } public string Password { get; set; } public string Username { get; set; } public string Network { get; set; } public string HeaderType { get; set; } public string RequestHost { get; set; } public string Path { get; set; } public string StreamSecurity { get; set; } public string AllowInsecure { get; set; } public string Sni { get; set; } public string Alpn { get; set; } = string.Empty; public string Fingerprint { get; set; } public string PublicKey { get; set; } public string ShortId { get; set; } public string SpiderX { get; set; } public string Mldsa65Verify { get; set; } public string Extra { get; set; } public bool? MuxEnabled { get; set; } public string Cert { get; set; } public string CertSha { get; set; } public string EchConfigList { get; set; } public string EchForceQuery { get; set; } public string Finalmask { get; set; } public string ProtoExtra { get; set; } [Obsolete("Use ProtocolExtraItem.Ports instead.")] public string Ports { get; set; } [Obsolete("Use ProtocolExtraItem.AlterId instead.")] public int AlterId { get; set; } [Obsolete("Use ProtocolExtraItem.Flow instead.")] public string Flow { get; set; } [Obsolete("Use ProfileItem.Password instead.")] public string Id { get; set; } [Obsolete("Use ProtocolExtraItem.xxx instead.")] public string Security { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/ProfileItemModel.cs ================================================ namespace ServiceLib.Models; [Serializable] public class ProfileItemModel : ReactiveObject { public bool IsActive { get; set; } public string IndexId { get; set; } public EConfigType ConfigType { get; set; } public string Remarks { get; set; } public string Address { get; set; } public int Port { get; set; } public string Network { get; set; } public string StreamSecurity { get; set; } public string Subid { get; set; } public string SubRemarks { get; set; } public int Sort { get; set; } [Reactive] public int Delay { get; set; } public decimal Speed { get; set; } [Reactive] public string DelayVal { get; set; } [Reactive] public string SpeedVal { get; set; } [Reactive] public string TodayUp { get; set; } [Reactive] public string TodayDown { get; set; } [Reactive] public string TotalUp { get; set; } [Reactive] public string TotalDown { get; set; } public string GetSummary() { var summary = $"[{ConfigType}] {Remarks}"; if (!ConfigType.IsComplexType()) { summary += $"({Address}:{Port})"; } return summary; } } ================================================ FILE: v2rayN/ServiceLib/Models/ProtocolExtraItem.cs ================================================ namespace ServiceLib.Models; public record ProtocolExtraItem { // vmess public string? AlterId { get; init; } public string? VmessSecurity { get; init; } // vless public string? Flow { get; init; } public string? VlessEncryption { get; init; } //public string? VisionSeed { get; init; } // shadowsocks //public string? PluginArgs { get; init; } public string? SsMethod { get; init; } // wireguard public string? WgPublicKey { get; init; } public string? WgPresharedKey { get; init; } public string? WgInterfaceAddress { get; init; } public string? WgReserved { get; init; } public int? WgMtu { get; init; } // hysteria2 public string? SalamanderPass { get; init; } public int? UpMbps { get; init; } public int? DownMbps { get; init; } public string? Ports { get; init; } public string? HopInterval { get; init; } // group profile public string? GroupType { get; init; } public string? ChildItems { get; init; } public string? SubChildItems { get; init; } public string? Filter { get; init; } public EMultipleLoad? MultipleLoad { get; init; } } ================================================ FILE: v2rayN/ServiceLib/Models/RetResult.cs ================================================ namespace ServiceLib.Models; public class RetResult { public bool Success { get; set; } public string? Msg { get; set; } public object? Data { get; set; } public RetResult(bool success = false) { Success = success; } public RetResult(bool success, string? msg) { Success = success; Msg = msg; } public RetResult(bool success, string? msg, object? data) { Success = success; Msg = msg; Data = data; } } ================================================ FILE: v2rayN/ServiceLib/Models/RoutingItem.cs ================================================ namespace ServiceLib.Models; [Serializable] public class RoutingItem { [PrimaryKey] public string Id { get; set; } public string Remarks { get; set; } public string Url { get; set; } public string RuleSet { get; set; } public int RuleNum { get; set; } public bool Enabled { get; set; } = true; public bool Locked { get; set; } public string CustomIcon { get; set; } public string CustomRulesetPath4Singbox { get; set; } public string DomainStrategy { get; set; } public string DomainStrategy4Singbox { get; set; } public int Sort { get; set; } public bool IsActive { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/RoutingItemModel.cs ================================================ namespace ServiceLib.Models; [Serializable] public class RoutingItemModel : RoutingItem { } ================================================ FILE: v2rayN/ServiceLib/Models/RoutingTemplate.cs ================================================ namespace ServiceLib.Models; [Serializable] public class RoutingTemplate { public string Version { get; set; } public RoutingItem[] RoutingItems { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/RulesItem.cs ================================================ namespace ServiceLib.Models; [Serializable] public class RulesItem { public string Id { get; set; } public string? Type { get; set; } public string? Port { get; set; } public string? Network { get; set; } public List? InboundTag { get; set; } public string? OutboundTag { get; set; } public List? Ip { get; set; } public List? Domain { get; set; } public List? Protocol { get; set; } public List? Process { get; set; } public bool Enabled { get; set; } = true; public string? Remarks { get; set; } public ERuleType? RuleType { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/RulesItemModel.cs ================================================ namespace ServiceLib.Models; [Serializable] public class RulesItemModel : RulesItem { public string InboundTags { get; set; } public string Ips { get; set; } public string Domains { get; set; } public string Protocols { get; set; } public string RuleTypeName { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/SemanticVersion.cs ================================================ namespace ServiceLib.Models; public class SemanticVersion { private readonly int major; private readonly int minor; private readonly int patch; private readonly string version; public SemanticVersion(int major, int minor, int patch) { this.major = major; this.minor = minor; this.patch = patch; version = $"{major}.{minor}.{patch}"; } public SemanticVersion(string? version) { try { if (string.IsNullOrEmpty(version)) { major = 0; minor = 0; patch = 0; return; } this.version = version.RemovePrefix('v'); var parts = this.version.Split('.'); if (parts.Length == 2) { major = int.Parse(parts.First()); minor = int.Parse(parts.Last()); patch = 0; } else if (parts.Length is 3 or 4) { major = int.Parse(parts[0]); minor = int.Parse(parts[1]); patch = int.Parse(parts[2]); } else { throw new ArgumentException("Invalid version string"); } } catch { major = 0; minor = 0; patch = 0; } } public override bool Equals(object? obj) { if (obj is SemanticVersion other) { return major == other.major && minor == other.minor && patch == other.patch; } else { return false; } } public override int GetHashCode() { return major.GetHashCode() ^ minor.GetHashCode() ^ patch.GetHashCode(); } /// /// Use ToVersionString(string? prefix) instead if possible. /// /// major.minor.patch public override string ToString() { return version; } public string ToVersionString(string? prefix = null) { if (prefix == null) { return version; } else { return $"{prefix}{version}"; } } public static bool operator ==(SemanticVersion v1, SemanticVersion v2) { return v1.Equals(v2); } public static bool operator !=(SemanticVersion v1, SemanticVersion v2) { return !v1.Equals(v2); } public static bool operator >=(SemanticVersion v1, SemanticVersion v2) { return v1.GreaterEquals(v2); } public static bool operator <=(SemanticVersion v1, SemanticVersion v2) { return v1.LessEquals(v2); } #region Private private bool GreaterEquals(SemanticVersion other) { if (major < other.major) { return false; } else if (major > other.major) { return true; } else { if (minor < other.minor) { return false; } else if (minor > other.minor) { return true; } else { if (patch < other.patch) { return false; } else if (patch > other.patch) { return true; } else { return true; } } } } private bool LessEquals(SemanticVersion other) { if (major < other.major) { return true; } else if (major > other.major) { return false; } else { if (minor < other.minor) { return true; } else if (minor > other.minor) { return false; } else { if (patch < other.patch) { return true; } else if (patch > other.patch) { return false; } else { return true; } } } } #endregion Private } ================================================ FILE: v2rayN/ServiceLib/Models/ServerSpeedItem.cs ================================================ namespace ServiceLib.Models; [Serializable] public class ServerSpeedItem : ServerStatItem { public long ProxyUp { get; set; } public long ProxyDown { get; set; } public long DirectUp { get; set; } public long DirectDown { get; set; } } [Serializable] public class TrafficItem { public ulong Up { get; set; } public ulong Down { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/ServerStatItem.cs ================================================ namespace ServiceLib.Models; [Serializable] public class ServerStatItem { [PrimaryKey] public string IndexId { get; set; } public long TotalUp { get; set; } public long TotalDown { get; set; } public long TodayUp { get; set; } public long TodayDown { get; set; } public long DateNow { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/ServerTestItem.cs ================================================ namespace ServiceLib.Models; [Serializable] public class ServerTestItem { public string? IndexId { get; set; } public string? Address { get; set; } public int Port { get; set; } public EConfigType ConfigType { get; set; } public bool AllowTest { get; set; } public int QueueNum { get; set; } public ProfileItem Profile { get; set; } public ECoreType CoreType { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/SingboxConfig.cs ================================================ namespace ServiceLib.Models; public class SingboxConfig { public Log4Sbox log { get; set; } public Dns4Sbox? dns { get; set; } public List inbounds { get; set; } public List outbounds { get; set; } public List? endpoints { get; set; } public Route4Sbox route { get; set; } public Experimental4Sbox? experimental { get; set; } } public class Log4Sbox { public bool? disabled { get; set; } public string level { get; set; } public string output { get; set; } public bool? timestamp { get; set; } } public class Dns4Sbox { public List servers { get; set; } public List rules { get; set; } public string? final { get; set; } public string? strategy { get; set; } public bool? disable_cache { get; set; } public bool? disable_expire { get; set; } public bool? independent_cache { get; set; } public int? cache_capacity { get; set; } public bool? reverse_mapping { get; set; } public string? client_subnet { get; set; } } public class Route4Sbox { public Rule4Sbox? default_domain_resolver { get; set; } // or string public bool? auto_detect_interface { get; set; } public List rules { get; set; } public List? rule_set { get; set; } public string? final { get; set; } } [Serializable] public class Rule4Sbox { public string? outbound { get; set; } public string? server { get; set; } public bool? disable_cache { get; set; } public string? type { get; set; } public string? mode { get; set; } public bool? ip_is_private { get; set; } public string? client_subnet { get; set; } public int? rewrite_ttl { get; set; } public bool? invert { get; set; } public string? clash_mode { get; set; } public List? inbound { get; set; } public List? protocol { get; set; } public List? network { get; set; } public List? port { get; set; } public List? port_range { get; set; } public List? geosite { get; set; } public List? domain { get; set; } public List? domain_suffix { get; set; } public List? domain_keyword { get; set; } public List? domain_regex { get; set; } public List? geoip { get; set; } public List? ip_cidr { get; set; } public List? source_ip_cidr { get; set; } public List? process_name { get; set; } public List? process_path { get; set; } public List? rule_set { get; set; } public List? rules { get; set; } public string? action { get; set; } public string? strategy { get; set; } public List? sniffer { get; set; } public string? rcode { get; set; } public List? query_type { get; set; } public List? answer { get; set; } public List? ns { get; set; } public List? extra { get; set; } public string? method { get; set; } public bool? no_drop { get; set; } public bool? source_ip_is_private { get; set; } public bool? ip_accept_any { get; set; } public int? source_port { get; set; } public List? source_port_range { get; set; } public List? network_type { get; set; } public bool? network_is_expensive { get; set; } public bool? network_is_constrained { get; set; } public List? wifi_ssid { get; set; } public List? wifi_bssid { get; set; } public bool? rule_set_ip_cidr_match_source { get; set; } public bool? rule_set_ip_cidr_accept_empty { get; set; } } [Serializable] public class Inbound4Sbox { public string type { get; set; } public string tag { get; set; } public string listen { get; set; } public int? listen_port { get; set; } public string interface_name { get; set; } public List? address { get; set; } public int? mtu { get; set; } public bool? auto_route { get; set; } public bool? strict_route { get; set; } public bool? endpoint_independent_nat { get; set; } public string? stack { get; set; } public List users { get; set; } } public class User4Sbox { public string username { get; set; } public string password { get; set; } } public class Outbound4Sbox : BaseServer4Sbox { public string? server { get; set; } public int? server_port { get; set; } public List? server_ports { get; set; } public string? uuid { get; set; } public string? security { get; set; } public int? alter_id { get; set; } public string? flow { get; set; } public string? hop_interval { get; set; } public int? up_mbps { get; set; } public int? down_mbps { get; set; } public string? auth_str { get; set; } public int? recv_window_conn { get; set; } public int? recv_window { get; set; } public bool? disable_mtu_discovery { get; set; } public string? method { get; set; } public string? username { get; set; } public string? password { get; set; } public string? congestion_control { get; set; } public string? version { get; set; } public string? network { get; set; } public string? packet_encoding { get; set; } public string? plugin { get; set; } public string? plugin_opts { get; set; } public List? outbounds { get; set; } public bool? interrupt_exist_connections { get; set; } public int? tolerance { get; set; } } public class Endpoints4Sbox : BaseServer4Sbox { public bool? system { get; set; } public string? name { get; set; } public int? mtu { get; set; } public List address { get; set; } public string private_key { get; set; } public int? listen_port { get; set; } public string? udp_timeout { get; set; } public int? workers { get; set; } public List peers { get; set; } } public class Peer4Sbox { public string address { get; set; } public int port { get; set; } public string public_key { get; set; } public string? pre_shared_key { get; set; } public List allowed_ips { get; set; } public int? persistent_keepalive_interval { get; set; } public List reserved { get; set; } } public class Tls4Sbox { public bool enabled { get; set; } public string? server_name { get; set; } public bool? insecure { get; set; } public List? alpn { get; set; } public Utls4Sbox? utls { get; set; } public Reality4Sbox? reality { get; set; } public bool? fragment { get; set; } public string? fragment_fallback_delay { get; set; } public bool? record_fragment { get; set; } public List? certificate { get; set; } public Ech4Sbox? ech { get; set; } } public class Ech4Sbox { public bool enabled { get; set; } public List? config { get; set; } public string? query_server_name { get; set; } } public class Multiplex4Sbox { public bool enabled { get; set; } public string protocol { get; set; } public int max_connections { get; set; } public bool? padding { get; set; } } public class Utls4Sbox { public bool enabled { get; set; } public string fingerprint { get; set; } } public class Reality4Sbox { public bool enabled { get; set; } public string public_key { get; set; } public string short_id { get; set; } } public class Transport4Sbox { public string? type { get; set; } public object? host { get; set; } public string? path { get; set; } public Headers4Sbox? headers { get; set; } public string? service_name { get; set; } public string? idle_timeout { get; set; } public string? ping_timeout { get; set; } public bool? permit_without_stream { get; set; } public int? max_early_data { get; set; } public string? early_data_header_name { get; set; } } public class Headers4Sbox { public string? Host { get; set; } } public class HyObfs4Sbox { public string? type { get; set; } public string? password { get; set; } } public class Server4Sbox : BaseServer4Sbox { public string? inet4_range { get; set; } public string? inet6_range { get; set; } public string? client_subnet { get; set; } public string? server { get; set; } public new string? domain_resolver { get; set; } [JsonPropertyName("interface")] public string? Interface { get; set; } public int? server_port { get; set; } public string? path { get; set; } public Headers4Sbox? headers { get; set; } // public List? path { get; set; } // hosts public Dictionary>? predefined { get; set; } // Deprecated in sing-box 1.12.0 , kept for backward compatibility public string? address { get; set; } public string? address_resolver { get; set; } public string? address_strategy { get; set; } public string? strategy { get; set; } // Deprecated End } public class Experimental4Sbox { public CacheFile4Sbox? cache_file { get; set; } public V2ray_Api4Sbox? v2ray_api { get; set; } public Clash_Api4Sbox? clash_api { get; set; } } public class V2ray_Api4Sbox { public string listen { get; set; } public Stats4Sbox stats { get; set; } } public class Clash_Api4Sbox { public string? external_controller { get; set; } public bool? store_selected { get; set; } } public class Stats4Sbox { public bool enabled { get; set; } public List? inbounds { get; set; } public List? outbounds { get; set; } public List? users { get; set; } } public class CacheFile4Sbox { public bool enabled { get; set; } public string? path { get; set; } public string? cache_id { get; set; } public bool? store_fakeip { get; set; } } public class Ruleset4Sbox { public string? tag { get; set; } public string? type { get; set; } public string? format { get; set; } public string? path { get; set; } public string? url { get; set; } public string? download_detour { get; set; } public string? update_interval { get; set; } } public abstract class DialFields4Sbox { public string? detour { get; set; } public string? bind_interface { get; set; } public string? inet4_bind_address { get; set; } public string? inet6_bind_address { get; set; } public int? routing_mark { get; set; } public bool? reuse_addr { get; set; } public string? netns { get; set; } public string? connect_timeout { get; set; } public bool? tcp_fast_open { get; set; } public bool? tcp_multi_path { get; set; } public bool? udp_fragment { get; set; } public Rule4Sbox? domain_resolver { get; set; } // or string public string? network_strategy { get; set; } public List? network_type { get; set; } public List? fallback_network_type { get; set; } public string? fallback_delay { get; set; } public Tls4Sbox? tls { get; set; } public Multiplex4Sbox? multiplex { get; set; } public Transport4Sbox? transport { get; set; } public HyObfs4Sbox? obfs { get; set; } } public abstract class BaseServer4Sbox : DialFields4Sbox { public string type { get; set; } public string tag { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/SpeedTestResult.cs ================================================ namespace ServiceLib.Models; [Serializable] public class SpeedTestResult { public string? IndexId { get; set; } public string? Delay { get; set; } public string? Speed { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/SsSIP008.cs ================================================ namespace ServiceLib.Models; public class SsSIP008 { public List? servers { get; set; } } [Serializable] public class SsServer { public string? remarks { get; set; } public string? server { get; set; } public string? server_port { get; set; } public string? method { get; set; } public string? password { get; set; } public string? plugin { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/SubItem.cs ================================================ namespace ServiceLib.Models; [Serializable] public class SubItem { [PrimaryKey] public string Id { get; set; } public string Remarks { get; set; } public string Url { get; set; } public string MoreUrl { get; set; } public bool Enabled { get; set; } = true; public string UserAgent { get; set; } = string.Empty; public int Sort { get; set; } public string? Filter { get; set; } public int AutoUpdateInterval { get; set; } public long UpdateTime { get; set; } public string? ConvertTarget { get; set; } public string? PrevProfile { get; set; } public string? NextProfile { get; set; } public int? PreSocksPort { get; set; } public string? Memo { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/UpdateResult.cs ================================================ namespace ServiceLib.Models; public class UpdateResult { public bool Success { get; set; } public string? Msg { get; set; } public SemanticVersion? Version { get; set; } public string? Url { get; set; } public UpdateResult(bool success, string? msg) { Success = success; Msg = msg; } public UpdateResult(bool success, SemanticVersion? version) { Success = success; Version = version; } } ================================================ FILE: v2rayN/ServiceLib/Models/V2rayConfig.cs ================================================ namespace ServiceLib.Models; public class V2rayConfig { public Log4Ray log { get; set; } public object dns { get; set; } public List inbounds { get; set; } public List outbounds { get; set; } public Routing4Ray routing { get; set; } public Metrics4Ray? metrics { get; set; } public Policy4Ray? policy { get; set; } public Stats4Ray? stats { get; set; } public Observatory4Ray? observatory { get; set; } public BurstObservatory4Ray? burstObservatory { get; set; } public string? remarks { get; set; } } public class Stats4Ray { } public class Metrics4Ray { public string tag { get; set; } } public class Policy4Ray { public SystemPolicy4Ray system { get; set; } } public class SystemPolicy4Ray { public bool statsOutboundUplink { get; set; } public bool statsOutboundDownlink { get; set; } } public class Log4Ray { public string? access { get; set; } public string? error { get; set; } public string? loglevel { get; set; } } public class Inbounds4Ray { public string tag { get; set; } public int port { get; set; } public string listen { get; set; } public string protocol { get; set; } public Sniffing4Ray sniffing { get; set; } public Inboundsettings4Ray settings { get; set; } } public class Inboundsettings4Ray { public string? auth { get; set; } public bool? udp { get; set; } public string? ip { get; set; } public string? address { get; set; } public List? clients { get; set; } public string? decryption { get; set; } public bool? allowTransparent { get; set; } public List? accounts { get; set; } } public class UsersItem4Ray { public string? id { get; set; } public int? alterId { get; set; } public string? email { get; set; } public string? security { get; set; } public string? encryption { get; set; } public string? flow { get; set; } } public class Sniffing4Ray { public bool enabled { get; set; } public List? destOverride { get; set; } public bool routeOnly { get; set; } } public class Outbounds4Ray { public string tag { get; set; } public string protocol { get; set; } public string? targetStrategy { get; set; } public Outboundsettings4Ray settings { get; set; } public StreamSettings4Ray streamSettings { get; set; } public Mux4Ray mux { get; set; } } public class Outboundsettings4Ray { public List? vnext { get; set; } public List? servers { get; set; } public Response4Ray? response { get; set; } public string domainStrategy { get; set; } public int? userLevel { get; set; } public FragmentItem4Ray? fragment { get; set; } public string? secretKey { get; set; } public Object? address { get; set; } public int? port { get; set; } public List? peers { get; set; } public bool? noKernelTun { get; set; } public int? mtu { get; set; } public List? reserved { get; set; } public int? workers { get; set; } public int? version { get; set; } } public class WireguardPeer4Ray { public string endpoint { get; set; } public string publicKey { get; set; } } public class VnextItem4Ray { public string address { get; set; } public int port { get; set; } public List users { get; set; } } public class ServersItem4Ray { public string email { get; set; } public string address { get; set; } public string? method { get; set; } public bool? ota { get; set; } public string? password { get; set; } public int port { get; set; } public int? level { get; set; } public string flow { get; set; } public List users { get; set; } } public class SocksUsersItem4Ray { public string user { get; set; } public string pass { get; set; } public int? level { get; set; } } public class Mux4Ray { public bool enabled { get; set; } public int? concurrency { get; set; } public int? xudpConcurrency { get; set; } public string? xudpProxyUDP443 { get; set; } } public class Response4Ray { public string type { get; set; } } public class Dns4Ray { public Dictionary? hosts { get; set; } public List servers { get; set; } public bool? serveStale { get; set; } public bool? enableParallelQuery { get; set; } public string? tag { get; set; } } public class DnsServer4Ray { public string? address { get; set; } public int? port { get; set; } public List? domains { get; set; } public bool? skipFallback { get; set; } public List? expectedIPs { get; set; } public string? tag { get; set; } } public class Routing4Ray { public string domainStrategy { get; set; } public List rules { get; set; } public List? balancers { get; set; } } [Serializable] public class RulesItem4Ray { public string? type { get; set; } public string? port { get; set; } public string? network { get; set; } public List? inboundTag { get; set; } public string? outboundTag { get; set; } public string? balancerTag { get; set; } public List? ip { get; set; } public List? domain { get; set; } public List? protocol { get; set; } public List? process { get; set; } } public class BalancersItem4Ray { public List? selector { get; set; } public BalancersStrategy4Ray? strategy { get; set; } public string? tag { get; set; } } public class BalancersStrategy4Ray { public string? type { get; set; } public BalancersStrategySettings4Ray? settings { get; set; } } public class BalancersStrategySettings4Ray { public int? expected { get; set; } public string? maxRTT { get; set; } public float? tolerance { get; set; } public List? baselines { get; set; } public List? costs { get; set; } } public class BalancersStrategySettingsCosts4Ray { public bool? regexp { get; set; } public string? match { get; set; } public float? value { get; set; } } public class Observatory4Ray { public List? subjectSelector { get; set; } public string? probeUrl { get; set; } public string? probeInterval { get; set; } public bool? enableConcurrency { get; set; } } public class BurstObservatory4Ray { public List? subjectSelector { get; set; } public BurstObservatoryPingConfig4Ray? pingConfig { get; set; } } public class BurstObservatoryPingConfig4Ray { public string? destination { get; set; } public string? connectivity { get; set; } public string? interval { get; set; } public int? sampling { get; set; } public string? timeout { get; set; } } public class StreamSettings4Ray { public string network { get; set; } public string security { get; set; } public TlsSettings4Ray? tlsSettings { get; set; } public TcpSettings4Ray? tcpSettings { get; set; } public KcpSettings4Ray? kcpSettings { get; set; } public WsSettings4Ray? wsSettings { get; set; } public HttpupgradeSettings4Ray? httpupgradeSettings { get; set; } public XhttpSettings4Ray? xhttpSettings { get; set; } public HttpSettings4Ray? httpSettings { get; set; } public QuicSettings4Ray? quicSettings { get; set; } public TlsSettings4Ray? realitySettings { get; set; } public GrpcSettings4Ray? grpcSettings { get; set; } public HysteriaSettings4Ray? hysteriaSettings { get; set; } public Finalmask4Ray? finalmask { get; set; } public Sockopt4Ray? sockopt { get; set; } } public class TlsSettings4Ray { public bool? allowInsecure { get; set; } public string? serverName { get; set; } public List? alpn { get; set; } public string? fingerprint { get; set; } public bool? show { get; set; } public string? publicKey { get; set; } public string? shortId { get; set; } public string? spiderX { get; set; } public string? mldsa65Verify { get; set; } public List? certificates { get; set; } public string? pinnedPeerCertSha256 { get; set; } public bool? disableSystemRoot { get; set; } public string? echConfigList { get; set; } public string? echForceQuery { get; set; } public Sockopt4Ray? echSockopt { get; set; } } public class CertificateSettings4Ray { public List? certificate { get; set; } public string? usage { get; set; } } public class TcpSettings4Ray { public Header4Ray header { get; set; } } public class Header4Ray { public string type { get; set; } public object request { get; set; } public object response { get; set; } } public class KcpSettings4Ray { public int mtu { get; set; } public int tti { get; set; } public int uplinkCapacity { get; set; } public int downlinkCapacity { get; set; } public bool congestion { get; set; } public int readBufferSize { get; set; } public int writeBufferSize { get; set; } } public class WsSettings4Ray { public string? path { get; set; } public string? host { get; set; } public Headers4Ray headers { get; set; } } public class Headers4Ray { [JsonPropertyName("User-Agent")] public string UserAgent { get; set; } } public class HttpupgradeSettings4Ray { public string? path { get; set; } public string? host { get; set; } } public class XhttpSettings4Ray { public string? path { get; set; } public string? host { get; set; } public string? mode { get; set; } public object? extra { get; set; } } public class HttpSettings4Ray { public string? path { get; set; } public List? host { get; set; } } public class QuicSettings4Ray { public string security { get; set; } public string key { get; set; } public Header4Ray header { get; set; } } public class GrpcSettings4Ray { public string? authority { get; set; } public string? serviceName { get; set; } public bool multiMode { get; set; } public int? idle_timeout { get; set; } public int? health_check_timeout { get; set; } public bool? permit_without_stream { get; set; } public int? initial_windows_size { get; set; } } public class HysteriaSettings4Ray { public int version { get; set; } public string? auth { get; set; } public string? up { get; set; } public string? down { get; set; } public HysteriaUdpHop4Ray? udphop { get; set; } } public class HysteriaUdpHop4Ray { public string? port { get; set; } public string? interval { get; set; } } public class Finalmask4Ray { public List? tcp { get; set; } public List? udp { get; set; } } public class Mask4Ray { public string type { get; set; } public object? settings { get; set; } } public class MaskSettings4Ray { public string? password { get; set; } public string? domain { get; set; } } public class AccountsItem4Ray { public string user { get; set; } public string pass { get; set; } } public class Sockopt4Ray { public string? dialerProxy { get; set; } } public class FragmentItem4Ray { public string? packets { get; set; } public string? length { get; set; } public string? interval { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/V2rayMetricsVars.cs ================================================ using System.Collections; namespace ServiceLib.Models; internal class V2rayMetricsVars { public V2rayMetricsVarsStats? stats { get; set; } } public class V2rayMetricsVarsStats { public Hashtable? outbound { get; set; } } public class V2rayMetricsVarsLink { public long downlink { get; set; } public long uplink { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/V2rayTcpRequest.cs ================================================ namespace ServiceLib.Models; public class V2rayTcpRequest { /// /// /// public RequestHeaders headers { get; set; } } public class RequestHeaders { /// /// /// public List Host { get; set; } } ================================================ FILE: v2rayN/ServiceLib/Models/VmessQRCode.cs ================================================ namespace ServiceLib.Models; /// /// https://github.com/2dust/v2rayN/wiki/ /// [Serializable] public class VmessQRCode { [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] public int v { get; set; } = 2; public string ps { get; set; } = string.Empty; public string add { get; set; } = string.Empty; [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] public int port { get; set; } = 0; public string id { get; set; } = string.Empty; [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] public int aid { get; set; } = 0; public string scy { get; set; } = string.Empty; public string net { get; set; } = string.Empty; public string type { get; set; } = string.Empty; public string host { get; set; } = string.Empty; public string path { get; set; } = string.Empty; public string tls { get; set; } = string.Empty; public string sni { get; set; } = string.Empty; public string alpn { get; set; } = string.Empty; public string fp { get; set; } = string.Empty; public string insecure { get; set; } = string.Empty; } ================================================ FILE: v2rayN/ServiceLib/Resx/ResUI.Designer.cs ================================================ //------------------------------------------------------------------------------ // // 此代码由工具生成。 // 运行时版本:4.0.30319.42000 // // 对此文件的更改可能会导致不正确的行为,并且如果 // 重新生成代码,这些更改将会丢失。 // //------------------------------------------------------------------------------ namespace ServiceLib.Resx { using System; /// /// 一个强类型的资源类,用于查找本地化的字符串等。 /// // 此类是由 StronglyTypedResourceBuilder // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen // (以 /str 作为命令选项),或重新生成 VS 项目。 [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class ResUI { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal ResUI() { } /// /// 返回此类使用的缓存的 ResourceManager 实例。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ServiceLib.Resx.ResUI", typeof(ResUI).Assembly); resourceMan = temp; } return resourceMan; } } /// /// 重写当前线程的 CurrentUICulture 属性,对 /// 使用此强类型资源类的所有资源查找执行重写。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// 查找类似 Do you want to append rules? Choose yes to append, no to replace. 的本地化字符串。 /// public static string AddBatchRoutingRulesYesNo { get { return ResourceManager.GetString("AddBatchRoutingRulesYesNo", resourceCulture); } } /// /// 查找类似 All 的本地化字符串。 /// public static string AllGroupServers { get { return ResourceManager.GetString("AllGroupServers", resourceCulture); } } /// /// 查找类似 Export share link to clipboard successfully 的本地化字符串。 /// public static string BatchExportURLSuccessfully { get { return ResourceManager.GetString("BatchExportURLSuccessfully", resourceCulture); } } /// /// 查找类似 Certificate not set 的本地化字符串。 /// public static string CertNotSet { get { return ResourceManager.GetString("CertNotSet", resourceCulture); } } /// /// 查找类似 Certificate set 的本地化字符串。 /// public static string CertSet { get { return ResourceManager.GetString("CertSet", resourceCulture); } } /// /// 查找类似 Please check the Configuration settings first. 的本地化字符串。 /// public static string CheckServerSettings { get { return ResourceManager.GetString("CheckServerSettings", resourceCulture); } } /// /// 查找类似 Invalid configuration format. 的本地化字符串。 /// public static string ConfigurationFormatIncorrect { get { return ResourceManager.GetString("ConfigurationFormatIncorrect", resourceCulture); } } /// /// 查找类似 Host filter 的本地化字符串。 /// public static string ConnectionsHostFilterTitle { get { return ResourceManager.GetString("ConnectionsHostFilterTitle", resourceCulture); } } /// /// 查找类似 Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. 的本地化字符串。 /// public static string CustomServerTips { get { return ResourceManager.GetString("CustomServerTips", resourceCulture); } } /// /// 查找类似 Downloading... 的本地化字符串。 /// public static string Downloading { get { return ResourceManager.GetString("Downloading", resourceCulture); } } /// /// 查找类似 Failed to convert configuration file 的本地化字符串。 /// public static string FailedConversionConfiguration { get { return ResourceManager.GetString("FailedConversionConfiguration", resourceCulture); } } /// /// 查找类似 Failed to generate default configuration file 的本地化字符串。 /// public static string FailedGenDefaultConfiguration { get { return ResourceManager.GetString("FailedGenDefaultConfiguration", resourceCulture); } } /// /// 查找类似 Failed to get the default configuration 的本地化字符串。 /// public static string FailedGetDefaultConfiguration { get { return ResourceManager.GetString("FailedGetDefaultConfiguration", resourceCulture); } } /// /// 查找类似 Failed to import custom configuration Configuration 的本地化字符串。 /// public static string FailedImportedCustomServer { get { return ResourceManager.GetString("FailedImportedCustomServer", resourceCulture); } } /// /// 查找类似 Failed to read configuration file 的本地化字符串。 /// public static string FailedReadConfiguration { get { return ResourceManager.GetString("FailedReadConfiguration", resourceCulture); } } /// /// 查找类似 Failed to run Core, please check the prompt information 的本地化字符串。 /// public static string FailedToRunCore { get { return ResourceManager.GetString("FailedToRunCore", resourceCulture); } } /// /// 查找类似 Please fill in the correct config template 的本地化字符串。 /// public static string FillCorrectConfigTemplateText { get { return ResourceManager.GetString("FillCorrectConfigTemplateText", resourceCulture); } } /// /// 查找类似 Please fill in the correct custom DNS 的本地化字符串。 /// public static string FillCorrectDNSText { get { return ResourceManager.GetString("FillCorrectDNSText", resourceCulture); } } /// /// 查找类似 Please enter the correct port format. 的本地化字符串。 /// public static string FillCorrectServerPort { get { return ResourceManager.GetString("FillCorrectServerPort", resourceCulture); } } /// /// 查找类似 Please enter the local listening port. 的本地化字符串。 /// public static string FillLocalListeningPort { get { return ResourceManager.GetString("FillLocalListeningPort", resourceCulture); } } /// /// 查找类似 Please enter the password. 的本地化字符串。 /// public static string FillPassword { get { return ResourceManager.GetString("FillPassword", resourceCulture); } } /// /// 查找类似 Please enter the address. 的本地化字符串。 /// public static string FillServerAddress { get { return ResourceManager.GetString("FillServerAddress", resourceCulture); } } /// /// 查找类似 Please browse to import Configuration configuration 的本地化字符串。 /// public static string FillServerAddressCustom { get { return ResourceManager.GetString("FillServerAddressCustom", resourceCulture); } } /// /// 查找类似 Please enter the user ID. 的本地化字符串。 /// public static string FillUUID { get { return ResourceManager.GetString("FillUUID", resourceCulture); } } /// /// 查找类似 Transport 的本地化字符串。 /// public static string GbTransport { get { return ResourceManager.GetString("GbTransport", resourceCulture); } } /// /// 查找类似 This is not the correct configuration, please check 的本地化字符串。 /// public static string Incorrectconfiguration { get { return ResourceManager.GetString("Incorrectconfiguration", resourceCulture); } } /// /// 查找类似 Initial Configuration 的本地化字符串。 /// public static string InitialConfiguration { get { return ResourceManager.GetString("InitialConfiguration", resourceCulture); } } /// /// 查找类似 Please do not use the insecure HTTP protocol subscription address 的本地化字符串。 /// public static string InsecureUrlProtocol { get { return ResourceManager.GetString("InsecureUrlProtocol", resourceCulture); } } /// /// 查找类似 Invalid address (URL) 的本地化字符串。 /// public static string InvalidUrlTip { get { return ResourceManager.GetString("InvalidUrlTip", resourceCulture); } } /// /// 查找类似 {0} {1} already up to date. 的本地化字符串。 /// public static string IsLatestCore { get { return ResourceManager.GetString("IsLatestCore", resourceCulture); } } /// /// 查找类似 {0} {1} already up to date. 的本地化字符串。 /// public static string IsLatestN { get { return ResourceManager.GetString("IsLatestN", resourceCulture); } } /// /// 查找类似 LAN 的本地化字符串。 /// public static string LabLAN { get { return ResourceManager.GetString("LabLAN", resourceCulture); } } /// /// 查找类似 Local 的本地化字符串。 /// public static string LabLocal { get { return ResourceManager.GetString("LabLocal", resourceCulture); } } /// /// 查找类似 Invalid backup file 的本地化字符串。 /// public static string LocalRestoreInvalidZipTips { get { return ResourceManager.GetString("LocalRestoreInvalidZipTips", resourceCulture); } } /// /// 查找类似 Address 的本地化字符串。 /// public static string LvAddress { get { return ResourceManager.GetString("LvAddress", resourceCulture); } } /// /// 查找类似 Automatic update interval (minutes) 的本地化字符串。 /// public static string LvAutoUpdateInterval { get { return ResourceManager.GetString("LvAutoUpdateInterval", resourceCulture); } } /// /// 查找类似 Convert target type 的本地化字符串。 /// public static string LvConvertTarget { get { return ResourceManager.GetString("LvConvertTarget", resourceCulture); } } /// /// 查找类似 Please leave blank if no conversion is required 的本地化字符串。 /// public static string LvConvertTargetTip { get { return ResourceManager.GetString("LvConvertTargetTip", resourceCulture); } } /// /// 查找类似 Count 的本地化字符串。 /// public static string LvCount { get { return ResourceManager.GetString("LvCount", resourceCulture); } } /// /// 查找类似 Custom icon 的本地化字符串。 /// public static string LvCustomIcon { get { return ResourceManager.GetString("LvCustomIcon", resourceCulture); } } /// /// 查找类似 Customize the rule-set of sing-box 的本地化字符串。 /// public static string LvCustomRulesetPath4Singbox { get { return ResourceManager.GetString("LvCustomRulesetPath4Singbox", resourceCulture); } } /// /// 查找类似 Enable update 的本地化字符串。 /// public static string LvEnabled { get { return ResourceManager.GetString("LvEnabled", resourceCulture); } } /// /// 查找类似 Security 的本地化字符串。 /// public static string LvEncryptionMethod { get { return ResourceManager.GetString("LvEncryptionMethod", resourceCulture); } } /// /// 查找类似 Remarks regular filter 的本地化字符串。 /// public static string LvFilter { get { return ResourceManager.GetString("LvFilter", resourceCulture); } } /// /// 查找类似 Remarks Memo 的本地化字符串。 /// public static string LvMemo { get { return ResourceManager.GetString("LvMemo", resourceCulture); } } /// /// 查找类似 More URLs, separated by commas; Subscription conversion will be invalid 的本地化字符串。 /// public static string LvMoreUrl { get { return ResourceManager.GetString("LvMoreUrl", resourceCulture); } } /// /// 查找类似 Next proxy remarks 的本地化字符串。 /// public static string LvNextProfile { get { return ResourceManager.GetString("LvNextProfile", resourceCulture); } } /// /// 查找类似 Port 的本地化字符串。 /// public static string LvPort { get { return ResourceManager.GetString("LvPort", resourceCulture); } } /// /// 查找类似 Previous proxy remarks 的本地化字符串。 /// public static string LvPrevProfile { get { return ResourceManager.GetString("LvPrevProfile", resourceCulture); } } /// /// 查找类似 Please make sure the Configuration remarks exist and are unique 的本地化字符串。 /// public static string LvPrevProfileTip { get { return ResourceManager.GetString("LvPrevProfileTip", resourceCulture); } } /// /// 查找类似 Remarks 的本地化字符串。 /// public static string LvRemarks { get { return ResourceManager.GetString("LvRemarks", resourceCulture); } } /// /// 查找类似 Type 的本地化字符串。 /// public static string LvServiceType { get { return ResourceManager.GetString("LvServiceType", resourceCulture); } } /// /// 查找类似 Sort 的本地化字符串。 /// public static string LvSort { get { return ResourceManager.GetString("LvSort", resourceCulture); } } /// /// 查找类似 Subs group 的本地化字符串。 /// public static string LvSubscription { get { return ResourceManager.GetString("LvSubscription", resourceCulture); } } /// /// 查找类似 Delay (ms) 的本地化字符串。 /// public static string LvTestDelay { get { return ResourceManager.GetString("LvTestDelay", resourceCulture); } } /// /// 查找类似 Speed (MB/s) 的本地化字符串。 /// public static string LvTestSpeed { get { return ResourceManager.GetString("LvTestSpeed", resourceCulture); } } /// /// 查找类似 TLS 的本地化字符串。 /// public static string LvTLS { get { return ResourceManager.GetString("LvTLS", resourceCulture); } } /// /// 查找类似 Download traffic today 的本地化字符串。 /// public static string LvTodayDownloadDataAmount { get { return ResourceManager.GetString("LvTodayDownloadDataAmount", resourceCulture); } } /// /// 查找类似 Upload traffic today 的本地化字符串。 /// public static string LvTodayUploadDataAmount { get { return ResourceManager.GetString("LvTodayUploadDataAmount", resourceCulture); } } /// /// 查找类似 Total download traffic 的本地化字符串。 /// public static string LvTotalDownloadDataAmount { get { return ResourceManager.GetString("LvTotalDownloadDataAmount", resourceCulture); } } /// /// 查找类似 Total upload traffic 的本地化字符串。 /// public static string LvTotalUploadDataAmount { get { return ResourceManager.GetString("LvTotalUploadDataAmount", resourceCulture); } } /// /// 查找类似 Transport 的本地化字符串。 /// public static string LvTransportProtocol { get { return ResourceManager.GetString("LvTransportProtocol", resourceCulture); } } /// /// 查找类似 URL (optional) 的本地化字符串。 /// public static string LvUrl { get { return ResourceManager.GetString("LvUrl", resourceCulture); } } /// /// 查找类似 User Agent 的本地化字符串。 /// public static string LvUserAgent { get { return ResourceManager.GetString("LvUserAgent", resourceCulture); } } /// /// 查找类似 WebDAV Check 的本地化字符串。 /// public static string LvWebDavCheck { get { return ResourceManager.GetString("LvWebDavCheck", resourceCulture); } } /// /// 查找类似 Remote folder name (optional) 的本地化字符串。 /// public static string LvWebDavDirName { get { return ResourceManager.GetString("LvWebDavDirName", resourceCulture); } } /// /// 查找类似 WebDAV Password 的本地化字符串。 /// public static string LvWebDavPassword { get { return ResourceManager.GetString("LvWebDavPassword", resourceCulture); } } /// /// 查找类似 WebDAV URL 的本地化字符串。 /// public static string LvWebDavUrl { get { return ResourceManager.GetString("LvWebDavUrl", resourceCulture); } } /// /// 查找类似 WebDAV User Name 的本地化字符串。 /// public static string LvWebDavUserName { get { return ResourceManager.GetString("LvWebDavUserName", resourceCulture); } } /// /// 查找类似 Add [Anytls] 的本地化字符串。 /// public static string menuAddAnytlsServer { get { return ResourceManager.GetString("menuAddAnytlsServer", resourceCulture); } } /// /// 查找类似 Add Child 的本地化字符串。 /// public static string menuAddChildServer { get { return ResourceManager.GetString("menuAddChildServer", resourceCulture); } } /// /// 查找类似 Add a custom configuration 的本地化字符串。 /// public static string menuAddCustomServer { get { return ResourceManager.GetString("menuAddCustomServer", resourceCulture); } } /// /// 查找类似 Add [HTTP] 的本地化字符串。 /// public static string menuAddHttpServer { get { return ResourceManager.GetString("menuAddHttpServer", resourceCulture); } } /// /// 查找类似 Add [Hysteria2] 的本地化字符串。 /// public static string menuAddHysteria2Server { get { return ResourceManager.GetString("menuAddHysteria2Server", resourceCulture); } } /// /// 查找类似 Add Policy Group 的本地化字符串。 /// public static string menuAddPolicyGroupServer { get { return ResourceManager.GetString("menuAddPolicyGroupServer", resourceCulture); } } /// /// 查找类似 Add Proxy Chain 的本地化字符串。 /// public static string menuAddProxyChainServer { get { return ResourceManager.GetString("menuAddProxyChainServer", resourceCulture); } } /// /// 查找类似 Import Share Links from clipboard 的本地化字符串。 /// public static string menuAddServerViaClipboard { get { return ResourceManager.GetString("menuAddServerViaClipboard", resourceCulture); } } /// /// 查找类似 Scan QR code in the image 的本地化字符串。 /// public static string menuAddServerViaImage { get { return ResourceManager.GetString("menuAddServerViaImage", resourceCulture); } } /// /// 查找类似 Scan QR code on the screen 的本地化字符串。 /// public static string menuAddServerViaScan { get { return ResourceManager.GetString("menuAddServerViaScan", resourceCulture); } } /// /// 查找类似 Add [Shadowsocks] 的本地化字符串。 /// public static string menuAddShadowsocksServer { get { return ResourceManager.GetString("menuAddShadowsocksServer", resourceCulture); } } /// /// 查找类似 Add [SOCKS] 的本地化字符串。 /// public static string menuAddSocksServer { get { return ResourceManager.GetString("menuAddSocksServer", resourceCulture); } } /// /// 查找类似 Add [Trojan] 的本地化字符串。 /// public static string menuAddTrojanServer { get { return ResourceManager.GetString("menuAddTrojanServer", resourceCulture); } } /// /// 查找类似 Add [TUIC] 的本地化字符串。 /// public static string menuAddTuicServer { get { return ResourceManager.GetString("menuAddTuicServer", resourceCulture); } } /// /// 查找类似 Add [VLESS] 的本地化字符串。 /// public static string menuAddVlessServer { get { return ResourceManager.GetString("menuAddVlessServer", resourceCulture); } } /// /// 查找类似 Add [VMess] 的本地化字符串。 /// public static string menuAddVmessServer { get { return ResourceManager.GetString("menuAddVmessServer", resourceCulture); } } /// /// 查找类似 Add [WireGuard] 的本地化字符串。 /// public static string menuAddWireguardServer { get { return ResourceManager.GetString("menuAddWireguardServer", resourceCulture); } } /// /// 查找类似 All configurations 的本地化字符串。 /// public static string menuAllServers { get { return ResourceManager.GetString("menuAllServers", resourceCulture); } } /// /// 查找类似 Backup and Restore 的本地化字符串。 /// public static string menuBackupAndRestore { get { return ResourceManager.GetString("menuBackupAndRestore", resourceCulture); } } /// /// 查找类似 Check Update 的本地化字符串。 /// public static string menuCheckUpdate { get { return ResourceManager.GetString("menuCheckUpdate", resourceCulture); } } /// /// 查找类似 Clear all service statistics 的本地化字符串。 /// public static string menuClearServerStatistics { get { return ResourceManager.GetString("menuClearServerStatistics", resourceCulture); } } /// /// 查找类似 Close 的本地化字符串。 /// public static string menuClose { get { return ResourceManager.GetString("menuClose", resourceCulture); } } /// /// 查找类似 Close Connection 的本地化字符串。 /// public static string menuConnectionClose { get { return ResourceManager.GetString("menuConnectionClose", resourceCulture); } } /// /// 查找类似 Close All Connections 的本地化字符串。 /// public static string menuConnectionCloseAll { get { return ResourceManager.GetString("menuConnectionCloseAll", resourceCulture); } } /// /// 查找类似 Copy proxy command to clipboard 的本地化字符串。 /// public static string menuCopyProxyCmdToClipboard { get { return ResourceManager.GetString("menuCopyProxyCmdToClipboard", resourceCulture); } } /// /// 查找类似 Clone selected 的本地化字符串。 /// public static string menuCopyServer { get { return ResourceManager.GetString("menuCopyServer", resourceCulture); } } /// /// 查找类似 DNS Settings 的本地化字符串。 /// public static string menuDNSSetting { get { return ResourceManager.GetString("menuDNSSetting", resourceCulture); } } /// /// 查找类似 Edit 的本地化字符串。 /// public static string menuEditServer { get { return ResourceManager.GetString("menuEditServer", resourceCulture); } } /// /// 查找类似 Exit 的本地化字符串。 /// public static string menuExit { get { return ResourceManager.GetString("menuExit", resourceCulture); } } /// /// 查找类似 Are you sure you want to exit? 的本地化字符串。 /// public static string menuExitTips { get { return ResourceManager.GetString("menuExitTips", resourceCulture); } } /// /// 查找类似 Export selected for complete configuration 的本地化字符串。 /// public static string menuExport2ClientConfig { get { return ResourceManager.GetString("menuExport2ClientConfig", resourceCulture); } } /// /// 查找类似 Export selected for complete configuration to clipboard 的本地化字符串。 /// public static string menuExport2ClientConfigClipboard { get { return ResourceManager.GetString("menuExport2ClientConfigClipboard", resourceCulture); } } /// /// 查找类似 Export Share Link to Clipboard 的本地化字符串。 /// public static string menuExport2ShareUrl { get { return ResourceManager.GetString("menuExport2ShareUrl", resourceCulture); } } /// /// 查找类似 Export Base64-encoded Share Links to Clipboard 的本地化字符串。 /// public static string menuExport2ShareUrlBase64 { get { return ResourceManager.GetString("menuExport2ShareUrlBase64", resourceCulture); } } /// /// 查找类似 Export 的本地化字符串。 /// public static string menuExportConfig { get { return ResourceManager.GetString("menuExportConfig", resourceCulture); } } /// /// 查找类似 Test real delay 的本地化字符串。 /// public static string menuFastRealPing { get { return ResourceManager.GetString("menuFastRealPing", resourceCulture); } } /// /// 查找类似 Full Config Template Setting 的本地化字符串。 /// public static string menuFullConfigTemplate { get { return ResourceManager.GetString("menuFullConfigTemplate", resourceCulture); } } /// /// 查找类似 Generate Policy Group 的本地化字符串。 /// public static string menuGenGroupServer { get { return ResourceManager.GetString("menuGenGroupServer", resourceCulture); } } /// /// 查找类似 Group by Region 的本地化字符串。 /// public static string menuGenRegionGroup { get { return ResourceManager.GetString("menuGenRegionGroup", resourceCulture); } } /// /// 查找类似 Global Hotkey Setting 的本地化字符串。 /// public static string menuGlobalHotkeySetting { get { return ResourceManager.GetString("menuGlobalHotkeySetting", resourceCulture); } } /// /// 查找类似 Help 的本地化字符串。 /// public static string menuHelp { get { return ResourceManager.GetString("menuHelp", resourceCulture); } } /// /// 查找类似 Import Rules From Clipboard 的本地化字符串。 /// public static string menuImportRulesFromClipboard { get { return ResourceManager.GetString("menuImportRulesFromClipboard", resourceCulture); } } /// /// 查找类似 Import Rules From File 的本地化字符串。 /// public static string menuImportRulesFromFile { get { return ResourceManager.GetString("menuImportRulesFromFile", resourceCulture); } } /// /// 查找类似 Import Rules From Subscription URL 的本地化字符串。 /// public static string menuImportRulesFromUrl { get { return ResourceManager.GetString("menuImportRulesFromUrl", resourceCulture); } } /// /// 查找类似 Backup to local 的本地化字符串。 /// public static string menuLocalBackup { get { return ResourceManager.GetString("menuLocalBackup", resourceCulture); } } /// /// 查找类似 Local 的本地化字符串。 /// public static string menuLocalBackupAndRestore { get { return ResourceManager.GetString("menuLocalBackupAndRestore", resourceCulture); } } /// /// 查找类似 Restore from local 的本地化字符串。 /// public static string menuLocalRestore { get { return ResourceManager.GetString("menuLocalRestore", resourceCulture); } } /// /// 查找类似 One-click multi-test latency and speed (Ctrl+E) 的本地化字符串。 /// public static string menuMixedTestServer { get { return ResourceManager.GetString("menuMixedTestServer", resourceCulture); } } /// /// 查找类似 Direct 的本地化字符串。 /// public static string menuModeDirect { get { return ResourceManager.GetString("menuModeDirect", resourceCulture); } } /// /// 查找类似 Global 的本地化字符串。 /// public static string menuModeGlobal { get { return ResourceManager.GetString("menuModeGlobal", resourceCulture); } } /// /// 查找类似 Do not change 的本地化字符串。 /// public static string menuModeNothing { get { return ResourceManager.GetString("menuModeNothing", resourceCulture); } } /// /// 查找类似 Rule 的本地化字符串。 /// public static string menuModeRule { get { return ResourceManager.GetString("menuModeRule", resourceCulture); } } /// /// 查找类似 Move to bottom 的本地化字符串。 /// public static string menuMoveBottom { get { return ResourceManager.GetString("menuMoveBottom", resourceCulture); } } /// /// 查找类似 Down 的本地化字符串。 /// public static string menuMoveDown { get { return ResourceManager.GetString("menuMoveDown", resourceCulture); } } /// /// 查找类似 Move up and down 的本地化字符串。 /// public static string menuMoveTo { get { return ResourceManager.GetString("menuMoveTo", resourceCulture); } } /// /// 查找类似 Move to group 的本地化字符串。 /// public static string menuMoveToGroup { get { return ResourceManager.GetString("menuMoveToGroup", resourceCulture); } } /// /// 查找类似 Move to top 的本地化字符串。 /// public static string menuMoveTop { get { return ResourceManager.GetString("menuMoveTop", resourceCulture); } } /// /// 查找类似 Up 的本地化字符串。 /// public static string menuMoveUp { get { return ResourceManager.GetString("menuMoveUp", resourceCulture); } } /// /// 查找类似 Clear all 的本地化字符串。 /// public static string menuMsgViewClear { get { return ResourceManager.GetString("menuMsgViewClear", resourceCulture); } } /// /// 查找类似 Copy 的本地化字符串。 /// public static string menuMsgViewCopy { get { return ResourceManager.GetString("menuMsgViewCopy", resourceCulture); } } /// /// 查找类似 Copy all 的本地化字符串。 /// public static string menuMsgViewCopyAll { get { return ResourceManager.GetString("menuMsgViewCopyAll", resourceCulture); } } /// /// 查找类似 Select all 的本地化字符串。 /// public static string menuMsgViewSelectAll { get { return ResourceManager.GetString("menuMsgViewSelectAll", resourceCulture); } } /// /// 查找类似 Open the storage location 的本地化字符串。 /// public static string menuOpenTheFileLocation { get { return ResourceManager.GetString("menuOpenTheFileLocation", resourceCulture); } } /// /// 查找类似 Option Setting 的本地化字符串。 /// public static string menuOptionSetting { get { return ResourceManager.GetString("menuOptionSetting", resourceCulture); } } /// /// 查找类似 Auto column width adjustment 的本地化字符串。 /// public static string menuProfileAutofitColumnWidth { get { return ResourceManager.GetString("menuProfileAutofitColumnWidth", resourceCulture); } } /// /// 查找类似 Promotion 的本地化字符串。 /// public static string menuPromotion { get { return ResourceManager.GetString("menuPromotion", resourceCulture); } } /// /// 查找类似 Latency Test 的本地化字符串。 /// public static string menuProxiesDelaytest { get { return ResourceManager.GetString("menuProxiesDelaytest", resourceCulture); } } /// /// 查找类似 Part Node Latency Test 的本地化字符串。 /// public static string menuProxiesDelaytestPart { get { return ResourceManager.GetString("menuProxiesDelaytestPart", resourceCulture); } } /// /// 查找类似 Refresh Proxies 的本地化字符串。 /// public static string menuProxiesReload { get { return ResourceManager.GetString("menuProxiesReload", resourceCulture); } } /// /// 查找类似 Select active node 的本地化字符串。 /// public static string menuProxiesSelectActivity { get { return ResourceManager.GetString("menuProxiesSelectActivity", resourceCulture); } } /// /// 查找类似 Test real delay 的本地化字符串。 /// public static string menuRealPingServer { get { return ResourceManager.GetString("menuRealPingServer", resourceCulture); } } /// /// 查找类似 Restart as Administrator 的本地化字符串。 /// public static string menuRebootAsAdmin { get { return ResourceManager.GetString("menuRebootAsAdmin", resourceCulture); } } /// /// 查找类似 Regional presets setting 的本地化字符串。 /// public static string menuRegionalPresets { get { return ResourceManager.GetString("menuRegionalPresets", resourceCulture); } } /// /// 查找类似 Default 的本地化字符串。 /// public static string menuRegionalPresetsDefault { get { return ResourceManager.GetString("menuRegionalPresetsDefault", resourceCulture); } } /// /// 查找类似 Iran 的本地化字符串。 /// public static string menuRegionalPresetsIran { get { return ResourceManager.GetString("menuRegionalPresetsIran", resourceCulture); } } /// /// 查找类似 Russia 的本地化字符串。 /// public static string menuRegionalPresetsRussia { get { return ResourceManager.GetString("menuRegionalPresetsRussia", resourceCulture); } } /// /// 查找类似 Reload 的本地化字符串。 /// public static string menuReload { get { return ResourceManager.GetString("menuReload", resourceCulture); } } /// /// 查找类似 Backup to remote (WebDAV) 的本地化字符串。 /// public static string menuRemoteBackup { get { return ResourceManager.GetString("menuRemoteBackup", resourceCulture); } } /// /// 查找类似 Remote (WebDAV) 的本地化字符串。 /// public static string menuRemoteBackupAndRestore { get { return ResourceManager.GetString("menuRemoteBackupAndRestore", resourceCulture); } } /// /// 查找类似 Restore from remote (WebDAV) 的本地化字符串。 /// public static string menuRemoteRestore { get { return ResourceManager.GetString("menuRemoteRestore", resourceCulture); } } /// /// 查找类似 Remove Child 的本地化字符串。 /// public static string menuRemoveChildServer { get { return ResourceManager.GetString("menuRemoveChildServer", resourceCulture); } } /// /// 查找类似 Remove duplicate 的本地化字符串。 /// public static string menuRemoveDuplicateServer { get { return ResourceManager.GetString("menuRemoveDuplicateServer", resourceCulture); } } /// /// 查找类似 Remove invalid by test results 的本地化字符串。 /// public static string menuRemoveInvalidServerResult { get { return ResourceManager.GetString("menuRemoveInvalidServerResult", resourceCulture); } } /// /// 查找类似 Remove selected 的本地化字符串。 /// public static string menuRemoveServer { get { return ResourceManager.GetString("menuRemoveServer", resourceCulture); } } /// /// 查找类似 Routing 的本地化字符串。 /// public static string menuRouting { get { return ResourceManager.GetString("menuRouting", resourceCulture); } } /// /// 查找类似 Add 的本地化字符串。 /// public static string menuRoutingAdvancedAdd { get { return ResourceManager.GetString("menuRoutingAdvancedAdd", resourceCulture); } } /// /// 查找类似 Import Rules 的本地化字符串。 /// public static string menuRoutingAdvancedImportRules { get { return ResourceManager.GetString("menuRoutingAdvancedImportRules", resourceCulture); } } /// /// 查找类似 Remove selected 的本地化字符串。 /// public static string menuRoutingAdvancedRemove { get { return ResourceManager.GetString("menuRoutingAdvancedRemove", resourceCulture); } } /// /// 查找类似 Set as active rule 的本地化字符串。 /// public static string menuRoutingAdvancedSetDefault { get { return ResourceManager.GetString("menuRoutingAdvancedSetDefault", resourceCulture); } } /// /// 查找类似 Routing Rule Details Setting 的本地化字符串。 /// public static string menuRoutingRuleDetailsSetting { get { return ResourceManager.GetString("menuRoutingRuleDetailsSetting", resourceCulture); } } /// /// 查找类似 Rule Settings 的本地化字符串。 /// public static string menuRoutingRuleSetting { get { return ResourceManager.GetString("menuRoutingRuleSetting", resourceCulture); } } /// /// 查找类似 Routing Setting 的本地化字符串。 /// public static string menuRoutingSetting { get { return ResourceManager.GetString("menuRoutingSetting", resourceCulture); } } /// /// 查找类似 Add Rule 的本地化字符串。 /// public static string menuRuleAdd { get { return ResourceManager.GetString("menuRuleAdd", resourceCulture); } } /// /// 查找类似 Export Selected Rules 的本地化字符串。 /// public static string menuRuleExportSelected { get { return ResourceManager.GetString("menuRuleExportSelected", resourceCulture); } } /// /// 查找类似 Rule List 的本地化字符串。 /// public static string menuRuleList { get { return ResourceManager.GetString("menuRuleList", resourceCulture); } } /// /// 查找类似 Rule mode 的本地化字符串。 /// public static string menuRulemode { get { return ResourceManager.GetString("menuRulemode", resourceCulture); } } /// /// 查找类似 Remove Rule 的本地化字符串。 /// public static string menuRuleRemove { get { return ResourceManager.GetString("menuRuleRemove", resourceCulture); } } /// /// 查找类似 Select all 的本地化字符串。 /// public static string menuSelectAll { get { return ResourceManager.GetString("menuSelectAll", resourceCulture); } } /// /// 查找类似 Configuration item 1, Auto add from subscription group 的本地化字符串。 /// public static string menuServerList { get { return ResourceManager.GetString("menuServerList", resourceCulture); } } /// /// 查找类似 Configuration Item 2, Select and add from self-built 的本地化字符串。 /// public static string menuServerList2 { get { return ResourceManager.GetString("menuServerList2", resourceCulture); } } /// /// 查找类似 Configuration item preview 的本地化字符串。 /// public static string menuServerListPreview { get { return ResourceManager.GetString("menuServerListPreview", resourceCulture); } } /// /// 查找类似 Configuration 的本地化字符串。 /// public static string menuServers { get { return ResourceManager.GetString("menuServers", resourceCulture); } } /// /// 查找类似 Set as active 的本地化字符串。 /// public static string menuSetDefaultServer { get { return ResourceManager.GetString("menuSetDefaultServer", resourceCulture); } } /// /// 查找类似 Settings 的本地化字符串。 /// public static string menuSetting { get { return ResourceManager.GetString("menuSetting", resourceCulture); } } /// /// 查找类似 Share 的本地化字符串。 /// public static string menuShareServer { get { return ResourceManager.GetString("menuShareServer", resourceCulture); } } /// /// 查找类似 Show or hide the main window 的本地化字符串。 /// public static string menuShowOrHideMainWindow { get { return ResourceManager.GetString("menuShowOrHideMainWindow", resourceCulture); } } /// /// 查找类似 Sort by test result 的本地化字符串。 /// public static string menuSortServerResult { get { return ResourceManager.GetString("menuSortServerResult", resourceCulture); } } /// /// 查找类似 Test download speed 的本地化字符串。 /// public static string menuSpeedServer { get { return ResourceManager.GetString("menuSpeedServer", resourceCulture); } } /// /// 查找类似 Add 的本地化字符串。 /// public static string menuSubAdd { get { return ResourceManager.GetString("menuSubAdd", resourceCulture); } } /// /// 查找类似 Delete 的本地化字符串。 /// public static string menuSubDelete { get { return ResourceManager.GetString("menuSubDelete", resourceCulture); } } /// /// 查找类似 Edit 的本地化字符串。 /// public static string menuSubEdit { get { return ResourceManager.GetString("menuSubEdit", resourceCulture); } } /// /// 查找类似 Update current subscription without proxy 的本地化字符串。 /// public static string menuSubGroupUpdate { get { return ResourceManager.GetString("menuSubGroupUpdate", resourceCulture); } } /// /// 查找类似 Update current subscription with proxy 的本地化字符串。 /// public static string menuSubGroupUpdateViaProxy { get { return ResourceManager.GetString("menuSubGroupUpdateViaProxy", resourceCulture); } } /// /// 查找类似 Subscription Group 的本地化字符串。 /// public static string menuSubscription { get { return ResourceManager.GetString("menuSubscription", resourceCulture); } } /// /// 查找类似 Subscription group settings 的本地化字符串。 /// public static string menuSubSetting { get { return ResourceManager.GetString("menuSubSetting", resourceCulture); } } /// /// 查找类似 Share 的本地化字符串。 /// public static string menuSubShare { get { return ResourceManager.GetString("menuSubShare", resourceCulture); } } /// /// 查找类似 Update subscriptions without proxy 的本地化字符串。 /// public static string menuSubUpdate { get { return ResourceManager.GetString("menuSubUpdate", resourceCulture); } } /// /// 查找类似 Update subscriptions with proxy 的本地化字符串。 /// public static string menuSubUpdateViaProxy { get { return ResourceManager.GetString("menuSubUpdateViaProxy", resourceCulture); } } /// /// 查找类似 System proxy 的本地化字符串。 /// public static string menuSystemproxy { get { return ResourceManager.GetString("menuSystemproxy", resourceCulture); } } /// /// 查找类似 Clear system proxy 的本地化字符串。 /// public static string menuSystemProxyClear { get { return ResourceManager.GetString("menuSystemProxyClear", resourceCulture); } } /// /// 查找类似 Do not change system proxy 的本地化字符串。 /// public static string menuSystemProxyNothing { get { return ResourceManager.GetString("menuSystemProxyNothing", resourceCulture); } } /// /// 查找类似 PAC mode 的本地化字符串。 /// public static string menuSystemProxyPac { get { return ResourceManager.GetString("menuSystemProxyPac", resourceCulture); } } /// /// 查找类似 Set system proxy 的本地化字符串。 /// public static string menuSystemProxySet { get { return ResourceManager.GetString("menuSystemProxySet", resourceCulture); } } /// /// 查找类似 Test tcping 的本地化字符串。 /// public static string menuTcpingServer { get { return ResourceManager.GetString("menuTcpingServer", resourceCulture); } } /// /// 查找类似 By test result 的本地化字符串。 /// public static string menuTestServerResult { get { return ResourceManager.GetString("menuTestServerResult", resourceCulture); } } /// /// 查找类似 {0} Website 的本地化字符串。 /// public static string menuWebsiteItem { get { return ResourceManager.GetString("menuWebsiteItem", resourceCulture); } } /// /// 查找类似 Core '{0}' does not support network type '{1}' 的本地化字符串。 /// public static string MsgCoreNotSupportNetwork { get { return ResourceManager.GetString("MsgCoreNotSupportNetwork", resourceCulture); } } /// /// 查找类似 Core '{0}' does not support protocol '{1}' 的本地化字符串。 /// public static string MsgCoreNotSupportProtocol { get { return ResourceManager.GetString("MsgCoreNotSupportProtocol", resourceCulture); } } /// /// 查找类似 Core '{0}' does not support protocol '{1}' when using transport '{2}' 的本地化字符串。 /// public static string MsgCoreNotSupportProtocolTransport { get { return ResourceManager.GetString("MsgCoreNotSupportProtocolTransport", resourceCulture); } } /// /// 查找类似 Downloaded GeoFile: {0} successfully 的本地化字符串。 /// public static string MsgDownloadGeoFileSuccessfully { get { return ResourceManager.GetString("MsgDownloadGeoFileSuccessfully", resourceCulture); } } /// /// 查找类似 Downloaded Core successfully 的本地化字符串。 /// public static string MsgDownloadV2rayCoreSuccessfully { get { return ResourceManager.GetString("MsgDownloadV2rayCoreSuccessfully", resourceCulture); } } /// /// 查找类似 Failed to import subscription content 的本地化字符串。 /// public static string MsgFailedImportSubscription { get { return ResourceManager.GetString("MsgFailedImportSubscription", resourceCulture); } } /// /// 查找类似 Filter, supports regular expressions 的本地化字符串。 /// public static string MsgFilterTitle { get { return ResourceManager.GetString("MsgFilterTitle", resourceCulture); } } /// /// 查找类似 Got subscription content successfully 的本地化字符串。 /// public static string MsgGetSubscriptionSuccessfully { get { return ResourceManager.GetString("MsgGetSubscriptionSuccessfully", resourceCulture); } } /// /// 查找类似 Group {0} child group node {1} error: {2}. Skipping this node. 的本地化字符串。 /// public static string MsgGroupChildGroupNodeError { get { return ResourceManager.GetString("MsgGroupChildGroupNodeError", resourceCulture); } } /// /// 查找类似 Group {0} child group node {1} warning: {2} 的本地化字符串。 /// public static string MsgGroupChildGroupNodeWarning { get { return ResourceManager.GetString("MsgGroupChildGroupNodeWarning", resourceCulture); } } /// /// 查找类似 Group {0} child node {1} error: {2}. Skipping this node. 的本地化字符串。 /// public static string MsgGroupChildNodeError { get { return ResourceManager.GetString("MsgGroupChildNodeError", resourceCulture); } } /// /// 查找类似 Group {0} child node {1} warning: {2} 的本地化字符串。 /// public static string MsgGroupChildNodeWarning { get { return ResourceManager.GetString("MsgGroupChildNodeWarning", resourceCulture); } } /// /// 查找类似 Group {0} has a cycle dependency on child node {1}. Skipping this node. 的本地化字符串。 /// public static string MsgGroupCycleDependency { get { return ResourceManager.GetString("MsgGroupCycleDependency", resourceCulture); } } /// /// 查找类似 Group {0} has no valid child node. 的本地化字符串。 /// public static string MsgGroupNoValidChildNode { get { return ResourceManager.GetString("MsgGroupNoValidChildNode", resourceCulture); } } /// /// 查找类似 Information 的本地化字符串。 /// public static string MsgInformationTitle { get { return ResourceManager.GetString("MsgInformationTitle", resourceCulture); } } /// /// 查找类似 The {0} property is invalid, please check 的本地化字符串。 /// public static string MsgInvalidProperty { get { return ResourceManager.GetString("MsgInvalidProperty", resourceCulture); } } /// /// 查找类似 Please enter the URL 的本地化字符串。 /// public static string MsgNeedUrl { get { return ResourceManager.GetString("MsgNeedUrl", resourceCulture); } } /// /// 查找类似 Not support protocol '{0}' 的本地化字符串。 /// public static string MsgNotSupportProtocol { get { return ResourceManager.GetString("MsgNotSupportProtocol", resourceCulture); } } /// /// 查找类似 No valid subscriptions set 的本地化字符串。 /// public static string MsgNoValidSubscription { get { return ResourceManager.GetString("MsgNoValidSubscription", resourceCulture); } } /// /// 查找类似 Resolved {0} successfully 的本地化字符串。 /// public static string MsgParsingSuccessfully { get { return ResourceManager.GetString("MsgParsingSuccessfully", resourceCulture); } } /// /// 查找类似 Routing rule {0} has an empty outbound tag. Fallback to proxy node only. 的本地化字符串。 /// public static string MsgRoutingRuleEmptyOutboundTag { get { return ResourceManager.GetString("MsgRoutingRuleEmptyOutboundTag", resourceCulture); } } /// /// 查找类似 Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. 的本地化字符串。 /// public static string MsgRoutingRuleOutboundNodeError { get { return ResourceManager.GetString("MsgRoutingRuleOutboundNodeError", resourceCulture); } } /// /// 查找类似 Routing rule {0} outbound node {1} not found. Fallback to proxy node only. 的本地化字符串。 /// public static string MsgRoutingRuleOutboundNodeNotFound { get { return ResourceManager.GetString("MsgRoutingRuleOutboundNodeNotFound", resourceCulture); } } /// /// 查找类似 Routing rule {0} outbound node {1} warning: {2} 的本地化字符串。 /// public static string MsgRoutingRuleOutboundNodeWarning { get { return ResourceManager.GetString("MsgRoutingRuleOutboundNodeWarning", resourceCulture); } } /// /// 查找类似 Filter, press Enter to execute 的本地化字符串。 /// public static string MsgServerTitle { get { return ResourceManager.GetString("MsgServerTitle", resourceCulture); } } /// /// 查找类似 Updates are not enabled, skip this subscription 的本地化字符串。 /// public static string MsgSkipSubscriptionUpdate { get { return ResourceManager.GetString("MsgSkipSubscriptionUpdate", resourceCulture); } } /// /// 查找类似 Started getting subscriptions 的本地化字符串。 /// public static string MsgStartGettingSubscriptions { get { return ResourceManager.GetString("MsgStartGettingSubscriptions", resourceCulture); } } /// /// 查找类似 Start parsing and processing subscription content 的本地化字符串。 /// public static string MsgStartParsingSubscription { get { return ResourceManager.GetString("MsgStartParsingSubscription", resourceCulture); } } /// /// 查找类似 Started updating {0}... 的本地化字符串。 /// public static string MsgStartUpdating { get { return ResourceManager.GetString("MsgStartUpdating", resourceCulture); } } /// /// 查找类似 Invalid subscription content 的本地化字符串。 /// public static string MsgSubscriptionDecodingFailed { get { return ResourceManager.GetString("MsgSubscriptionDecodingFailed", resourceCulture); } } /// /// 查找类似 Subscription next proxy {0} not found. Skipping. 的本地化字符串。 /// public static string MsgSubscriptionNextProfileNotFound { get { return ResourceManager.GetString("MsgSubscriptionNextProfileNotFound", resourceCulture); } } /// /// 查找类似 Subscription previous proxy {0} not found. Skipping. 的本地化字符串。 /// public static string MsgSubscriptionPrevProfileNotFound { get { return ResourceManager.GetString("MsgSubscriptionPrevProfileNotFound", resourceCulture); } } /// /// 查找类似 Unpacking... 的本地化字符串。 /// public static string MsgUnpacking { get { return ResourceManager.GetString("MsgUnpacking", resourceCulture); } } /// /// 查找类似 Subscription update ended 的本地化字符串。 /// public static string MsgUpdateSubscriptionEnd { get { return ResourceManager.GetString("MsgUpdateSubscriptionEnd", resourceCulture); } } /// /// 查找类似 Subscription update started 的本地化字符串。 /// public static string MsgUpdateSubscriptionStart { get { return ResourceManager.GetString("MsgUpdateSubscriptionStart", resourceCulture); } } /// /// 查找类似 Updated Core successfully 的本地化字符串。 /// public static string MsgUpdateV2rayCoreSuccessfully { get { return ResourceManager.GetString("MsgUpdateV2rayCoreSuccessfully", resourceCulture); } } /// /// 查找类似 Updated Core successfully! Restarting service... 的本地化字符串。 /// public static string MsgUpdateV2rayCoreSuccessfullyMore { get { return ResourceManager.GetString("MsgUpdateV2rayCoreSuccessfullyMore", resourceCulture); } } /// /// 查找类似 Successful operation. Click the settings menu to reboot the app. 的本地化字符串。 /// public static string NeedRebootTips { get { return ResourceManager.GetString("NeedRebootTips", resourceCulture); } } /// /// 查找类似 Non-VMess or SS protocol 的本地化字符串。 /// public static string NonvmessOrssProtocol { get { return ResourceManager.GetString("NonvmessOrssProtocol", resourceCulture); } } /// /// 查找类似 The Core file (file name: {1}) was not found under the folder ({0}), please download and put it in the folder, download address: {2} 的本地化字符串。 /// public static string NotFoundCore { get { return ResourceManager.GetString("NotFoundCore", resourceCulture); } } /// /// 查找类似 Not run as Admin 的本地化字符串。 /// public static string NotRunAsAdmin { get { return ResourceManager.GetString("NotRunAsAdmin", resourceCulture); } } /// /// 查找类似 Scan completed, no valid QR code found 的本地化字符串。 /// public static string NoValidQRcodeFound { get { return ResourceManager.GetString("NoValidQRcodeFound", resourceCulture); } } /// /// 查找类似 Operation failed, please check and retry 的本地化字符串。 /// public static string OperationFailed { get { return ResourceManager.GetString("OperationFailed", resourceCulture); } } /// /// 查找类似 Operation successful 的本地化字符串。 /// public static string OperationSuccess { get { return ResourceManager.GetString("OperationSuccess", resourceCulture); } } /// /// 查找类似 Please Add At Least One Configuration 的本地化字符串。 /// public static string PleaseAddAtLeastOneServer { get { return ResourceManager.GetString("PleaseAddAtLeastOneServer", resourceCulture); } } /// /// 查找类似 Please fill Remarks 的本地化字符串。 /// public static string PleaseFillRemarks { get { return ResourceManager.GetString("PleaseFillRemarks", resourceCulture); } } /// /// 查找类似 Please select the encryption method 的本地化字符串。 /// public static string PleaseSelectEncryption { get { return ResourceManager.GetString("PleaseSelectEncryption", resourceCulture); } } /// /// 查找类似 Please select a protocol 的本地化字符串。 /// public static string PleaseSelectProtocol { get { return ResourceManager.GetString("PleaseSelectProtocol", resourceCulture); } } /// /// 查找类似 Please select rules 的本地化字符串。 /// public static string PleaseSelectRules { get { return ResourceManager.GetString("PleaseSelectRules", resourceCulture); } } /// /// 查找类似 Please select the Configuration first 的本地化字符串。 /// public static string PleaseSelectServer { get { return ResourceManager.GetString("PleaseSelectServer", resourceCulture); } } /// /// 查找类似 Global hotkey {0} registration failed, reason: {1} 的本地化字符串。 /// public static string RegisterGlobalHotkeyFailed { get { return ResourceManager.GetString("RegisterGlobalHotkeyFailed", resourceCulture); } } /// /// 查找类似 Global hotkey {0} registered successfully 的本地化字符串。 /// public static string RegisterGlobalHotkeySuccessfully { get { return ResourceManager.GetString("RegisterGlobalHotkeySuccessfully", resourceCulture); } } /// /// 查找类似 Configurations deduplication completed. Old: {0}, New: {1}. 的本地化字符串。 /// public static string RemoveDuplicateServerResult { get { return ResourceManager.GetString("RemoveDuplicateServerResult", resourceCulture); } } /// /// 查找类似 Removed {0} invalid test results. 的本地化字符串。 /// public static string RemoveInvalidServerResultTip { get { return ResourceManager.GetString("RemoveInvalidServerResultTip", resourceCulture); } } /// /// 查找类似 Are you sure you want to remove the rules? 的本地化字符串。 /// public static string RemoveRules { get { return ResourceManager.GetString("RemoveRules", resourceCulture); } } /// /// 查找类似 Are you sure you want to remove? 的本地化字符串。 /// public static string RemoveServer { get { return ResourceManager.GetString("RemoveServer", resourceCulture); } } /// /// 查找类似 {0}, one of the required fields. 的本地化字符串。 /// public static string RoutingRuleDetailRequiredTips { get { return ResourceManager.GetString("RoutingRuleDetailRequiredTips", resourceCulture); } } /// /// 查找类似 Run as Admin 的本地化字符串。 /// public static string RunAsAdmin { get { return ResourceManager.GetString("RunAsAdmin", resourceCulture); } } /// /// 查找类似 The client configuration file is saved at: {0} 的本地化字符串。 /// public static string SaveClientConfigurationIn { get { return ResourceManager.GetString("SaveClientConfigurationIn", resourceCulture); } } /// /// 查找类似 Please set a valid domain 的本地化字符串。 /// public static string ServerNameMustBeValidDomain { get { return ResourceManager.GetString("ServerNameMustBeValidDomain", resourceCulture); } } /// /// 查找类似 {0} : {1}/s↑ | {2}/s↓ 的本地化字符串。 /// public static string SpeedDisplayText { get { return ResourceManager.GetString("SpeedDisplayText", resourceCulture); } } /// /// 查找类似 Testing... 的本地化字符串。 /// public static string Speedtesting { get { return ResourceManager.GetString("Speedtesting", resourceCulture); } } /// /// 查找类似 Test completed 的本地化字符串。 /// public static string SpeedtestingCompleted { get { return ResourceManager.GetString("SpeedtestingCompleted", resourceCulture); } } /// /// 查找类似 Press ESC to terminate the test 的本地化字符串。 /// public static string SpeedtestingPressEscToExit { get { return ResourceManager.GetString("SpeedtestingPressEscToExit", resourceCulture); } } /// /// 查找类似 Skip test 的本地化字符串。 /// public static string SpeedtestingSkip { get { return ResourceManager.GetString("SpeedtestingSkip", resourceCulture); } } /// /// 查找类似 Test terminating... 的本地化字符串。 /// public static string SpeedtestingStop { get { return ResourceManager.GetString("SpeedtestingStop", resourceCulture); } } /// /// 查找类似 Starting retesting failed parts, {0} remaining. Press ESC to terminate... 的本地化字符串。 /// public static string SpeedtestingTestFailedPart { get { return ResourceManager.GetString("SpeedtestingTestFailedPart", resourceCulture); } } /// /// 查找类似 Waiting... 的本地化字符串。 /// public static string SpeedtestingWait { get { return ResourceManager.GetString("SpeedtestingWait", resourceCulture); } } /// /// 查找类似 Starting service ({0})... 的本地化字符串。 /// public static string StartService { get { return ResourceManager.GetString("StartService", resourceCulture); } } /// /// 查找类似 For group please leave blank here 的本地化字符串。 /// public static string SubUrlTips { get { return ResourceManager.GetString("SubUrlTips", resourceCulture); } } /// /// 查找类似 Configuration successful. {0} 的本地化字符串。 /// public static string SuccessfulConfiguration { get { return ResourceManager.GetString("SuccessfulConfiguration", resourceCulture); } } /// /// 查找类似 Custom configuration Configuration imported successfully 的本地化字符串。 /// public static string SuccessfullyImportedCustomServer { get { return ResourceManager.GetString("SuccessfullyImportedCustomServer", resourceCulture); } } /// /// 查找类似 {0} Configurations have been imported from clipboard 的本地化字符串。 /// public static string SuccessfullyImportedServerViaClipboard { get { return ResourceManager.GetString("SuccessfullyImportedServerViaClipboard", resourceCulture); } } /// /// 查找类似 Successfully scanned and imported the shared link 的本地化字符串。 /// public static string SuccessfullyImportedServerViaScan { get { return ResourceManager.GetString("SuccessfullyImportedServerViaScan", resourceCulture); } } /// /// 查找类似 Incorrect password, please try again. 的本地化字符串。 /// public static string SudoIncorrectPasswordTip { get { return ResourceManager.GetString("SudoIncorrectPasswordTip", resourceCulture); } } /// /// 查找类似 Add Common DNS Hosts 的本地化字符串。 /// public static string TbAddCommonDNSHosts { get { return ResourceManager.GetString("TbAddCommonDNSHosts", resourceCulture); } } /// /// 查找类似 Do Not Add Non-Proxy Protocol Outbound 的本地化字符串。 /// public static string TbAddProxyProtocolOutboundOnly { get { return ResourceManager.GetString("TbAddProxyProtocolOutboundOnly", resourceCulture); } } /// /// 查找类似 Address 的本地化字符串。 /// public static string TbAddress { get { return ResourceManager.GetString("TbAddress", resourceCulture); } } /// /// 查找类似 Allow Insecure 的本地化字符串。 /// public static string TbAllowInsecure { get { return ResourceManager.GetString("TbAllowInsecure", resourceCulture); } } /// /// 查找类似 ALPN 的本地化字符串。 /// public static string TbAlpn { get { return ResourceManager.GetString("TbAlpn", resourceCulture); } } /// /// 查找类似 Alter ID 的本地化字符串。 /// public static string TbAlterId { get { return ResourceManager.GetString("TbAlterId", resourceCulture); } } /// /// 查找类似 Auto refresh 的本地化字符串。 /// public static string TbAutoRefresh { get { return ResourceManager.GetString("TbAutoRefresh", resourceCulture); } } /// /// 查找类似 Auto scroll to end 的本地化字符串。 /// public static string TbAutoScrollToEnd { get { return ResourceManager.GetString("TbAutoScrollToEnd", resourceCulture); } } /// /// 查找类似 Domain, IP, process are auto-sorted when saving 的本地化字符串。 /// public static string TbAutoSort { get { return ResourceManager.GetString("TbAutoSort", resourceCulture); } } /// /// 查找类似 Block SVCB and HTTPS Queries 的本地化字符串。 /// public static string TbBlockSVCBHTTPSQueries { get { return ResourceManager.GetString("TbBlockSVCBHTTPSQueries", resourceCulture); } } /// /// 查找类似 Block ECH and HTTP/3 availability checks when enabled 的本地化字符串。 /// public static string TbBlockSVCBHTTPSQueriesTips { get { return ResourceManager.GetString("TbBlockSVCBHTTPSQueriesTips", resourceCulture); } } /// /// 查找类似 Bootstrap DNS 的本地化字符串。 /// public static string TbBootstrapDNS { get { return ResourceManager.GetString("TbBootstrapDNS", resourceCulture); } } /// /// 查找类似 Resolve DNS server domains, requires IP 的本地化字符串。 /// public static string TbBootstrapDNSTips { get { return ResourceManager.GetString("TbBootstrapDNSTips", resourceCulture); } } /// /// 查找类似 Browse 的本地化字符串。 /// public static string TbBrowse { get { return ResourceManager.GetString("TbBrowse", resourceCulture); } } /// /// 查找类似 Cancel 的本地化字符串。 /// public static string TbCancel { get { return ResourceManager.GetString("TbCancel", resourceCulture); } } /// /// 查找类似 Certificate Pinning 的本地化字符串。 /// public static string TbCertPinning { get { return ResourceManager.GetString("TbCertPinning", resourceCulture); } } /// /// 查找类似 Pinned certificate (fill in either one) ///When specified, the certificate will be pinned, and "Allow Insecure" will be disabled. /// ///The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. 的本地化字符串。 /// public static string TbCertPinningTips { get { return ResourceManager.GetString("TbCertPinningTips", resourceCulture); } } /// /// 查找类似 Certificate fingerprint (SHA-256) 的本地化字符串。 /// public static string TbCertSha256Tips { get { return ResourceManager.GetString("TbCertSha256Tips", resourceCulture); } } /// /// 查找类似 Clear system proxy 的本地化字符串。 /// public static string TbClearSystemProxy { get { return ResourceManager.GetString("TbClearSystemProxy", resourceCulture); } } /// /// 查找类似 Policy Group 的本地化字符串。 /// public static string TbConfigTypePolicyGroup { get { return ResourceManager.GetString("TbConfigTypePolicyGroup", resourceCulture); } } /// /// 查找类似 Proxy Chain 的本地化字符串。 /// public static string TbConfigTypeProxyChain { get { return ResourceManager.GetString("TbConfigTypeProxyChain", resourceCulture); } } /// /// 查找类似 Confirm 的本地化字符串。 /// public static string TbConfirm { get { return ResourceManager.GetString("TbConfirm", resourceCulture); } } /// /// 查找类似 Connections 的本地化字符串。 /// public static string TbConnections { get { return ResourceManager.GetString("TbConnections", resourceCulture); } } /// /// 查找类似 Core Type 的本地化字符串。 /// public static string TbCoreType { get { return ResourceManager.GetString("TbCoreType", resourceCulture); } } /// /// 查找类似 Enable Custom DNS 的本地化字符串。 /// public static string TbCustomDNSEnable { get { return ResourceManager.GetString("TbCustomDNSEnable", resourceCulture); } } /// /// 查找类似 Custom DNS Enabled, This Page's Settings Invalid 的本地化字符串。 /// public static string TbCustomDNSEnabledPageInvalid { get { return ResourceManager.GetString("TbCustomDNSEnabledPageInvalid", resourceCulture); } } /// /// 查找类似 V2ray Custom DNS 的本地化字符串。 /// public static string TbCustomDnsRay { get { return ResourceManager.GetString("TbCustomDnsRay", resourceCulture); } } /// /// 查找类似 sing-box Custom DNS 的本地化字符串。 /// public static string TbCustomDnsSingbox { get { return ResourceManager.GetString("TbCustomDnsSingbox", resourceCulture); } } /// /// 查找类似 Direct Target Resolution Strategy 的本地化字符串。 /// public static string TbDirectResolveStrategy { get { return ResourceManager.GetString("TbDirectResolveStrategy", resourceCulture); } } /// /// 查找类似 If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used. 的本地化字符串。 /// public static string TbDirectResolveStrategyTips { get { return ResourceManager.GetString("TbDirectResolveStrategyTips", resourceCulture); } } /// /// 查找类似 Display GUI 的本地化字符串。 /// public static string TbDisplayGUI { get { return ResourceManager.GetString("TbDisplayGUI", resourceCulture); } } /// /// 查找类似 Display Log 的本地化字符串。 /// public static string TbDisplayLog { get { return ResourceManager.GetString("TbDisplayLog", resourceCulture); } } /// /// 查找类似 DNS Hosts: ("domain1 ip1 ip2" per line) 的本地化字符串。 /// public static string TbDNSHostsConfig { get { return ResourceManager.GetString("TbDNSHostsConfig", resourceCulture); } } /// /// 查找类似 Supports DNS Object; Click to view documentation 的本地化字符串。 /// public static string TbDnsObjectDoc { get { return ResourceManager.GetString("TbDnsObjectDoc", resourceCulture); } } /// /// 查找类似 Please fill in DNS Structure, Click to view the document 的本地化字符串。 /// public static string TbDnsSingboxObjectDoc { get { return ResourceManager.GetString("TbDnsSingboxObjectDoc", resourceCulture); } } /// /// 查找类似 Domain strategy 的本地化字符串。 /// public static string TbdomainStrategy { get { return ResourceManager.GetString("TbdomainStrategy", resourceCulture); } } /// /// 查找类似 sing-box domain strategy 的本地化字符串。 /// public static string TbdomainStrategy4Singbox { get { return ResourceManager.GetString("TbdomainStrategy4Singbox", resourceCulture); } } /// /// 查找类似 Domestic DNS 的本地化字符串。 /// public static string TbDomesticDNS { get { return ResourceManager.GetString("TbDomesticDNS", resourceCulture); } } /// /// 查找类似 By default, invoked only during routing for resolution 的本地化字符串。 /// public static string TbDomesticDNSTips { get { return ResourceManager.GetString("TbDomesticDNSTips", resourceCulture); } } /// /// 查找类似 EchConfigList 的本地化字符串。 /// public static string TbEchConfigList { get { return ResourceManager.GetString("TbEchConfigList", resourceCulture); } } /// /// 查找类似 EchForceQuery 的本地化字符串。 /// public static string TbEchForceQuery { get { return ResourceManager.GetString("TbEchForceQuery", resourceCulture); } } /// /// 查找类似 Edit 的本地化字符串。 /// public static string TbEdit { get { return ResourceManager.GetString("TbEdit", resourceCulture); } } /// /// 查找类似 Enable Tun 的本地化字符串。 /// public static string TbEnableTunAs { get { return ResourceManager.GetString("TbEnableTunAs", resourceCulture); } } /// /// 查找类似 FakeIP 的本地化字符串。 /// public static string TbFakeIP { get { return ResourceManager.GetString("TbFakeIP", resourceCulture); } } /// /// 查找类似 Applies globally by default, with built-in FakeIP filtering (sing-box only). 的本地化字符串。 /// public static string TbFakeIPTips { get { return ResourceManager.GetString("TbFakeIPTips", resourceCulture); } } /// /// 查找类似 Fallback 的本地化字符串。 /// public static string TbFallback { get { return ResourceManager.GetString("TbFallback", resourceCulture); } } /// /// 查找类似 Fetch Certificate 的本地化字符串。 /// public static string TbFetchCert { get { return ResourceManager.GetString("TbFetchCert", resourceCulture); } } /// /// 查找类似 Fetch Certificate Chain 的本地化字符串。 /// public static string TbFetchCertChain { get { return ResourceManager.GetString("TbFetchCertChain", resourceCulture); } } /// /// 查找类似 Finalmask 的本地化字符串。 /// public static string TbFinalmask { get { return ResourceManager.GetString("TbFinalmask", resourceCulture); } } /// /// 查找类似 Fingerprint 的本地化字符串。 /// public static string TbFingerprint { get { return ResourceManager.GetString("TbFingerprint", resourceCulture); } } /// /// 查找类似 Flow 的本地化字符串。 /// public static string TbFlow5 { get { return ResourceManager.GetString("TbFlow5", resourceCulture); } } /// /// 查找类似 Full certificate (chain), PEM format 的本地化字符串。 /// public static string TbFullCertTips { get { return ResourceManager.GetString("TbFullCertTips", resourceCulture); } } /// /// 查找类似 This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. 的本地化字符串。 /// public static string TbFullConfigTemplateDesc { get { return ResourceManager.GetString("TbFullConfigTemplateDesc", resourceCulture); } } /// /// 查找类似 Enable Full Config Template 的本地化字符串。 /// public static string TbFullConfigTemplateEnable { get { return ResourceManager.GetString("TbFullConfigTemplateEnable", resourceCulture); } } /// /// 查找类似 Global Hotkey Settings 的本地化字符串。 /// public static string TbGlobalHotkeySetting { get { return ResourceManager.GetString("TbGlobalHotkeySetting", resourceCulture); } } /// /// 查找类似 Set directly by pressing the keyboard; takes effect after restart 的本地化字符串。 /// public static string TbGlobalHotkeySettingTip { get { return ResourceManager.GetString("TbGlobalHotkeySettingTip", resourceCulture); } } /// /// 查找类似 Generate 的本地化字符串。 /// public static string TbGUID { get { return ResourceManager.GetString("TbGUID", resourceCulture); } } /// /// 查找类似 Camouflage type 的本地化字符串。 /// public static string TbHeaderType { get { return ResourceManager.GetString("TbHeaderType", resourceCulture); } } /// /// 查找类似 Congestion control 的本地化字符串。 /// public static string TbHeaderType8 { get { return ResourceManager.GetString("TbHeaderType8", resourceCulture); } } /// /// 查找类似 Port hopping interval 的本地化字符串。 /// public static string TbHopInt7 { get { return ResourceManager.GetString("TbHopInt7", resourceCulture); } } /// /// 查找类似 UUID(id) 的本地化字符串。 /// public static string TbId { get { return ResourceManager.GetString("TbId", resourceCulture); } } /// /// 查找类似 Password 的本地化字符串。 /// public static string TbId3 { get { return ResourceManager.GetString("TbId3", resourceCulture); } } /// /// 查找类似 Password(Optional) 的本地化字符串。 /// public static string TbId4 { get { return ResourceManager.GetString("TbId4", resourceCulture); } } /// /// 查找类似 UUID(id) 的本地化字符串。 /// public static string TbId5 { get { return ResourceManager.GetString("TbId5", resourceCulture); } } /// /// 查找类似 Most Stable 的本地化字符串。 /// public static string TbLeastLoad { get { return ResourceManager.GetString("TbLeastLoad", resourceCulture); } } /// /// 查找类似 Lowest Latency 的本地化字符串。 /// public static string TbLeastPing { get { return ResourceManager.GetString("TbLeastPing", resourceCulture); } } /// /// 查找类似 Address (IPv4, IPv6) 的本地化字符串。 /// public static string TbLocalAddress { get { return ResourceManager.GetString("TbLocalAddress", resourceCulture); } } /// /// 查找类似 Mldsa65Verify 的本地化字符串。 /// public static string TbMldsa65Verify { get { return ResourceManager.GetString("TbMldsa65Verify", resourceCulture); } } /// /// 查找类似 Transport protocol(network) 的本地化字符串。 /// public static string TbNetwork { get { return ResourceManager.GetString("TbNetwork", resourceCulture); } } /// /// 查找类似 Do not change system proxy 的本地化字符串。 /// public static string TbNotChangeSystemProxy { get { return ResourceManager.GetString("TbNotChangeSystemProxy", resourceCulture); } } /// /// 查找类似 Parallel Query 的本地化字符串。 /// public static string TbParallelQuery { get { return ResourceManager.GetString("TbParallelQuery", resourceCulture); } } /// /// 查找类似 Path 的本地化字符串。 /// public static string TbPath { get { return ResourceManager.GetString("TbPath", resourceCulture); } } /// /// 查找类似 obfs password 的本地化字符串。 /// public static string TbPath7 { get { return ResourceManager.GetString("TbPath7", resourceCulture); } } /// /// 查找类似 Auto add filtered configuration from subscription groups 的本地化字符串。 /// public static string TbPolicyGroupSubChildTip { get { return ResourceManager.GetString("TbPolicyGroupSubChildTip", resourceCulture); } } /// /// 查找类似 Policy Group Type 的本地化字符串。 /// public static string TbPolicyGroupType { get { return ResourceManager.GetString("TbPolicyGroupType", resourceCulture); } } /// /// 查找类似 Port 的本地化字符串。 /// public static string TbPort { get { return ResourceManager.GetString("TbPort", resourceCulture); } } /// /// 查找类似 Configuration port range 的本地化字符串。 /// public static string TbPorts7 { get { return ResourceManager.GetString("TbPorts7", resourceCulture); } } /// /// 查找类似 Will cover the port, separate with commas (,) 的本地化字符串。 /// public static string TbPorts7Tips { get { return ResourceManager.GetString("TbPorts7Tips", resourceCulture); } } /// /// 查找类似 Socks port 的本地化字符串。 /// public static string TbPreSocksPort { get { return ResourceManager.GetString("TbPreSocksPort", resourceCulture); } } /// /// 查找类似 Custom config socks port 的本地化字符串。 /// public static string TbPreSocksPort4Sub { get { return ResourceManager.GetString("TbPreSocksPort4Sub", resourceCulture); } } /// /// 查找类似 Private Key 的本地化字符串。 /// public static string TbPrivateKey { get { return ResourceManager.GetString("TbPrivateKey", resourceCulture); } } /// /// 查找类似 Proxies 的本地化字符串。 /// public static string TbProxies { get { return ResourceManager.GetString("TbProxies", resourceCulture); } } /// /// 查找类似 Public Key 的本地化字符串。 /// public static string TbPublicKey { get { return ResourceManager.GetString("TbPublicKey", resourceCulture); } } /// /// 查找类似 Random 的本地化字符串。 /// public static string TbRandom { get { return ResourceManager.GetString("TbRandom", resourceCulture); } } /// /// 查找类似 v2ray Full Config Template 的本地化字符串。 /// public static string TbRayFullConfigTemplate { get { return ResourceManager.GetString("TbRayFullConfigTemplate", resourceCulture); } } /// /// 查找类似 Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document 的本地化字符串。 /// public static string TbRayFullConfigTemplateDesc { get { return ResourceManager.GetString("TbRayFullConfigTemplateDesc", resourceCulture); } } /// /// 查找类似 Alias (remarks) 的本地化字符串。 /// public static string TbRemarks { get { return ResourceManager.GetString("TbRemarks", resourceCulture); } } /// /// 查找类似 Remote DNS 的本地化字符串。 /// public static string TbRemoteDNS { get { return ResourceManager.GetString("TbRemoteDNS", resourceCulture); } } /// /// 查找类似 By default, invoked only during routing for resolution; ensure the remote server can reach this DNS 的本地化字符串。 /// public static string TbRemoteDNSTips { get { return ResourceManager.GetString("TbRemoteDNSTips", resourceCulture); } } /// /// 查找类似 Proxy Target Resolution Strategy 的本地化字符串。 /// public static string TbRemoteResolveStrategy { get { return ResourceManager.GetString("TbRemoteResolveStrategy", resourceCulture); } } /// /// 查找类似 If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. 的本地化字符串。 /// public static string TbRemoteResolveStrategyTips { get { return ResourceManager.GetString("TbRemoteResolveStrategyTips", resourceCulture); } } /// /// 查找类似 Camouflage domain(host) 的本地化字符串。 /// public static string TbRequestHost { get { return ResourceManager.GetString("TbRequestHost", resourceCulture); } } /// /// 查找类似 Reserved (2,3,4) 的本地化字符串。 /// public static string TbReserved { get { return ResourceManager.GetString("TbReserved", resourceCulture); } } /// /// 查找类似 Reset 的本地化字符串。 /// public static string TbReset { get { return ResourceManager.GetString("TbReset", resourceCulture); } } /// /// 查找类似 Round Robin 的本地化字符串。 /// public static string TbRoundRobin { get { return ResourceManager.GetString("TbRoundRobin", resourceCulture); } } /// /// 查找类似 socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。 /// public static string TbRoutingInboundTagTips { get { return ResourceManager.GetString("TbRoutingInboundTagTips", resourceCulture); } } /// /// 查找类似 Domain 的本地化字符串。 /// public static string TbRoutingRuleDomain { get { return ResourceManager.GetString("TbRoutingRuleDomain", resourceCulture); } } /// /// 查找类似 IP or IP CIDR 的本地化字符串。 /// public static string TbRoutingRuleIP { get { return ResourceManager.GetString("TbRoutingRuleIP", resourceCulture); } } /// /// 查找类似 Process (Linux/Windows) 的本地化字符串。 /// public static string TbRoutingRuleProcess { get { return ResourceManager.GetString("TbRoutingRuleProcess", resourceCulture); } } /// /// 查找类似 Pre-defined Rule Set List 的本地化字符串。 /// public static string TbRoutingTabRuleList { get { return ResourceManager.GetString("TbRoutingTabRuleList", resourceCulture); } } /// /// 查找类似 *Separate rules by commas (,); For a literal comma use <COMMA>; Prefix # to ignore a rule 的本地化字符串。 /// public static string TbRoutingTips { get { return ResourceManager.GetString("TbRoutingTips", resourceCulture); } } /// /// 查找类似 (Domain or IP or Proc Name) and Port and Protocol and Inbound Tag => Outbound Tag 的本地化字符串。 /// public static string TbRuleMatchingTips { get { return ResourceManager.GetString("TbRuleMatchingTips", resourceCulture); } } /// /// 查找类似 Rule object Doc 的本地化字符串。 /// public static string TbRuleobjectDoc { get { return ResourceManager.GetString("TbRuleobjectDoc", resourceCulture); } } /// /// 查找类似 Can fill in the configuration remarks, please make sure it exist and are unique 的本地化字符串。 /// public static string TbRuleOutboundTagTip { get { return ResourceManager.GetString("TbRuleOutboundTagTip", resourceCulture); } } /// /// 查找类似 Rule Type 的本地化字符串。 /// public static string TbRuleType { get { return ResourceManager.GetString("TbRuleType", resourceCulture); } } /// /// 查找类似 You can set separate rules for Routing and DNS, or select "ALL" to apply to both 的本地化字符串。 /// public static string TbRuleTypeTips { get { return ResourceManager.GetString("TbRuleTypeTips", resourceCulture); } } /// /// 查找类似 sing-box Full Config Template 的本地化字符串。 /// public static string TbSBFullConfigTemplate { get { return ResourceManager.GetString("TbSBFullConfigTemplate", resourceCulture); } } /// /// 查找类似 Add Outbound and Endpoint Config Only, Click to view the document 的本地化字符串。 /// public static string TbSBFullConfigTemplateDesc { get { return ResourceManager.GetString("TbSBFullConfigTemplateDesc", resourceCulture); } } /// /// 查找类似 Encryption method (security) 的本地化字符串。 /// public static string TbSecurity { get { return ResourceManager.GetString("TbSecurity", resourceCulture); } } /// /// 查找类似 Encryption 的本地化字符串。 /// public static string TbSecurity3 { get { return ResourceManager.GetString("TbSecurity3", resourceCulture); } } /// /// 查找类似 User(Optional) 的本地化字符串。 /// public static string TbSecurity4 { get { return ResourceManager.GetString("TbSecurity4", resourceCulture); } } /// /// 查找类似 Encryption 的本地化字符串。 /// public static string TbSecurity5 { get { return ResourceManager.GetString("TbSecurity5", resourceCulture); } } /// /// 查找类似 Select Profile 的本地化字符串。 /// public static string TbSelectProfile { get { return ResourceManager.GetString("TbSelectProfile", resourceCulture); } } /// /// 查找类似 Serve Stale 的本地化字符串。 /// public static string TbServeStale { get { return ResourceManager.GetString("TbServeStale", resourceCulture); } } /// /// 查找类似 Set system proxy 的本地化字符串。 /// public static string TbSetSystemProxy { get { return ResourceManager.GetString("TbSetSystemProxy", resourceCulture); } } /// /// 查找类似 Click to import default DNS config 的本地化字符串。 /// public static string TbSettingDnsImportDefConfig { get { return ResourceManager.GetString("TbSettingDnsImportDefConfig", resourceCulture); } } /// /// 查找类似 Advanced proxy settings, protocol selection (optional) 的本地化字符串。 /// public static string TbSettingsAdvancedProtocol { get { return ResourceManager.GetString("TbSettingsAdvancedProtocol", resourceCulture); } } /// /// 查找类似 Allow connections from the LAN 的本地化字符串。 /// public static string TbSettingsAllowLAN { get { return ResourceManager.GetString("TbSettingsAllowLAN", resourceCulture); } } /// /// 查找类似 Auto hide on startup 的本地化字符串。 /// public static string TbSettingsAutoHideStartup { get { return ResourceManager.GetString("TbSettingsAutoHideStartup", resourceCulture); } } /// /// 查找类似 Automatic update interval for Geo files (hours) 的本地化字符串。 /// public static string TbSettingsAutoUpdateInterval { get { return ResourceManager.GetString("TbSettingsAutoUpdateInterval", resourceCulture); } } /// /// 查找类似 Users in China region can ignore this item 的本地化字符串。 /// public static string TbSettingsChinaUserTip { get { return ResourceManager.GetString("TbSettingsChinaUserTip", resourceCulture); } } /// /// 查找类似 Color 的本地化字符串。 /// public static string TbSettingsColor { get { return ResourceManager.GetString("TbSettingsColor", resourceCulture); } } /// /// 查找类似 Core: basic settings 的本地化字符串。 /// public static string TbSettingsCore { get { return ResourceManager.GetString("TbSettingsCore", resourceCulture); } } /// /// 查找类似 Core: KCP settings 的本地化字符串。 /// public static string TbSettingsCoreKcp { get { return ResourceManager.GetString("TbSettingsCoreKcp", resourceCulture); } } /// /// 查找类似 Core Type settings 的本地化字符串。 /// public static string TbSettingsCoreType { get { return ResourceManager.GetString("TbSettingsCoreType", resourceCulture); } } /// /// 查找类似 Font family (requires restart) 的本地化字符串。 /// public static string TbSettingsCurrentFontFamily { get { return ResourceManager.GetString("TbSettingsCurrentFontFamily", resourceCulture); } } /// /// 查找类似 Install the font to the system, select or fill in the font name, restart the settings 的本地化字符串。 /// public static string TbSettingsCurrentFontFamilyLinuxTip { get { return ResourceManager.GetString("TbSettingsCurrentFontFamilyLinuxTip", resourceCulture); } } /// /// 查找类似 Copy the font TTF/TTC file to the directory gui Fonts; Reopen the settings window 的本地化字符串。 /// public static string TbSettingsCurrentFontFamilyTip { get { return ResourceManager.GetString("TbSettingsCurrentFontFamilyTip", resourceCulture); } } /// /// 查找类似 Custom PAC file path 的本地化字符串。 /// public static string TbSettingsCustomSystemProxyPacPath { get { return ResourceManager.GetString("TbSettingsCustomSystemProxyPacPath", resourceCulture); } } /// /// 查找类似 Custom system proxy script file path 的本地化字符串。 /// public static string TbSettingsCustomSystemProxyScriptPath { get { return ResourceManager.GetString("TbSettingsCustomSystemProxyScriptPath", resourceCulture); } } /// /// 查找类似 Allow Insecure 的本地化字符串。 /// public static string TbSettingsDefAllowInsecure { get { return ResourceManager.GetString("TbSettingsDefAllowInsecure", resourceCulture); } } /// /// 查找类似 Default TLS fingerprint 的本地化字符串。 /// public static string TbSettingsDefFingerprint { get { return ResourceManager.GetString("TbSettingsDefFingerprint", resourceCulture); } } /// /// 查找类似 User-Agent 的本地化字符串。 /// public static string TbSettingsDefUserAgent { get { return ResourceManager.GetString("TbSettingsDefUserAgent", resourceCulture); } } /// /// 查找类似 This parameter is valid only for tcp/http and ws 的本地化字符串。 /// public static string TbSettingsDefUserAgentTips { get { return ResourceManager.GetString("TbSettingsDefUserAgentTips", resourceCulture); } } /// /// 查找类似 Sniffing type 的本地化字符串。 /// public static string TbSettingsDestOverride { get { return ResourceManager.GetString("TbSettingsDestOverride", resourceCulture); } } /// /// 查找类似 Display real-time speed (requires restart) 的本地化字符串。 /// public static string TbSettingsDisplayRealTimeSpeed { get { return ResourceManager.GetString("TbSettingsDisplayRealTimeSpeed", resourceCulture); } } /// /// 查找类似 Outbound DNS address 的本地化字符串。 /// public static string TbSettingsDomainDNSAddress { get { return ResourceManager.GetString("TbSettingsDomainDNSAddress", resourceCulture); } } /// /// 查找类似 Outbound Freedom domain Strategy 的本地化字符串。 /// public static string TbSettingsDomainStrategy4Freedom { get { return ResourceManager.GetString("TbSettingsDomainStrategy4Freedom", resourceCulture); } } /// /// 查找类似 Default domain strategy for outbound 的本地化字符串。 /// public static string TbSettingsDomainStrategy4Out { get { return ResourceManager.GetString("TbSettingsDomainStrategy4Out", resourceCulture); } } /// /// 查找类似 Double-clicking Configuration makes it active 的本地化字符串。 /// public static string TbSettingsDoubleClick2Activate { get { return ResourceManager.GetString("TbSettingsDoubleClick2Activate", resourceCulture); } } /// /// 查找类似 Automatically adjust column width after subscription update 的本地化字符串。 /// public static string TbSettingsEnableAutoAdjustMainLvColWidth { get { return ResourceManager.GetString("TbSettingsEnableAutoAdjustMainLvColWidth", resourceCulture); } } /// /// 查找类似 Enable cache file for sing-box (ruleset files) 的本地化字符串。 /// public static string TbSettingsEnableCacheFile4Sbox { get { return ResourceManager.GetString("TbSettingsEnableCacheFile4Sbox", resourceCulture); } } /// /// 查找类似 Check for pre-release updates 的本地化字符串。 /// public static string TbSettingsEnableCheckPreReleaseUpdate { get { return ResourceManager.GetString("TbSettingsEnableCheckPreReleaseUpdate", resourceCulture); } } /// /// 查找类似 Enable sorting Configurations by drag-n-drop (requires restart) 的本地化字符串。 /// public static string TbSettingsEnableDragDropSort { get { return ResourceManager.GetString("TbSettingsEnableDragDropSort", resourceCulture); } } /// /// 查找类似 Enable fragment 的本地化字符串。 /// public static string TbSettingsEnableFragment { get { return ResourceManager.GetString("TbSettingsEnableFragment", resourceCulture); } } /// /// 查找类似 Enable hardware acceleration (requires restart) 的本地化字符串。 /// public static string TbSettingsEnableHWA { get { return ResourceManager.GetString("TbSettingsEnableHWA", resourceCulture); } } /// /// 查找类似 Enable IPv6 Address 的本地化字符串。 /// public static string TbSettingsEnableIPv6Address { get { return ResourceManager.GetString("TbSettingsEnableIPv6Address", resourceCulture); } } /// /// 查找类似 Exception 的本地化字符串。 /// public static string TbSettingsException { get { return ResourceManager.GetString("TbSettingsException", resourceCulture); } } /// /// 查找类似 Exclusions: Do not use proxy server for addresses beginning with the following. Use semicolon (;) to separate entries. 的本地化字符串。 /// public static string TbSettingsExceptionTip { get { return ResourceManager.GetString("TbSettingsExceptionTip", resourceCulture); } } /// /// 查找类似 Exclusions: Do not use proxy server for the following addresses. Use comma (,) to separate entries. 的本地化字符串。 /// public static string TbSettingsExceptionTip2 { get { return ResourceManager.GetString("TbSettingsExceptionTip2", resourceCulture); } } /// /// 查找类似 Font Size 的本地化字符串。 /// public static string TbSettingsFontSize { get { return ResourceManager.GetString("TbSettingsFontSize", resourceCulture); } } /// /// 查找类似 Geo files source (optional) 的本地化字符串。 /// public static string TbSettingsGeoFilesSource { get { return ResourceManager.GetString("TbSettingsGeoFilesSource", resourceCulture); } } /// /// 查找类似 Hide to tray when closing the window 的本地化字符串。 /// public static string TbSettingsHide2TrayWhenClose { get { return ResourceManager.GetString("TbSettingsHide2TrayWhenClose", resourceCulture); } } /// /// 查找类似 If the system does not have a tray function, please do not enable it 的本地化字符串。 /// public static string TbSettingsHide2TrayWhenCloseTip { get { return ResourceManager.GetString("TbSettingsHide2TrayWhenCloseTip", resourceCulture); } } /// /// 查找类似 Hysteria Max bandwidth (Up/Down) 的本地化字符串。 /// public static string TbSettingsHysteriaBandwidth { get { return ResourceManager.GetString("TbSettingsHysteriaBandwidth", resourceCulture); } } /// /// 查找类似 Current connection info test URL 的本地化字符串。 /// public static string TbSettingsIPAPIUrl { get { return ResourceManager.GetString("TbSettingsIPAPIUrl", resourceCulture); } } /// /// 查找类似 Keep older entries when de-duplicating 的本地化字符串。 /// public static string TbSettingsKeepOlderDedupl { get { return ResourceManager.GetString("TbSettingsKeepOlderDedupl", resourceCulture); } } /// /// 查找类似 Language (Restart) 的本地化字符串。 /// public static string TbSettingsLanguage { get { return ResourceManager.GetString("TbSettingsLanguage", resourceCulture); } } /// /// 查找类似 System sudo password 的本地化字符串。 /// public static string TbSettingsLinuxSudoPassword { get { return ResourceManager.GetString("TbSettingsLinuxSudoPassword", resourceCulture); } } /// /// 查找类似 The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart. 的本地化字符串。 /// public static string TbSettingsLinuxSudoPasswordTip { get { return ResourceManager.GetString("TbSettingsLinuxSudoPasswordTip", resourceCulture); } } /// /// 查找类似 Enable Log 的本地化字符串。 /// public static string TbSettingsLogEnabled { get { return ResourceManager.GetString("TbSettingsLogEnabled", resourceCulture); } } /// /// 查找类似 Enable logging to file 的本地化字符串。 /// public static string TbSettingsLogEnabledToFile { get { return ResourceManager.GetString("TbSettingsLogEnabledToFile", resourceCulture); } } /// /// 查找类似 Log Level 的本地化字符串。 /// public static string TbSettingsLogLevel { get { return ResourceManager.GetString("TbSettingsLogLevel", resourceCulture); } } /// /// 查找类似 macOS displays this in the Dock (requires restart) 的本地化字符串。 /// public static string TbSettingsMacOSShowInDock { get { return ResourceManager.GetString("TbSettingsMacOSShowInDock", resourceCulture); } } /// /// 查找类似 Main layout orientation (requires restart) 的本地化字符串。 /// public static string TbSettingsMainGirdOrientation { get { return ResourceManager.GetString("TbSettingsMainGirdOrientation", resourceCulture); } } /// /// 查找类似 The number of concurrent tests during multi-test 的本地化字符串。 /// public static string TbSettingsMixedConcurrencyCount { get { return ResourceManager.GetString("TbSettingsMixedConcurrencyCount", resourceCulture); } } /// /// 查找类似 sing-box Mux Protocol 的本地化字符串。 /// public static string TbSettingsMux4SboxProtocol { get { return ResourceManager.GetString("TbSettingsMux4SboxProtocol", resourceCulture); } } /// /// 查找类似 Turn on Mux Multiplexing 的本地化字符串。 /// public static string TbSettingsMuxEnabled { get { return ResourceManager.GetString("TbSettingsMuxEnabled", resourceCulture); } } /// /// 查找类似 v2rayN settings 的本地化字符串。 /// public static string TbSettingsN { get { return ResourceManager.GetString("TbSettingsN", resourceCulture); } } /// /// 查找类似 New Port for LAN 的本地化字符串。 /// public static string TbSettingsNewPort4LAN { get { return ResourceManager.GetString("TbSettingsNewPort4LAN", resourceCulture); } } /// /// 查找类似 Do not use proxy servers for local (intranet) addresses 的本地化字符串。 /// public static string TbSettingsNotProxyLocalAddress { get { return ResourceManager.GetString("TbSettingsNotProxyLocalAddress", resourceCulture); } } /// /// 查找类似 Auth pass 的本地化字符串。 /// public static string TbSettingsPass { get { return ResourceManager.GetString("TbSettingsPass", resourceCulture); } } /// /// 查找类似 Custom DNS (multiple, separated by commas (,)) 的本地化字符串。 /// public static string TbSettingsRemoteDNS { get { return ResourceManager.GetString("TbSettingsRemoteDNS", resourceCulture); } } /// /// 查找类似 Route Only 的本地化字符串。 /// public static string TbSettingsRouteOnly { get { return ResourceManager.GetString("TbSettingsRouteOnly", resourceCulture); } } /// /// 查找类似 Routing rules source (optional) 的本地化字符串。 /// public static string TbSettingsRoutingRulesSource { get { return ResourceManager.GetString("TbSettingsRoutingRulesSource", resourceCulture); } } /// /// 查找类似 Enable second mixed port 的本地化字符串。 /// public static string TbSettingsSecondLocalPortEnabled { get { return ResourceManager.GetString("TbSettingsSecondLocalPortEnabled", resourceCulture); } } /// /// 查找类似 Set Win10 UWP Loopback 的本地化字符串。 /// public static string TbSettingsSetUWP { get { return ResourceManager.GetString("TbSettingsSetUWP", resourceCulture); } } /// /// 查找类似 Turn on Sniffing 的本地化字符串。 /// public static string TbSettingsSniffingEnabled { get { return ResourceManager.GetString("TbSettingsSniffingEnabled", resourceCulture); } } /// /// 查找类似 Mixed Port 的本地化字符串。 /// public static string TbSettingsSocksPort { get { return ResourceManager.GetString("TbSettingsSocksPort", resourceCulture); } } /// /// 查找类似 Pac port = +3; Xray API port = +4; mihomo API port = +5; 的本地化字符串。 /// public static string TbSettingsSocksPortTip { get { return ResourceManager.GetString("TbSettingsSocksPortTip", resourceCulture); } } /// /// 查找类似 Speed Ping Test URL 的本地化字符串。 /// public static string TbSettingsSpeedPingTestUrl { get { return ResourceManager.GetString("TbSettingsSpeedPingTestUrl", resourceCulture); } } /// /// 查找类似 Speed Test Single Timeout Value 的本地化字符串。 /// public static string TbSettingsSpeedTestTimeout { get { return ResourceManager.GetString("TbSettingsSpeedTestTimeout", resourceCulture); } } /// /// 查找类似 Speed Test URL 的本地化字符串。 /// public static string TbSettingsSpeedTestUrl { get { return ResourceManager.GetString("TbSettingsSpeedTestUrl", resourceCulture); } } /// /// 查找类似 sing-box ruleset files source (optional) 的本地化字符串。 /// public static string TbSettingsSrsFilesSource { get { return ResourceManager.GetString("TbSettingsSrsFilesSource", resourceCulture); } } /// /// 查找类似 Start on boot 的本地化字符串。 /// public static string TbSettingsStartBoot { get { return ResourceManager.GetString("TbSettingsStartBoot", resourceCulture); } } /// /// 查找类似 Set this with admin privileges, get admin privileges after startup 的本地化字符串。 /// public static string TbSettingsStartBootTip { get { return ResourceManager.GetString("TbSettingsStartBootTip", resourceCulture); } } /// /// 查找类似 Enable traffic statistics (requires restart) 的本地化字符串。 /// public static string TbSettingsStatistics { get { return ResourceManager.GetString("TbSettingsStatistics", resourceCulture); } } /// /// 查找类似 Subscription conversion URL 的本地化字符串。 /// public static string TbSettingsSubConvert { get { return ResourceManager.GetString("TbSettingsSubConvert", resourceCulture); } } /// /// 查找类似 System proxy settings 的本地化字符串。 /// public static string TbSettingsSystemproxy { get { return ResourceManager.GetString("TbSettingsSystemproxy", resourceCulture); } } /// /// 查找类似 Theme 的本地化字符串。 /// public static string TbSettingsTheme { get { return ResourceManager.GetString("TbSettingsTheme", resourceCulture); } } /// /// 查找类似 Tray right-click menu display limit 的本地化字符串。 /// public static string TbSettingsTrayMenuServersLimit { get { return ResourceManager.GetString("TbSettingsTrayMenuServersLimit", resourceCulture); } } /// /// 查找类似 Auto Route 的本地化字符串。 /// public static string TbSettingsTunAutoRoute { get { return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture); } } /// /// 查找类似 Tun Mode settings 的本地化字符串。 /// public static string TbSettingsTunMode { get { return ResourceManager.GetString("TbSettingsTunMode", resourceCulture); } } /// /// 查找类似 MTU 的本地化字符串。 /// public static string TbSettingsTunMtu { get { return ResourceManager.GetString("TbSettingsTunMtu", resourceCulture); } } /// /// 查找类似 Stack 的本地化字符串。 /// public static string TbSettingsTunStack { get { return ResourceManager.GetString("TbSettingsTunStack", resourceCulture); } } /// /// 查找类似 Strict Route 的本地化字符串。 /// public static string TbSettingsTunStrictRoute { get { return ResourceManager.GetString("TbSettingsTunStrictRoute", resourceCulture); } } /// /// 查找类似 Enable UDP 的本地化字符串。 /// public static string TbSettingsUdpEnabled { get { return ResourceManager.GetString("TbSettingsUdpEnabled", resourceCulture); } } /// /// 查找类似 Auth user 的本地化字符串。 /// public static string TbSettingsUser { get { return ResourceManager.GetString("TbSettingsUser", resourceCulture); } } /// /// 查找类似 Use System Hosts 的本地化字符串。 /// public static string TbSettingsUseSystemHosts { get { return ResourceManager.GetString("TbSettingsUseSystemHosts", resourceCulture); } } /// /// 查找类似 Set Upstream Proxy Tag 的本地化字符串。 /// public static string TbSetUpstreamProxyDetour { get { return ResourceManager.GetString("TbSetUpstreamProxyDetour", resourceCulture); } } /// /// 查找类似 Short Id 的本地化字符串。 /// public static string TbShortId { get { return ResourceManager.GetString("TbShortId", resourceCulture); } } /// /// 查找类似 SNI 的本地化字符串。 /// public static string TbSNI { get { return ResourceManager.GetString("TbSNI", resourceCulture); } } /// /// 查找类似 Sorting 的本地化字符串。 /// public static string TbSorting { get { return ResourceManager.GetString("TbSorting", resourceCulture); } } /// /// 查找类似 Chain 的本地化字符串。 /// public static string TbSortingChain { get { return ResourceManager.GetString("TbSortingChain", resourceCulture); } } /// /// 查找类似 Default 的本地化字符串。 /// public static string TbSortingDefault { get { return ResourceManager.GetString("TbSortingDefault", resourceCulture); } } /// /// 查找类似 Delay 的本地化字符串。 /// public static string TbSortingDelay { get { return ResourceManager.GetString("TbSortingDelay", resourceCulture); } } /// /// 查找类似 Download Speed 的本地化字符串。 /// public static string TbSortingDownSpeed { get { return ResourceManager.GetString("TbSortingDownSpeed", resourceCulture); } } /// /// 查找类似 Download Traffic 的本地化字符串。 /// public static string TbSortingDownTraffic { get { return ResourceManager.GetString("TbSortingDownTraffic", resourceCulture); } } /// /// 查找类似 Host 的本地化字符串。 /// public static string TbSortingHost { get { return ResourceManager.GetString("TbSortingHost", resourceCulture); } } /// /// 查找类似 Name 的本地化字符串。 /// public static string TbSortingName { get { return ResourceManager.GetString("TbSortingName", resourceCulture); } } /// /// 查找类似 Network 的本地化字符串。 /// public static string TbSortingNetwork { get { return ResourceManager.GetString("TbSortingNetwork", resourceCulture); } } /// /// 查找类似 Time 的本地化字符串。 /// public static string TbSortingTime { get { return ResourceManager.GetString("TbSortingTime", resourceCulture); } } /// /// 查找类似 Type 的本地化字符串。 /// public static string TbSortingType { get { return ResourceManager.GetString("TbSortingType", resourceCulture); } } /// /// 查找类似 Upload Speed 的本地化字符串。 /// public static string TbSortingUpSpeed { get { return ResourceManager.GetString("TbSortingUpSpeed", resourceCulture); } } /// /// 查找类似 Upload Traffic 的本地化字符串。 /// public static string TbSortingUpTraffic { get { return ResourceManager.GetString("TbSortingUpTraffic", resourceCulture); } } /// /// 查找类似 Spider X 的本地化字符串。 /// public static string TbSpiderX { get { return ResourceManager.GetString("TbSpiderX", resourceCulture); } } /// /// 查找类似 TLS 的本地化字符串。 /// public static string TbStreamSecurity { get { return ResourceManager.GetString("TbStreamSecurity", resourceCulture); } } /// /// 查找类似 PAC mode 的本地化字符串。 /// public static string TbSystemProxyPac { get { return ResourceManager.GetString("TbSystemProxyPac", resourceCulture); } } /// /// 查找类似 Validate Regional Domain IPs 的本地化字符串。 /// public static string TbValidateDirectExpectedIPs { get { return ResourceManager.GetString("TbValidateDirectExpectedIPs", resourceCulture); } } /// /// 查找类似 When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs 的本地化字符串。 /// public static string TbValidateDirectExpectedIPsDesc { get { return ResourceManager.GetString("TbValidateDirectExpectedIPsDesc", resourceCulture); } } /// /// 查找类似 The delay: {0} ms, {1} 的本地化字符串。 /// public static string TestMeOutput { get { return ResourceManager.GetString("TestMeOutput", resourceCulture); } } /// /// 查找类似 Advanced DNS Settings 的本地化字符串。 /// public static string ThAdvancedDNSSettings { get { return ResourceManager.GetString("ThAdvancedDNSSettings", resourceCulture); } } /// /// 查找类似 Basic DNS Settings 的本地化字符串。 /// public static string ThBasicDNSSettings { get { return ResourceManager.GetString("ThBasicDNSSettings", resourceCulture); } } /// /// 查找类似 Active 的本地化字符串。 /// public static string TipActiveServer { get { return ResourceManager.GetString("TipActiveServer", resourceCulture); } } /// /// 查找类似 Routing setting has changed 的本地化字符串。 /// public static string TipChangeRouting { get { return ResourceManager.GetString("TipChangeRouting", resourceCulture); } } /// /// 查找类似 System proxy setting has changed 的本地化字符串。 /// public static string TipChangeSystemProxy { get { return ResourceManager.GetString("TipChangeSystemProxy", resourceCulture); } } /// /// 查找类似 Please turn off when there is an abnormal disconnection 的本地化字符串。 /// public static string TipDisplayLog { get { return ResourceManager.GetString("TipDisplayLog", resourceCulture); } } /// /// 查找类似 *Default value tcp 的本地化字符串。 /// public static string TipNetwork { get { return ResourceManager.GetString("TipNetwork", resourceCulture); } } /// /// 查找类似 * After setting this value, a socks service will be started using Xray/sing-box(Tun) to provide functions such as speed display 的本地化字符串。 /// public static string TipPreSocksPort { get { return ResourceManager.GetString("TipPreSocksPort", resourceCulture); } } /// /// 查找类似 XHTTP Extra raw JSON, format: { XHTTP Object } 的本地化字符串。 /// public static string TransportExtraTip { get { return ResourceManager.GetString("TransportExtraTip", resourceCulture); } } /// /// 查找类似 *tcp camouflage type 的本地化字符串。 /// public static string TransportHeaderTypeTip1 { get { return ResourceManager.GetString("TransportHeaderTypeTip1", resourceCulture); } } /// /// 查找类似 *kcp camouflage type 的本地化字符串。 /// public static string TransportHeaderTypeTip2 { get { return ResourceManager.GetString("TransportHeaderTypeTip2", resourceCulture); } } /// /// 查找类似 *QUIC camouflage type 的本地化字符串。 /// public static string TransportHeaderTypeTip3 { get { return ResourceManager.GetString("TransportHeaderTypeTip3", resourceCulture); } } /// /// 查找类似 *grpc mode 的本地化字符串。 /// public static string TransportHeaderTypeTip4 { get { return ResourceManager.GetString("TransportHeaderTypeTip4", resourceCulture); } } /// /// 查找类似 *xhttp mode 的本地化字符串。 /// public static string TransportHeaderTypeTip5 { get { return ResourceManager.GetString("TransportHeaderTypeTip5", resourceCulture); } } /// /// 查找类似 *ws/http upgrade/xhttp path 的本地化字符串。 /// public static string TransportPathTip1 { get { return ResourceManager.GetString("TransportPathTip1", resourceCulture); } } /// /// 查找类似 *h2 path 的本地化字符串。 /// public static string TransportPathTip2 { get { return ResourceManager.GetString("TransportPathTip2", resourceCulture); } } /// /// 查找类似 *QUIC key/KCP seed 的本地化字符串。 /// public static string TransportPathTip3 { get { return ResourceManager.GetString("TransportPathTip3", resourceCulture); } } /// /// 查找类似 *grpc service name 的本地化字符串。 /// public static string TransportPathTip4 { get { return ResourceManager.GetString("TransportPathTip4", resourceCulture); } } /// /// 查找类似 *kcp seed 的本地化字符串。 /// public static string TransportPathTip5 { get { return ResourceManager.GetString("TransportPathTip5", resourceCulture); } } /// /// 查找类似 *http host separated by commas (,) 的本地化字符串。 /// public static string TransportRequestHostTip1 { get { return ResourceManager.GetString("TransportRequestHostTip1", resourceCulture); } } /// /// 查找类似 *ws/http upgrade/xhttp host 的本地化字符串。 /// public static string TransportRequestHostTip2 { get { return ResourceManager.GetString("TransportRequestHostTip2", resourceCulture); } } /// /// 查找类似 *h2 host separated by commas (,) 的本地化字符串。 /// public static string TransportRequestHostTip3 { get { return ResourceManager.GetString("TransportRequestHostTip3", resourceCulture); } } /// /// 查找类似 *QUIC security 的本地化字符串。 /// public static string TransportRequestHostTip4 { get { return ResourceManager.GetString("TransportRequestHostTip4", resourceCulture); } } /// /// 查找类似 *grpc Authority 的本地化字符串。 /// public static string TransportRequestHostTip5 { get { return ResourceManager.GetString("TransportRequestHostTip5", resourceCulture); } } /// /// 查找类似 Upgrade App does not exist 的本地化字符串。 /// public static string UpgradeAppNotExistTip { get { return ResourceManager.GetString("UpgradeAppNotExistTip", resourceCulture); } } } } ================================================ FILE: v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ارسال دسته ای آدرس اینترنتی اشتراک گذاری به کلیپ بورد با موفقیت. لطفا ابتدا تنظیمات سرور را بررسی کنید فرمت پیکربندی نادرست است توجه داشته باشید که پیکربندی سفارشی کاملاً به پیکربندی خود شما بستگی دارد و با همه تنظیمات کار نمی کند. اگر می خواهید از پروکسی سیستم استفاده کنید، لطفاً پورت درحال شنود را به صورت دستی تغییر دهید. درحال دانلود... تبدیل فایل پیکربندی انجام نشد فایل پیکربندی پیش فرض ایجاد نشد پیکربندی پیش فرض دریافت نشد سرور پیکربندی سفارشی وارد نشد فایل پیکربندی خوانده نشد لطفا فرمت صحیح پورت سرور را وارد کنید. لطفاً پورت گوش دادن محلی را وارد کنید. لطفا رمز عبور را وارد کنید. لطفا آدرس سرور را وارد کنید. لطفا شناسه کاربری را وارد کنید. این پیکربندی صحیحی نیست، لطفا بررسی کنید. پیکربندی اولیه {0} {1} در حال حاضر به روز است. {0} {1} در حال حاضر به روز است. آدرس امنیت پورت نوع گروه فرعی ترافیک دانلود امروز ترافیک اپلود امروز کل ترافیک دانلود کل ترافیک آپلود جابجایی هسته با موفقیت دانلود شد محتوای اشتراک با موفقیت وارد نشد محتوای اشتراک با موفقیت دریافت شد هیچ اشتراک معتبری تنظیم نشده است {0} با موفقیت حل شد شروع به دریافت اشتراک کرد شروع به‌روزرسانی {0}... محتوای اشتراک نامعتبر است باز کردن بسته بندی... بروزرسانی اشتراک به پایان رسید بروزرسانی اشتراک شروع شد هسته با موفقیت بروزرسانی شد هسته با موفقیت بروزرسانی شد! راه اندازی مجدد سرویس... پروتکل غیر VMess یا ss فایل Core (نام فایل: {1}) در زیر پوشه ({0}) یافت نشد، لطفاً آن را دانلود کرده و در پوشه قرار دهید، آدرس دانلود: {2} اسکن کامل شد، QRcode معتبری یافت نشد عملیات انجام نشد، لطفا بررسی کنید و دوباره امتحان کنید لطفا ملاحظات را پر کنید لطفاً روش رمزگذاری را انتخاب کنید لطفا یک پروتکل انتخاب کنید لطفا ابتدا سرور را انتخاب کنید حذف مجدد سرورها تکمیل شد. قدیمی: {0}، جدید: {1}. آیا مطمئن هستید که می خواهید سرور را حذف کنید؟ فایل پیکربندی کلاینت در این آدرس ذخیره می شود: {0} شروع سرویس ({0})... پیکربندی با موفقیت انجام شد{0} سرور پیکربندی سفارشی با موفقیت وارد شد. {0} سرورها از کلیپ بورد وارد شده اند. آدرس اینترنتی با موفقیت اسکن و وارد شد پینگ سرویس فعلی: {0} ms, {1} عملیات با موفقیت انجام شد لطفا قوانین را انتخاب کنید آیا مطمئن هستید که می خواهید قوانین را حذف کنید؟ {0}، یکی از فیلدهای الزامی است. ملاحظات آدرس اینترنتی (اختیاری) شمارش لطفا آدرس (آدرس اینترنتی) را وارد کنید آآیا می خواهید قوانین را اضافه کنید؟ بله برای افزودن، نه برای جایگزینی را انتخاب کنید. GeoFile بارگیری شد: {0} با موفقیت اطلاعات نماد سفارشی لطفاً DNS سفارشی صحیح را پر کنید * مسیر ws * مسیر h2 *QUIC key/Kcp seed *grpc serviceName *هاست http جدا شده با کاما (،) *هاست ws *هاست h2 با کاما (،) جدا شده است *QUIC securty *tcp camouflage type *kcp camouflage type *QUIC camouflage type *حالت grpc TLS *Kcp seed ثبت کلید جهانی {0} انجام نشد، دلیل: {1} کلید میانبر جهانی {0} با موفقیت ثبت شد همه سرورها لطفاً برای وارد کردن پیکربندی سرور مرور کنید درحال تست کردن... شبکه محلی محلی فیلتر سرورها بررسی بروزرسانی بستن خروج تنظیم کلید میانبر کلی کمک تنظیمات پارامتر ترفیع بارگذاری مجدد تنظیم مسیریابی سرورها تنظیمات اشتراک فعلی را بدون پروکسی به روز شود اشتراک فعلی را با پروکسی به روز شود گروه اشتراک تنظیم گروه اشتراک اشتراک را بدون پروکسی به روز شود اشتراک را با پروکسی به روز شود پروکسی سیستم پاک کردن پروکسی سیستم پروکسی سیستم تغییر نکند حالت Pac تنظیم پراکسی سیستم رنگ زبان وارد کردن URL انبوه از کلیپ بورد اسکن کد QR روی صفحه سرور انتخاب شده را شبیه سازی کنید سرورهای تکراری را حذف کنید حذف سرورهای انتخابی به عنوان سرور فعال تنظیم کنید تمام آمار خدمات را پاک کنید آزمایش سرورها با تاخیر واقعی مرتب سازی بر اساس نتیجه تست تست سرعت دانلود سرورها تست سرورها با tcping سرور انتخابی را برای پیکربندی کلاینت صادر کنید URL های اشتراک گذاری را به کلیپ بورد صادر کنید یک سرور پیکربندی سفارشی اضافه شود سرور [شادوساکس] را اضافه کنید سرور [ساکس] را اضافه کنید سرور [تروجان] را اضافه کنید سرور [VLESS] را اضافه کنید سرور [VMess] را اضافه کنید انتخاب همه همه را پاک کن کپی کپی همه انتخاب همه اضافه کردن حذف ویرایش اشتراک به روز رسانی فعال شد مرتب سازی عامل کاربر لغو تایید انتقال آدرس اعطای مجوز ناامن Alpn AlterId اثرانگشت نوع Camouflage UUID(id) پروتکل جابجایی (شبکه) مسیر پورت نام مستعار (ملاحظات) Camouflage domain(host) روش رمزگذاری (امنیتی) SNI TLS *مقدار پیش فرض tcp نوع هسته جریان ساختن رمزعبور رمز عبور (اختیاری) UUID(id) رمزگذاری کاربر (اختیاری) رمزگذاری txtPreSocksPort * پس از تنظیم این مقدار، یک سرویس جوراب با استفاده از Xray/sing-box (Tun) برای ارائه عملکردهایی مانند نمایش سرعت شروع می شود. مرور کردن ویرایش تنظیمات پیشرفته پروکسی، انتخاب پروتکل (اختیاری) اتصالات از LAN را مجاز کنید مخفی کردن خودکار هنگام راه اندازی فاصله به روز رسانی خودکار برای فایل های Geo (ساعت) هسته: تنظیمات اولیه V2ray Custom DNS هسته: تنظیمات KCP تنظیمات CoreType اعطای مجوز ناامن Outbound Freedom domainStrategy پس از به‌روزرسانی اشتراک، عرض ستون به صورت خودکار تنظیم شود به روز رسانی های پیش از انتشار را بررسی شود استثنا استثناها: از سرور پروکسی برای آدرس هایی که با موارد زیر شروع می شوند استفاده نکنید. برای جدا کردن ورودی ها از نقطه ویرگول (;) استفاده کنید. نمایش سرعت واقعی (نیاز به راه اندازی مجدد) ورودی‌های قدیمی‌تر را هنگام حذف کپی نگه دارید ثبت گزارش های محلی سطح ثبت رویداد فعال کردن Mux Multiplexing تنظیمات v2rayN مجوز احراز هویت سفارشی DNS (multiple, separated by commas (,)) تنظیم کردن Win10 UWP Loopback فعال کردن Sniffing پورت Mixed درهنگام راه ائدازی شروع شود فعال کردن آمار ترافیک (نیاز به راه اندازی مجدد) آدرس اینترنتی تبدیل اشتراک تنظیمات پراکسی سیستم محدودیت نمایش سرورهای منوی سینی کلیک راست فعال سازی UDP تایید کاربر پاک کردن پروکسی سیستم نمایش رابط کاربری گرافیکی تنظیم کلید میانبر جهانی مستقیماً با فشار دادن صفحه کلید تنظیم کنید. پس از راه اندازی مجدد اعمال می شود پروکسی سیستم را تغییر ندهید بازنشانی تنظیم پراکسی سیستم حالت Pac اشتراک گذاری سرور(Ctrl+F) مسیریابی به عنوان ادمین اجرا نمی شود اجرا به عنوان ادمین به پایین حرکت شود(B) پایین حرکت به بالا بالا فیلتر، از عبارات منظم پشتیبانی می کند {0} وب سایت اضافه کردن وارد کردن مجموعه قوانین حذف انتخاب شده تنظیم کردن به عنوان قانون فعال استراتژی دامنه لیست مجموعه قوانین از پیش تعریف شده *قوانین را با کاما (,) جدا کنید. برای کاما به معنای واقعی کلمه از <COMMA>; پیشوند # برای نادیده گرفتن یک قانون وارد کردن قوانین از کلیپ بورد وارد کردن قوانین از فایل وارد کردن قوانین از آدرس اینترنتی Sub تنظیم قانون اضافه کردن قانون صادر کردن قوانین انتخاب شده فهرست قوانین حذف قوانین تنظیم جزئیات قانون مسیریابی دامنه و آی پی در هنگام ذخیره به طور خودکار مرتب می شوند مستندات شی قانون پشتیبانی از DnsObject. برای مشاهده مستندات کلیک کنید گروه لطفا اینجا را خالی بگذارید تنظیمات مسیریابی تغییر کرده است تنظیمات پراکسی سیستم تغییر کرده است فقط مسیر از سرورهای پروکسی برای آدرس های محلی (اینترانت) استفاده نکنید تاخیر و سرعت تست با یک کلیک (Ctrl+E) تاخیر (میلی‌ثانیه) سرعت (MB/s) Core اجرا نشد، لطفاً گزارش را ببینید Remarks regular filter نمایش گزارش فعال سازی Tun پورت جدید برای LAN تنظیمات TunMode انتقال به گروه فعال کردن مرتب‌سازی سرورها با کشیدن و رها کردن (نیاز به راه‌اندازی مجدد) بازخوانی خودکار رد شدن از آزمون ویرایش سرور دوبار کلیک کردن سرور باعث فعال شدن آن می شود تست تکمیل شد اثر انگشت tls پیش فرض User-Agent این پارامتر فقط برای tcp/http و ws معتبر است FontFamily (نیاز به راه اندازی مجدد) فایل TTF/TTC فونت را در دایرکتوری guiFonts کپی کنید. پنجره تنظیمات را دوباره باز کنید پورت Pac = +3; پورت Xray API = +4; پورت mihomo API = +5; این را با امتیازات ادمین تنظیم کنید، پس از راه اندازی، امتیازات مدیر را دریافت کنید اندازه فونت یمقدار تاخیر تست سرعت منفرد /آدرس اینترنتی SpeedTest بالا و پایین حرکت کنید PublicKey ShortId SpiderX فعال‌ سازی شتاب‌ دهنده سخت‌ افزاری (نیاز به راه‌اندازی مجدد) در انتظار آزمایش... برای پایان دادن به ESC فشار دهید لطفاً در صورت قطع غیرعادی آن را خاموش کنید به روز رسانی ها فعال نیستند، از این اشتراک رد شوید به عنوان مدیر راه اندازی مجدد نشانی‌های وب بیشتر که با کاما از هم جدا شده‌اند. تبدیل اشتراک نامعتبر خواهد بود {0} : {1}/s↑ | {2}/s↓ فاصله به روز رسانی خودکار (دقیقه) فعال کردن ورود به فایل تبدیل نوع هدف اگر نیازی به تبدیل نیست، لطفاً خالی بگذارید تنظیمات DNS sing-box Custom DNS لطفا ساختار DNS را پر کنید، برای مشاهده سند کلیک کنید برای وارد کردن تنظیمات پیش‌فرض DNS کلیک کنید استراتژی دامنه sing-box پروتکل sing-box Mux Process (Linux/Windows) IP or IP CIDR دامنه Add [Hysteria2] server حداکتر پهنای باند هیستریا (آپلود/دانلود) استفاده کردن از System Hosts افزودن سرور [TUIC] کنترل تراکم نام مستعار پروکسی قبلی نام مستعار پروکسی بعدی لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند مسیریابی خودکار مسیریابی سخت‌گیرانه پشته شبکه MTU فعال سازی آدرس IPv6 افزودن سرور [WireGuard] کلید خصوصی Reserved (2,3,4) آدرس (IPv4, IPv6) پسورد obfs (Domain or IP or ProcName) and Port and Protocol and InboundTag => OutboundTag خودکار ScrollToEnd آدرس اینترنتی تست پینگ سرعت پایان تست... *grpc Authority افزودن سرور [HTTP] فعال کردن فرگمنت فعال کردن کش فایل مجموعه قوانین برای sing-box سفارش سازی مجموعه قوانین sing box عملکرد موفقیت آمیز بود، روی منوی تنظیمات کلیک کنید تا برنامه راه اندازی مجدد شود. باز کردن محل ذخیره سازی مرتب سازی Chain پیش فرض تاخیر سرعت دانلود ترافیک دانلود هاست نام شبکه زمان نوع سرعت اپلود ترافیک آپلود اتصالات بستن اتصال تمام اتصالات را ببندید پروکسی نوع قانون مستقیم جهانی تغییر نده قانون تست تأخیر تست تاخیر قسمت گره (نقطه اتصال) تازه سازی پروکسی ها انتخاب گره فعال استراتژی دامنه پیش فرض برای خروجی جهت چیدمان اصلی (نیاز به راه اندازی مجدد) آدرس DNS خروجی تنظیم خودکار عرض ستون صادر کردن پیوندهای اشتراک گذاری کدگذاری شده با Base64 به کلیپ بورد صادر کردن سرور انتخاب شده برای پیکربندی کامل به کلیپ بورد نمایش یا پنهان کردن پنجره اصلی پیکربندی سفارشی ساکس پورت پشتیبان گیری و بازیابی پشتیبان گیری به محلی بازیابی از محلی پشتیبان گیری از راه دور (WebDAV) بازیابی از راه دور (WebDAV) محلی از راه دور (WebDAV) آدرس اینترنتی WebDav نام کاربری WebDav پسورد WebDav چک کردن WebDav نام پوشه راه دور (اختیاری) فایل پشتیبان نامعتبر است فیلتر هسته فعال سازی منبع فایل های جغرافیایی (اختیاری) منبع فایل های مجموعه قوانین sing-box (اختیاری) برنامه ارتقا وجود ندارد منبع قوانین مسیریابی (اختیاری) تنظیمات از پیش تعیین شده منطقه ای پیش فرض روسیه ایران کاربران در منطقه چین می توانند این مورد را نادیده بگیرند اسکن کردن QRcode موجود در تصویر آدرس نامعتبر (آدرس اینترنتی) لطفاً از آدرس اشتراک پروتکل HTTP ناامن استفاده نکنید فونت را روی سیستم نصب کنید و تنظیمات را مجددا راه اندازی کنید آیا مطمئن هستید که خارج می شوید؟ یادداشت ملاحظات رمز عبور sudo سیستم The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart. *حالت xhttp جیسون خام XHTTP Extra, فرمت: { XHTTPObject } هنگام بستن پنجره در سینی پنهان شوید تعداد همزمان در طول چند آزمون موارد استثنا: از سرور پروکسی برای آدرس های زیر استفاده نکنید. برای جدا کردن ورودی ها از کاما (،) استفاده کنید. نوع Sniffing فعال کردن دومین پورت ترکیبی socks:پورت محلی، socks2: پورت دوم محلی، socks3: پورت LAN تم کپی کردن دستور پروکسی در کلیپ بورد شروع آزمایش مجدد قطعات ناموفق، {0} باقی مانده است. برای خاتمه ESC را فشار دهید... با نتیجه آزمایش حذف نامعتبر با نتایج آزمایش {0} نتایج آزمایش نامعتبر حذف شد. محدوده پورت سرور مخفی و پورت می شود، با کاما (،) جدا می شود صادر کردن سرور URL آزمایش اطلاعات اتصال فعلی Can fill in the configuration remarks, please make sure it exist and are unique Incorrect password, please try again. Mldsa65Verify Add [Anytls] Configuration Remote DNS Domestic DNS Direct Target Resolution Strategy Proxy Target Resolution Strategy Add Common DNS Hosts FakeIP Block SVCB and HTTPS Queries DNS Hosts: ("domain1 ip1 ip2" per line) Basic DNS Settings Advanced DNS Settings Validate Regional Domain IPs When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs Enable Custom DNS Custom DNS Enabled, This Page's Settings Invalid Block ECH and HTTP/3 availability checks when enabled Please fill in the correct config template Full Config Template Setting Enable Full Config Template v2ray Full Config Template Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document Do Not Add Non-Proxy Protocol Outbound Set Upstream Proxy Tag sing-box Full Config Template Add Outbound and Endpoint Config Only, Click to view the document This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. Start parsing and processing subscription content Select Profile Applies globally by default, with built-in FakeIP filtering (sing-box only). Please Add At Least One Configuration Policy Group Proxy Chain Lowest Latency Random Round Robin Most Stable Policy Group Type Add Policy Group Configuration Add Proxy Chain Configuration Add Child Configuration Remove Child Configuration Configuration item 1, Auto add from subscription group Fallback Core '{0}' does not support network type '{1}' Core '{0}' does not support protocol '{1}' when using transport '{2}' Core '{0}' does not support protocol '{1}' The {0} property is invalid, please check Not support protocol '{0}' If the system does not have a tray function, please do not enable it Rule Type You can set separate rules for Routing and DNS, or select "ALL" to apply to both Bootstrap DNS Resolve DNS server domains, requires IP Test real delay Auto add filtered configuration from subscription groups Certificate Pinning Server Certificate (PEM format, optional) When specified, the certificate will be pinned, and "Allow Insecure" will be disabled. The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. Fetch Certificate Fetch Certificate Chain Please set a valid domain Certificate not set Certificate set Custom PAC file path Custom system proxy script file path macOS displays this in the Dock (requires restart) Configuration Item 2, Select and add from self-built EchConfigList EchForceQuery Full certificate (chain), PEM format Certificate fingerprint (SHA-256) Serve Stale Parallel Query By default, invoked only during routing for resolution By default, invoked only during routing for resolution; ensure the remote server can reach this DNS If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used. If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. Port hopping interval Configuration item preview Finalmask Routing rule {0} outbound node {1} warning: {2} Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. Group {0} has a cycle dependency on child node {1}. Skipping this node. Group {0} child node {1} warning: {2} Group {0} child node {1} error: {2}. Skipping this node. Group {0} child group node {1} warning: {2} Group {0} child group node {1} error: {2}. Skipping this node. Group {0} has no valid child node. Routing rule {0} has an empty outbound tag. Fallback to proxy node only. Routing rule {0} outbound node {1} not found. Fallback to proxy node only. Subscription previous proxy {0} not found. Skipping. Subscription next proxy {0} not found. Skipping. Generate Policy Group All configurations Group by Region ================================================ FILE: v2rayN/ServiceLib/Resx/ResUI.fr.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Lien de partage exporté vers le presse-papiers avec succès Veuillez vérifier d’abord la config Format de configuration incorrect Attention : la configuration personnalisée dépend entièrement de vos paramètres et peut ne pas activer toutes les fonctions. Pour utiliser le proxy système, changez le port d’écoute manuellement. Téléchargement commencé... Échec de la conversion de la configuration Échec de la génération du fichier de configuration par défaut Échec de l’obtention de la configuration par défaut Échec de l’importation de la configuration personnalisée Échec de la lecture de la configuration Veuillez saisir un port au format correct Veuillez saisir le port d’écoute local Veuillez saisir le mot de passe Veuillez saisir l’adresse Veuillez saisir l’ID utilisateur Configuration incorrecte, veuillez vérifier Initialiser la configuration {0} {1} est déjà à jour. {0} {1} est déjà à jour. Adresse Méthode de chiffrement Port Type Grp abonn. Téléchargement du jour Téléversement du jour Total téléchargé Total téléversé Protoc. Transp. Téléchargement du Core réussi Échec de l’importation du contenu d’abonnement Contenu d’abonnement récupéré avec succès Aucun abonnement valide défini Analyse de {0} réussie Début de la récupération du contenu d’abonnement Début de la mise à jour de {0}... Contenu d’abonnement invalide Décompression en cours...... Fin de la mise à jour des abonnements Début de la mise à jour des abonnements Mise à jour du Core réussie Mise à jour du Core réussie ! Redémarrage du service... Protocole non VMess ni SS Fichier Core introuvable dans le dossier ({0}) (nom de fichier : {1}). Veuillez le télécharger puis le placer dans le dossier. Adresse de téléchargement : {2} Analyse terminée, aucun QR code valide trouvé Échec de l’opération, veuillez vérifier et réessayer Veuillez saisir un alias Veuillez sélectionner une méthode de chiffrement Veuillez sélectionner un protocole Sélectionnez d’abord une configuration Déduplication des configurations terminée. Quantité initiale : {0}, quantité actuelle : {1}. Confirmer la suppression ? Le fichier de configuration client est enregistré dans : {0} Démarrage du service ({0})... Configuration réussie. {0} Configuration personnalisée importée avec succès {0} configurations importées depuis le presse-papiers Lien de partage importé par analyse avec succès Latence actuelle : {0} ms, {1} Opération réussie Veuillez d’abord sélectionner une règle Confirmer la suppression de la règle ? {0}, au moins un champ est obligatoire. Alias Adresse optionnelle (URL) Quantité Veuillez saisir l’URL Ajouter les règles ? Choisir "Oui" pour ajouter, "Non" pour tout remplacer. Téléchargement du fichier Geo : {0} réussi Information Icône personnalisée Veuillez saisir un DNS personnalisé valide *chemin ws/httpupgrade/xhttp *chemin h2 *clé de chiffrement QUIC *nom de service gRPC *hôte http, séparés par des virgules (,) *hôte ws/httpupgrade/xhttp *hôte h2, séparés par des virgules (,) *méthode de chiffrement QUIC *type de camouflage tcp *type de camouflage kcp *type de camouflage QUIC *mode gRPC TLS *graine Kcp Échec de l’enregistrement du raccourci global {0}, raison : {1} Raccourci global {0} enregistré avec succès Tous Parcourir pour importer la configuration Test en cours... Réseau local Local Filtre : Entrée pour exécuter Rechercher mises à jour Fermer Quitter Paramètres des raccourcis globaux Aide Paramètres config Promotion Redémarrer Paramètres de routage Fichiers de config Paramètres Mettre à jour l’abonnement actuel (sans proxy) Mettre à jour l’abonnement actuel (via le proxy) Groupe abonnements Paramètres des groupes d’abonnement Mettre à jour tous les abonnements (sans proxy) Mettre à jour tous les abonnements (via le proxy) Proxy système Effacer le proxy système Ne pas modifier le proxy système Mode PAC Configuration auto proxy système Couleur Langue (redémarrage requis) Importer liens depuis le presse-papiers Scanner le QR code à l’écran Cloner la sélection Supprimer les doublons Supprimer la sélection (multi-sélection) Définir comme actif (Entrée) Effacer toutes les statistiques de service Tester la latence de connexion réelle (multi-sélect) Trier selon les résultats de test Tester la vitesse (multi-sélection) Tester la latence Tcping (multi-sélection) Exporter la configuration complète sélectionnée Exporter les liens de partage vers le presse-papiers (multi-sélection) Ajouter une configuration personnalisée Ajouter [Shadowsocks] Ajouter [SOCKS] Ajouter [Trojan] Ajouter [VLESS] Ajouter [VMess] Tout sélectionner Tout effacer Copier Tout copier Tout sélect Ajouter Supprimer Éditer Partager Activer MAJ Tri User-Agent (optionnel) Annuler OK Méthode de transport sous-jacente (transport) Adresse (address) Ignorer la vérification du certificat (allowInsecure) ALPN ID supplémentaire (alterId) Empreinte Type de camouflage (type) ID utilisateur (id) Protocole (network) Chemin (path) Port (port) Alias (remarks) Domaine de camouflage (host) Méthode de chiffrement (security) SNI Sécurité couche transport (TLS) *tcp par défaut ; un mauvais choix bloque la connexion Type de Core Contrôle de flux (flow) Générer Mot de passe (password) Mot de passe (optionnel) ID utilisateur (id) Mode chiffrement (encryption) Nom d’utilisateur (optionnel) Mode chiffrement (encryption) Port Socks *Valeur du port Socks (config perso, optionnelle). Si défini, Xray/sing-box (Tun) démarre un service Socks en amont supplémentaire pour fournir le routage sélectif et l’affichage de la vitesse. Parcourir Éditer Paramètres avancés du proxy, choix du protocole (optionnel) Autoriser les connexions depuis le LAN Masquer la fenêtre au démarrage Intervalle de mise à jour auto des fichiers Geo (heures) Core : paramètres de base DNS personnalisé v2ray Core : paramètres KCP Paramètres du type de Core Ignorer la vérif. du certificat (Dangereux) domainStrategy de Freedom (sortant) Auto-ajuster la largeur des colonnes après maj. abonnements Vérifier les mises à jour pré-version (à activer avec prudence) Exceptions Exceptions : pour les adresses commençant par les caractères ci-dessous, ne pas utiliser le proxy. Séparez par des points-virgules (;). Afficher la vitesse en temps réel (redémarrage requis) À la déduplication, garder l’élément au plus petit numéro Activer les journaux Niveau de journalisation Activer Mux multiplex Paramètres v2rayN Mot de passe d’authentification DNS perso (plusieurs configurables, séparés par virgules) Lever la restriction de proxy en boucle locale pour les applications Win10 UWP Activer le sniffing de trafic Port d’écoute mixte local Lancer au démarrage (peut échouer) Activer les statistiques de trafic (redémarrage requis) URL de conversion d’abonnement (optionnel) Paramètres du proxy système Limite du nombre de configurations affichées dans le menu du plateau Activer l’UDP Nom d’utilisateur d’authentification Effacer le proxy système Afficher l’interface principale Paramètres des raccourcis globaux Appuyez directement sur les touches pour définir ; prendra effet après redémarrage Ne pas modifier le proxy système Réinitialiser Configurer automatiquement le proxy système Mode PAC Partager Routage Exécuter sans droits administrateur Exécuter en tant qu’administrateur Déplacer tout en bas Descendre Déplacer tout en haut Monter Filtre (regex pris en charge) Site officiel de {0} Ajouter un jeu de règles Importer 1-clic du jeu de règles Suppr. règles sélectionnées Définir comme règles actives (Entrée) Stratégie résolution domaine Liste des jeux de règles prédef. *Règles de routage définies, séparées par des virgules (,); remplacez la virgule dans les expressions régulières par <COMMA> Importer règles du presse-papiers Importer règles depuis fichier Importer depuis URL d’abonnement Paramètres du jeu de règles Ajouter une règle Exporter les règles sélectionnées vers le presse-papiers Liste des règles Supprimer les règles sélectionnées Paramètres détaillés des règles de routage Tri automatique par domaine, IP et nom de processus lors de l’enregistrement Documentation détaillée des règles Saisie DnsObject prise en charge (format JSON), cliquer pour doc Laissez vide pour les groupes ordinaires Paramètres de routage modifiés Paramètres du proxy système modifiés Routage uniquement (routeOnly) Ne pas utiliser le proxy pour les adresses locales (Intranet) Tester latence et vitesse multithread en un clic (Ctrl+E) Latence (ms) Vitesse (Mo/s) Échec d’exécution du Core, veuillez consulter les messages Filtrage par alias (regex) Afficher les logs Activer Tun Ouvrir un nouveau port pour le LAN Paramètres du mode Tun Déplacer vers un groupe d’abonnement Activer le tri par glisser-déposer des configurations (redémarrage requis) Actualisation automatique Ignorer le test Éditer Double-cliquer sur l’interface principale pour activer Test terminé Empreinte TLS par défaut (fingerprint) Agent utilisateur (User-Agent) Valable uniquement pour les protocoles tcp/http et ws Police actuelle (redémarrage requis) Copiez les fichiers de police TTF/TTC dans le dossier guiFonts, effet après redémarrage Port PAC = +3 ; port API Xray = +4 ; port API mihomo = +5 ; Définir cette option avec des privilèges administrateur pour obtenir les droits admin au démarrage Taille de police Valeur délai d'expiration test vitesse unique URL du fichier de test de vitesse Déplacer vers haut/bas PublicKey ShortId SpiderX Activer l’accélération matérielle (redémarrage requis) En attente du test... Appuyer sur Échap pour arrêter Désactiver cette option si coupure anormale Mise à jour désactivée, abonnement ignoré Redémarrer en tant qu’administrateur Adresses suppl. (URL), séparées par des virgules ; la conversion d’abonnement sera désactivée Intervalle maj. auto (min) Activer l’enregistrement des journaux dans un fichier Type cible conv. d’abonnement Laisser vide si aucune conversion n’est nécessaire Paramètres DNS DNS personnalisé sing-box Saisissez la structure JSON DNS ; cliquez pour voir la doc. Cliquez pour importer la config DNS par défaut Stratégie résolution domaine (sing-box) Protocole de multiplexage Mux (sing-box) Process (Linux/Windows) IP ou IP CIDR Domaine Ajouter [Hysteria2] Bande passante maximale Hysteria (Up/Down) Utiliser les hosts du système Ajouter [TUIC] Algo contrôle congestion Alias de config du proxy amont Alias de config du proxy aval Assurez-vous que l’alias config existe et est unique Routage automatique Routage strict Pile de protocoles MTU Activer IPv6 Ajouter [WireGuard] PrivateKey Reserved (2,3,4) Address (IPv4,IPv6) Mot de passe d’obfuscation (obfs password) (Domaine ou IP ou nom de processus) avec Port et Protocole et InboundTag => OutboundTag Défilement automatique vers la fin Adresse de test de connexion réelle Arrêt du test en cours... *Autorité gRPC Ajouter [HTTP] Activer le fragmentation (Fragment) Activer le fichier de cache de sing-box (fichiers règles) Set de règles sing-box perso Opération réussie. Veuillez redémarrer l’app via le menu Paramètres. Ouvrir l’emplacement du fichier Tri Chaîne de routage Par défaut Latence Vitesse de téléchargement Trafic téléchargé Hôte Nom Réseau Heure Type Vitesse d’envoi Trafic envoyé Connexions en cours Fermer la connexion Fermer toutes les connexions Proxies actuels Mode règle Direct Global Suivre la configuration d’origine Règle Test de latence Test de latence partiel actuel Actualiser Définir comme actif (Entrée) Stratégie de résolution par défaut des sortants Orientation mise en page principale (redémarrage requis) Adresse de résolution de domaine pour sortants Ajuster auto. la largeur des colonnes Exporter les liens de partage en Base64 vers le presse-papiers (multi-sélection) Exporter la configuration complète sélectionnée vers le presse-papiers Afficher ou masquer l’interface principale Port Socks config personnalisée Sauvegarder et restaurer Sauvegarder localement Restaurer localement Sauvegarder à distance (WebDAV) Restaurer à distance (WebDAV) Local Distant (WebDAV) Adresse du serveur WebDAV Compte WebDAV Mot de passe WebDAV Vérification de disponibilité WebDAV Nom du dossier distant (optionnel) Fichier de sauvegarde invalide Filtre d’hôte Actif Source des fichiers Geo (optionnel) Source des fichiers de jeux de règles sing-box (optionnel) L’application d’outil de mise à niveau est introuvable Source des jeux de règles de routage (optionnel) Réglages régionaux prédéfinis Région par défaut Russie Iran Les utilisateurs de Chine peuvent ignorer Scanner le QR code dans l’image Adresse (URL) invalide N’utilisez pas d’adresse d’abonnement HTTP non sécurisée Installer la police sur le système, choisir ou saisir son nom, effet après redémarrage Voulez-vous vraiment quitter ? Mémo Mot de passe sudo système Le mot de passe sera vérifié en ligne de commande. En cas d’échec ou de dysfonctionnement, redémarrez l’application. Il n’est pas stocké et doit être saisi à chaque redémarrage. *Mode XHTTP JSON brut XHTTP Extra, format : { XHTTPObject } Masquer dans la barre d’état à la fermeture de la fenêtre Niveau de concurrence lors des tests multithread Exceptions : ne pas utiliser le proxy pour ces adresses, séparées par des virgules (,). Type de détection de trafic Activer un second port d’écoute local Socks : port local ; Socks2 : deuxième port local ; Socks3 : port LAN Thème Copier la cmd proxy terminal dans le presse-papiers Recommencer le test des éléments échoués, {0} restants. Appuyez sur Échap pour arrêter... Selon le résultat des tests Supprimer les éléments invalides selon résultats tests {0} résultats de test invalides supprimés. Plage de ports sautés Écrase le port ; pour plusieurs groupes, séparer par virgules (,) Exporter Adresse de test d’informations de connexion actuelles Vous pouvez saisir un alias de configuration ; assurez-vous qu’il existe et qu’il est unique Mot de passe incorrect, veuillez réessayer. Mldsa65Verify Ajouter [Anytls] DNS distant DNS direct Direct Target Resolution Strategy Proxy Target Resolution Strategy Ajouter des hôtes DNS courants FakeIP Bloquer les requêtes SVCB et HTTPS Hôtes DNS : (« domaine1 ip1 ip2 » une ligne par entrée) Paramètres DNS de base Paramètres DNS avancés Valider les IP des domaines de la région concernée Après config, les IP renvoyées des domaines régionaux (ex. geosite:cn - geoip:cn) seront vérifiées ; seules les IP attendues seront retournées. Activer le DNS personnalisé DNS personnalisé activé ; la configuration de cette page sera ignorée Une fois activé, bloque les requêtes ECH et de disponibilité HTTP/3 Veuillez saisir un modèle de configuration valide Paramètres du modèle de config complet Activer le modèle de config. complet Modèle de configuration complet v2ray Ajoute seulement la config sortante, routing.balancers et routing.rules.outboundTag. Voir la doc. N’ajoutez pas de sorties pour protocoles non-proxy. Définir le tag de proxy amont Modèle de configuration complet sing-box Ajoute uniquement la configuration des sortants et des endpoints ; cliquer pour voir la documentation Cette fonction s’adresse aux utilisateurs avancés et aux besoins spécifiques. Une fois activée, les paramètres de base du Core, du DNS et du routage sont ignorés. Vous devez vous assurer que la configuration des ports du proxy système, des statistiques de trafic, etc., est correcte — tout est à votre charge. Début de l’analyse et du traitement du contenu d’abonnement Choisir une config. Actif globalement par défaut, avec filtre FakeIP intégré ; ne fonctionne que dans sing-box Veuillez ajouter au moins une configuration Groupe de stratégie Chaîne de proxy Latence minimale Aléatoire Round Robin Le plus stable Type de groupe de stratégie Ajouter un groupe de stratégie Ajouter une chaîne de proxy Ajouter une sous-configuration Supprimer une sous-configuration Configuration item 1, Auto add from subscription group Basculement (failover) Le cœur « {0} » ne prend pas en charge le type de réseau « {1} » Le cœur « {0} » ne prend pas en charge le protocole « {1} » avec le mode de transport « {2} » Le cœur « {0} » ne prend pas en charge le protocole « {1} » La propriété {0} est invalide, veuillez vérifier Protocole « {0} » non pris en charge Si le système n’a pas de zone de notif., n’activez pas cette option Type de règle Des règles peuvent être définies séparément pour Routing et DNS ; ALL les applique aux deux DNS d’amorçage Résoudre le nom du serveur DNS ; doit être spécifié en IP Test 1-clic de latence réelle Ajout auto des configs filtrées depuis les groupes d’abonnement Certificate Pinning Pinned certificate (fill in either one) When specified, the certificate will be pinned, and "Allow Insecure" will be disabled. The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. Obtenir le certificat Obtenir la chaîne de certificats Veuillez définir un domaine valide Certificat non configuré Certificat configuré Chemin fichier PAC personnalisé Chemin script proxy système personnalisé Afficher dans le Dock de macOS (redém. requis) Élément de config 2 : choisir et ajouter depuis self-hosted EchConfigList EchForceQuery Certificat complet (chaîne), format PEM Empreinte du certificat (SHA-256) Cache optimiste Requête parallèle Par défaut, utilisé uniquement lors du routage pour la résolution. Par défaut, invoqué uniquement au routage pour la résolution. Vérifiez que le serveur distant peut joindre ce DNS. Si non défini ou « AsIs », le DNS système est utilisé ; sinon, le module DNS interne est utilisé. Si non défini ou « AsIs », la résolution DNS est assurée par le serveur distant ; sinon, le module DNS interne est utilisé. Intervalle de saut de port Aperçu des sous-config Finalmask Routing rule {0} outbound node {1} warning: {2} Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. Group {0} has a cycle dependency on child node {1}. Skipping this node. Group {0} child node {1} warning: {2} Group {0} child node {1} error: {2}. Skipping this node. Group {0} child group node {1} warning: {2} Group {0} child group node {1} error: {2}. Skipping this node. Group {0} has no valid child node. Routing rule {0} has an empty outbound tag. Fallback to proxy node only. Routing rule {0} outbound node {1} not found. Fallback to proxy node only. Subscription previous proxy {0} not found. Skipping. Subscription next proxy {0} not found. Skipping. Generate Policy Group All configurations Group by Region ================================================ FILE: v2rayN/ServiceLib/Resx/ResUI.hu.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Sikeresen exportálta a megosztási linket a vágólapra Kérjük, először ellenőrizze a konfigurációs beállításokat. Érvénytelen konfigurációs formátum. Ne feledje, hogy az egyéni konfiguráció teljes mértékben a saját konfigurációjától függ, és nem működik minden beállítással. Ha a rendszerproxyt szeretné használni, kérjük, manuálisan módosítsa a figyelő portot. Letöltés... Nem sikerült a konfigurációs fájl konvertálása Nem sikerült az alapértelmezett konfigurációs fájl generálása Nem sikerült lekérni az alapértelmezett konfigurációt Nem sikerült importálni az egyéni konfigurációt Nem sikerült olvasni a konfigurációs fájlt Kérjük, adja meg a helyes port formátumot. Kérjük, adja meg a helyi figyelő portot. Kérjük, adja meg a jelszót. Kérjük, adja meg a címet. Kérjük, adja meg a felhasználói azonosítót. Ez nem a megfelelő konfiguráció, kérjük, ellenőrizze Kezdeti konfiguráció {0} {1} már naprakész. {0} {1} már naprakész. Cím Biztonság Port Típus Előfizetés csoport Mai letöltési forgalom Mai feltöltési forgalom Összes letöltési forgalom Összes feltöltési forgalom Szállítás A Core sikeresen letöltve Nem sikerült importálni az előfizetés tartalmát Az előfizetés tartalma sikeresen lekérve Nincs érvényes előfizetés beállítva Resolved {0} successfully Előfizetések lekérdezése elindult Frissítés indítása: {0}... Érvénytelen előfizetés tartalom Kicsomagolás... Előfizetés frissítése befejeződött Előfizetés frissítése elindult A Core sikeresen frissítve A Core sikeresen frissítve! Szolgáltatás újraindítása... Nem VMess vagy SS protokoll A Core fájl (fájlnév: {1}) nem található a mappában ({0}), kérjük, töltse le és helyezze a mappába, letöltési cím: {2} Szkennelés befejeződött, nem található érvényes QR kód Művelet sikertelen, kérjük, ellenőrizze és próbálja újra Kérjük, töltse ki a megjegyzéseket Kérjük, válassza ki a titkosítási módszert Kérjük, válassza ki a protokollt Kérjük, először válassza ki a konfigurációt Konfigurációk deduplikálása befejeződött. Régi: {0}, Új: {1}. Biztosan eltávolítja a konfigurációt? Az ügyfélkonfigurációs fájl mentése itt: {0} Szolgáltatás indítása ({0})... Konfiguráció sikeres. {0} Egyéni konfiguráció sikeresen importálva {0} konfiguráció importálva a vágólapról Sikeresen beolvasta és importálta a megosztott linket A késleltetés: {0} ms, {1} Művelet sikeres Kérjük, válasszon szabályokat Biztosan eltávolítja a szabályokat? {0}, az egyik kötelező mező. Megjegyzések URL (opcionális) Darabszám Kérjük, adja meg az URL-t Hozzá szeretne fűzni szabályokat? Igen a hozzáfűzéshez, nem a cseréhez. A GeoFile: {0} sikeresen letöltve Információ Egyéni ikon Kérjük, töltse ki a helyes egyéni DNS-t *ws/http upgrade/xhttp elérési út *h2 elérési út *QUIC kulcs/KCP seed *grpc szolgáltatásnév *http host vesszővel elválasztva (,) *ws/http upgrade/xhttp host *h2 host vesszővel elválasztva (,) *QUIC biztonság *tcp álcázási típus *kcp álcázási típus *QUIC álcázási típus *grpc mód TLS *kcp seed Globális gyorsbillentyű {0} regisztrációja sikertelen, ok: {1} Globális gyorsbillentyű {0} sikeresen regisztrálva Összes Kérjük, tallózzon a konfiguráció importálásához Tesztelés... LAN Helyi Konfigurációs szűrő, Enter billentyűvel végrehajtható Frissítés ellenőrzése Bezárás Kilépés Globális gyorsbillentyű beállítás Súgó Opció beállítás Promóció Újratöltés Útválasztási beállítás Konfigurációk Beállítások Aktuális előfizetés frissítése proxy nélkül Aktuális előfizetés frissítése proxyval Előfizetési csoport Előfizetési csoport beállításai Előfizetések frissítése proxy nélkül Előfizetések frissítése proxyval Rendszerproxy Rendszerproxy törlése Ne változtassa meg a rendszerproxyt PAC mód Rendszerproxy beállítása Szín Nyelv (Újraindítás) Megosztási linkek importálása vágólapról QR kód beolvasása a képernyőről Kijelölt konfiguráció klónozása Ismétlődő konfigurációk eltávolítása Kijelölt konfigurációk eltávolítása Beállítás aktív konfigurációként Összes szolgáltatás statisztika törlése Konfigurációk valós késleltetésének tesztelése Rendezés teszteredmény szerint Konfigurációk letöltési sebességének tesztelése Konfigurációk tesztelése tcpinggel Kijelölt konfiguráció exportálása teljes konfigurációként Megosztási link exportálása vágólapra Egyéni konfiguráció hozzáadása [Shadowsocks] konfiguráció hozzáadása [SOCKS] konfiguráció hozzáadása [Trojan] konfiguráció hozzáadása [VLESS] konfiguráció hozzáadása [VMess] konfiguráció hozzáadása Összes kijelölése Összes törlése Másolás Összes másolása Összes kijelölése Hozzáadás Törlés Szerkesztés Megosztás Frissítés engedélyezése Rendezés User Agent Mégsem Megerősítés Szállítás Cím Nem biztonságos engedélyezése ALPN Alter ID Ujjlenyomat Álcázási típus UUID(id) Szállítási protokoll(hálózat) Elérési út Port Alias (megjegyzések) Álcázási tartomány(host) Titkosítási módszer (biztonság) SNI TLS *Alapértelmezett érték tcp Core Típus Flow Generálás Jelszó Jelszó(Opcionális) UUID(id) Titkosítás Felhasználó(Opcionális) Titkosítás Socks port * A beállítás után egy socks szolgáltatás indul az Xray/sing-box(Tun) segítségével, hogy olyan funkciókat biztosítson, mint a sebességkijelzés Tallózás Szerkesztés Haladó proxy beállítások, protokoll kiválasztása (opcionális) Kapcsolatok engedélyezése a LAN-ról Automatikus elrejtés indításkor Geo fájlok automatikus frissítési intervalluma (órák) Core: alapbeállítások V2ray Custom DNS Core: KCP beállítások Core Típus beállítások Nem biztonságos engedélyezése Kimenő Freedom tartomány stratégia Oszlopszélesség automatikus beállítása előfizetés frissítése után Előzetes kiadás frissítések ellenőrzése Kivétel Kivételek: Ne használjon proxy szervert a következő címmel kezdődő címekhez. Pontosvesszővel (;) válassza el a bejegyzéseket. Valós idejű sebesség megjelenítése (újraindítást igényel) Régebbi bejegyzések megtartása deduplikáláskor Naplózás engedélyezése Naplózási szint Mux Multiplexing bekapcsolása v2rayN beállítások Hitelesítési jelszó Egyéni DNS (több, vesszővel (,) elválasztva) Win10 UWP Loopback beállítása Sniffing bekapcsolása Vegyes port Indítás rendszerindításkor Forgalmi statisztikák engedélyezése (újraindítást igényel) Előfizetés konverziós URL Rendszerproxy beállítások Tálca jobb egérgombos menü konfigurációk megjelenítési limitje UDP engedélyezése Hitelesítési felhasználó Rendszerproxy törlése GUI megjelenítése Globális gyorsbillentyű beállítások Közvetlenül beállítható billentyűnyomással; újraindítás után lép életbe Ne változtassa meg a rendszerproxyt Visszaállítás Rendszerproxy beállítása PAC mód Konfiguráció megosztása Útválasztás Nem rendszergazdaként fut Futtatás rendszergazdaként Mozgatás alulra Le Mozgatás felülre Fel Szűrő, támogatja a reguláris kifejezéseket {0} Weboldal Hozzáadás Szabályok importálása Kijelölt eltávolítása Beállítás aktív szabályként Tartomány stratégia Előre definiált szabálykészlet lista *Szabályok elválasztása vesszővel (,); Szó szerinti vesszőhöz használja a <COMMA>-t; Előtag # a szabály figyelmen kívül hagyásához Szabályok importálása vágólapról Szabályok importálása fájlból Szabályok importálása előfizetési URL-ből Szabály beállítások Szabály hozzáadása Kijelölt szabályok exportálása Szabálylista Szabály eltávolítása Útválasztási szabály részleteinek beállítása Tartomány, IP, folyamat automatikusan rendeződik mentéskor Szabály objektum dokumentum Támogatja a DNS objektumot; Kattintson a dokumentáció megtekintéséhez Csoport esetén hagyja üresen Útválasztási beállítás megváltozott Rendszerproxy beállítás megváltozott Csak útválasztás Ne használjon proxy szervert helyi (intranet) címekhez Egykattintásos többszörös késleltetés és sebesség teszt (Ctrl+E) Késleltetés (ms) Sebesség (MB/s) Nem sikerült futtatni a Core-t, kérjük, ellenőrizze a prompt információt Megjegyzések reguláris szűrője Napló megjelenítése Tun engedélyezése Új port a LAN-hoz Tun mód beállítások Mozgatás csoportba Konfigurációk rendezésének engedélyezése húzással (újraindítást igényel) Automatikus frissítés Teszt kihagyása Konfiguráció szerkesztése Dupla kattintás a konfigurációra aktiválja Teszt befejeződött Alapértelmezett TLS ujjlenyomat User-Agent Ez a paraméter csak tcp/http és ws esetén érvényes Betűtípus (újraindítást igényel) Másolja a TTF/TTC betűtípus fájlt a gui Fonts könyvtárba; Nyissa meg újra a beállítások ablakot Pac port = +3; Xray API port = +4; mihomo API port = +5; Rendszergazdai jogosultságokkal állítsa be, indítás után szerezzen rendszergazdai jogosultságokat Betűméret Sebességteszt egyszeri időtúllépési érték Sebességteszt URL Mozgatás fel és le Nyilvános kulcs Rövid azonosító Spider X Hardveres gyorsítás engedélyezése (újraindítást igényel) Tesztelésre vár... ESC megnyomásával megszakítható Kérjük, kapcsolja ki rendellenes megszakadás esetén A frissítések nincsenek engedélyezve, kihagyja ezt az előfizetést Újraindítás rendszergazdaként További URL-ek, vesszővel elválasztva; Az előfizetés konverzió érvénytelen lesz {0} : {1}/s↑ | {2}/s↓ Automatikus frissítési intervallum (percek) Naplózás engedélyezése fájlba Konverziós cél típus Kérjük, hagyja üresen, ha nincs szükség konverzióra DNS beállítások sing-box Custom DNS Kérjük, töltse ki a DNS struktúrát, kattintson a dokumentum megtekintéséhez Kattintson az alapértelmezett DNS konfiguráció importálásához sing-box tartomány stratégia sing-box Mux protokoll Process (Linux/Windows) IP vagy IP CIDR Tartomány Hysteria2 konfiguráció hozzáadása Hysteria Max sávszélesség (Fel/Le) Rendszer Hosts használata TUIC konfiguráció hozzáadása Torlódásvezérlés Előző proxy konfiguráció megjegyzései Következő proxy konfiguráció megjegyzései Kérjük, győződjön meg arról, hogy a konfigurációs megjegyzések léteznek és egyediek Automatikus útválasztás Szigorú útválasztás Hálózati verem MTU IPv6 cím engedélyezése WireGuard konfiguráció hozzáadása Privát kulcs Fenntartott (2,3,4) Cím (IPv4, IPv6) obfs jelszó (Tartomány vagy IP vagy folyamatnév) és port és protokoll és bejövő címke => kimenő címke Automatikus görgetés a végére Sebesség Ping Teszt URL Teszt megszakítása... *grpc Authority HTTP konfiguráció hozzáadása Fragment engedélyezése Gyorsítótár fájl engedélyezése sing-boxhoz (szabálykészlet fájlok) A sing-box szabálykészletének testreszabása Sikeres művelet. Kattintson a beállítások menüre az alkalmazás újraindításához. Fájl helyének megnyitása Rendezés Lánc Alapértelmezett Késleltetés Letöltési sebesség Letöltési forgalom Host Név Hálózat Idő Típus Feltöltési sebesség Feltöltési forgalom Kapcsolatok Kapcsolat bezárása Összes kapcsolat bezárása Proxyk Szabály mód Közvetlen Globális Ne változtassa meg Szabály Késleltetés teszt Részleges csomópont késleltetés teszt Proxyk frissítése Aktív csomópont kiválasztása Alapértelmezett tartomány stratégia kimenő forgalomhoz Fő elrendezés iránya (újraindítást igényel) Kimenő DNS cím Oszlopszélesség automatikus beállítása Base64-kódolt megosztási linkek exportálása vágólapra Kijelölt konfiguráció exportálása teljes konfigurációként a vágólapra Főablak megjelenítése vagy elrejtése Egyéni konfiguráció socks portja Biztonsági mentés és visszaállítás Biztonsági mentés helyi tárolóba Visszaállítás helyi tárolóból Biztonsági mentés távoli helyre (WebDAV) Visszaállítás távoli helyről (WebDAV) Helyi Távoli (WebDAV) WebDAV URL WebDAV felhasználónév WebDAV jelszó WebDAV ellenőrzés Távoli mappa neve (opcionális) Érvénytelen biztonsági mentés fájl Host szűrő Aktív Geo fájlok forrása (opcionális) sing-box szabálykészlet fájlok forrása (opcionális) Frissítő alkalmazás nem létezik Útválasztási szabályok forrása (opcionális) Regionális előbeállítások Alapértelmezett Oroszország Irán Kínai régióban lévő felhasználók figyelmen kívül hagyhatják ezt az elemet QR kód beolvasása a képből Érvénytelen cím (URL) Kérjük, ne használjon nem biztonságos HTTP protokoll előfizetési címet Telepítse a betűtípust a rendszerbe, válassza ki vagy töltse ki a betűtípus nevét, indítsa újra a beállításokat Biztosan ki akar lépni? Megjegyzések Rendszer sudo jelszó A jelszót a parancssoron keresztül ellenőrizzük. Ha egy érvényesítési hiba miatt az alkalmazás hibásan működik, indítsa újra az alkalmazást. A jelszó nem kerül tárolásra, és minden újraindítás után újra meg kell adni. *xhttp mód XHTTP Extra nyers JSON, formátum: { XHTTP Objektum } Ablak bezárásakor a tálcára rejtés A párhuzamos tesztek száma több teszt során Kivételek: Ne használjon proxy szervert a következő címekhez. Vesszővel (,) válassza el a bejegyzéseket. Sniffing típus Második vegyes port engedélyezése socks: helyi port, socks2: második helyi port, socks3: LAN port Téma Proxy parancs másolása vágólapra Sikertelen részek újratesztelése elindult, {0} maradt. ESC megnyomásával megszakítható... Teszt eredmény szerint Érvénytelenek eltávolítása teszteredmények alapján Eltávolítva {0} érvénytelen teszteredmény. Konfigurációs port tartomány A portot lefedi, vesszővel (,) elválasztva Konfiguráció exportálása Aktuális kapcsolat info teszt URL Kitöltheti a konfigurációs megjegyzéseket, kérjük, győződjön meg róla, hogy létezik és egyedi Helytelen jelszó, próbálja újra. Mldsa65Verify [Anytls] konfiguráció hozzáadása Remote DNS Domestic DNS Direct Target Resolution Strategy Proxy Target Resolution Strategy Add Common DNS Hosts FakeIP Block SVCB and HTTPS Queries DNS Hosts: ("domain1 ip1 ip2" per line) Basic DNS Settings Advanced DNS Settings Validate Regional Domain IPs When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs Enable Custom DNS Custom DNS Enabled, This Page's Settings Invalid Block ECH and HTTP/3 availability checks when enabled Please fill in the correct config template Full Config Template Setting Enable Full Config Template v2ray Full Config Template Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document Do Not Add Non-Proxy Protocol Outbound Set Upstream Proxy Tag sing-box Full Config Template Add Outbound and Endpoint Config Only, Click to view the document This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. Start parsing and processing subscription content Select Profile Applies globally by default, with built-in FakeIP filtering (sing-box only). Please Add At Least One Configuration Policy Group Proxy Chain Lowest Latency Random Round Robin Most Stable Policy Group Type Add Policy Group Configuration Add Proxy Chain Configuration Add Child Configuration Remove Child Configuration Configuration item 1, Auto add from subscription group Fallback Core '{0}' does not support network type '{1}' Core '{0}' does not support protocol '{1}' when using transport '{2}' Core '{0}' does not support protocol '{1}' The {0} property is invalid, please check Not support protocol '{0}' If the system does not have a tray function, please do not enable it You can set separate rules for Routing and DNS, or select "ALL" to apply to both Rule Type Bootstrap DNS Resolve DNS server domains, requires IP Test real delay Auto add filtered configuration from subscription groups Certificate Pinning Pinned certificate (fill in either one) When specified, the certificate will be pinned, and "Allow Insecure" will be disabled. The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. Fetch Certificate Fetch Certificate Chain Please set a valid domain Certificate not set Certificate set Custom PAC file path Custom system proxy script file path macOS displays this in the Dock (requires restart) Configuration Item 2, Select and add from self-built EchConfigList EchForceQuery Full certificate (chain), PEM format Certificate fingerprint (SHA-256) Serve Stale Parallel Query By default, invoked only during routing for resolution By default, invoked only during routing for resolution; ensure the remote server can reach this DNS If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used. If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. Port hopping interval Configuration item preview Finalmask Routing rule {0} outbound node {1} warning: {2} Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. Group {0} has a cycle dependency on child node {1}. Skipping this node. Group {0} child node {1} warning: {2} Group {0} child node {1} error: {2}. Skipping this node. Group {0} child group node {1} warning: {2} Group {0} child group node {1} error: {2}. Skipping this node. Group {0} has no valid child node. Routing rule {0} has an empty outbound tag. Fallback to proxy node only. Routing rule {0} outbound node {1} not found. Fallback to proxy node only. Subscription previous proxy {0} not found. Skipping. Subscription next proxy {0} not found. Skipping. Generate Policy Group All configurations Group by Region ================================================ FILE: v2rayN/ServiceLib/Resx/ResUI.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Export share link to clipboard successfully Please check the Configuration settings first. Invalid configuration format. Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. Downloading... Failed to convert configuration file Failed to generate default configuration file Failed to get the default configuration Failed to import custom configuration Configuration Failed to read configuration file Please enter the correct port format. Please enter the local listening port. Please enter the password. Please enter the address. Please enter the user ID. This is not the correct configuration, please check Initial Configuration {0} {1} already up to date. {0} {1} already up to date. Address Security Port Type Subs group Download traffic today Upload traffic today Total download traffic Total upload traffic Transport Downloaded Core successfully Failed to import subscription content Got subscription content successfully No valid subscriptions set Resolved {0} successfully Started getting subscriptions Started updating {0}... Invalid subscription content Unpacking... Subscription update ended Subscription update started Updated Core successfully Updated Core successfully! Restarting service... Non-VMess or SS protocol The Core file (file name: {1}) was not found under the folder ({0}), please download and put it in the folder, download address: {2} Scan completed, no valid QR code found Operation failed, please check and retry Please fill Remarks Please select the encryption method Please select a protocol Please select the Configuration first Configurations deduplication completed. Old: {0}, New: {1}. Are you sure you want to remove? The client configuration file is saved at: {0} Starting service ({0})... Configuration successful. {0} Custom configuration Configuration imported successfully {0} Configurations have been imported from clipboard Successfully scanned and imported the shared link The delay: {0} ms, {1} Operation successful Please select rules Are you sure you want to remove the rules? {0}, one of the required fields. Remarks URL (optional) Count Please enter the URL Do you want to append rules? Choose yes to append, no to replace. Downloaded GeoFile: {0} successfully Information Custom icon Please fill in the correct custom DNS *ws/http upgrade/xhttp path *h2 path *QUIC key/KCP seed *grpc service name *http host separated by commas (,) *ws/http upgrade/xhttp host *h2 host separated by commas (,) *QUIC security *tcp camouflage type *kcp camouflage type *QUIC camouflage type *grpc mode TLS *kcp seed Global hotkey {0} registration failed, reason: {1} Global hotkey {0} registered successfully All Please browse to import Configuration configuration Testing... LAN Local Filter, press Enter to execute Check Update Close Exit Global Hotkey Setting Help Option Setting Promotion Reload Routing Setting Configuration Settings Update current subscription without proxy Update current subscription with proxy Subscription Group Subscription group settings Update subscriptions without proxy Update subscriptions with proxy System proxy Clear system proxy Do not change system proxy PAC mode Set system proxy Color Language (Restart) Import Share Links from clipboard Scan QR code on the screen Clone selected Remove duplicate Remove selected Set as active Clear all service statistics Test real delay Sort by test result Test download speed Test tcping Export selected for complete configuration Export Share Link to Clipboard Add a custom configuration Add [Shadowsocks] Add [SOCKS] Add [Trojan] Add [VLESS] Add [VMess] Select all Clear all Copy Copy all Select all Add Delete Edit Share Enable update Sort User Agent Cancel Confirm Transport Address Allow Insecure ALPN Alter ID Fingerprint Camouflage type UUID(id) Transport protocol(network) Path Port Alias (remarks) Camouflage domain(host) Encryption method (security) SNI TLS *Default value tcp Core Type Flow Generate Password Password(Optional) UUID(id) Encryption User(Optional) Encryption Socks port * After setting this value, a socks service will be started using Xray/sing-box(Tun) to provide functions such as speed display Browse Edit Advanced proxy settings, protocol selection (optional) Allow connections from the LAN Auto hide on startup Automatic update interval for Geo files (hours) Core: basic settings V2ray Custom DNS Core: KCP settings Core Type settings Allow Insecure Outbound Freedom domain Strategy Automatically adjust column width after subscription update Check for pre-release updates Exception Exclusions: Do not use proxy server for addresses beginning with the following. Use semicolon (;) to separate entries. Display real-time speed (requires restart) Keep older entries when de-duplicating Enable Log Log Level Turn on Mux Multiplexing v2rayN settings Auth pass Custom DNS (multiple, separated by commas (,)) Set Win10 UWP Loopback Turn on Sniffing Mixed Port Start on boot Enable traffic statistics (requires restart) Subscription conversion URL System proxy settings Tray right-click menu display limit Enable UDP Auth user Clear system proxy Display GUI Global Hotkey Settings Set directly by pressing the keyboard; takes effect after restart Do not change system proxy Reset Set system proxy PAC mode Share Routing Not run as Admin Run as Admin Move to bottom Down Move to top Up Filter, supports regular expressions {0} Website Add Import Rules Remove selected Set as active rule Domain strategy Pre-defined Rule Set List *Separate rules by commas (,); For a literal comma use <COMMA>; Prefix # to ignore a rule Import Rules From Clipboard Import Rules From File Import Rules From Subscription URL Rule Settings Add Rule Export Selected Rules Rule List Remove Rule Routing Rule Details Setting Domain, IP, process are auto-sorted when saving Rule object Doc Supports DNS Object; Click to view documentation For group please leave blank here Routing setting has changed System proxy setting has changed Route Only Do not use proxy servers for local (intranet) addresses One-click multi-test latency and speed (Ctrl+E) Delay (ms) Speed (MB/s) Failed to run Core, please check the prompt information Remarks regular filter Display Log Enable Tun New Port for LAN Tun Mode settings Move to group Enable sorting Configurations by drag-n-drop (requires restart) Auto refresh Skip test Edit Double-clicking Configuration makes it active Test completed Default TLS fingerprint User-Agent This parameter is valid only for tcp/http and ws Font family (requires restart) Copy the font TTF/TTC file to the directory gui Fonts; Reopen the settings window Pac port = +3; Xray API port = +4; mihomo API port = +5; Set this with admin privileges, get admin privileges after startup Font Size Speed Test Single Timeout Value Speed Test URL Move up and down Public Key Short Id Spider X Enable hardware acceleration (requires restart) Waiting... Press ESC to terminate the test Please turn off when there is an abnormal disconnection Updates are not enabled, skip this subscription Restart as Administrator More URLs, separated by commas; Subscription conversion will be invalid {0} : {1}/s↑ | {2}/s↓ Automatic update interval (minutes) Enable logging to file Convert target type Please leave blank if no conversion is required DNS Settings sing-box Custom DNS Please fill in DNS Structure, Click to view the document Click to import default DNS config sing-box domain strategy sing-box Mux Protocol Process (Linux/Windows) IP or IP CIDR Domain Add [Hysteria2] Hysteria Max bandwidth (Up/Down) Use System Hosts Add [TUIC] Congestion control Previous proxy remarks Next proxy remarks Please make sure the Configuration remarks exist and are unique Auto Route Strict Route Stack MTU Enable IPv6 Address Add [WireGuard] Private Key Reserved (2,3,4) Address (IPv4, IPv6) obfs password (Domain or IP or Proc Name) and Port and Protocol and Inbound Tag => Outbound Tag Auto scroll to end Speed Ping Test URL Test terminating... *grpc Authority Add [HTTP] Enable fragment Enable cache file for sing-box (ruleset files) Customize the rule-set of sing-box Successful operation. Click the settings menu to reboot the app. Open the storage location Sorting Chain Default Delay Download Speed Download Traffic Host Name Network Time Type Upload Speed Upload Traffic Connections Close Connection Close All Connections Proxies Rule mode Direct Global Do not change Rule Latency Test Part Node Latency Test Refresh Proxies Select active node Default domain strategy for outbound Main layout orientation (requires restart) Outbound DNS address Auto column width adjustment Export Base64-encoded Share Links to Clipboard Export selected for complete configuration to clipboard Show or hide the main window Custom config socks port Backup and Restore Backup to local Restore from local Backup to remote (WebDAV) Restore from remote (WebDAV) Local Remote (WebDAV) WebDAV URL WebDAV User Name WebDAV Password WebDAV Check Remote folder name (optional) Invalid backup file Host filter Active Geo files source (optional) sing-box ruleset files source (optional) Upgrade App does not exist Routing rules source (optional) Regional presets setting Default Russia Iran Users in China region can ignore this item Scan QR code in the image Invalid address (URL) Please do not use the insecure HTTP protocol subscription address Install the font to the system, select or fill in the font name, restart the settings Are you sure you want to exit? Remarks Memo System sudo password The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart. *xhttp mode XHTTP Extra raw JSON, format: { XHTTP Object } Hide to tray when closing the window The number of concurrent tests during multi-test Exclusions: Do not use proxy server for the following addresses. Use comma (,) to separate entries. Sniffing type Enable second mixed port socks: local port, socks2: second local port, socks3: LAN port Theme Copy proxy command to clipboard Starting retesting failed parts, {0} remaining. Press ESC to terminate... By test result Remove invalid by test results Removed {0} invalid test results. Configuration port range Will cover the port, separate with commas (,) Export Current connection info test URL Can fill in the configuration remarks, please make sure it exist and are unique Incorrect password, please try again. Mldsa65Verify Add [Anytls] Remote DNS Domestic DNS Direct Target Resolution Strategy Proxy Target Resolution Strategy Add Common DNS Hosts FakeIP Block SVCB and HTTPS Queries DNS Hosts: ("domain1 ip1 ip2" per line) Basic DNS Settings Advanced DNS Settings Validate Regional Domain IPs When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs Enable Custom DNS Custom DNS Enabled, This Page's Settings Invalid Block ECH and HTTP/3 availability checks when enabled Please fill in the correct config template Full Config Template Setting Enable Full Config Template v2ray Full Config Template Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document Do Not Add Non-Proxy Protocol Outbound Set Upstream Proxy Tag sing-box Full Config Template Add Outbound and Endpoint Config Only, Click to view the document This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. Start parsing and processing subscription content Select Profile Applies globally by default, with built-in FakeIP filtering (sing-box only). Please Add At Least One Configuration Policy Group Proxy Chain Lowest Latency Random Round Robin Most Stable Policy Group Type Add Policy Group Add Proxy Chain Add Child Remove Child Configuration item 1, Auto add from subscription group Fallback Core '{0}' does not support network type '{1}' Core '{0}' does not support protocol '{1}' when using transport '{2}' Core '{0}' does not support protocol '{1}' The {0} property is invalid, please check Not support protocol '{0}' If the system does not have a tray function, please do not enable it You can set separate rules for Routing and DNS, or select "ALL" to apply to both Rule Type Bootstrap DNS Resolve DNS server domains, requires IP Test real delay Auto add filtered configuration from subscription groups Certificate Pinning Pinned certificate (fill in either one) When specified, the certificate will be pinned, and "Allow Insecure" will be disabled. The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. Fetch Certificate Fetch Certificate Chain Please set a valid domain Certificate not set Certificate set Custom PAC file path Custom system proxy script file path macOS displays this in the Dock (requires restart) Configuration Item 2, Select and add from self-built EchConfigList EchForceQuery Full certificate (chain), PEM format Certificate fingerprint (SHA-256) Serve Stale Parallel Query By default, invoked only during routing for resolution By default, invoked only during routing for resolution; ensure the remote server can reach this DNS If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used. If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. Port hopping interval Configuration item preview Finalmask Routing rule {0} outbound node {1} warning: {2} Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. Group {0} has a cycle dependency on child node {1}. Skipping this node. Group {0} child node {1} warning: {2} Group {0} child node {1} error: {2}. Skipping this node. Group {0} child group node {1} warning: {2} Group {0} child group node {1} error: {2}. Skipping this node. Group {0} has no valid child node. Routing rule {0} has an empty outbound tag. Fallback to proxy node only. Routing rule {0} outbound node {1} not found. Fallback to proxy node only. Subscription previous proxy {0} not found. Skipping. Subscription next proxy {0} not found. Skipping. Generate Policy Group All configurations Group by Region ================================================ FILE: v2rayN/ServiceLib/Resx/ResUI.ru.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Ссылка успешно скопирована в буфер обмена Сначала проверьте настройки сервера Недопустимый формат конфигурации Обратите внимание, что пользовательская конфигурация полностью зависит от вашей собственной конфигурации и работает не со всеми настройками. Если вы хотите использовать системный прокси, то измените порт прослушивания вручную Загрузка... Не удалось преобразовать файл конфигурации Не удалось создать файл конфигурации по умолчанию Не удалось получить конфигурацию по умолчанию Не удалось импортировать сервер пользовательской конфигурации Не удалось прочитать файл конфигурации Укажите порт сервера в правильном формате Введите локальный порт для прослушивания Введите пароль Введите адрес сервера Введите идентификатор пользователя Некорректная конфигурация. Проверьте Исходная конфигурация {0} {1} является последней версией {0} {1} является последней версией Адрес Метод шифрования Порт Тип Группа подписки Загружено трафика сегодня Отправлено сегодня Всего загружено Всего отдано Протокол Ядро успешно загружено Не удалось импортировать подписку Содержимое подписки успешно импортировано Нет установленных подписок Парсинг {0} прошел успешно Начинаю получать подписки Начинаю обновление {0}... Некорректное содержимое подписки Распаковка… Обновление подписки закончено Обновление подписки начинается Ядро успешно обновлено Успешное обновление ядра! Перезапуск службы... Не является протоколом VMess или Shadowsocks Файл ядра ({1}) не найден в папке {0}. Скачайте по адресу {2} и поместите его туда Сканирование завершено, не найден корректный QR код Операция безуспешна, проверьте и попробуйте ещё раз Введите примечания Выберите метод шифрования Выберите протокол Сначала выберите сервер Удаление дублей завершено. Старая: {0}, Новая: {1} Вы уверены, что хотите удалить сервер? Файл конфигурации клиента сохранен по адресу: {0} Запуск сервиса ({0})... Конфигурация выполнена успешно {0} Пользовательская конфигурация сервера успешно импортирована {0} серверов импортировано из буфера обмена Сканирование URL-адреса импорта прошло успешно Задержка текущего сервера: {0} мс, {1} Операция успешна Выберите правила Вы уверены, что хотите удалить правила? {0}: одно из обязательных полей Примечания URL (необязательно) Количество Введите URL-адрес Хотите добавить правила? Выберите «Да» для добавления или «Нет» для замены Загрузка GeoFile {0} прошла успешно Информация Пользовательская иконка Введите корректный пользовательский DNS *WebSocket-путь *HTTP2-путь *QUIC-ключ / KCP-seed Имя сервиса *gRPC *http-хосты, разделённые запятыми (,) *WebSocket-хост *HTTP2-хосты, разделённые запятыми (,) Безопасность *QUIC Тип *TCP-камуфляжа Тип *KCP-камуфляжа Тип *QUIC-камуфляжа Режим *gRPC TLS *KCP-seed Не удалось зарегистрировать глобальную горячую клавишу {0}, причина: {1} Глобальная горячая клавиша {0} зарегистрирована успешно Все серверы Выберите файл конфигурации сервера для импорта Тестирование... LAN Локальный Фильтр серверов Проверить обновления Закрыть Выход Глобальная настройка горячих клавиш Помощь Настройка параметров Содействие Перезагрузка Настройки маршрутизации Серверы Настройки Обновить текущую подписку без прокси Обновить подписку через прокси Группа подписки Настройки группы подписки Обновить подписку без прокси Обновить подписку с прокси Системный прокси Очистить системный прокси Не менять системный прокси Режим PAC Установить системный прокси Цвет Язык (требуется перезапуск) Импорт массива URL из буфера обмена Сканировать QR-код с экрана Клонировать выбранный сервер Удалить дубликаты серверов Удалить выбранные серверы Установить как активный сервер Очистить всю статистику Тест на реальную задержку сервера Сортировать по результату теста Тест на скорость загрузки сервера Тест задержки с tcping Экспортировать выбранный сервер для клиента Экспорт URL-адресов общего доступа в буфер обмена Добавить сервер пользовательской конфигурации Добавить сервер [Shadowsocks] Добавить сервер [SOCKS] Добавить сервер [Trojan] Добавить сервер [VLESS] Добавить сервер [VMess] Выбрать все Очистить все Скопировать Скопировать все Выбрать все Добавить Удалить Редактировать Поделиться Включены обновления Сортировка Заголовок User-Agent Отмена Подтвердить Транспорт Адрес Разрешить небезопасные ALPN AlterId Отпечаток Тип камуфляжа UUID (id) Транспортный протокол сети Путь Порт Примечание Маскирующий домен (хост) Метод шифрования SNI TLS *По-умолчанию TCP Ядро Поток Генерировать Пароль Пароль(Необязательно) UUID(id) Шифрование Пользователь(Необязательно) Шифрования Порт SOCKS * После установки этого значения служба SOCKS будет запущена с использованием Xray/sing-box(TUN) для обеспечения таких функций, как отображение скорости Просмотр Редактировать Расширенные настройки прокси, выбор протокола (необязательно) Разрешить подключения из локальной сети Автоскрытие при автозапуске Интервал автоматического обновления Geo в часах Ядро: базовые настройки V2ray Custom DNS Ядро: настройки KCP Настройки типа ядра Разрешить небезопасные «Freedom»: стратегия обработки доменов исходящего трафика Автоматически регулировать ширину столбца после обновления подписки Проверить наличие предварительных обновлений Исключение Исключение. Не используйте прокси-сервер для адресов, начинающихся с (,), используйте точку с запятой (;) Показывать скорость в реальном времени Сохранить старые при удалении дублей Записывать логи Уровень записи логов Включить мультиплексирование Mux Настройки v2rayN Пароль аутентификации Пользовательский DNS (если несколько, то делите запятыми (,)) Разрешить loopback для приложений UWP (Win10) Включить сниффинг Смешанный порт Автозапуск Включить статистику (требуется перезагрузка) URL конвертации подписок Настройки системного прокси Лимит серверов в меню трея Включить UDP Имя пользователя (логин) Очистить системный прокси Показать GUI Настройка горячих клавиш Установите непосредственно, нажав на клавиатуру, вступит в силу после перезапуска Не изменять системный прокси Обнулить Установить системный прокси Режим PAC Поделиться сервером Маршрутизация Пользователь Администратор Спуститься вниз Вниз Подняться наверх Вверх Фильтр, поддерживает regex {0} веб-сайт Добавить Добавить расширенные правила Удалить выбранные Установить как активное правило Доменная стратегия Предустановленный список наборов правил *Разделяйте правила запятыми (,). Литерал «,» — <COMMA>. Префикс # — отключить правило Импорт правил из буфера обмена Импорт правил из файла Импорт правил из URL Настройка правил Добавить правило Экспорт выделенных правил Список правил Удалить правила Детальные настройки правил маршрутизации Домен и IP автоматически сортируются при сохранении Документация RuleObject Поддерживаются DNS-объекты, нажмите для просмотра документации Необязательное поле Настройки маршрутизации изменены Системные прокси изменены Только маршрут Не используйте прокси-серверы для локальных (интранет) адресов Тест задержки и скорости всех серверов (Ctrl+E) Задержка (мс) Скорость (МБ/с) Не удалось запустить ядро, посмотрите логи Фильтр примечаний (Regex) Показать логи Режим VPN Новый порт для локальной сети Настройки режима TUN Перейти в группу Включить сортировку перетаскиванием сервера (требуется перезагрузка) Автообновление Пропустить тест Редактировать сервер Двойной клик чтобы сделать сервер активным Тест завершен TLS отпечаток по умолчанию User-Agent Параметр действует только для TCP/HTTP и WebSocket (WS) Шрифт (требуется перезагрузка) Скопируйте файл шрифта TTF/TTC в каталог guiFonts и заново откройте окно настроек Pac порт = +3,Xray API порт = +4, mihomo API порт = +5 Установите это с правами администратора Размер шрифта Тайм-аут одиночного тестирования скорости URL для тестирования скорости Переместить вверх/вниз PublicKey ShortId SpiderX Включить аппаратное ускорение (требуется перезагрузка) Ожидание тестирования… нажмите ESC для отмены Отключите при аномальном разрыве соединения Обновления не включены — подписка пропущена Перезагрузить как администратор Дополнительные URL через запятую, конвертация подписки недоступна {0} : {1}/s↑ | {2}/s↓ Интервал автоматического обновления в минутах Включить запись логов в файл Преобразовать тип цели Если преобразование не требуется, оставьте поле пустым Настройки DNS sing-box Custom DNS Заполните структуру DNS, нажмите, чтобы открыть документ Нажмите, чтобы импортировать конфигурацию DNS по умолчанию Стратегия домена для sing-box Протокол Mux для sing-box Process (Linux/Windows) IP-адрес или сеть CIDR Домен Добавить сервер [Hysteria2] Максимальная пропускная способность Hysteria (загрузка/отдача) Использовать системные узлы Добавить сервер [TUIC] Контроль перегрузок Примечания к предыдущему прокси Примечания к следующему прокси Убедитесь, что примечание существует и является уникальным Автоматическая маршрутизация Строгая маршрутизация Сетевой стек MTU Включить IPv6 адреса Добавить сервер [WireGuard] Приватный ключ Зарезервировано (2, 3, 4) Адрес (Ipv4,Ipv6) Пароль obfs (Домен или IP или имя процесса) и порт, и протокол, и InboundTag => OutboundTag Автоматическая прокрутка в конец URL для быстрой проверки реальной задержки Отмена тестирования... * gRPC Authority (HTTP/2 псевдозаголовок :authority) Добавить сервер [HTTP] Включить фрагментацию (Fragment) Включить файл кэша для sing-box (файлы наборов правил) Пользовательский набор правил для sing-box Операция успешна. Перезапустите приложение Открыть место хранения Сортировка Цепочка По умолчанию Задержка Скорость загрузки Скачанный трафик Узел Имя Сеть Время Тип Скорость отдачи Отправленный трафик Соединения Закрыть соединение Закрыть все соединения Прокси Режим правила Прямое соединение Глобальный режим Не менять Правила Тест задержки Тест задержки выбранных узлов Обновить прокси Сделать узел активным Стратегия домена по умолчанию для исходящих Основная ориентация макета (требуется перезагрузка) Исходящий DNS адрес Автоматическая регулировка ширины столбца Экспорт ссылок в формате Base64 в буфер обмена Экспортировать выбранный сервер для полной конфигурации в буфер обмена Показать или скрыть главное окно Пользовательская конфигурация порта SOCKS Резервное копирование и восстановление Сохранить в файл Восстановить из файла Резервное копирование на удалённый сервер (WebDAV) Восстановить с удалённого сервера (WebDAV) Локально Удалённо (WebDAV) URL WebDAV Имя пользователя WebDAV Пароль WebDAV Проверить WebDAV Имя удаленной папки (необязательно) Неверный файл резервной копии Фильтр хостов Активный Источник файлов Geo (необязательно) Источник файлов наборов правил sing-box (необязательно) Программы для обновления не существует Источник правил маршрутизации Региональные пресеты По умолчанию (Китай) Россия Иран Пользователи из Китая могут пропустить этот пункт Сканировать QR-код с изображения Неверный адрес (Url) Не используйте небезопасный адрес подписки по протоколу HTTP Установите шрифт в систему и перезапустите настройки Вы уверены, что хотите выйти? Заметка (Memo) Пароль sudo системы Пароль sudo будет проверен в терминале. Если из-за ошибки проверки приложение начнёт работать некорректно, перезапустите его. Пароль не сохраняется — его нужно вводить после каждого перезапуска. *XHTTP-режим Дополнительный „сырой“ JSON для XHTTP, формат: { XHTTP Object } Скрыть в трее при закрытии окна Количество одновременно выполняемых тестов при многоэтапном тестировании Исключение. Не используйте прокси-сервер для адресов с запятой (,) Тип сниффинга Включить второй смешанный порт socks: локальный порт, socks2: второй локальный порт, socks3: LAN порт Темы Копировать команду прокси в буфер обмена Повторное тестирование неудачных элементов, осталось {0}. Нажмите ESC для остановки… По результату теста Удалить недействительные по результатам теста Удалено {0} недействительных Диапазон портов сервера Заменит указанный порт, перечисляйте через запятую (,) Экспортировать конфигурацию URL для тестирования текущего соединения Можно указать название (Remarks) из конфигурации, убедитесь, что оно существует и уникально Неверный пароль, попробуйте ещё раз. Mldsa65Verify Добавить сервер [Anytls] Удалённый DNS Внутренний DNS Direct Target Resolution Strategy Proxy Target Resolution Strategy Добавить стандартные записи hosts (DNS) FakeIP Блокировать DNS-запросы SVCB и HTTPS DNS hosts: (каждая строка в формате "domain1 ip1 ip2") Базовые настройки DNS Расширенные настройки DNS Проверять IP-адреса региональных доменов При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn - geoip:cn), и оставляет только ожидаемые IP-адреса Включить пользовательский DNS Включён пользовательский DNS — настройки на этой странице не применяются Block ECH and HTTP/3 availability checks when enabled Пожалуйста, заполните корректный шаблон конфигурации Настройка полного шаблона конфигурации Включить полный шаблон конфигурации Полный шаблон конфигурации v2ray Добавляет только конфигурацию исходящих (outbound), а также routing.balancers и routing.rules.outboundTag. Нажмите, чтобы открыть документ Не добавлять исходящие для непрокси-протоколов Задать тег верхнего прокси (upstream) Полный шаблон конфигурации sing-box Добавляет только конфигурацию Outbound и Endpoint. Нажмите, чтобы открыть документ Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную. Start parsing and processing subscription content Select Profile Applies globally by default, with built-in FakeIP filtering (sing-box only). Please Add At Least One Configuration Policy Group Proxy Chain Lowest Latency Random Round Robin Most Stable Policy Group Type Add Policy Group Configuration Add Proxy Chain Configuration Add Child Configuration Remove Child Configuration Configuration item 1, Auto add from subscription group Fallback Core '{0}' does not support network type '{1}' Core '{0}' does not support protocol '{1}' when using transport '{2}' Core '{0}' does not support protocol '{1}' The {0} property is invalid, please check Not support protocol '{0}' If the system does not have a tray function, please do not enable it You can set separate rules for Routing and DNS, or select "ALL" to apply to both Rule Type Bootstrap DNS Resolve DNS server domains, requires IP Test real delay Auto add filtered configuration from subscription groups Certificate Pinning Pinned certificate (fill in either one) When specified, the certificate will be pinned, and "Allow Insecure" will be disabled. The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. Fetch Certificate Fetch Certificate Chain Please set a valid domain Certificate not set Certificate set Custom PAC file path Custom system proxy script file path macOS displays this in the Dock (requires restart) Configuration Item 2, Select and add from self-built EchConfigList EchForceQuery Full certificate (chain), PEM format Certificate fingerprint (SHA-256) Serve Stale Parallel Query By default, invoked only during routing for resolution By default, invoked only during routing for resolution; ensure the remote server can reach this DNS If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used. If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. Port hopping interval Configuration item preview Finalmask Routing rule {0} outbound node {1} warning: {2} Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. Group {0} has a cycle dependency on child node {1}. Skipping this node. Group {0} child node {1} warning: {2} Group {0} child node {1} error: {2}. Skipping this node. Group {0} child group node {1} warning: {2} Group {0} child group node {1} error: {2}. Skipping this node. Group {0} has no valid child node. Routing rule {0} has an empty outbound tag. Fallback to proxy node only. Routing rule {0} outbound node {1} not found. Fallback to proxy node only. Subscription previous proxy {0} not found. Skipping. Subscription next proxy {0} not found. Skipping. Generate Policy Group All configurations Group by Region ================================================ FILE: v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 导出分享链接至剪贴板成功 请先检查设置 配置格式不正确 注意,自定义配置完全依赖您自己的配置,不能使用所有设置功能。如需使用系统代理请手动修改监听端口。 下载开始... 转换配置失败 生成默认配置文件失败 获取默认配置失败 导入自定义配置失败 读取配置失败 请填写正确格式的端口 请填写本地监听端口 请填写密码 请填写地址 请填写用户 ID 配置不正确,请检查 初始化配置 {0} {1} 已是最新版本。 {0} {1} 已是最新版本。 地址 加密方式 端口 类型 订阅分组 今日下载 今日上传 总下载 总上传 传输协议 下载 Core 成功 导入订阅内容失败 获取订阅内容成功 未设置有效的订阅 解析 {0} 成功 开始获取订阅内容 开始更新 {0}... 无效的订阅内容 正在解压...... 更新订阅结束 更新订阅开始 更新 Core 成功 更新 Core 成功!正在重启服务... 非 VMess 或 ss 协议 在文件夹 ({0}) 下未找到 Core 文件 (文件名: {1}),请下载后放入文件夹,下载地址: {2} 扫描完成,未发现有效二维码 操作失败,请检查并重试 请填写别名 请选择加密方式 请选择协议 请先选择配置 配置去重完成。原数量: {0},现数量: {1}。 是否确定移除? 客户端配置文件保存在:{0} 启动服务 ({0})... 配置成功。 {0} 成功导入自定义配置 成功从剪贴板导入 {0} 个配置 扫描导入分享链接成功 当前延迟: {0} ms,{1} 操作成功 请先选择规则 是否确定移除规则? {0},必填其中一项. 别名 可选地址 (Url) 数量 请填写 Url 是否追加规则?选择"是"则追加,选择"否"则全部替换。 下载 GeoFile:{0} 成功 信息 自定义图标 请填写正确的自定义 DNS *ws/httpupgrade/xhttp path *h2 path *QUIC 加密密钥 *grpc serviceName *http host 中间逗号 (,) 分隔 *ws/httpupgrade/xhttp host *h2 host 中间逗号 (,) 分隔 *QUIC 加密方式 *tcp 伪装类型 *kcp 伪装类型 *QUIC 伪装类型 *grpc 模式 TLS *Kcp seed 注册全局热键 {0} 失败,原因:{1} 注册全局热键 {0} 成功 所有 请浏览导入配置 测试中... 局域网 本地 过滤器,按回车执行 检查更新 关闭 退出 全局热键设置 帮助 参数设置 推广 重启服务 路由设置 配置项 设置 更新当前订阅 (不通过代理) 更新当前订阅 (通过代理) 订阅分组 订阅分组设置 更新全部订阅 (不通过代理) 更新全部订阅 (通过代理) 系统代理 清除系统代理 不改变系统代理 Pac 模式 自动配置系统代理 颜色 语言 (需重启) 从剪贴板导入分享链接 扫描屏幕上的二维码 克隆所选 移除重复 移除所选 (多选) 设为活动 清除所有服务统计数据 测试真连接延迟 (多选) 按测试结果排序 测试速度 (多选) 测试延迟 Tcping (多选) 导出所选完整配置 导出分享链接至剪贴板 (多选) 添加自定义配置 添加 [Shadowsocks] 添加 [SOCKS] 添加 [Trojan] 添加 [VLESS] 添加 [VMess] 全选 清除所有 复制 复制所有 全选 添加 删除 编辑 分享 启用更新 排序 User Agent (可选) 取消 确定 底层传输方式 (transport) 地址 (address) 跳过证书验证 (allowInsecure) Alpn 额外 ID (alterId) Fingerprint 伪装类型 (type) 用户 ID (id) 传输协议 (network) 路径 (path) 端口 (port) 别名 (remarks) 伪装域名 (host) 加密方式 (security) SNI 传输层安全 (TLS) *默认 tcp,选错会无法连接 Core 类型 流控 (flow) 生成 密码 (password) 密码 (可选) 用户 ID (id) 加密方式 (encryption) 用户名 (可选) 加密方式 (encryption) Socks 端口 *自定义配置的 Socks 端口值,可不设置;当设置此值后,将使用 Xray/sing-box (Tun) 额外启动一个前置 Socks 服务,提供分流和速度显示等功能 浏览 编辑 高级代理设置,协议选择 (可选) 允许来自局域网的连接 启动后隐藏窗口 自动更新 Geo 文件的间隔 (小时) Core: 基础设置 v2ray 自定义 DNS Core: KCP 设置 Core 类型设置 默认跳过证书验证 (allowInsecure) Outbound Freedom domainStrategy 自动调整配置列宽在更新订阅后 检查 Pre-Release 更新 (请谨慎启用) 例外 例外:对于下列字符开头的地址,不使用代理配置。使用分号 (;) 分隔。 显示实时速度 (需重启) 去重时保留序号较小的项 启用日志 日志等级 开启 Mux 多路复用 v2rayN 设置 认证密码 自定义 DNS (可多个,用逗号 (,) 分隔) 解除 Win10 UWP 应用回环代理限制 开启流量探测 本地混合监听端口 开机启动 (可能会不成功) 启用流量统计 (需重启) 订阅转换网址 (可选) 系统代理设置 托盘右键菜单配置展示数量限制 开启 UDP 认证用户名 清除系统代理 显示主界面 全局热键设置 直接按键盘进行设置,重启后生效 不改变系统代理 重置 自动配置系统代理 Pac 模式 分享 路由 以非管理员身份运行 以管理员身份运行 下移至底 下移 上移至顶 上移 过滤器 (支持正则) {0} 官网 添加规则集 一键导入规则集 移除所选规则 设为活动规则 域名解析策略 预定义规则集列表 *设置的路由规则,用逗号 (,) 分隔;正则中的逗号用 <COMMA> 替代 从剪贴板中导入规则 从文件中导入规则 从订阅 Url 中导入规则 规则集设置 添加规则 导出所选规则至剪贴板 规则列表 移除所选规则 路由规则详情设置 保存时 Domain, IP, 进程名 自动排序 规则详细说明文档 支持填写 DnsObject,JSON 格式,点击查看文档 普通分组此处请留空 路由设置改变 系统代理设置改变 仅限路由 (routeOnly) 请勿将代理服务器用于本地 (Intranet) 地址 一键多线程测试延迟和速度 (Ctrl+E) 延迟 (ms) 速度 (MB/s) 运行 Core 失败,请查看提示信息 别名正则过滤 显示日志 启用 Tun 为局域网开启新的端口 Tun 模式设置 移至订阅分组 启用配置拖放排序 (需重启) 自动刷新 跳过测试 编辑 主界面双击设为活动 测试完成 默认 TLS 指纹 (fingerprint) 用户代理 (User-Agent) 仅对 tcp/http、ws 协议生效 当前字体 (需重启) 拷贝字体 TTF/TTC 文件到目录 guiFonts,重启生效 Pac 端口 = +3;Xray API 端口 = +4;mihomo API 端口 = +5; 以管理员权限设置此项,在启动后获得管理员权限 字体大小 测速单个超时值 测速文件地址 移至上下 PublicKey ShortId SpiderX 启用硬件加速 (需重启) 等待测试... 按 ESC 可终止测试 当有异常断流时请关闭 未启用更新,跳过此订阅 以管理员身份重启 更多地址 (url),用逗号 (,) 分隔;订阅转换将失效 自动更新间隔 (分钟) 启用日志存到文件 订阅转换目标类型 不需要转换时请留空 DNS 设置 sing-box 自定义 DNS 请填写 DNS JSON 结构,点击查看文档 点击导入默认 DNS 配置 sing-box 域名解析策略 sing-box Mux 多路复用协议 进程 (Linux/Windows) IP 或 IP CIDR Domain 添加 [Hysteria2] Hysteria 最大带宽 (Up/Dw) 使用系统 hosts 添加 [TUIC] 拥塞控制算法 前置代理配置别名 落地代理配置別名 请确保配置别名存在并唯一 自动路由 严格路由 协议栈 MTU 启用 IPv6 添加 [WireGuard] PrivateKey Reserved (2,3,4) Address (IPv4,IPv6) 混淆密码 (obfs password) (Domain 或 IP 或 进程名) 与 Port 与 Protocol 与 InboundTag => OutboundTag 自动滚动到末尾 真连接测试地址 测试终止中... *grpc Authority 添加 [HTTP] 启用分片 (Fragment) 启用 sing-box (规则集文件) 的缓存文件 自定义 sing-box rule-set 操作成功。请点击设置菜单重启应用。 打开存储所在的位置 排序 路由链 默认 延迟 下载速度 下载流量 主机 名称 网络 时间 类型 上传速度 上传流量 当前连接 关闭连接 关闭所有连接 当前代理 规则模式 直连 全局 随原配置 规则 延迟测试 当前部分延迟测试 刷新 设为活动 Outbound 默认解析策略 主界面布局方向 (需重启) Outbound 域名解析地址 自动调整列宽 导出分享链接至剪贴板 (多选) Base64 编码 导出所选完整配置至剪贴板 显示或隐藏主界面 自定义配置的 Socks 端口 备份和还原 备份到本地 从本地恢复 备份到远程 (WebDAV) 从远程恢复 (WebDAV) 本地 远程 (WebDAV) WebDav 服务器地址 WebDav 账户 WebDav 密码 WebDav 可用检查 远程文件夹名称 (可选) 无效备份文件 主机过滤器 活动 Geo 文件来源 (可选) sing-box ruleset 文件来源 (可选) 升级工具 App 不存在 路由规则集来源 (可选) 区域预置设置 默认区域 俄罗斯 伊朗 中国区域用户可忽略此项 扫描图片中的二维码 地址 (Url) 无效 请不要使用不安全的 HTTP 协议订阅地址 安装字体到系统中,选择或填入字体名称,重启生效 是否确定退出? 备注备忘 系统的 sudo 密码 密码将调用命令行校验,如果因为校验错误导致无法正常运行时,请重启本应用。 密码不会存储,每次重启后都需要再次输入。 *XHTTP 模式 XHTTP Extra 原始 JSON,格式: { XHTTPObject } 关闭窗口时隐藏至托盘 多线程测试时的并发数量 例外:对于下列地址不使用代理配置。使用逗号 (,) 分隔。 流量探测类型 开启第二个本地监听端口 Socks:本地端口,Socks2:第二个本地端口,Socks3:局域网端口 主题 复制终端代理命令至剪贴板 开始对失败部分进行重新测试,剩余 {0} 个。可按 ESC 终止... 按测试结果 按测试结果移除无效 移除无效测试结果 {0} 个。 跳跃端口范围 会覆盖端口,多组时用逗号 (,) 隔开 导出 当前连接信息测试地址 可以填写配置别名,请确保存在并唯一 密码错误,请重试。 Mldsa65Verify 添加 [Anytls] 远程 DNS 直连 DNS 直连目标解析策略 代理目标解析策略 添加常用 DNS Hosts FakeIP 阻止 SVCB 和 HTTPS 查询 DNS Hosts:(“域名1 ip1 ip2” 一行一个) DNS 基础设置 DNS 进阶设置 校验相应地区域名 IP 配置后,会对相应地区域名(如 geosite:cn - geoip:cn)的返回 IP 进行校验,仅返回期望 IP 启用自定义 DNS 自定义 DNS 已启用,此页面配置将无效 开启后将阻止 ECH 和 HTTP/3 可用性查询 请填写正确的配置模板 完整配置模板设置 启用完整配置模板 v2ray 完整配置模板 仅添加出站配置,routing.balancers 和 routing.rules.outboundTag,点击查看文档 不添加非代理协议出站 设置上游代理 tag sing-box 完整配置模板 仅添加出站和端点配置,点击查看文档 此功能供高级用户和有特殊需求的用户使用。 启用此功能后,将忽略 Core 的基础设置,DNS 设置 ,路由设置。你需要保证系统代理的端口和流量统计等功能的配置正确,一切都由你来设置。 开始解析和处理订阅内容 选择配置 默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效 请至少添加一个配置 策略组 链式代理 最低延迟 随机 负载均衡 最稳定 策略组类型 添加策略组 添加链式代理 添加子配置 删除子配置 子配置项一,从订阅分组中自动添加 故障转移 核心 '{0}' 不支持网络类型 '{1}' 核心 '{0}' 在使用传输方式 '{2}' 时不支持协议 '{1}' 核心 '{0}' 不支持协议 '{1}' {0} 属性无效,请检查 不支持协议 '{0}' 如果系统没有托盘功能,请不要开启 规则类型 可对 Routing 和 DNS 单独设定规则,ALL 则都生效 Bootstrap DNS 解析 DNS 服务器域名,需指定为 IP 一键测试真连接延迟 自动从订阅分组添加过滤后的配置 固定证书 固定证书(二选一填写即可) 当指定此证书后,将固定该证书,并禁用“跳过证书验证”选项。 “获取证书”操作可能失败,原因包括使用了自签名证书,或系统中存在不受信任甚至恶意的 CA。 获取证书 获取证书链 请设置有效的域名 证书未设置 证书已设置 自定义 PAC 文件路径 自定义系统代理脚本文件路径 macOS 在 Dock 栏中显示 (需重启) 子配置项二,从自建中选择添加 EchConfigList EchForceQuery 完整证书(链),PEM 格式 证书指纹(SHA-256) 乐观缓存 并行查询 默认仅在路由阶段被调用解析 默认仅在路由阶段被调用解析;请确保远程服务器可访问该 DNS 当未选择或 "AsIs" 时,使用系统 DNS 进行解析;否则,使用内部 DNS 模块解析。 当未选择或 "AsIs" 时,由远程服务器端 DNS 解析;否则,使用内部 DNS 模块解析。 端口跳跃间隔 子配置项预览 Finalmask 路由规则 {0} 出站节点 {1} 警告:{2} 路由规则 {0} 出站节点 {1} 错误:{2}。已回退为仅使用代理节点。 节点组 {0} 与子节点 {1} 存在循环依赖,已跳过该节点。 节点组 {0} 子节点 {1} 警告:{2} 节点组 {0} 子节点 {1} 错误:{2}。已跳过该节点。 节点组 {0} 子节点组 {1} 警告:{2} 节点组 {0} 子节点组 {1} 错误:{2}。已跳过该节点。 节点组 {0} 下没有有效的子节点。 路由规则 {0} 的出站标签为空,已回退为仅使用代理节点。 路由规则 {0} 的出站节点 {1} 未找到,已回退为仅使用代理节点。 订阅前置节点 {0} 未找到,已跳过。 订阅后置节点 {0} 未找到,已跳过。 一键生成策略组 全部配置项 按地区分组 ================================================ FILE: v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 匯出分享連結至剪貼簿成功 請先檢查設定 設定格式不正確 注意,自訂設定完全依賴您自行輸入的內容,部分功能可能無法使用。如需啟用系統代理,請手動調整監聽埠。 下載開始... 轉換設定失敗 生成預設設定檔失敗 取得預設設定失敗 匯入自訂設定失敗 讀取設定失敗 請填寫有效的埠號 請填寫本機偵聽埠 請填寫密碼 請填寫位址 請填寫使用者 ID 設定不正確,請檢查 初始化設定 {0} {1} 已是最新版本。 {0} {1} 已是最新版本。 位址 加密方式 類型 訂閱分組 今日下載 今日上傳 總下載 總上傳 傳輸協定 下載 Core 成功 匯入訂閱內容失敗 獲取訂閱內容成功 未設定有效的訂閱 解析 {0} 成功 開始獲取訂閱內容 開始更新 {0}... 無效的訂閱內容 正在解壓...... 更新訂閱結束 更新訂閱開始 更新 Core 成功 更新 Core 成功!正在重啟服務... 非 VMess 或 SS 協定 在資料夾 ({0}) 中找不到 Core 檔案(檔名:{1})。請下載後放入該資料夾。下載網址:{2} 掃描完成,未發現有效二維碼 操作失敗,請檢查後重試 請填寫別名 請選擇加密方式 請選擇協定 請先選擇設定 去重完成。原數量: {0},現數量: {1}。 是否確定移除? 用戶端設定檔儲存在:{0} 啟動服務 ({0})... 設定成功。{0} 成功匯入自訂節點 成功從剪貼簿匯入 {0} 個節點 掃描匯入分享連結成功 目前延遲: {0} ms,{1} 操作成功 請先選擇規則 是否確定移除規則? {0},至少需填寫其中一項。 別名 可選位址 (URL) 數量 請填寫 URL 是否追加規則?選擇"是"則追加,選擇"否"則完全取代。 下載 GeoFile:{0} 成功 資訊 自訂圖示 請填寫正確的自訂 DNS *ws/httpupgrade/xhttp path *h2 path *QUIC 加密金鑰 *grpc serviceName *http host 中間逗號 (,) 分隔 *ws/httpupgrade/xhttp host *h2 host 中間逗號 (,) 分隔 *QUIC 加密方式 *TCP 偽裝類型 *KCP 偽裝類型 *QUIC 偽裝類型 *GRPC 模式 TLS *KCP seed 註冊全域快速鍵 {0} 失敗,原因:{1} 註冊全域快速鍵 {0} 成功 所有 請選擇要匯入的設定檔 測試中... 區域網路 本機 過濾器,按 Enter 執行 檢查更新 關閉 退出 全域快速鍵設定 說明 參數設定 推廣 重啟服務 路由設定 設定檔 設定 更新目前訂閱 (不透過代理) 更新目前訂閱 (透過代理) 訂閱分組 訂閱分組設定 更新全部訂閱 (不透過代理) 更新全部訂閱 (透過代理) 系統代理 清除系統代理 不改變系統代理 PAC 模式 自動設定系統代理 顏色 語言 (需重啟) 從剪貼簿匯入分享連結 掃描螢幕上的二維碼 複製所選 移除重複 移除所選 (多選) 設為活動 清除所有服務統計資料 測試真連線延遲 (多選) 按測試結果排序 測試速度 (多選) 測試延遲 Tcping (多選) 匯出所選完整設定 匯出分享連結至剪貼簿 (多選) 新增自訂節點 新增 [Shadowsocks] 節點 新增 [SOCKS] 節點 新增 [Trojan] 節點 新增 [VLESS] 節點 新增 [VMess] 節點 全選 清除所有 複製 複製所有 全選 新增 刪除 編輯 分享 啟用更新 排序 User Agent (可選) 取消 確定 底層傳輸方式 (transport) 位址 (address) 跳過憑證驗證 (allowinsecure) ALPN 額外 ID (alterid) Fingerprint 偽裝類型 (type) 使用者 ID (id) 傳輸協定 (network) 路徑 (path) 埠 (port) 別名 (remarks) 偽裝域名 (host) 加密方式 (security) SNI 傳輸層安全性 (TLS) *預設 TCP,選錯會無法連線 Core 類型 流控 (flow) 生成 密碼 (password) 密碼 (可選) 使用者 ID (id) 加密方式 (encryption) 使用者名稱 (可選) 加密方式 (encryption) SOCKS 埠 *自訂設定的 Socks 埠值,可留空;當設定此值後,將使用 Xray/sing-box (Tun) 額外啟動一個前置 Socks 服務,提供分流和速度顯示等功能 瀏覽 編輯 進階代理設定,協定選擇 (可選) 允許來自區域網路的連線 啟動後隱藏視窗 自動更新 Geo 檔案的間隔 (小時) Core: 基礎設定 v2ray 自訂 DNS Core: KCP 設定 Core 類型設定 預設跳過憑證驗證 (allowinsecure) Outbound Freedom domainStrategy 在更新訂閱後自動調整列寬 檢查 Pre-Release 更新 (請謹慎啟用) 例外 例外:對於下列字元開頭的位址,不使用代理。使用分號 (;) 分隔。 顯示即時速度(需重啟) 去重時保留序號較小的項 啟用日誌 日誌等級 開啟 Mux 多路復用 v2rayN 設定 認證密碼 自訂 DNS (可多個,用逗號 (,) 分隔) 解除 Win10 UWP 應用回環代理限制 開啟流量探測 本機混合偵聽埠 開機啟動 (可能會不成功) 啟用流量統計(需重啟) 訂閱轉換網址 (可選) 系統代理設定 工具列右鍵選單設定展示數量限制 開啟 UDP 認證使用者名稱 清除系統代理 顯示主介面 全域快速鍵設定 直接按鍵盤進行設定,重啟後生效 不改變系統代理 重設 自動設定系統代理 PAC 模式 分享 路由 以非管理員身份執行 以管理員身份執行 下移至底部 下移 上移至頂部 上移 過濾 (允許正則) {0} 官網 新增規則集 一鍵匯入規則集 移除所選規則 設為活動規則 域名解析策略 預定義規則集列表 *設定的路由規則,用逗號 (,) 分隔;正則中的逗號用 <COMMA> 替代 從剪貼簿中匯入規則 從檔案中匯入規則 從訂閱 URL 中匯入規則 規則集設定 新增規則 匯出所選規則至剪貼簿 規則列表 移除所選規則 路由規則詳情設定 儲存時 Domain, IP, 行程名 自動排序 規則詳細說明檔案 支援填寫 DnsObject,JSON 格式,點擊查看說明 普通分組此處請留空 路由設定已改變 系統代理設定已改變 僅限路由 (routeOnly) 請勿將代理伺服器用於本機(Intranet)位址 一鍵延遲與速度測試 (Ctrl+E) 延遲 (ms) 速度 (MB/s) 執行 Core 失敗,請查看提示訊息 別名正則過濾 顯示日誌 啟用 Tun 為區域網路開啟新的埠 Tun 模式設定 移至訂閱分組 啟用拖放排序 (需重啟) 自動重新整理 跳過測試 編輯 主介面輕按兩下設為活動 測試完成 預設 TLS 指紋 (fingerprint) 使用者代理 (User-Agent) 僅對 TCP/HTTP、WS 協定生效 目前字型 (需重啟) 複製字型 TTF/TTC 檔案到目錄 guiFonts,重新啟動後生效 Pac 連接埠 = +3;Xray API 連接埠 = +4;mihomo API 連接埠 = +5; 以管理員權限設定此項,在啟動後獲得管理員權限 字型大小 測速單個超時值 測速檔案位址 移至上下 PublicKey ShortId SpiderX 啟用硬體加速 (需重啟) 等待測試中... 按 ECS 以終止測試 當有異常斷流時請關閉 未啟動更新,跳過此訂閱 以管理員身份重啟 更多位址 (url),用逗號 (,) 分隔;訂閱轉換將失效 自動更新間隔 (分鐘) 啟動日誌存到檔案 訂閱轉換目標類型 不需要轉換時請留空 DNS設定 sing-box 自訂 DNS 請填寫 DNS JSON 結構,點擊查看檔案 點擊匯入預設 DNS 設定 sing-box 域名解析策略 sing-box Mux 多路復用協定 行程 (Linux/Windows) IP 或 IP CIDR Domain 新增 [Hysteria2] 節點 Hysteria 最大頻寬 (Up/Dw) 使用系統 hosts 新增 [TUIC] 節點 擁塞控制算法 前置代理節點別名 落地代理節點別名 請確保節點別名存在並且唯一 自動路由 嚴格路由 協定堆疊 MTU 啟用 IPv6 新增 [WireGuard] 節點 PrivateKey Reserved (2,3,4) Address (Ipv4,Ipv6) 混淆密碼 (obfs password) (Domain 或 IP 或 行程名) 與 Port 與 Protocol 與 InboundTag => OutboundTag 自動滾動到末尾 真連線測試位址 測試終止中... *grpc Authority 新增 [HTTP] 節點 啟用分片(Fragment) 啟用 sing-box(規則集檔案)的快取檔案 自訂 sing-box rule-set 操作成功。請點選設定選單重啟應用程式。 打開儲存所在的位置 排序 路由鏈 預設 延遲 下載速度 下載流量 主機 名稱 網路 時間 類型 上傳速度 上傳流量 目前連線 關閉連線 關閉所有連線 目前代理 規則模式 直連 全局 隨原配置 規則 延遲測試 目前部分節點延遲測試 重新整理 設為活動節點 Outbound 預設解析策略 主界面佈局方向 (需重啟) Outbound 域名解析位址 自動調整列寬 匯出分享連結至剪貼簿 (多選) Base64 編碼 匯出所選完整設定至剪貼簿 顯示或隱藏主介面 自訂 Socks 連接埠 備份和還原 備份到本地 從本地恢復 備份到遠端 (WebDAV) 從遠端恢復 (WebDAV) 本地 遠端 (WebDAV) WebDav 伺服器位址 WebDav 帳戶 WebDav 密碼 WebDav 可用檢查 遠端資料夾名稱 (可選) 無效備份檔案 主機過濾 活動 Geo 檔案來源 (可選) sing-box ruleset 檔案來源 (可選) 升級工具 App 不存在 路由規則集來源 (可選) 區域預置設定 預設區域 俄羅斯 伊朗 中國區域用戶可忽略此項 掃描圖片中的二維碼 位址 (Url) 無效 請不要使用不安全的 HTTP 協定訂閱位址 安裝字體到系統中,選擇或填入字體名稱,重新啟動後生效 確定要退出嗎? 備註備忘 系統的 sudo 密碼 密碼將調用命令行校驗,如果因為校驗錯誤導致無法正常運行時,請重啟本應用。密碼不會存儲,每次重啟後都需要再次輸入。 *xhttp 模式 XHTTP Extra 原始 JSON,格式: { XHTTPObject } 關閉視窗時隱藏至托盤 多執行緒測試時的並發數量 例外:對於下列位址不使用代理,使用逗號 (,) 分隔。 流量探測類型 開啟第二個本機監聽埠 socks:本地埠,socks2:第二個本地埠,socks3:區域網路埠 主題 複製終端代理指令至剪貼簿 開始對失敗部分進行重新測試,剩餘 {0} 個。可按 ESC 終止... 按測試結果 按測試結果移除無效 移除無效測試結果 {0} 個。 跳躍埠範圍 會覆蓋埠,多組時用逗號 (,) 隔開 匯出 目前連接資訊測試地址 可以填寫節點別名,請確保存在並唯一 密碼錯誤,請重試。 Mldsa65Verify 新增 [Anytls] 節點 遠程 DNS 直連 DNS 直連目標解析策略 代理目標解析策略 新增常用 DNS Hosts FakeIP 阻止 SVCB 和 HTTPS 查詢 DNS Hosts:(“網域名稱1 ip1 ip2” 一行一個) DNS 基礎設定 DNS 進階設定 校驗相應地區域名 IP 配置後,會對相應地區域名(如 geosite:cn - geoip:cn)的返回 IP 進行校驗,僅返回期望 IP 啟用自訂 DNS 自訂 DNS 已啟用,此頁面配置將無效 開啟後將阻止 ECH 和 HTTP/3 可用性查詢 請填寫正確的配置範本 完整配置範本設定 啟用完整配置範本 v2ray 完整配置範本 僅添加出站配置,routing.balancers 和 routing.rules.outboundTag,點擊查看文檔 不添加非代理協定出站 設定上游代理 tag sing-box 完整配置範本 僅添加出站和端點配置,點擊查看文檔 此功能供高級用戶和有特殊需求的用戶使用。 啟用此功能後,將忽略 Core 的基礎設定,DNS 設定 ,路由設定。你需要保證系統代理的埠和流量統計等功能的配置正確,一切都由你來設定。 開始解析和處理訂閱內容 選擇節點 默認全域生效,內置 FakeIP 過濾,僅在 sing-box 中生效 請至少添加一個節點 策略組 鏈式代理 最低延遲 隨機 負載均衡 最穩定 策略組類型 新增策略組 新增鏈式代理 新增子配置 刪除子配置 子配置項目一,從訂閱分組中自動新增 容錯移轉 核心 '{0}' 不支援網路類型 '{1}' 核心 '{0}' 在使用傳輸方式 '{2}' 時不支援協定 '{1}' 核心 '{0}' 不支援協定 '{1}' {0} 屬性無效,請檢查 不支援協定 '{0}' 如果系統沒有託盤功能,請不要開啟 規則類型 可對 Routing 和 DNS 單獨設定規則,ALL 則都生效 Bootstrap DNS 解析 DNS 伺服器網域名稱,需指定為 IP 一鍵測試真連線延遲 自動從訂閱分組新增過濾後的配置 憑證綁定 固定憑證(二選一填寫即可) 若已指定,憑證將會被綁定,並且「跳過憑證驗證」將被停用。 若使用自簽憑證,或系統中存在不受信任或惡意的 CA,「取得憑證」動作可能會失敗。 獲取憑證 獲取憑證鏈 請設定有效的網域名稱 尚未設定憑證 已設定憑證 自訂 PAC 檔案路徑 自訂系統代理程式腳本檔案路徑 macOS 在 Dock 欄顯示 (需重啟) 子配置項二,從自建中選擇新增 EchConfigList EchForceQuery 完整憑證(鏈),PEM 格式 憑證指紋(SHA-256) 提供過期快取(Serve Stale) 并行查詢 預設僅在路由期間進行解析時調用 預設僅在路由期間進行解析時調用;請確保遠端伺服器能連線至此 DNS 若未設定或為 "AsIs",使用系統 DNS 解析;否則將使用內建 DNS 模組。 若未設定或為 "AsIs",由遠端伺服器的 DNS 解析;否則將使用內建 DNS 模組。 連接埠跳轉間隔 子配置項預覽 Finalmask 路由規則 {0} 的出站節點 {1} 發出警告:{2} 路由規則 {0} 的出站節點 {1} 發生錯誤:{2}。已回退為僅使用代理節點。 節點組 {0} 與子節點 {1} 存在循環依賴。已跳過此節點。 節點組 {0} 的子節點 {1} 發出警告:{2} 節點組 {0} 的子節點 {1} 發生錯誤:{2}。已跳過此節點。 節點組 {0} 的子節點組 {1} 發出警告:{2} 節點組 {0} 的子節點組 {1} 發生錯誤:{2}。已跳過此節點。 節點組 {0} 沒有可用的有效子節點。 路由規則 {0} 的出站標籤為空。已回退為僅使用代理節點。 找不到路由規則 {0} 的出站節點 {1}。已回退為僅使用代理節點。 找不到訂閱的前一個代理 {0}。已跳過。 找不到訂閱的下一個代理 {0}。已跳過。 生成策略組 所有配置項 按區域分組 ================================================ FILE: v2rayN/ServiceLib/Sample/SampleClientConfig ================================================ { "log": { "access": "Vaccess.log", "error": "Verror.log", "loglevel": "warning" }, "inbounds": [], "outbounds": [ { "protocol": "freedom", "tag": "direct" }, { "protocol": "blackhole", "tag": "block" } ], "routing": { "domainStrategy": "IPIfNonMatch", "rules": [ { "inboundTag": [ "api" ], "outboundTag": "api", "type": "field" } ] } } ================================================ FILE: v2rayN/ServiceLib/Sample/SampleHttpRequest ================================================ {"version":"1.1","method":"GET","path":[$requestPath$],"headers":{"Host":[$requestHost$],"User-Agent":[$requestUserAgent$],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}} ================================================ FILE: v2rayN/ServiceLib/Sample/SampleHttpResponse ================================================ {"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}} ================================================ FILE: v2rayN/ServiceLib/Sample/SampleInbound ================================================ { "tag": "tag1", "port": 10808, "protocol": "socks", "listen": "127.0.0.1", "settings": { "auth": "noauth", "udp": true, "allowTransparent": false }, "sniffing": { "enabled": true, "destOverride": [ "http", "tls" ] } } ================================================ FILE: v2rayN/ServiceLib/Sample/SampleOutbound ================================================ { "tag": "proxy", "protocol": "vmess", "settings": { "vnext": [ { "address": "v2ray.cool", "port": 10086, "users": [ { "id": "a3482e88-686a-4a58-8126-99c9df64b7bf", "security": "auto" } ] } ], "servers": [ { "address": "v2ray.cool", "method": "chacha20", "ota": false, "password": "123456", "port": 10086, "level": 1 } ] }, "streamSettings": { "network": "tcp" }, "mux": { "enabled": false } } ================================================ FILE: v2rayN/ServiceLib/Sample/SingboxSampleClientConfig ================================================ { "log": { "level": "debug", "timestamp": true }, "inbounds": [], "outbounds": [ { "type": "direct", "tag": "direct" } ], "route": { "rules": [] } } ================================================ FILE: v2rayN/ServiceLib/Sample/SingboxSampleOutbound ================================================ { "type": "vless", "tag": "proxy", "server": "", "server_port": 443 } ================================================ FILE: v2rayN/ServiceLib/Sample/clash_mixin_yaml ================================================ # # 配置文件内容不会被修改,混合行为只会发生在内存中 # # 注意下面缩进,请用支持yaml显示的编辑器打开 # # 使用clash配置文件关键字则覆盖原配置 # # removed-rules 循环匹配rules数组每行,符合则移除当前行 (此规则请放最前面) # # append-rules 数组合并至原配置rules数组后 # prepend-rules 数组合并至原配置rules数组前 # append-proxies 数组合并至原配置proxies数组后 # prepend-proxies 数组合并至原配置proxies数组前 # append-proxy-groups 数组合并至原配置proxy-groups数组后 # prepend-proxy-groups 数组合并至原配置proxy-groups数组前 # append-rule-providers 数组合并至原配置rule-providers数组后 # prepend-rule-providers 数组合并至原配置rule-providers数组前 # dns: enable: true enhanced-mode: fake-ip nameserver: - 114.114.114.114 - 223.5.5.5 - 8.8.8.8 fallback: [] fake-ip-filter: - +.stun.*.* - +.stun.*.*.* - +.stun.*.*.*.* - +.stun.*.*.*.*.* - "*.n.n.srv.nintendo.net" - +.stun.playstation.net - xbox.*.*.microsoft.com - "*.*.xboxlive.com" - "*.msftncsi.com" - "*.msftconnecttest.com" - WORKGROUP ================================================ FILE: v2rayN/ServiceLib/Sample/clash_tun_yaml ================================================ tun: enable: true stack: gvisor dns-hijack: - 0.0.0.0:53 auto-route: true auto-detect-interface: true ================================================ FILE: v2rayN/ServiceLib/Sample/custom_routing_black ================================================ [ { "remarks": "绕过bittorrent", "outboundTag": "direct", "protocol": [ "bittorrent" ] }, { "remarks": "api.ip.sb", "outboundTag": "proxy", "domain": [ "api.ip.sb" ] }, { "remarks": "阻断udp443", "outboundTag": "block", "port": "443", "network": "udp" }, { "remarks": "代理Google", "outboundTag": "proxy", "domain": [ "geosite:google" ] }, { "remarks": "绕过局域网IP", "outboundTag": "direct", "ip": [ "geoip:private" ] }, { "remarks": "绕过局域网域名", "outboundTag": "direct", "domain": [ "geosite:private" ] }, { "remarks": "代理海外公共DNSIP", "outboundTag": "proxy", "ip": [ "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001", "1.1.1.2", "1.0.0.2", "2606:4700:4700::1112", "2606:4700:4700::1002", "1.1.1.3", "1.0.0.3", "2606:4700:4700::1113", "2606:4700:4700::1003", "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844", "94.140.14.14", "94.140.15.15", "2a10:50c0::ad1:ff", "2a10:50c0::ad2:ff", "94.140.14.15", "94.140.15.16", "2a10:50c0::bad1:ff", "2a10:50c0::bad2:ff", "94.140.14.140", "94.140.14.141", "2a10:50c0::1:ff", "2a10:50c0::2:ff", "208.67.222.222", "208.67.220.220", "2620:119:35::35", "2620:119:53::53", "208.67.222.123", "208.67.220.123", "2620:119:35::123", "2620:119:53::123", "9.9.9.9", "149.112.112.112", "2620:fe::9", "2620:fe::fe", "9.9.9.11", "149.112.112.11", "2620:fe::11", "2620:fe::fe:11", "9.9.9.10", "149.112.112.10", "2620:fe::10", "2620:fe::fe:10", "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff", "77.88.8.88", "77.88.8.2", "2a02:6b8::feed:bad", "2a02:6b8:0:1::feed:bad", "77.88.8.7", "77.88.8.3", "2a02:6b8::feed:a11", "2a02:6b8:0:1::feed:a11" ] }, { "remarks": "代理海外公共DNS域名", "outboundTag": "proxy", "domain": [ "domain:cloudflare-dns.com", "domain:one.one.one.one", "domain:dns.google", "domain:adguard-dns.com", "domain:opendns.com", "domain:umbrella.com", "domain:quad9.net", "domain:yandex.net" ] }, { "remarks": "代理IP", "outboundTag": "proxy", "ip": [ "geoip:facebook", "geoip:fastly", "geoip:google", "geoip:netflix", "geoip:telegram", "geoip:twitter" ] }, { "remarks": "代理GFW", "outboundTag": "proxy", "domain": [ "geosite:gfw", "geosite:greatfire" ] }, { "remarks": "最终直连", "port": "0-65535", "outboundTag": "direct" } ] ================================================ FILE: v2rayN/ServiceLib/Sample/custom_routing_global ================================================ [ { "remarks": "阻断udp443", "outboundTag": "block", "port": "443", "network": "udp" }, { "remarks": "绕过局域网IP", "outboundTag": "direct", "ip": [ "geoip:private" ] }, { "remarks": "绕过局域网域名", "outboundTag": "direct", "domain": [ "geosite:private" ] }, { "remarks": "最终代理", "port": "0-65535", "outboundTag": "proxy" } ] ================================================ FILE: v2rayN/ServiceLib/Sample/custom_routing_white ================================================ [ { "remarks": "阻断udp443", "outboundTag": "block", "port": "443", "network": "udp" }, { "remarks": "代理Google", "outboundTag": "proxy", "domain": [ "geosite:google" ] }, { "remarks": "绕过局域网IP", "outboundTag": "direct", "ip": [ "geoip:private" ] }, { "remarks": "绕过局域网域名", "outboundTag": "direct", "domain": [ "geosite:private" ] }, { "remarks": "绕过中国公共DNSIP", "outboundTag": "direct", "ip": [ "223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1", "119.29.29.29", "1.12.12.12", "120.53.53.53", "2402:4e00::", "2402:4e00:1::", "180.76.76.76", "2400:da00::6666", "114.114.114.114", "114.114.115.115", "114.114.114.119", "114.114.115.119", "114.114.114.110", "114.114.115.110", "180.184.1.1", "180.184.2.2", "101.226.4.6", "218.30.118.6", "123.125.81.6", "140.207.198.6", "1.2.4.8", "210.2.4.8", "52.80.66.66", "117.50.22.22", "2400:7fc0:849e:200::4", "2404:c2c0:85d8:901::4", "117.50.10.10", "52.80.52.52", "2400:7fc0:849e:200::8", "2404:c2c0:85d8:901::8", "117.50.60.30", "52.80.60.30" ] }, { "remarks": "绕过中国公共DNS域名", "outboundTag": "direct", "domain": [ "domain:alidns.com", "domain:doh.pub", "domain:dot.pub", "domain:360.cn", "domain:onedns.net" ] }, { "remarks": "绕过中国IP", "outboundTag": "direct", "ip": [ "geoip:cn" ] }, { "remarks": "绕过中国域名", "outboundTag": "direct", "domain": [ "geosite:cn" ] } ] ================================================ FILE: v2rayN/ServiceLib/Sample/dns_singbox_normal ================================================ { "servers": [ { "tag": "remote", "type": "tcp", "server": "8.8.8.8", "detour": "proxy" }, { "tag": "local", "type": "udp", "server": "223.5.5.5" } ], "rules": [ { "rule_set": [ "geosite-google" ], "server": "remote", "strategy": "prefer_ipv4" }, { "rule_set": [ "geosite-cn" ], "server": "local", "strategy": "prefer_ipv4" } ], "final": "remote", "strategy": "prefer_ipv4" } ================================================ FILE: v2rayN/ServiceLib/Sample/dns_v2ray_normal ================================================ { "hosts": { "dns.google": "8.8.8.8", "proxy.example.com": "127.0.0.1" }, "servers": [ { "address": "1.1.1.1", "skipFallback": true, "domains": [ "geosite:google" ] }, { "address": "223.5.5.5", "skipFallback": true, "domains": [ "geosite:cn" ], "expectIPs": [ "geoip:cn" ] }, "1.1.1.1", "8.8.8.8", "https://dns.google/dns-query" ] } ================================================ FILE: v2rayN/ServiceLib/Sample/kill_as_sudo_linux_sh ================================================ #!/bin/bash # # Process Terminator Script for Linux # This script forcibly terminates a process and all its child processes # # Check if PID argument is provided if [ $# -ne 1 ]; then echo "Usage: $0 " exit 1 fi PID=$1 # Validate that input is a valid PID (numeric) if ! [[ "$PID" =~ ^[0-9]+$ ]]; then echo "Error: The PID must be a numeric value" exit 1 fi # Check if the process exists if ! ps -p $PID > /dev/null; then echo "Warning: No process found with PID $PID" exit 0 fi # Recursive function to find and kill all child processes kill_children() { local parent=$1 local children=$(ps -o pid --no-headers --ppid "$parent") # Output information about processes being terminated echo "Processing children of PID: $parent..." # Process each child for child in $children; do # Recursively find and kill child's children first kill_children "$child" # Force kill the child process echo "Terminating child process: $child" kill -9 "$child" 2>/dev/null || true done } echo "============================================" echo "Starting termination of process $PID and all its children" echo "============================================" # Try graceful termination first echo "Attempting graceful termination (SIGTERM) of PID: $PID" kill -15 "$PID" 2>/dev/null || true sleep 1 # If still running, fall back to kill_children if ps -p $PID > /dev/null; then echo "Process $PID did not exit after SIGTERM; proceeding with forced termination of its children and itself" else echo "Process $PID exited cleanly after SIGTERM" exit 0 fi # Find and kill all child processes kill_children "$PID" # Finally kill the main process echo "Terminating main process: $PID" kill -9 "$PID" 2>/dev/null || true echo "============================================" echo "Process $PID and all its children have been terminated" echo "============================================" exit 0 ================================================ FILE: v2rayN/ServiceLib/Sample/kill_as_sudo_osx_sh ================================================ #!/bin/bash # # Process Terminator Script for macOS # This script forcibly terminates a process and all its descendant processes # # Check if PID argument is provided if [ $# -ne 1 ]; then echo "Usage: $0 " exit 1 fi PID=$1 # Validate that input is a valid PID (numeric) if ! [[ "$PID" =~ ^[0-9]+$ ]]; then echo "Error: The PID must be a numeric value" exit 1 fi # Check if the process exists - using kill -0 which is more reliable on macOS if ! kill -0 $PID 2>/dev/null; then echo "Warning: No process found with PID $PID" exit 0 fi # Recursive function to find and kill all descendant processes kill_descendants() { local parent=$1 # Use ps -axo for macOS to ensure all processes are included local children=$(ps -axo pid=,ppid= | awk -v ppid=$parent '$2==ppid {print $1}') echo "Processing children of PID: $parent..." for child in $children; do kill_descendants "$child" echo "Terminating child process: $child" kill -9 "$child" 2>/dev/null || true done } echo "============================================" echo "Starting termination of process $PID and all its descendants" echo "============================================" # Try graceful termination first echo "Attempting graceful termination (SIGTERM) of PID: $PID" kill -15 "$PID" 2>/dev/null || true sleep 1 # If still running, fall back to kill_descendants # Use the macOS-native 'kill -0' check if kill -0 $PID 2>/dev/null; then echo "Process $PID did not exit after SIGTERM; proceeding with forced termination of its descendants and itself" else echo "Process $PID exited cleanly after SIGTERM" exit 0 fi # Find and kill all descendant processes kill_descendants "$PID" # Finally kill the main process echo "Terminating main process: $PID" kill -9 "$PID" 2>/dev/null || true echo "============================================" echo "Process $PID and all its descendants have been terminated" echo "============================================" exit 0 ================================================ FILE: v2rayN/ServiceLib/Sample/linux_autostart_config ================================================ [Desktop Entry] Type=Application Exec=$ExecPath$ Hidden=false NoDisplay=false X-GNOME-Autostart-enabled=true Name[en_US]=v2rayN Name=v2rayN Comment[en_US]=v2rayN Comment=v2rayN ================================================ FILE: v2rayN/ServiceLib/Sample/pac ================================================ var proxy = '__PROXY__'; var rules = [ [ [], [ "000webhost.com", "030buy.com", "0rz.tw", "1-apple.com.tw", "1000giri.net", "100ke.org", "10beasts.net", "10conditionsoflove.com", "10musume.com", "111666.best", "123rf.com", "12bet.com", "12vpn.com", "12vpn.net", "1337x.to", "138.com", "141-hk.com", "141hongkong.com", "141jj.com", "141tube.com", "1688.com.au", "17.live", "173ng.com", "177pic.info", "17t17p.com", "18board.com", "18comic.org", "18comic.vip", "18hmanga.click", "18jav.tv", "18onlygirls.com", "18p2p.com", "18virginsex.com", "1949er.org", "1984.city", "1984bbs.com", "1984bbs.org", "1991way.com", "1bao.org", "1dumb.com", "1e100.net", "1eew.com", "1lib.domains", "1lib.sk", "1mobile.com", "1point3acres.com", "1pondo.tv", "2-hand.info", "2000fun.com", "2008xianzhang.info", "2021hkcharter.com", "2047.name", "2047.one", "2049bbs.xyz", "21andy.com", "21join.com", "21pron.com", "21sextury.com", "228.net.tw", "233abc.com", "233v2.com", "24hrs.ca", "25u.com", "2du5.com", "2lipstube.com", "2shared.com", "2waky.com", "3-a.net", "30boxes.com", "315lz.com", "32red.com", "36rain.com", "3a5a.com", "3arabtv.com", "3boys2girls.com", "3d-game.com", "3proxy.ru", "3ren.ca", "3tui.net", "404museum.com", "466453.com", "4bluestones.biz", "4chan.com", "4dq.com", "4everland.io", "4everproxy.com", "4gtv.tv", "4irc.com", "4mydomain.com", "4pu.com", "4rbtv.com", "4shared.com", "4sqi.net", "500px.com", "500px.org", "50webs.com", "51.ca", "51jav.org", "51luoben.com", "5278.cc", "5299.tv", "55comic.com", "5657.com.tw", "5i01.com", "5isotoi5.org", "5maodang.com", "611.icu", "611study.com", "611study.icu", "63i.com", "64museum.org", "64tianwang.com", "64wiki.com", "66.ca", "666kb.com", "666pool.cn", "69shu.com", "69shuba.com", "69shuba.cx", "6do.news", "6do.world", "6park.com", "6parkbbs.com", "6parker.com", "6parknews.com", "7capture.com", "7cow.com", "7mmtv.tv", "8-d.com", "85cc.net", "85cc.us", "85st.com", "881903.com", "888.com", "888poker.com", "89-64.org", "8964museum.com", "8news.com.tw", "8world.com", "8z1.net", "91dasai.com", "91jinman.com", "91porn.com", "91porny.com", "91vps.club", "92ccav.com", "991.com", "99btgc01.com", "99cn.info", "9bis.com", "9bis.net", "9cache.com", "9gag.com", "9news.com.au", "a-normal-day.com", "a5.com.ru", "a5vpn.com", "aamacau.com", "abc.com", "abc.net.au", "abc.xyz", "abchinese.com", "abebooks.co.uk", "abebooks.com", "ablwang.com", "aboluowang.com", "about.me", "abplive.com", "abs.edu", "acast.com", "accim.org", "accountkit.com", "aceros-de-hispania.com", "acevpn.com", "acg.rip", "acg18.me", "acgbox.link", "acgbox.org", "acgkj.com", "acgnx.se", "acmedia365.com", "acmetoy.com", "acnw.com.au", "actfortibet.org", "actimes.com.au", "activpn.com", "aculo.us", "adcex.com", "addictedtocoffee.de", "addyoutube.com", "adelaidebbs.com", "adguard-vpn.com", "admob.com", "adpl.org.hk", "ads-twitter.com", "adsense.com", "adult-sex-games.com", "adultfriendfinder.com", "adultkeep.net", "advanscene.com", "advertfan.com", "advertisercommunity.com", "ae.org", "aei.org", "aenhancers.com", "aex.com", "af.mil", "afantibbs.com", "afr.com", "afreecatv.com", "agemys.net", "agnesb.fr", "agoogleaday.com", "agro.hk", "ai-kan.net", "ai-wen.net", "aiosearch.com", "aiph.net", "airasia.com", "airconsole.com", "aircrack-ng.org", "airitilibrary.com", "airvpn.org", "aisex.com", "ait.org.tw", "aiv-cdn.net", "aiv-delivery.net", "aiweiwei.com", "aiweiweiblog.com", "ajsands.com", "akademiye.org", "akamai.net", "akamaihd.net", "akamaistream.net", "akamaized.net", "akiba-online.com", "akiba-web.com", "akinator.com", "akow.org", "al-islam.com", "al-qimmah.net", "alabout.com", "alanhou.com", "alarab.qa", "alasbarricadas.org", "alexlur.org", "alforattv.net", "alhayat.com", "alicejapan.co.jp", "aliengu.com", "alive.bar", "aljazeera.com", "aljazeera.net", "alkasir.com", "all4mom.org", "allcoin.com", "allconnected.co", "alldrawnsex.com", "allervpn.com", "allfinegirls.com", "allgirlmassage.com", "allgirlsallowed.org", "allgravure.com", "alliance.org.hk", "allinfa.com", "alljackpotscasino.com", "allmovie.com", "allowed.org", "almasdarnews.com", "almostmy.com", "alphaporno.com", "alternate-tools.com", "alternativeto.net", "altrec.com", "alvinalexander.com", "alwaysdata.com", "alwaysdata.net", "alwaysvpn.com", "am730.com.hk", "amazon.co.jp", "amazon.com", "amazonvideo.com", "ameba.jp", "ameblo.jp", "america.gov", "american.edu", "americangreencard.com", "americanunfinished.com", "americorps.gov", "amiblockedornot.com", "amigobbs.net", "amitabhafoundation.us", "amnesty.org", "amnesty.org.hk", "amnesty.tw", "amnestyusa.org", "amnyemachen.org", "ampproject.org", "amtb-taipei.org", "amuletmc.com", "anchor.fm", "anchorfree.com", "ancsconf.org", "andfaraway.net", "android-x86.org", "android.com", "androidapksfree.com", "androidcombo.com", "androidify.com", "androidplus.co", "androidtv.com", "andygod.com", "angela-merkel.de", "angelfire.com", "angola.org", "angularjs.org", "animecrazy.net", "animeshippuuden.com", "animezilla.com", "aniscartujo.com", "annas-archive.org", "annas-archive.se", "annatam.com", "anobii.com", "anonfiles.com", "anontext.com", "anonymise.us", "anonymitynetwork.com", "anonymizer.com", "anonymouse.org", "anpopo.com", "answering-islam.org", "antd.org", "anthonycalzadilla.com", "anthropic.com", "anti1984.com", "antichristendom.com", "antisocial.science", "antiwave.net", "antpool.com", "anws.gov.tw", "anyporn.com", "anysex.com", "ao3.org", "aobo.com.au", "aofriend.com", "aofriend.com.au", "aojiao.org", "aol.ca", "aol.co.uk", "aol.com", "aolnews.com", "aomedia.org", "aomiwang.com", "apartmentratings.com", "apartments.com", "apat1989.org", "apetube.com", "api.ai", "apiary.io", "apigee.com", "apk-dl.com", "apk.support", "apk.tw", "apkcombo.com", "apkmirror.com", "apkmonk.com", "apkplz.com", "apkpure.com", "apkpure.net", "aplusvpn.com", "appadvice.com", "appbrain.com", "appdefensealliance.dev", "appdownloader.net", "appledaily.com", "appledaily.com.hk", "appledaily.com.tw", "appshopper.com", "appsocks.net", "appspot-preview.com", "appspot.com", "appsto.re", "aptoide.com", "archive.fo", "archive.is", "archive.li", "archive.md", "archive.org", "archive.ph", "archive.today", "archive.vn", "archiveofourown.com", "archiveofourown.org", "archives.gov", "archives.gov.tw", "arctosia.com", "areca-backup.org", "arena.taipei", "arethusa.su", "arlingtoncemetery.mil", "army.mil", "arstechnica.net", "art4tibet1998.org", "arte.tv", "artofpeacefoundation.org", "artstation.com", "artsy.net", "arunachalforests.gov.in", "arvanstorage.ir", "asacp.org", "asdfg.jp", "asg.to", "asia-gaming.com", "asiaharvest.org", "asianage.com", "asianews.it", "asiansexdiary.com", "asiaone.com", "asiatgp.com", "asiatimes.com", "asiatoday.us", "ask.com", "askstudent.com", "askynz.net", "aspi.org.au", "aspistrategist.org.au", "asrockind.com", "assembla.com", "assimp.org", "astrill.com", "atc.org.au", "atchinese.com", "atgfw.org", "athenaeizou.com", "atlanta168.com", "atnext.com", "audacy.com", "audionow.com", "authorizeddns.net", "authorizeddns.org", "autodraw.com", "av-e-body.com", "av.com", "av.movie", "av01.tv", "avaaz.org", "avbody.tv", "avcity.tv", "avcool.com", "avdb.in", "avdb.tv", "avfantasy.com", "avg.com", "avgle.com", "avidemux.org", "avmo.pw", "avmoo.com", "avmoo.net", "avmoo.pw", "avoision.com", "avyahoo.com", "axios.com", "axureformac.com", "azerbaycan.tv", "azerimix.com", "azirevpn.com", "azurewebsites.net", "b-cdn.net", "b-ok.cc", "b0ne.com", "baby-kingdom.com", "babylonbee.com", "babynet.com.hk", "backchina.com", "backpackers.com.tw", "backtotiananmen.com", "bad.news", "badiucao.com", "badjojo.com", "badoo.com", "bahamut.com.tw", "baidu.jp", "baijie.org", "bailandaily.com", "baixing.me", "baizhi.org", "bakgeekhome.tk", "bamgrid.com", "banana-vpn.com", "band.us", "bandcamp.com", "bandpage.com", "bandwagonhost.com", "bangbrosnetwork.com", "bangchen.net", "bangdream.space", "bangkokpost.com", "bangumi.moe", "bangyoulater.com", "bankmobilevibe.com", "bannedbook.org", "bannednews.org", "banorte.com", "baramangaonline.com", "barenakedislam.com", "barnabu.co.uk", "barton.de", "bartvpn.com", "bastillepost.com", "bayvoice.net", "bb-chat.tv", "bbc.co.uk", "bbc.com", "bbc.in", "bbcchinese.com", "bbchat.tv", "bbci.co.uk", "bbg.gov", "bbkz.com", "bbnradio.org", "bbs-tw.com", "bbsdigest.com", "bbsfeed.com", "bbsland.com", "bbsmo.com", "bbsone.com", "bbtoystore.com", "bcast.co.nz", "bcc.com.tw", "bcchinese.net", "bcex.ca", "bcmorning.com", "bcrncdn.com", "bdsmvideos.net", "beaconevents.com", "beanfun.com", "bearteach.com", "bebo.com", "beeg.com", "beepool.com", "beepool.org", "beevpn.com", "behance.net", "behindkink.com", "beijing1989.com", "beijing2022.art", "beijingspring.com", "beijingzx.org", "belamionline.com", "bell.wiki", "bemywife.cc", "beric.me", "berlinerbericht.de", "berlintwitterwall.com", "berm.co.nz", "bestforchina.org", "bestgore.com", "bestpornstardb.com", "bestvpn.com", "bestvpnanalysis.com", "bestvpnforchina.net", "bestvpnserver.com", "bestvpnservice.com", "bestvpnusa.com", "bet365.com", "betaclouds.net", "betfair.com", "betterhash.net", "betternet.co", "bettervpn.com", "bettween.com", "betvictor.com", "bewww.net", "beyondfirewall.com", "bfnn.org", "bfsh.hk", "bgme.me", "bgvpn.com", "bianlei.com", "biantailajiao.com", "biantailajiao.in", "biblesforamerica.org", "bibliocommons.com", "bibox.com", "bic2011.org", "bidswitch.net", "biedian.me", "big.one", "bigfools.com", "biggo.com.tw", "bigjapanesesex.com", "bigmoney.biz", "bignews.org", "bignewsnetwork.com", "bigone.com", "bigsound.org", "bild.de", "bilibili.tv", "biliworld.com", "billypan.com", "binance.com", "binance.org", "binance.us", "binancezh.cc", "bing.com", "binux.me", "binwang.me", "bipic.net", "bird.so", "bit-z.com", "bit.do", "bit.ly", "bitbay.net", "bitchute.com", "bitcointalk.org", "bitcoinworld.com", "bitfinex.com", "bitget.com", "bithumb.com", "bitmex.com", "bitshare.com", "bitsnoop.com", "bitterwinter.org", "bitvise.com", "bitz.ai", "bitz.com", "bizhat.com", "bjnewlife.org", "bjs.org", "bjzc.org", "bl-doujinsouko.com", "blacked.com", "blacklogic.com", "blackmagicdesign.com", "blackvpn.com", "blewpass.com", "blingblingsquad.net", "blinkx.com", "blinw.com", "blip.tv", "blockcast.it", "blockcn.com", "blockedbyhk.com", "blockless.com", "blocktempo.com", "blog.de", "blog.jp", "blogblog.com", "blogcatalog.com", "blogcity.me", "blogdns.org", "blogger.com", "blogimg.jp", "blogjav.net", "bloglines.com", "bloglovin.com", "blogs.com", "blogspot.ae", "blogspot.al", "blogspot.am", "blogspot.ba", "blogspot.be", "blogspot.bg", "blogspot.ca", "blogspot.cat", "blogspot.ch", "blogspot.cl", "blogspot.co.uk", "blogspot.com", "blogspot.com.ar", "blogspot.com.au", "blogspot.com.br", "blogspot.com.by", "blogspot.com.co", "blogspot.com.cy", "blogspot.com.ee", "blogspot.com.eg", "blogspot.com.es", "blogspot.com.mt", "blogspot.com.ng", "blogspot.com.tr", "blogspot.com.uy", "blogspot.cz", "blogspot.de", "blogspot.dk", "blogspot.fi", "blogspot.fr", "blogspot.gr", "blogspot.hk", "blogspot.hr", "blogspot.hu", "blogspot.ie", "blogspot.in", "blogspot.is", "blogspot.it", "blogspot.jp", "blogspot.kr", "blogspot.li", "blogspot.lt", "blogspot.lu", "blogspot.md", "blogspot.mk", "blogspot.mx", "blogspot.my", "blogspot.nl", "blogspot.no", "blogspot.pe", "blogspot.pt", "blogspot.qa", "blogspot.ro", "blogspot.ru", "blogspot.se", "blogspot.sg", "blogspot.si", "blogspot.sk", "blogspot.sn", "blogspot.tw", "blogspot.ug", "blogtd.net", "blogtd.org", "bloodshed.net", "bloomberg.cn", "bloomberg.com", "bloomberg.de", "bloombergview.com", "bloomfortune.com", "blubrry.com", "blueangellive.com", "bmdru.com", "bmfinn.com", "bnbstatic.com", "bnews.co", "bnext.com.tw", "bnn.co", "bnrmetal.com", "bntrace.com", "boardreader.com", "bod.asia", "bodog88.com", "bolehvpn.net", "bonbonme.com", "bonfoundation.org", "bongacams.com", "boobstagram.com", "book.com.tw", "bookdepository.com", "bookepub.com", "booklive.jp", "bookmeter.com", "books.com.tw", "booktopia.com.au", "bookwalker.com.tw", "bookwalker.jp", "bootstrapcdn.com", "borgenmagazine.com", "bot.nu", "botanwang.com", "bowenpress.com", "box.com", "box.net", "boxpn.com", "boxun.com", "boxun.tv", "boxunblog.com", "boxunclub.com", "boyangu.com", "boyfriendtv.com", "boysfood.com", "boysmaster.com", "br.st", "brainyquote.com", "brandonhutchinson.com", "braumeister.org", "brave.com", "bravotube.net", "brazzers.com", "breached.to", "break.com", "breakgfw.com", "breaking911.com", "breakingtweets.com", "breakwall.net", "briian.com", "brill.com", "brizzly.com", "broadbook.com", "broadpressinc.com", "brockbbs.com", "brookings.edu", "brucewang.net", "brutaltgp.com", "bsky.app", "bsky.network", "bsky.social", "bt2mag.com", "bt4g.org", "bt4gprx.com", "bt95.com", "btaia.com", "btbit.net", "btbtav.com", "btbtt.co", "btbtt.me", "btc.com", "btc98.com", "btcbank.bank", "btctrade.im", "btdig.com", "btdigg.org", "btguard.com", "btku.me", "btku.org", "btloader.com", "btspread.com", "btsynckeys.com", "budaedu.org", "buddhanet.com.tw", "buddhistchannel.tv", "buffered.com", "bullguard.com", "bullog.org", "bullogger.com", "bumingbai.net", "bunbunhk.com", "busayari.com", "business-humanrights.org", "business.page", "business.site", "businessinsider.com", "businessinsider.com.au", "businesstoday.com.tw", "businessweek.com", "businessweekly.com.tw", "busu.org", "busytrade.com", "buugaa.com", "buzzhand.com", "buzzhand.net", "buzzorange.com", "buzzsprout.com", "bvpn.com", "bwbx.io", "bwgyhw.com", "bwh1.net", "bwsj.hk", "bx.in.th", "bx.tl", "bybit.com", "bynet.co.il", "bypasscensorship.org", "byrut.org", "byteoversea.com", "c-est-simple.com", "c-span.org", "c-spanvideo.org", "c.gle", "c100tibet.org", "c2cx.com", "c3pool.com", "ca.gov", "cableav.tv", "cablegatesearch.net", "cachefly.com", "cachefly.net", "cachinese.com", "cacnw.com", "cactusvpn.com", "cafepress.com", "cahr.org.tw", "calameo.com", "calebelston.com", "calendarz.com", "calgarychinese.ca", "calgarychinese.com", "calgarychinese.net", "cam4.com", "cam4.jp", "cam4.sg", "camfrog.com", "campaign-archive.com", "campaignforuyghurs.org", "cams.com", "cams.org.sg", "canadameet.com", "canalporno.com", "cantonese.asia", "canyu.org", "cao.im", "caobian.info", "caochangqing.com", "caoporn.us", "cap.org.hk", "captainfawcett.com", "carabinasypistolas.com", "cardinalkungfoundation.org", "careerengine.us", "carfax.com", "cari.com.my", "caribbeancom.com", "carmotorshow.com", "carousell.com.hk", "carrd.co", "carryzhou.com", "cartoon18.com", "cartoonmovement.com", "casadeltibetbcn.org", "casatibet.org.mx", "casinobellini.com", "casinoking.com", "casinoriva.com", "castbox.fm", "catbox.moe", "catch22.net", "catchgod.com", "catfightpayperview.xxx", "catholic.org.hk", "catholic.org.tw", "cathvoice.org.tw", "cato.org", "cattt.com", "caus.com", "cbc.ca", "cbsnews.com", "cbtc.org.hk", "cc.com", "cccat.cc", "cccat.co", "ccdtr.org", "ccfd.org.tw", "cchere.com", "ccim.org", "cclife.ca", "cclife.org", "cclifefl.org", "ccthere.com", "ccthere.net", "cctmweb.net", "cctongbao.com", "ccu.edu.tw", "ccue.ca", "ccue.com", "ccvoice.ca", "ccw.org.tw", "cdbook.org", "cdcparty.com", "cdef.org", "cdig.info", "cdjp.org", "cdn-apple.com", "cdn-telegram.org", "cdnews.com.tw", "cdninstagram.com", "cdp1989.org", "cdp1998.org", "cdp2006.org", "cdpa.url.tw", "cdpeu.org", "cdpuk.co.uk", "cdpusa.org", "cdpweb.org", "cdpwu.org", "cdw.com", "cecc.gov", "celestiallight.org", "cellulo.info", "cenews.eu", "centauro.com.br", "centerforhumanreprod.com", "centralnation.com", "centurys.net", "certificate-transparency.org", "cex.io", "cfhks.org.hk", "cfos.de", "cfr.org", "cfsh99.com", "cftfc.com", "cgdepot.org", "cgst.edu", "change.org", "changeip.name", "changeip.net", "changeip.org", "changp.com", "changsa.net", "channel8news.sg", "channelnewsasia.com", "chanworld.org", "chaoex.com", "chaos.social", "chapm25.com", "character.ai", "chatgpt.com", "chatnook.com", "chaturbate.com", "checkgfw.com", "chengmingmag.com", "chenguangcheng.com", "chenpokong.com", "chenpokong.net", "chenpokongvip.com", "cherrysave.com", "chhongbi.org", "chicagoncmtv.com", "china-mmm.jp.net", "china-mmm.net", "china-review.com.ua", "china-week.com", "china101.com", "china18.org", "china21.com", "china21.org", "china5000.us", "chinaaffairs.org", "chinaaid.me", "chinaaid.net", "chinaaid.org", "chinaaid.us", "chinachange.org", "chinachannel.hk", "chinacitynews.be", "chinacomments.org", "chinademocrats.org", "chinadialogue.net", "chinadigitaltimes.net", "chinaelections.org", "chinaeweekly.com", "chinafile.com", "chinafreepress.org", "chinagate.com", "chinageeks.org", "chinagfw.org", "chinagonet.com", "chinagreenparty.org", "chinahorizon.org", "chinahrc.org", "chinahush.com", "chinainperspective.com", "chinainterimgov.org", "chinalaborwatch.org", "chinalawandpolicy.com", "chinalawtranslate.com", "chinamule.com", "chinamz.org", "chinanewscenter.com", "chinapost.com.tw", "chinapress.com.my", "chinarightsia.org", "chinasmile.net", "chinasocialdemocraticparty.com", "chinasoul.org", "chinasucks.net", "chinatopsex.com", "chinatown.com.au", "chinatweeps.com", "chinauncensored.tv", "chinaway.org", "chinaworker.info", "chinaxchina.com", "chinayouth.org.hk", "chinayuanmin.org", "chinese-hermit.net", "chinese-leaders.org", "chinese-memorial.org", "chinesedaily.com", "chinesedailynews.com", "chinesedemocracy.com", "chinesegay.org", "chinesen.de", "chinesenews.net.au", "chinesepen.org", "chineseradioseattle.com", "chinesetalks.net", "chineseupress.com", "chingcheong.com", "chinman.net", "chithu.org", "chobit.cc", "chosun.com", "chped.com", "chrdnet.com", "christianfreedom.org", "christianstudy.com", "christiantimes.org.hk", "christusrex.org", "chrlawyers.hk", "chrome.com", "chromecast.com", "chromeexperiments.com", "chromercise.com", "chromestatus.com", "chromium.org", "cht.com.tw", "chuang-yen.org", "chubold.com", "chubun.com", "chuizi.net", "churchinhongkong.org", "chushigangdrug.ch", "ci-en.jp", "cia.gov", "cici.com", "ciciai.com", "ciciaicdn.com", "cienen.com", "cineastentreff.de", "cipfg.org", "circlethebayfortibet.org", "cirosantilli.com", "citizencn.com", "citizenlab.ca", "citizenlab.org", "citizenpowerforchina.org", "citizenscommission.hk", "citizensradio.org", "city365.ca", "city9x.com", "citypopulation.de", "citytalk.tw", "civicparty.hk", "civildisobediencemovement.org", "civilhrfront.org", "civiliangunner.com", "civilmedia.tw", "civitai.com", "cixiaoya.club", "cjb.net", "ck101.com", "clarionproject.org", "classicalguitarblog.net", "claude.ai", "clb.org.hk", "cleansite.biz", "cleansite.info", "cleansite.us", "clearharmony.net", "clearsurance.com", "clearwisdom.net", "clementine-player.org", "clickme.net", "clinica-tibet.ru", "clipconverter.cc", "clipfish.de", "clips4sale.com", "cloakpoint.com", "cloudcone.com", "cloudflare-dns.com", "cloudflare-ipfs.com", "cloudfunctions.net", "cloudokyo.cloud", "club1069.com", "clubhouseapi.com", "clyp.it", "cmcn.org", "cmegroup.com", "cmi.org.tw", "cmoinc.org", "cms.gov", "cmu.edu", "cmule.com", "cmule.org", "cmx.im", "cn-proxy.com", "cn6.eu", "cna.com.tw", "cnabc.com", "cnbeta.com.tw", "cnd.org", "cnet.com", "cnex.org.cn", "cnineu.com", "cnitter.com", "cnn.com", "cnpolitics.org", "cnproxy.com", "cnyes.com", "coat.co.jp", "cobinhood.com", "cochina.co", "cochina.org", "code1984.com", "codeshare.io", "codeskulptor.org", "cofacts.tw", "coffeemanga.to", "coinbase.com", "coinbene.com", "coinegg.com", "coinex.com", "coingecko.com", "coingi.com", "coinmarketcap.com", "coinrail.co.kr", "cointiger.com", "cointobe.com", "coinut.com", "colacloud.net", "collateralmurder.com", "collateralmurder.org", "com.uk", "comedycentral.com", "comefromchina.com", "comic-mega.me", "commandarms.com", "comments.app", "commentshk.com", "communistcrimes.org", "communitychoicecu.com", "comparitech.com", "compileheart.com", "compress.to", "compython.net", "congyu.moe", "conoha.jp", "constitutionalism.solutions", "contactmagazine.net", "convio.net", "coobay.com", "cool18.com", "coolaler.com", "coolder.com", "coolloud.org.tw", "coolncute.com", "coolstuffinc.com", "corumcollege.com", "cos-moe.com", "cosplayjav.pl", "costco.com", "cotweet.com", "counter.social", "coursehero.com", "covenantswatch.org.tw", "coze.com", "cpj.org", "cpu-monkey.com", "cq99.us", "crackle.com", "crashlytics.com", "crazypool.org", "crazys.cc", "crazyshit.com", "crbug.com", "crchina.org", "crd-net.org", "creaders.net", "creadersnet.com", "creativelab5.com", "cristyli.com", "crocotube.com", "crossfire.co.kr", "crossthewall.net", "crossvpn.net", "crosswall.org", "croxyproxy.com", "crrev.com", "crucial.com", "crunchyroll.com", "cruxpool.com", "crwdcntrl.net", "crypto.com", "cryptographyengineering.com", "csdparty.com", "csis.org", "csmonitor.com", "csuchen.de", "csw.org.uk", "ct.org.tw", "ctao.org", "ctfriend.net", "ctinews.com", "ctitv.com.tw", "ctowc.org", "cts.com.tw", "ctwant.com", "cuhk.edu.hk", "cuhkacs.org", "cuihua.org", "cuiweiping.net", "culture.tw", "cumlouder.com", "cuntcrack.com", "curvefish.com", "cusp.hk", "cusu.hk", "cutout.pro", "cutscenes.net", "cw.com.tw", "cwb.gov.tw", "cyberctm.com", "cyberghostvpn.com", "cynscribe.com", "cytode.us", "cz.cc", "d-fukyu.com", "d.cash", "d100.net", "d2bay.com", "d2pass.com", "dabr.co.uk", "dabr.eu", "dabr.me", "dabr.mobi", "dadazim.com", "dadi360.com", "dafabet.com", "dafagood.com", "dafahao.com", "dafoh.org", "daftporn.com", "dagelijksestandaard.nl", "daidostup.ru", "dailidaili.com", "dailymail.co.uk", "dailymotion.com", "dailysabah.com", "dailyview.tw", "daiphapinfo.net", "dajiyuan.com", "dajiyuan.de", "dajiyuan.eu", "dalailama-archives.org", "dalailama.com", "dalailama.mn", "dalailama.ru", "dalailama80.org", "dalailamacenter.org", "dalailamafellows.org", "dalailamafilm.com", "dalailamafoundation.org", "dalailamahindi.com", "dalailamainaustralia.org", "dalailamajapanese.com", "dalailamaprotesters.info", "dalailamaquotes.org", "dalailamatrust.org", "dalailamavisit.org.nz", "dalailamaworld.com", "dalianmeng.org", "daliulian.org", "danke4china.net", "danwei.org", "daolan.net", "daozhongxing.org", "darktech.org", "darktoy.net", "darpa.mil", "darrenliuwei.com", "dashlane.com", "dastrassi.org", "data-vocabulary.org", "data.gov.tw", "datalabour.com", "daum.net", "david-kilgour.com", "dawangidc.com", "daxa.cn", "daylife.com", "db.tt", "dbgjd.com", "dcard.tw", "dcmilitary.com", "ddc.com.tw", "ddex.io", "ddhw.info", "ddns.info", "ddns.me.uk", "ddns.mobi", "ddns.ms", "ddns.name", "ddns.net", "ddns.us", "de-sci.org", "deadhouse.org", "deadline.com", "deaftone.com", "debug.com", "deck.ly", "deck.new", "decodet.co", "deepai.org", "deepdiscount.com", "deepmind.com", "deezer.com", "definebabe.com", "deja.com", "delcamp.net", "delicious.com", "democrats.org", "demosisto.hk", "deno.com", "deno.dev", "depositphotos.com", "derekhsu.homeip.net", "desc.se", "desipro.de", "dessci.com", "destroy-china.jp", "detroitnews.com", "deutsche-welle.de", "deviantart.com", "deviantart.net", "devio.us", "devpn.com", "devv.ai", "dfas.mil", "dfn.org", "dharamsalanet.com", "dharmakara.net", "dhcp.biz", "diaoyuislands.org", "difangwenge.org", "dify.ai", "digg.com", "digiland.tw", "digisfera.com", "digitalnomadsproject.org", "diigo.com", "dilber.se", "dipity.com", "directcreative.com", "discoins.com", "disconnect.me", "discord.com", "discord.gg", "discord.media", "discordapp.com", "discordapp.net", "discuss.com.hk", "discuss4u.com", "dish.com", "disk.yandex", "disney-plus.net", "disneyplus.com", "disp.cc", "disqus.com", "dit-inc.us", "diyin.org", "dizhidizhi.com", "dizhuzhishang.com", "djangosnippets.org", "djorz.com", "dl-laby.jp", "dlive.tv", "dlsite.com", "dlsite.jp", "dlyoutube.com", "dm530.net", "dma.mil", "dmarcnetworks.com", "dmc.nico", "dmcdn.net", "dmhy.org", "dmm.co.jp", "dmm.com", "dns-dns.com", "dns-stuff.com", "dns04.com", "dns05.com", "dns1.us", "dns2.us", "dns2go.com", "dnscrypt.org", "dnset.com", "dnsrd.com", "dnssec.net", "dnvod.tv", "doc.new", "docker.com", "docker.io", "docs.new", "doctorvoice.org", "documentingreality.com", "dogfartnetwork.com", "dojin.com", "dok-forum.net", "dolc.de", "dolf.org.hk", "domain.club.tw", "domain.glass", "domaintoday.com.au", "donga.com", "dongtaiwang.com", "dongtaiwang.net", "dongyangjing.com", "donmai.us", "dontfilter.us", "dontmovetochina.com", "doom9.org", "doosho.com", "doourbest.org", "dorjeshugden.com", "dotplane.com", "dotsub.com", "dotvpn.com", "doub.io", "doubibackup.com", "doubiyunbackup.com", "doublethinklab.org", "douchi.space", "dougscripts.com", "doujincafe.com", "dowei.org", "dowjones.com", "dphk.org", "dpool.top", "dpp.org.tw", "dpr.info", "dragonex.io", "dragonsprings.org", "dreamamateurs.com", "drepung.org", "drgan.net", "drmingxia.org", "dropbooks.tv", "dropbox.com", "dropboxapi.com", "dropboxusercontent.com", "drsunacademy.com", "drtuber.com", "dscn.info", "dsmtp.com", "dssott.com", "dstk.dk", "dtiblog.com", "dtic.mil", "dtwang.org", "duanzhihu.com", "dubox.com", "duck.com", "duckduckgo.com", "duckload.com", "duckmylife.com", "duga.jp", "duihua.org", "duihuahrjournal.org", "dumb1.com", "dunyabulteni.net", "duoweitimes.com", "duping.net", "duplicati.com", "dupola.com", "dupola.net", "dushi.ca", "duyaoss.com", "dvdpac.com", "dvorak.org", "dw-world.com", "dw-world.de", "dw.com", "dw.de", "dweb.link", "dwheeler.com", "dwnews.com", "dwnews.net", "dxiong.com", "dynamic-dns.net", "dynamicdns.biz", "dynamicdns.co.uk", "dynamicdns.me.uk", "dynamicdns.org.uk", "dynawebinc.com", "dyndns-ip.com", "dyndns-pics.com", "dyndns.org", "dyndns.pro", "dynssl.com", "dynu.com", "dynu.net", "dysfz.cc", "dzze.com", "e-classical.com.tw", "e-gold.com", "e-hentai.org", "e-hentaidb.com", "e-info.org.tw", "e-traderland.net", "e-zone.com.hk", "e123.hk", "e621.net", "earlytibet.com", "earthcam.com", "earthvpn.com", "eastasiaforum.org", "eastern-ark.com", "easternlightning.org", "eastturkestan.com", "eastturkistan-gov.org", "eastturkistan.net", "eastturkistancc.org", "eastturkistangovernmentinexile.us", "easyca.ca", "easypic.com", "ebc.net.tw", "ebony-beauty.com", "ebookbrowse.com", "ebookee.com", "ebtcbank.com", "ecfa.org.tw", "echofon.com", "ecimg.tw", "eckosia.org", "ecministry.net", "economist.com", "ecstart.com", "edgecastcdn.net", "edgesuite.net", "edicypages.com", "edmontonchina.cn", "edmontonservice.com", "edns.biz", "edoors.com", "edrdg.org", "edubridge.com", "edupro.org", "edx-cdn.org", "eesti.ee", "eevpn.com", "efcc.org.hk", "effers.com", "efksoft.com", "efreenews.com", "efukt.com", "eic-av.com", "eireinikotaerukai.com", "eisbb.com", "eksisozluk.com", "elconfidencial.com", "electionsmeter.com", "elgoog.im", "ellawine.org", "elpais.com", "eltondisney.com", "emaga.com", "emanna.com", "embr.in", "emilylau.org.hk", "emory.edu", "empfil.com", "emule-ed2k.com", "emulefans.com", "emuparadise.me", "enanyang.my", "enca.com", "encrypt.me", "encyclopedia.com", "enewstree.com", "enfal.de", "engadget.com", "engagedaily.org", "englishforeveryone.org", "englishfromengland.co.uk", "englishpen.org", "enlighten.org.tw", "entermap.com", "entnt.com", "epac.to", "episcopalchurch.org", "epochhk.com", "epochtimes-bg.com", "epochtimes-romania.com", "epochtimes.co.il", "epochtimes.co.kr", "epochtimes.com", "epochtimes.com.tw", "epochtimes.cz", "epochtimes.de", "epochtimes.fr", "epochtimes.ie", "epochtimes.it", "epochtimes.jp", "epochtimes.ru", "epochtimes.se", "epochtimestr.com", "epochweek.com", "epochweekly.com", "eporner.com", "eprice.com.hk", "equinenow.com", "erabaru.net", "eracom.com.tw", "eraysoft.com.tr", "erepublik.com", "erights.net", "eriversoft.com", "erktv.com", "ernestmandel.org", "erodaizensyu.com", "erodoujinlog.com", "erodoujinworld.com", "eromanga-kingdom.com", "eromangadouzin.com", "eromon.net", "eroprofile.com", "eroticsaloon.net", "eslite.com", "esmtp.biz", "esu.im", "esurance.com", "etaa.org.au", "etadult.com", "etaiwannews.com", "etherdelta.com", "ethermine.org", "etherscan.com", "etherscan.io", "etizer.org", "etokki.com", "etowns.net", "etowns.org", "etsy.com", "ettoday.net", "etvonline.hk", "eu.org", "eucasino.com", "eulam.com", "eurekavpt.com", "eurodasp.com", "euronews.com", "europa.eu", "everipedia.org", "evozi.com", "evschool.net", "exam.gov.tw", "exblog.jp", "exchristian.hk", "excite.co.jp", "exhentai.org", "exmo.com", "exmormon.org", "expatshield.com", "expecthim.com", "expekt.com", "experts-univers.com", "exploader.net", "expofutures.com", "expressvpn.com", "exrates.me", "extmatrix.com", "extrabux.com", "extremetube.com", "ey.gov.tw", "eyevio.jp", "eyny.com", "ezpc.tk", "ezpeer.com", "ezua.com", "f-droid.org", "f2pool.com", "f8.com", "fa.gov.tw", "facebook.br", "facebook.com", "facebook.de", "facebook.design", "facebook.hu", "facebook.in", "facebook.net", "facebook.nl", "facebook.se", "facebookmail.com", "facebookquotes4u.com", "faceless.me", "facesofnyfw.com", "facesoftibetanselfimmolators.info", "factchecklab.org", "factpedia.org", "fail.hk", "faith100.org", "faithfuleye.com", "faiththedog.info", "fakku.net", "fallenark.com", "falsefire.com", "falun-co.org", "falun-ny.net", "falunart.org", "falunasia.info", "falunau.org", "falunaz.net", "falundafa-dc.org", "falundafa-florida.org", "falundafa-nc.org", "falundafa-pa.net", "falundafa-sacramento.org", "falundafa.org", "falundafaindia.org", "falundafamuseum.org", "falungong.club", "falungong.de", "falungong.org.uk", "falunhr.org", "faluninfo.de", "faluninfo.net", "falunpilipinas.net", "falunworld.net", "familyfed.org", "famunion.com", "fan-qiang.com", "fanbox.cc", "fandom.com", "fangbinxing.com", "fangeming.com", "fangeqiang.com", "fanglizhi.info", "fangmincn.org", "fangong.org", "fangongheike.com", "fanhaodang.com", "fanhaolou.com", "fanqiang.network", "fanqiang.tk", "fanqiangdang.com", "fanqiangdang.org", "fanqianghou.com", "fanqiangyakexi.net", "fanqiangzhe.com", "fanswong.com", "fantv.hk", "fanyue.info", "fapdu.com", "faproxy.com", "faqserv.com", "fartit.com", "farwestchina.com", "fast.com", "fastestvpn.com", "fastly.net", "fastpic.ru", "fastssh.com", "faststone.org", "fatakat-n.club", "fatbtc.com", "favotter.net", "favstar.fm", "fawanghuihui.org", "faydao.com", "faz.net", "fb.com", "fb.me", "fb.watch", "fbaddins.com", "fbcdn.net", "fbsbx.com", "fbworkmail.com", "fc2.com", "fc2blog.net", "fc2china.com", "fc2cn.com", "fc2web.com", "fda.gov.tw", "fdc64.de", "fdc64.jp", "fdc64.org", "fdc89.jp", "feedburner.com", "feeder.co", "feedly.com", "feedx.net", "feelssh.com", "feer.com", "feifeiss.com", "feitian-california.org", "feitianacademy.org", "feixiaohao.com", "feministteacher.com", "fengzhenghu.com", "fengzhenghu.net", "fevernet.com", "ff.im", "fffff.at", "fflick.com", "ffvpn.com", "fgmtv.net", "fgmtv.org", "fhreports.net", "figprayer.com", "fileflyer.com", "fileforum.com", "files2me.com", "fileserve.com", "filesor.com", "fillthesquare.org", "filmingfortibet.org", "filthdump.com", "financetwitter.com", "financialexpress.com", "finchvpn.com", "findbook.tw", "findmespot.com", "findyoutube.com", "findyoutube.net", "fingerdaily.com", "finler.net", "firearmsworld.net", "firebaseio.com", "firefox.com", "fireofliberty.info", "fireofliberty.org", "firetweet.io", "firstfivefollowers.com", "firstory.me", "firstpost.com", "firstrade.com", "fish.audio", "fizzik.com", "flagsonline.it", "flecheinthepeche.fr", "fleshbot.com", "fleursdeslettres.com", "flexpool.io", "flgg.us", "flgjustice.org", "flickr.com", "flickrhivemind.net", "flickriver.com", "fling.com", "flipboard.com", "flipkart.com", "flitto.com", "flnet.org", "flog.tw", "flowhongkong.net", "flurry.com", "flypool.org", "flyvpn.com", "flyzy2005.com", "fmnnow.com", "fnac.be", "fnac.com", "fochk.org", "focustaiwan.tw", "focusvpn.com", "fofg-europe.net", "fofg.org", "fofldfradio.org", "foolsmountain.com", "fooooo.com", "footprint.net", "footwiball.com", "forbes.com", "foreignaffairs.com", "foreignpolicy.com", "form.new", "forms.new", "forum4hk.com", "forums-free.com", "fotile.me", "fountmedia.io", "fourthinternational.org", "foxbusiness.com", "foxgay.com", "foxsub.com", "foxtang.com", "fpmt-osel.org", "fpmt.org", "fpmt.tw", "fpmtmexico.org", "fqok.org", "fqrouter.com", "frank2019.me", "franklc.com", "freakshare.com", "free-gate.org", "free-hada-now.org", "free-proxy.cz", "free-ss.site", "free-ssh.com", "free.bg", "free.com.tw", "free.fr", "free4u.com.ar", "freealim.com", "freebeacon.com", "freebearblog.org", "freebrowser.org", "freechal.com", "freechina.net", "freechina.news", "freechinaforum.org", "freechinaweibo.com", "freeddns.com", "freeddns.org", "freedl.org", "freedomchina.info", "freedomcollection.org", "freedomhongkong.org", "freedomhouse.org", "freedomsherald.org", "freeforums.org", "freefq.com", "freefuckvids.com", "freegao.com", "freehongkong.org", "freeilhamtohti.org", "freekazakhs.org", "freekwonpyong.org", "freelotto.com", "freeman2.com", "freemoren.com", "freemorenews.com", "freemuse.org", "freenet-china.org", "freenetproject.org", "freenewscn.com", "freeones.com", "freeopenvpn.com", "freeoz.org", "freeproxylists.net", "freerk.com", "freess.org", "freessh.us", "freetcp.com", "freetibet.net", "freetibet.org", "freetibetanheroes.org", "freetls.fastly.net", "freetribe.me", "freeviewmovies.com", "freevpn.me", "freevpn.nl", "freewallpaper4.me", "freewebs.com", "freewechat.com", "freeweibo.com", "freewww.biz", "freewww.info", "freexinwen.com", "freeyellow.com", "freeyoutubeproxy.net", "freezhihu.org", "friday.tw", "frienddy.com", "friendfeed-media.com", "friendfeed.com", "friendfinder.com", "friends-of-tibet.org", "friendsoftibet.org", "fril.jp", "fring.com", "fringenetwork.com", "from-pr.com", "from-sd.com", "fromchinatousa.net", "frommel.net", "frontlinedefenders.org", "frootvpn.com", "froth.zone", "fscked.org", "fsurf.com", "ft.com", "ftchinese.com", "ftp1.biz", "ftpserver.biz", "ftv.com.tw", "ftvnews.com.tw", "ftx.com", "fucd.com", "fuchsia.dev", "fuckccp.com", "fuckccp.xyz", "fuckcnnic.net", "fuckgfw.org", "fuckgfw233.org", "fulione.com", "fullerconsideration.com", "fullservicegame.com", "fulue.com", "funami.tech", "funf.tw", "funkyimg.com", "funp.com", "fuq.com", "furbo.org", "furhhdl.org", "furinkan.com", "furrybar.com", "futurechinaforum.org", "futuremessage.org", "fux.com", "fuyin.net", "fuyindiantai.org", "fuyu.org.tw", "fw.cm", "fxcm-chinese.com", "fxnetworks.com", "fzh999.com", "fzh999.net", "fzlm.com", "g-area.org", "g-desktop.ru", "g-queen.com", "g.co", "g0v.social", "g6hentai.com", "gab.com", "gabocorp.com", "gaeproxy.com", "gaforum.org", "gagaoolala.com", "galaxymacau.com", "galenwu.com", "gallup.com", "galstars.net", "game735.com", "gamebase.com.tw", "gamejolt.com", "gamer.com.tw", "gamerp.jp", "gamez.com.tw", "gamousa.com", "ganges.com", "ganjing.com", "ganjing.world", "ganjingworld.com", "gaoming.net", "gaopi.net", "gaozhisheng.net", "gaozhisheng.org", "gardennetworks.com", "gardennetworks.org", "gartlive.com", "garudalinux.org", "gate-project.com", "gate.io", "gatecoin.com", "gather.com", "gatherproxy.com", "gati.org.tw", "gaybubble.com", "gaycn.net", "gayhub.com", "gaymap.cc", "gaymenring.com", "gaytube.com", "gaywatch.com", "gazotube.com", "gcc.org.hk", "gclooney.com", "gclubs.com", "gcmasia.com", "gcpnews.com", "gcr.io", "gdaily.org", "gdbt.net", "gdzf.org", "geek-art.net", "geekerhome.com", "geekheart.info", "gekikame.com", "gelbooru.com", "gemini.com", "generated.photos", "genius.com", "geocities.co.jp", "geocities.com", "geocities.jp", "geph.io", "gerefoundation.org", "get.app", "get.dev", "get.how", "get.page", "getastrill.com", "getchu.com", "getcloak.com", "getfoxyproxy.org", "getgom.com", "geti2p.net", "getiton.com", "getjetso.com", "getlantern.org", "getmalus.com", "getmdl.io", "getoutline.org", "getsession.org", "getsocialscope.com", "getsync.com", "gettr.com", "gettrials.com", "gettyimages.hk", "getuploader.com", "gfbv.de", "gfgold.com.hk", "gfnormal05at.com", "gfsale.com", "gfw.org.ua", "gfw.press", "gfw.report", "gfwatch.org", "ggjav.com", "ggpht.com", "ggssl.com", "ghanely.me", "ghidra-sre.org", "ghostpath.com", "ghproxy.com", "ghut.org", "giantessnight.com", "gifree.com", "giga-web.jp", "gigacircle.com", "giganews.com", "gigporno.ru", "girlbanker.com", "git.io", "gitbook.io", "gitbooks.io", "githack.com", "github.blog", "github.com", "github.io", "githubassets.com", "githubcopilot.com", "githubusercontent.com", "gitlab.com", "gitlab.net", "gizlen.net", "gjczz.com", "glarity.app", "glass8.eu", "global.ssl.fastly.net", "globaljihad.net", "globalmediaoutreach.com", "globalmuseumoncommunism.org", "globalrescue.net", "globaltm.org", "globalvoices.org", "globalvoicesonline.org", "globalvpn.net", "glock.com", "gloryhole.com", "glorystar.me", "gluckman.com", "glype.com", "gmail.com", "gmgard.com", "gmhz.org", "gmiddle.com", "gmiddle.net", "gmll.org", "gmodules.com", "gmp4.com", "gmx.net", "gnci.org.hk", "gnews.org", "go-pki.com", "go-to-zlibrary.se", "go.com", "go141.com", "go5.dev", "goagent.biz", "goagentplus.com", "goagle.de", "gobet.cc", "godaddy.com", "godfootsteps.org", "godns.work", "godoc.org", "godsdirectcontact.co.uk", "godsdirectcontact.org", "godsdirectcontact.org.tw", "godsimmediatecontact.com", "gofundme.com", "gogle.de", "gogole.com", "gogotunnel.com", "gohappy.com.tw", "gokbayrak.com", "golang.org", "goldbet.com", "goldbetsports.com", "golden-ages.org", "goldeneyevault.com", "goldenfrog.com", "goldjizz.com", "goldstep.net", "goldwave.com", "gongm.in", "gongmeng.info", "gongminliliang.com", "gongwt.com", "goo.gl", "goo.gle", "goo.ne.jp", "good.news", "gooday.xyz", "gooddns.info", "goodhope.school", "goodnewsnetwork.org", "goodreaders.com", "goodreads.com", "goodtv.com.tw", "goodtv.tv", "goofind.com", "googel.de", "google-analytics.com", "google-base.de", "google.ad", "google.ae", "google.al", "google.am", "google.as", "google.at", "google.az", "google.ba", "google.be", "google.bf", "google.bg", "google.bi", "google.bj", "google.bs", "google.bt", "google.by", "google.ca", "google.cat", "google.cd", "google.cf", "google.cg", "google.ch", "google.ci", "google.cl", "google.cm", "google.cn", "google.co", "google.co.ao", "google.co.bw", "google.co.ck", "google.co.cr", "google.co.id", "google.co.il", "google.co.in", "google.co.jp", "google.co.ke", "google.co.kr", "google.co.ls", "google.co.ma", "google.co.mz", "google.co.nz", "google.co.th", "google.co.tz", "google.co.ug", "google.co.uk", "google.co.uz", "google.co.ve", "google.co.vi", "google.co.za", "google.co.zm", "google.co.zw", "google.com", "google.com.af", "google.com.ag", "google.com.ai", "google.com.ar", "google.com.au", "google.com.bd", "google.com.bh", "google.com.bn", "google.com.bo", "google.com.br", "google.com.bz", "google.com.co", "google.com.cu", "google.com.cy", "google.com.do", "google.com.ec", "google.com.eg", "google.com.et", "google.com.fj", "google.com.gh", "google.com.gi", "google.com.gt", "google.com.hk", "google.com.jm", "google.com.kh", "google.com.kw", "google.com.lb", "google.com.ly", "google.com.mm", "google.com.mt", "google.com.mx", "google.com.my", "google.com.na", "google.com.nf", "google.com.ng", "google.com.ni", "google.com.np", "google.com.om", "google.com.pa", "google.com.pe", "google.com.pg", "google.com.ph", "google.com.pk", "google.com.pr", "google.com.py", "google.com.qa", "google.com.sa", "google.com.sb", "google.com.sg", "google.com.sl", "google.com.sv", "google.com.tj", "google.com.tr", "google.com.tw", "google.com.ua", "google.com.uy", "google.com.vc", "google.com.vn", "google.cv", "google.cz", "google.de", "google.dev", "google.dj", "google.dk", "google.dm", "google.dz", "google.ee", "google.es", "google.fi", "google.fm", "google.fr", "google.ga", "google.ge", "google.gg", "google.gl", "google.gm", "google.gp", "google.gr", "google.gy", "google.hn", "google.hr", "google.ht", "google.hu", "google.ie", "google.im", "google.iq", "google.is", "google.it", "google.je", "google.jo", "google.kg", "google.ki", "google.kz", "google.la", "google.li", "google.lk", "google.lt", "google.lu", "google.lv", "google.md", "google.me", "google.mg", "google.mk", "google.ml", "google.mn", "google.ms", "google.mu", "google.mv", "google.mw", "google.ne", "google.nl", "google.no", "google.nr", "google.nu", "google.pl", "google.pn", "google.ps", "google.pt", "google.ro", "google.rs", "google.ru", "google.rw", "google.sc", "google.se", "google.sh", "google.si", "google.sk", "google.sm", "google.sn", "google.so", "google.sr", "google.st", "google.td", "google.tg", "google.tk", "google.tl", "google.tm", "google.tn", "google.to", "google.tt", "google.vg", "google.vu", "google.ws", "googleanalytics.com", "googleapis.com", "googleapps.com", "googlearth.com", "googleartproject.com", "googleblog.com", "googlebot.com", "googlechinawebmaster.com", "googlecode.com", "googlecommerce.com", "googledomains.com", "googledrive.com", "googleearth.com", "googlefiber.net", "googlegroups.com", "googlehosted.com", "googleideas.com", "googleinsidesearch.com", "googlelocal.nl", "googlemail.com", "googlemaps.sv", "googlemashups.com", "googlepagecreator.com", "googleplay.com", "googleplus.com", "googlescholar.com", "googlesource.com", "googlesyndication.com", "googleusercontent.com", "googlevideo.com", "googleweblight.com", "googlezip.net", "gopetition.com", "goproxing.net", "goreforum.com", "goregrish.com", "gospelherald.com", "got-game.org", "gotdns.ch", "gotgeeks.com", "gotquestions.org", "gotrusted.com", "gotw.ca", "gov.ir", "gov.taipei", "gov.tw", "gr8domain.biz", "gr8name.biz", "gradconnection.com", "grammaly.com", "grandtrial.org", "grangorz.org", "graph.org", "graphis.ne.jp", "graphql.org", "gravatar.com", "greasespot.net", "greasyfork.org", "great-firewall.com", "great-roc.org", "greatfire.org", "greatfirewall.biz", "greatfirewallofchina.org", "greatroc.org", "greatroc.tw", "greatzhonghua.org", "greenfieldbookstore.com.hk", "greenparty.org.tw", "greenpeace.com.tw", "greenpeace.org", "greenreadings.com", "greenvpn.net", "greenvpn.org", "grindr.com", "grok.com", "grotty-monday.com", "ground.news", "gs-discuss.com", "gsearch.media", "gstatic.com", "gstf.org", "gtricks.com", "gts-vpn.com", "gtv.org", "gtv1.org", "gu-chu-sum.org", "guaguass.com", "guaguass.org", "guancha.org", "guaneryu.com", "guangming.com.my", "guardster.com", "guishan.org", "gumroad.com", "gun-world.net", "gunsamerica.com", "gunsandammo.com", "guo.media", "guruonline.hk", "gutteruncensored.com", "gvideo.de", "gvlib.com", "gvm.com.tw", "gvt0.com", "gvt1.com", "gvt3.com", "gwave.com", "gwins.org", "gwtproject.org", "gyalwarinpoche.com", "gyatsostudio.com", "gzm.tv", "gzone-anime.info", "h-china.org", "h-comic.com", "h-moe.com", "h1n1china.org", "h528.com", "h5dm.com", "h5galgame.me", "hacg.club", "hacg.in", "hacg.li", "hacg.me", "hacg.red", "hacken.cc", "hacker.org", "hackmd.io", "hackthatphone.net", "hahlo.com", "haijiao.com", "haiwaikan.com", "hakkatv.org.tw", "halktv.com.tr", "handcraftedsoftware.org", "hanime.tv", "hanime1.me", "hanminzu.org", "hanunyi.com", "hao.news", "hao123.com", "hao123img.com", "happy-vpn.com", "haproxy.org", "hardsextube.com", "harunyahya.com", "hasi.wang", "hatena.ne.jp", "hautelook.com", "hautelookcdn.com", "have8.com", "hbg.com", "hbo.com", "hcaptcha.com", "hclips.com", "hdlt.me", "hdsky.me", "hdtvb.net", "hdzog.com", "he.net", "heartyit.com", "heavy-r.com", "hec.su", "hecaitou.net", "hechaji.com", "heeact.edu.tw", "hegre-art.com", "helixstudios.net", "helloandroid.com", "helloavgirls.com", "helloqueer.com", "helloss.pw", "hellotxt.com", "hellouk.org", "helpeachpeople.com", "helplinfen.com", "helpster.de", "helpzhuling.org", "henduohao.com", "hentai.to", "hentaipaw.com", "hentaitube.tv", "hentaivideoworld.com", "heqinglian.net", "here.com", "heritage.org", "heroku.com", "herokuapp.com", "herominers.com", "heungkongdiscuss.com", "hexieshe.com", "hexieshe.xyz", "hexxeh.net", "heyuedi.com", "heyzo.com", "hgamefree.info", "hgseav.com", "hhdcb3office.org", "hhthesakyatrizin.org", "hi-on.org.tw", "hiccears.com", "hidden-advent.org", "hide.me", "hidecloud.com", "hidein.net", "hideipvpn.com", "hideman.net", "hideme.nl", "hidemy.name", "hidemyass.com", "hidemycomp.com", "higfw.com", "highpeakspureearth.com", "highrockmedia.com", "hightail.com", "hihiforum.com", "hihistory.net", "hiitch.com", "hikinggfw.org", "hilive.tv", "himalaya.exchange", "himalayan-foundation.org", "himalayanglacier.com", "himemix.com", "himemix.net", "hindustantimes.com", "hinet.net", "hitbtc.com", "hitomi.la", "hiveon.net", "hiwifi.com", "hizb-ut-tahrir.info", "hizb-ut-tahrir.org", "hizbuttahrir.org", "hjclub.info", "hjd.tw", "hjd2048.com", "hk-pub.com", "hk01.com", "hk32168.com", "hkacg.com", "hkacg.net", "hkatvnews.com", "hkbc.net", "hkbf.org", "hkbookcity.com", "hkchronicles.com", "hkchurch.org", "hkci.org.hk", "hkcmi.edu", "hkcnews.com", "hkcoc.com", "hkdailynews.com.hk", "hkday.net", "hkdc.us", "hkdf.org", "hkej.com", "hkepc.com", "hket.com", "hkfaa.com", "hkfreezone.com", "hkfront.org", "hkgalden.com", "hkgolden.com", "hkgpao.com", "hkheadline.com", "hkhkhk.com", "hkhrc.org.hk", "hkip.org.uk", "hkjc.com", "hkjp.org", "hklft.com", "hklts.org.hk", "hkmap.live", "hkopentv.com", "hkpeanut.com", "hkptu.org", "hkreadingcity.net", "hkreporter.com", "hku.hk", "hkusu.net", "hkvwet.com", "hkwcc.org.hk", "hmoegirl.com", "hmonghot.com", "hmv.co.jp", "hmvdigital.ca", "hmvdigital.com", "hnjhj.com", "hnntube.com", "ho5ho.com", "hojemacau.com.mo", "hola.com", "hola.org", "holymountaincn.com", "holyspiritspeaks.org", "home.saxo", "homedepot.com", "homeperversion.com", "homeservershow.com", "honeynet.org", "hongkongfp.com", "hongmeimei.com", "hongzhi.li", "honven.xyz", "hootsuite.com", "hoover.org", "hoovers.com", "hopedialogue.org", "hopto.org", "hornygamer.com", "hornytrip.com", "horrorporn.com", "hostloc.com", "hotair.com", "hotav.tv", "hotcoin.com", "hotcool.tw", "hotels.cn", "hotfrog.com.tw", "hotgoo.com", "hotpornshow.com", "hotpot.hk", "hotshame.com", "hotspotshield.com", "hottg.com", "hotvpn.com", "hougaige.com", "house.gov", "howtoforge.com", "hoxx.com", "hoy.tv", "hoyolab.com", "hpa.gov.tw", "hpjav.com", "hqcdp.org", "hqjapanesesex.com", "hqmovies.com", "hrcchina.org", "hrcir.com", "hrea.org", "hrichina.org", "hrntt.org", "hrtsea.com", "hrw.org", "hrweb.org", "hsex.men", "hsjp.net", "hsselite.com", "hst.net.tw", "hstern.net", "hstt.net", "ht.ly", "htkou.net", "htl.li", "html5rocks.com", "https443.net", "https443.org", "hua-yue.net", "huaglad.com", "huaibinmall.com", "huanghuagang.org", "huangyiyu.com", "huaren.us", "huaren4us.com", "huashangnews.com", "huasing.org", "huaxia-news.com", "huaxiabao.org", "huaxin.ph", "huayuworld.org", "hudatoriq.web.id", "hudson.org", "huffingtonpost.com", "huffpost.com", "huggingface.co", "hugoroy.eu", "huhaitai.com", "huhamhire.com", "huhangfei.com", "huigui.tw", "huiyi.in", "hulkshare.com", "hulu.com", "huluim.com", "humanparty.me", "humanrightsbriefing.org", "humanrightspressawards.org", "hung-ya.com", "hungerstrikeforaids.org", "huobi.co", "huobi.com", "huobi.me", "huobi.pro", "huobi.sc", "huobi.vc", "huobipool.com", "huobipro.com", "huping.net", "hurgokbayrak.com", "hurriyet.com.tr", "hustler.com", "hustlercash.com", "hut2.ru", "hutianyi.net", "hutong9.net", "huyandex.com", "hwadzan.tw", "hwayue.org.tw", "hxwk.org", "hxwq.org", "hybrid-analysis.com", "hyperrate.com", "hypothes.is", "hyread.com.tw", "hysteria.network", "i-cable.com", "i-part.com.tw", "i-scmp.com", "i1.hk", "i2p2.de", "i2runner.com", "i818hk.com", "iam.soy", "iamtopone.com", "iask.bz", "iask.ca", "iav19.com", "iavian.net", "ibiblio.org", "iblist.com", "iblogserv-f.net", "ibros.org", "ibtimes.com", "ibvpn.com", "ibytedtos.com", "icams.com", "icedrive.net", "icerocket.com", "icij.org", "icl-fi.org", "icoco.com", "iconpaper.org", "icu-project.org", "idaiwan.com", "idemocracy.asia", "identi.ca", "idiomconnection.com", "idlcoyote.com", "idope.se", "idouga.com", "idreamx.com", "idv.tw", "ieasy5.com", "ied2k.net", "ienergy1.com", "ifanqiang.com", "ifcss.org", "ifjc.org", "ifreewares.com", "ift.tt", "ig.com", "igcd.net", "igfw.net", "igfw.tech", "igmg.de", "ignitedetroit.net", "igoogle.ae", "igoogle.am", "igoogle.as", "igoogle.at", "igoogle.az", "igoogle.ba", "igoogle.be", "igoogle.bg", "igoogle.ca", "igoogle.cd", "igoogle.ci", "igoogle.co.id", "igoogle.co.jp", "igoogle.co.kr", "igoogle.co.ma", "igoogle.co.uk", "igoogle.com", "igoogle.de", "igoogle.dj", "igoogle.dk", "igoogle.es", "igoogle.fi", "igoogle.fm", "igoogle.fr", "igoogle.gg", "igoogle.gl", "igoogle.gr", "igoogle.ie", "igoogle.is", "igoogle.it", "igoogle.jo", "igoogle.kz", "igoogle.mn", "igoogle.ms", "igoogle.nl", "igoogle.no", "igoogle.nu", "igoogle.ro", "igoogle.ru", "igoogle.rw", "igoogle.sc", "igoogle.sh", "igoogle.sm", "igoogle.sn", "igoogle.tk", "igoogle.tm", "igoogle.to", "igoogle.tt", "igoogle.vu", "igoogle.ws", "igotmail.com.tw", "igvita.com", "ihakka.net", "ihao.org", "iicns.com", "ikstar.com", "ikwb.com", "ilbe.com", "ilhamtohtiinstitute.org", "illawarramercury.com.au", "illusionfactory.com", "ilove80.be", "ilovelongtoes.com", "im88.tw", "imageab.com", "imagefap.com", "imageflea.com", "imageglass.org", "images-gaytube.com", "imageshack.us", "imagevenue.com", "imagezilla.net", "imago-images.com", "imb.org", "imdb.com", "img.ly", "imgasd.com", "imgchili.net", "imgmega.com", "imgur.com", "imkev.com", "imlive.co", "imlive.com", "immigration.gov.tw", "immoral.jp", "impact.org.au", "impp.mn", "improd.works", "in-disguise.com", "in.com", "in99.org", "incapdns.net", "incloak.com", "incredibox.fr", "independent.co.uk", "india.com", "indiablooms.com", "indianarrative.com", "indiandefensenews.in", "indianexpress.com", "indiatimes.com", "indiatoday.in", "indiemerch.com", "indsr.org.tw", "info-graf.fr", "informer.com", "infura.io", "inherit.live", "initiativesforchina.org", "inkbunny.net", "inkui.com", "inlang.com", "inmediahk.net", "innermongolia.org", "inoreader.com", "inote.tw", "insecam.org", "inside.com.tw", "insidevoa.com", "instagram.com", "instanthq.com", "institut-tibetain.org", "interactivebrokers.com", "internet.org", "internetdefenseleague.org", "internetfreedom.org", "internetpopculture.com", "inthenameofconfuciusmovie.com", "invidio.us", "inxian.com", "iownyour.org", "ipalter.com", "ipdefenseforum.com", "ipfire.org", "ipfs.io", "iphone4hongkong.com", "iphonehacks.com", "iphonetaiwan.org", "iphonix.fr", "ipicture.ru", "ipify.org", "ipjetable.net", "ipobar.com", "ipoock.com", "iportal.me", "ippotv.com", "ipredator.se", "iptv.com.tw", "iptvbin.com", "ipvanish.com", "irangov.ir", "iredmail.org", "irib.ir", "irna.ir", "ironpython.net", "ironsocket.com", "is-a-hunter.com", "is.gd", "isaacmao.com", "isasecret.com", "isgreat.org", "ishr.ch", "islahhaber.net", "islam.org.hk", "islamawareness.net", "islamhouse.com", "islamicity.com", "islamicpluralism.org", "islamtoday.net", "ismaelan.com", "ismalltits.com", "ismprofessional.net", "isohunt.com", "israbox.com", "issuu.com", "istars.co.nz", "istarshine.com", "istef.info", "istiqlalhewer.com", "istockphoto.com", "isunaffairs.com", "isuntv.com", "isupportuyghurs.org", "itaboo.info", "itaiwan.gov.tw", "italiatibet.org", "itasoftware.com", "itch.io", "itemdb.com", "itemfix.com", "ithome.com.tw", "itiger.com", "itsaol.com", "itshidden.com", "itsky.it", "itweet.net", "iu45.com", "iuhrdf.org", "iuksky.com", "ivacy.com", "iverycd.com", "ivonblog.com", "ivpn.net", "iwara.tv", "ixquick.com", "ixxx.com", "iyouport.com", "iyouport.org", "izaobao.us", "izihost.org", "izles.net", "izlesem.org", "j.mp", "jable.tv", "jackjia.com", "jamaat.org", "jamestown.org", "jamyangnorbu.com", "jan.ai", "jandyx.com", "janwongphoto.com", "japan-whores.com", "japanhdv.com", "japantimes.co.jp", "jav.com", "jav101.com", "jav321.com", "jav68.tv", "jav777.cc", "javakiba.org", "javbus.co", "javbus.com", "javbus.sbs", "javdb.com", "javfinder.ai", "javfor.me", "javfree.me", "javhd.com", "javhip.com", "javhub.net", "javhuge.com", "javlibrary.com", "javmobile.net", "javmoo.com", "javmoo.xyz", "javseen.com", "javtag.com", "javtrailers.com", "javzoo.com", "javzz.com", "jbtalks.cc", "jbtalks.com", "jbtalks.my", "jcpenney.com", "jdwsy.com", "jeanyim.com", "jetos.com", "jex.com", "jfqu36.club", "jfqu37.xyz", "jgoodies.com", "jiangweiping.com", "jiaoyou8.com", "jichangtj.com", "jiehua.cz", "jiepang.com", "jieshibaobao.com", "jifangge.com", "jigglegifs.com", "jigong1024.com", "jigsy.com", "jihadology.net", "jiji.com", "jims.net", "jinbushe.org", "jingpin.org", "jingsim.org", "jinhai.de", "jinpianwang.com", "jinrizhiyi.news", "jinroukong.com", "jintian.net", "jinx.com", "jiruan.net", "jitouch.com", "jizzthis.com", "jjgirls.com", "jkb.cc", "jkforum.net", "jkub.com", "jma.go.jp", "jmcomic.me", "jmscult.com", "joachims.org", "jobso.tv", "joinbbs.net", "joinclubhouse.com", "joinmastodon.org", "joinpeertube.org", "joins.com", "jornaldacidadeonline.com.br", "jotform.com", "journalchretien.net", "journalofdemocracy.org", "joymiihub.com", "joyourself.com", "jp1lib.org", "jpopforum.net", "jquery.com", "jqueryui.com", "jrf.org.tw", "jsdelivr.net", "jsfiddle.net", "jshell.net", "jtvnw.net", "jubushoushen.com", "judicial.gov.tw", "juhuaren.com", "jukujo-club.com", "juliepost.com", "juliereyc.com", "junauza.com", "june4commemoration.org", "junefourth-20.net", "jungleheart.com", "junglobal.net", "juoaa.com", "justdied.com", "justfreevpn.com", "justhost.ru", "justicefortenzin.org", "justmysocks.net", "justmysocks1.net", "justmysockscn.com", "justpaste.it", "justtristan.com", "juyuange.org", "juziyue.com", "jw.org", "jwmusic.org", "jwplayer.com", "jyxf.net", "ka-wai.com", "kadokawa.co.jp", "kagyu.org", "kagyu.org.za", "kagyumonlam.org", "kagyunews.com.hk", "kagyuoffice.org", "kagyuoffice.org.tw", "kaiyuan.de", "kakao.com", "kalachakralugano.org", "kanald.com.tr", "kangye.org", "kankan.today", "kannewyork.com", "kanshifang.com", "kantie.org", "kanzhongguo.com", "kanzhongguo.eu", "kaotic.com", "karayou.com", "karkhung.com", "karmapa-teachings.org", "karmapa.org", "kawaiikawaii.jp", "kawase.com", "kba-tx.org", "kcoolonline.com", "kebrum.com", "kechara.com", "keepandshare.com", "keezmovies.com", "kemono.party", "kendatire.com", "kendincos.net", "kenengba.com", "keontech.net", "kepard.com", "keso.cn", "kex.com", "keycdn.com", "kfd.me", "khabdha.org", "khatrimaza.org", "khmusic.com.tw", "kichiku-doujinko.com", "kik.com", "killwall.com", "kindle4rss.com", "kindleren.com", "kingdomsalvation.org", "kinghost.com", "kingkong.com.tw", "kingstone.com.tw", "kink.com", "kinmen.org.tw", "kinmen.travel", "kinokuniya.com", "kir.jp", "kissbbao.cn", "kissjav.com", "kiwi.kz", "kk-whys.co.jp", "kkbox.com", "kknews.cc", "klip.me", "kmuh.org.tw", "knowledgerush.com", "knowyourmeme.com", "ko-fi.com", "kobe-np.co.jp", "kobo.com", "kobobooks.com", "kodingen.com", "kompozer.net", "konachan.com", "kone.com", "koolsolutions.com", "koornk.com", "koranmandarin.com", "korea.net", "korenan2.com", "kpkuang.org", "kqes.net", "kraken.com", "krtc.com.tw", "krtco.com.tw", "ksdl.org", "ksnews.com.tw", "kspcoin.com", "ktzhk.com", "kuaichedao.co", "kucoin.com", "kui.name", "kukuku.uk", "kun.im", "kurashsultan.com", "kurtmunger.com", "kusocity.com", "kwcg.ca", "kwongwah.com.my", "kxsw.life", "kyofun.com", "kyohk.net", "kyoyue.com", "kyzyhello.com", "kzaobao.com", "kzeng.info", "la-forum.org", "labiennale.org", "ladbrokes.com", "lagranepoca.com", "lala.im", "lalulalu.com", "lama.com.tw", "lamayeshe.com", "lamenhu.com", "lamnia.co.uk", "lamrim.com", "landofhope.tv", "lanterncn.cn", "lantosfoundation.org", "laod.cn", "laogai.org", "laogairesearch.org", "laomiu.com", "laowang.vip", "laoyang.info", "laqingdan.net", "larsgeorge.com", "lastcombat.com", "lastfm.es", "latelinenews.com", "latibet.org", "lausan.hk", "law.com", "lbank.info", "ldplayer.net", "ldplayer.tw", "le-vpn.com", "leafyvpn.net", "lecloud.net", "ledger.com", "leeao.com.cn", "lefora.com", "left21.hk", "legalporno.com", "legra.ph", "legsjapan.com", "leirentv.ca", "leisurecafe.ca", "leisurepro.com", "lematin.ch", "lemonde.fr", "lenwhite.com", "lerosua.org", "lesoir.be", "lester850.info", "letou.com", "letscorp.net", "lflink.com", "lflinkup.com", "lflinkup.net", "lflinkup.org", "lfpcontent.com", "lhakar.org", "lhasocialwork.org", "li.taipei", "liangyou.net", "liangzhichuanmei.com", "lianyue.net", "liaowangxizang.net", "liberal.org.hk", "libertysculpturepark.com", "libertytimes.com.tw", "libraryinformationtechnology.com", "libredd.it", "lidecheng.com", "lifemiles.com", "lighten.org.tw", "lighti.me", "lightnovel.cn", "lightyearvpn.com", "lih.kg", "lihkg.com", "like.com", "lilaoshibushinilaoshi.com", "limelight.moe", "limiao.net", "line-apps.com", "line-scdn.net", "line.me", "linglingfa.com", "lingualeo.com", "lingvodics.com", "link-o-rama.com", "linkedin.com", "linkideo.com", "linksalpha.com", "linktr.ee", "linkuswell.com", "linpie.com", "linux.org.hk", "linuxtoy.org", "lionsroar.com", "lipuman.com", "liquiditytp.com", "liquidvpn.com", "list-manage.com", "listennotes.com", "listentoyoutube.com", "listorious.com", "litenews.hk", "lithium.com", "liu-xiaobo.org", "liuhanyu.com", "liujianshu.com", "liuxiaobo.net", "liuxiaotong.com", "live.com", "livecoin.net", "livedoor.jp", "liveleak.com", "livemint.com", "livestation.com", "livestream.com", "livevideo.com", "livingonline.us", "livingstream.com", "liwangyang.com", "lizhizhuangbi.com", "lkcn.net", "llss.me", "lmsys.org", "load.to", "lobsangwangyal.com", "localbitcoins.com", "localdomain.ws", "localpresshk.com", "lockestek.com", "logbot.net", "logiqx.com", "logmein.com", "logos.com.hk", "londonchinese.ca", "longhair.hk", "longmusic.com", "longtermly.net", "longtoes.com", "lookpic.com", "looktoronto.com", "loongese.com", "lorenzetti.com.br", "lotsawahouse.org", "lotuslight.org.hk", "lotuslight.org.tw", "loukky.com", "loved.hk", "lovetvshow.com", "lpsg.com", "lrfz.com", "lrip.org", "lsd.org.hk", "lsforum.net", "lsm.org", "lsmchinese.org", "lsmkorean.org", "lsmradio.com", "lsmwebcast.com", "lsxszzg.com", "ltn.com.tw", "luckydesigner.space", "luckymobile.ca", "ludepress.com", "luke54.com", "luke54.org", "lupm.org", "lushstories.com", "luxebc.com", "lvhai.org", "lvv2.com", "ly.gov.tw", "lyfhk.net", "lzjscript.com", "lzmtnews.org", "m-sport.co.uk", "m-team.cc", "m.me", "m8008.com", "macgamestore.com", "machbbs.com", "macrovpn.com", "macts.com.tw", "mad-ar.ch", "madewithcode.com", "madonna-av.com", "madou.club", "madrau.com", "madthumbs.com", "magic-net.info", "mahabodhi.org", "mahjongsoul.com", "maiio.net", "mail-archive.com", "mail.ru", "mailchimp.com", "maildns.xyz", "mainichi.jp", "maiplus.com", "maizhong.org", "makemymood.com", "makkahnewspaper.com", "malaysiakini.com", "mamingzhe.com", "manchukuo.net", "manchustate.org", "mandiant.com", "mangabz.com", "mangafox.com", "mangafox.me", "mangmang.run", "manhuabika.com", "manhuache.com", "manhuagui.com", "maniash.com", "manicur4ik.ru", "mansion.com", "mansionpoker.com", "manta.com", "manyvoices.news", "maplew.com", "marc.info", "marguerite.su", "martau.com", "martincartoons.com", "martinoei.com", "martsangkagyuofficial.org", "maruta.be", "marxist.com", "marxist.net", "marxists.org", "mash.to", "mashash.com", "maskedip.com", "mastodon.cloud", "mastodon.host", "mastodon.online", "mastodon.social", "mastodon.xyz", "matainja.com", "material.io", "mathable.io", "mathiew-badimon.com", "matome-plus.com", "matome-plus.net", "matrix.org", "matters.news", "matters.town", "mattwilcox.net", "maxai.co", "maxing.jp", "mayimayi.com", "mcadforums.com", "mcaf.ee", "mcfog.com", "mcreasite.com", "mcusercontent.com", "md-t.org", "me.com", "me.me", "meansys.com", "media.org.hk", "mediachinese.com", "mediafire.com", "mediafreakcity.com", "mediawiki.org", "medicalnewstoday.com", "medium.com", "mee6.xyz", "meetav.com", "meetup.com", "mefeedia.com", "meforum.org", "mefound.com", "mega.co.nz", "mega.io", "mega.nz", "megalodon.jp", "megaproxy.com", "megarotic.com", "megavideo.com", "megurineluka.com", "meirixiaochao.com", "meizhong.blog", "meizhong.report", "melon365.com", "meltoday.com", "memehk.com", "memes.tw", "memorybbs.com", "memri.org", "memrijttm.org", "mercari.com", "mercari.jp", "mercatox.com", "mercdn.net", "mercyprophet.org", "mergersandinquisitions.com", "mergersandinquisitions.org", "meridian-trust.org", "meripet.biz", "meripet.com", "merit-times.com.tw", "merlinblog.xyz", "meshrep.com", "mesotw.com", "messenger.com", "meta.com", "metacafe.com", "metacubex.one", "metafilter.com", "metamask.io", "metart.com", "metarthunter.com", "meteorshowersonline.com", "metro.taipei", "metrohk.com.hk", "metrolife.ca", "metroradio.com.hk", "mewe.com", "meyou.jp", "meyul.com", "mfxmedia.com", "mgoon.com", "mgstage.com", "mh4u.org", "mhradio.org", "mi.com", "miami-airport.com", "michaelmarketl.com", "microsoft.com", "microvpn.com", "middle-way.net", "mihk.hk", "mihr.com", "mihua.org", "mikanani.me", "mikesoltys.com", "mikocon.com", "milph.net", "milsurps.com", "mimiai.net", "mimivip.com", "mimivv.com", "mindrolling.org", "mingdemedia.org", "minghui-a.org", "minghui-b.org", "minghui-school.org", "minghui.or.kr", "minghui.org", "mingjinglishi.com", "mingjingnews.com", "mingjingtimes.com", "mingpao.com", "mingpaocanada.com", "mingpaomonthly.com", "mingpaonews.com", "mingpaony.com", "mingpaosf.com", "mingpaotor.com", "mingpaovan.com", "mingshengbao.com", "minhhue.net", "miniforum.org", "miningpoolhub.com", "ministrybooks.org", "minjian-danganguan.org", "minzhuhua.net", "minzhuzhanxian.com", "minzhuzhongguo.org", "miraheze.org", "miroguide.com", "mirror.xyz", "mirrorbooks.com", "mirrormedia.com.tw", "mirrormedia.mg", "missav.com", "missav.ws", "mist.vip", "mit.edu", "mitao.com.tw", "mitbbs.com", "mitbbsau.com", "miuipolska.pl", "mixero.com", "mixi.jp", "mixpod.com", "mixx.com", "mizzmona.com", "mjib.gov.tw", "mk5000.com", "mlc.ai", "mlcool.com", "mlzs.work", "mm-cg.com", "mmaaxx.com", "mmmca.com", "mnewstv.com", "mobatek.net", "mobile01.com", "mobileways.de", "moby.to", "mobypicture.com", "mod.io", "modernchinastudies.org", "moeaic.gov.tw", "moeerolibrary.com", "moegirl.org", "moeshare.cc", "moeyy.xyz", "mofa.gov.tw", "mofaxiehui.com", "mofos.com", "mog.com", "mohu.club", "mohu.rocks", "moj.gov.tw", "mojim.com", "mol.gov.tw", "molihua.org", "momoshop.com.tw", "mondex.org", "money-link.com.tw", "moneydj.com", "moneyhome.biz", "monica.im", "monitorchina.org", "monitorware.com", "monlamit.org", "monocloud.me", "monster.com", "moodyz.com", "moomoo.com", "moon.fm", "moonbbs.com", "moonbbs.info", "moonbingo.com", "mooo.com", "moptt.tw", "morbell.com", "moresci.sale", "morningsun.org", "moroneta.com", "mos.ru", "mosucloud.site", "motherless.com", "motor4ik.ru", "mousebreaker.com", "movements.org", "moviefap.com", "mozilla.org", "moztw.org", "mp3buscador.com", "mp3ye.eu", "mpettis.com", "mpfinance.com", "mpinews.com", "mponline.hk", "mqxd.org", "mrbasic.com", "mrbonus.com", "mrface.com", "mrslove.com", "mrtweet.com", "msa-it.org", "msguancha.com", "msha.gov", "msn.com", "msn.com.tw", "mstdn.social", "mswe1.org", "mt.co.kr", "mthruf.com", "mtw.tl", "mtzfile.pw", "mubi.com", "muchosucko.com", "mullvad.net", "multiply.com", "multiproxy.org", "multiupload.com", "mummysgold.com", "murmur.tw", "muscdn.com", "musicade.net", "musical.ly", "musixmatch.com", "muslimvideo.com", "muzi.com", "muzi.net", "muzu.tv", "mvdis.gov.tw", "mvg.jp", "mx981.com", "my-formosa.com", "my-private-network.co.uk", "my-proxy.com", "my03.com", "myactimes.com", "myanniu.com", "myaudiocast.com", "myav.com.tw", "mybbs.us", "mybet.com", "myca168.com", "mycanadanow.com", "mychat.to", "mychinamyhome.com", "mychinanet.com", "mychinanews.com", "mychinese.news", "mycnnews.com", "mycould.com", "mydad.info", "mydati.com", "myddns.com", "myeasytv.com", "myeclipseide.com", "myforum.com.hk", "myfreecams.com", "myfreepaysite.com", "myfreshnet.com", "myftp.info", "myip.com", "myiphide.com", "myiphider.com", "myjs.tw", "mykomica.org", "mylftv.com", "mymaji.com", "mymediarom.com", "mymoe.moe", "mymom.info", "mymusic.net.tw", "mynetav.net", "mynetav.org", "mynumber.org", "myparagliding.com", "mypicasa.com", "mypicture.info", "mypikpak.com", "mypop3.net", "mypop3.org", "mypopescu.com", "myradio.hk", "myreadingmanga.info", "mysecondarydns.com", "mysinablog.com", "myspace.com", "myspacecdn.com", "mytalkbox.com", "mytizi.com", "mywife.cc", "mywww.biz", "myz.info", "naacoalition.org", "naitik.net", "naixi.net", "nakido.com", "nakuz.com", "nalandabodhi.org", "nalandawest.org", "namgyal.org", "namgyalmonastery.org", "nanhuyt.com", "nanopool.org", "nanyang.com", "nanyangpost.com", "nanzao.com", "naol.ca", "naol.cc", "narod.ru", "nasa.gov", "nat.gov.tw", "nat.moe", "natado.com", "national-lottery.co.uk", "nationalawakening.org", "nationalgeographic.com", "nationalinterest.org", "nationalreview.com", "nationsonline.org", "nationwide.com", "naughtyamerica.com", "naver.com", "naver.jp", "navy.mil", "naweeklytimes.com", "nbc.com", "nbcnews.com", "nbtvpn.com", "nbyy.tv", "nccwatch.org.tw", "nch.com.tw", "nchrd.org", "ncn.org", "ncol.com", "nde.de", "ndi.org", "ndr.de", "ndtv.com", "ned.org", "nekoslovakia.net", "nengcard.com", "neo-miracle.com", "neoforged.net", "neowin.net", "nesnode.com", "netalert.me", "netbirds.com", "netcolony.com", "netfirms.com", "netflav.com", "netflix.com", "netflix.net", "netlify.app", "netme.cc", "netsarang.com", "netsneak.com", "network54.com", "networkedblogs.com", "networktunnel.net", "neverforget8964.org", "new-3lunch.net", "new-akiba.com", "new96.ca", "newcenturymc.com", "newcenturynews.com", "newchen.com", "newgrounds.com", "newhighlandvision.com", "newindianexpress.com", "newipnow.com", "newlandmagazine.com.au", "newmitbbs.com", "newnews.ca", "news.com.au", "news1.kr", "news100.com.tw", "news18.com", "newsancai.com", "newsblur.com", "newschinacomment.org", "newscn.org", "newsdetox.ca", "newsdh.com", "newsmagazine.asia", "newsmax.com", "newspeak.cc", "newstamago.com", "newstapa.org", "newstarnet.com", "newstatesman.com", "newsweek.com", "newtaiwan.com.tw", "newtalk.tw", "newthuhole.com", "newyorker.com", "newyorktimes.com", "nexon.com", "next11.co.jp", "nextapple.com", "nextapple.tw", "nextdigital.com.hk", "nextmag.com.tw", "nextmedia.com", "nexton-net.jp", "nexttv.com.tw", "nf.id.au", "nfjtyd.com", "nflxext.com", "nflximg.com", "nflximg.net", "nflxso.net", "nflxvideo.net", "nftstorage.link", "ng.mil", "nga.mil", "ngensis.com", "nhentai.net", "nhi.gov.tw", "nhk-ondemand.jp", "nic.gov", "nicovideo.jp", "nighost.org", "nightlife141.com", "nightswatch.top", "nike.com", "nikke-en.com", "nikke-jp.com", "nikke-kr.com", "nikkei.com", "ninecommentaries.com", "ning.com", "ninjacloak.com", "ninjaproxy.ninja", "nintendium.com", "nirsoft.net", "nitter.cc", "nitter.net", "niu.moe", "niusnews.com", "njactb.org", "njav.tv", "njuice.com", "nlfreevpn.com", "nmsl.website", "nnews.eu", "no-ip.com", "no-ip.org", "nobel.se", "nobelprize.org", "nobodycanstop.us", "nodeseek.com", "nodesnoop.com", "nokogiri.org", "nokola.com", "noodlevpn.com", "norbulingka.org", "nordcdn.com", "nordstrom.com", "nordstromimage.com", "nordstrommedia.com", "nordstromrack.com", "nordvpn.com", "nos.nl", "note.com", "notepad-plus-plus.org", "notion.site", "nottinghampost.com", "novelasia.com", "now.com", "now.im", "nownews.com", "nowtorrents.com", "noxinfluencer.com", "noypf.com", "npa.go.jp", "npa.gov.tw", "npm.edu.tw", "npm.gov.tw", "npnt.me", "npsboost.com", "nradio.me", "nrk.no", "ns.ci", "ns01.biz", "ns01.info", "ns01.us", "ns02.biz", "ns02.info", "ns02.us", "ns1.name", "ns2.name", "ns3.name", "nsc.gov.tw", "ntbk.gov.tw", "ntbna.gov.tw", "ntbt.gov.tw", "ntd.tv", "ntdsf.tv", "ntdtv.ca", "ntdtv.co.kr", "ntdtv.com", "ntdtv.com.tw", "ntdtv.cz", "ntdtv.jp", "ntdtv.org", "ntdtv.ru", "ntdtvla.com", "ntrfun.com", "ntu.edu.tw", "nu.nl", "nubiles.net", "nudezz.com", "nuexpo.com", "nukistream.com", "nurgo-software.com", "nusatrip.com", "nutaku.net", "nutsvpn.work", "nuuvem.com", "nuvid.com", "nvdst.com", "nvquan.org", "nvtongzhisheng.org", "nwtca.org", "nyaa.eu", "nyaa.si", "nybooks.com", "nydailynews.com", "nydus.ca", "nyinfor.com", "nylon-angel.com", "nylonstockingsonline.com", "nypost.com", "nyt.com", "nytchina.com", "nytcn.me", "nytco.com", "nyti.ms", "nytimes.com", "nytimes.map.fastly.net", "nytimg.com", "nytstyle.com", "nzchinese.com", "nzchinese.net.nz", "o3o.ca", "oaistatic.com", "oaiusercontent.com", "oanda.com", "oann.com", "oauth.net", "observechina.net", "obutu.com", "obyte.org", "ocaspro.com", "occupytiananmen.com", "oclp.hk", "ocreampies.com", "ocry.com", "october-review.org", "oculus.com", "oculuscdn.com", "odysee.com", "oex.com", "offbeatchina.com", "officeoftibet.com", "ofile.org", "ogaoga.org", "ogate.org", "ohchr.org", "ohmyrss.com", "oikos.com.tw", "oiktv.com", "oizoblog.com", "ok.ru", "ok.xxx", "okayfreedom.com", "okcoin.com", "okex.com", "okinawatimes.co.jp", "okk.tw", "okpool.me", "okx.com", "old-cat.net", "olehdtv.com", "olelive.com", "olevod.com", "olumpo.com", "olympicwatch.org", "omct.org", "omgili.com", "omni7.jp", "omnitalk.com", "omnitalk.org", "omny.fm", "omtrdc.net", "omy.sg", "on.cc", "on2.com", "onapp.com", "one.one", "onedrive.com", "onedumb.com", "onejav.com", "onesto.re", "onestore.co.kr", "onevps.com", "onion.city", "onion.ly", "onion666.com", "onlinecha.com", "onlineyoutube.com", "onlygayvideo.com", "onlytweets.com", "onmoon.com", "onmoon.net", "onmypc.biz", "onmypc.info", "onmypc.net", "onmypc.org", "onthehunt.com", "ontrac.com", "oojj.de", "open-assistant.io", "open.com.hk", "openai.com", "openallweb.com", "opendemocracy.net", "opendesktop.org", "opendn.xyz", "openervpn.in", "openid.net", "openleaks.org", "opensea.io", "openstreetmap.org", "opentech.fund", "openvpn.net", "openvpn.org", "openwebster.com", "openwrt.org.cn", "opera-mini.net", "opera.com", "opus-gaming.com", "organcare.org.tw", "organharvestinvestigation.net", "organiccrap.com", "orgasm.com", "orgfree.com", "oricon.co.jp", "orient-doll.com", "orientaldaily.com.my", "orn.jp", "orzistic.org", "osfoora.com", "osm.tw", "otcbtc.com", "otnd.org", "otto.de", "otzo.com", "ourdearamy.com", "ourhobby.com", "oursogo.com", "oursteps.com.au", "oursweb.net", "ourtv.hk", "over-blog.com", "overcast.fm", "overdaily.org", "overplay.net", "ovi.com", "ovpn.com", "ow.ly", "owind.com", "owl.li", "owltail.com", "oxfordscholarship.com", "oxid.it", "oyax.com", "oyghan.com", "ozchinese.com", "ozvoice.org", "ozxw.com", "ozyoyo.com", "pachosting.com", "pacificpoker.com", "packetix.net", "pacom.mil", "pacopacomama.com", "padmanet.com", "page.link", "page.tl", "page2rss.com", "pages.dev", "pagodabox.com", "paimon.moe", "palacemoon.com", "paldengyal.com", "paljorpublications.com", "palmislife.com", "paltalk.com", "pancakeswap.finance", "pandafan.pub", "pandapow.co", "pandapow.net", "pandavpn-jp.com", "pandavpnpro.com", "pandora.com", "pandora.tv", "panluan.net", "panoramio.com", "pao-pao.net", "paper.li", "paperb.us", "paperclip.tk", "paradisehill.cc", "paradisepoker.com", "parkansky.com", "parler.com", "parse.com", "parsevideo.com", "partycasino.com", "partypoker.com", "passion.com", "passiontimes.hk", "paste.ee", "pastebin.com", "pastie.org", "pathtosharepoint.com", "patreon.com", "patreonusercontent.com", "pawoo.net", "paxful.com", "pbs.org", "pbwiki.com", "pbworks.com", "pbxes.com", "pbxes.org", "pcanywhere.net", "pcc.gov.tw", "pcdvd.com.tw", "pcgamestorrents.com", "pchome.com.tw", "pchomeec.tw", "pcij.org", "pcloud.com", "pcmarket.com.hk", "pcstore.com.tw", "pct.org.tw", "pdetails.com", "pdproxy.com", "peace.ca", "peacefire.org", "peacehall.com", "peeasian.com", "peing.net", "pekingduck.org", "pemulihan.or.id", "pen.io", "penchinese.com", "penchinese.net", "pendrivelinux.com", "pentalogic.net", "penthouse.com", "pentoy.hk", "peoplebookcafe.com", "peoplenews.tw", "peopo.org", "percy.in", "perfect-privacy.com", "perfectgirls.net", "perfectvpn.net", "periscope.tv", "perplexity.ai", "persecutionblog.com", "persiankitty.com", "pewresearch.org", "pfd.org.hk", "phapluan.org", "phayul.com", "philborges.com", "phmsociety.org", "phncdn.com", "phonegap.com", "photodharma.net", "photofocus.com", "photonmedia.net", "phprcdn.com", "phptutorial.net", "phuquocservices.com", "piaotia.com", "picacomic.com", "picacomiccn.com", "picasaweb.com", "picgo.net", "picidae.net", "picturedip.com", "picturesocial.com", "picuki.com", "pigav.com", "pimg.tw", "pin-cong.com", "pin6.com", "pincong.rocks", "ping.fm", "pinimg.com", "pinkrod.com", "pinoy-n.com", "pinterest.at", "pinterest.ca", "pinterest.co.uk", "pinterest.com", "pinterest.de", "pinterest.dk", "pinterest.fr", "pinterest.jp", "pinterest.nl", "pinterest.se", "pionex.com", "pipii.tv", "piposay.com", "piraattilahti.org", "piring.com", "pixeldrain.com", "pixelqi.com", "pixiv.net", "pixiv.org", "pixivsketch.net", "pixnet.in", "pixnet.net", "pk.com", "pki.goog", "pkqjiasu.com", "pkuanvil.com", "placemix.com", "play-asia.com", "playbeasts.com", "playboy.com", "playboyplus.com", "player.fm", "playno1.com", "playpcesor.com", "plays.com.tw", "plexvpn.pro", "plixi.com", "plm.org.hk", "plunder.com", "plurk.com", "plus.codes", "plus28.com", "plusbb.com", "pmatehunter.com", "pmates.com", "po2b.com", "pobieramy.top", "podbean.com", "podcast.app", "podcast.co", "podictionary.com", "poe.com", "points-media.com", "pokerstars.com", "pokerstars.net", "pokerstrategy.com", "politicalchina.org", "politicalconsultation.org", "politico.eu", "politiscales.net", "poloniex.com", "polymarket.com", "polymer-project.org", "polymerhk.com", "poolbinance.com", "poolin.com", "popai.pro", "popo.tw", "popvote.hk", "popxi.click", "popyard.com", "popyard.org", "porn.biz", "porn.com", "porn2.com", "porn5.com", "pornbase.org", "pornerbros.com", "pornhd.com", "pornhost.com", "pornhub.com", "pornhubdeutsch.net", "pornhubpremium.com", "pornmate.com", "pornmm.net", "pornoxo.com", "pornrapidshare.com", "pornsharing.com", "pornsocket.com", "pornstarbyface.com", "pornstarclub.com", "porntube.com", "porntubenews.com", "porntvblog.com", "pornvisit.com", "port25.biz", "portablevpn.nl", "poskotanews.com", "post01.com", "post76.com", "post852.com", "postadult.com", "potato.im", "potatso.com", "potvpn.com", "pourquoi.tw", "power.com", "powerapple.com", "powercx.com", "powerphoto.org", "powerpointninja.com", "pp.ru", "ppy.sh", "prayforchina.net", "prcleader.org", "premeforwindows7.com", "premproxy.com", "presentation.new", "presentationzen.com", "president.ir", "presidentlee.tw", "pressreader.com", "prestige-av.com", "prettyvirgin.com", "primevideo.com", "printfriendly.com", "prism-break.org", "prisoneralert.com", "pritunl.com", "privacybox.de", "privacyguides.org", "private.com", "privateinternetaccess.com", "privatepaste.com", "privatetunnel.com", "privatevpn.com", "privoxy.org", "procopytips.com", "prohashing.com", "project-syndicate.org", "prosiben.de", "proton.me", "protonvpn.com", "provideocoalition.com", "provpnaccounts.com", "proxfree.com", "proxifier.com", "proxlet.com", "proxomitron.info", "proxpn.com", "proxy.org", "proxy1.xyz", "proxyanonimo.es", "proxydns.com", "proxylist.org.uk", "proxynetwork.org.uk", "proxyroad.com", "proxytunnel.net", "proxz.com", "proyectoclubes.com", "prozz.net", "psblog.name", "pscp.tv", "pshvpn.com", "psiphon.ca", "psiphon3.com", "psiphontoday.com", "pstatic.net", "pt.im", "pts.org.tw", "ptt.cc", "pttgame.com", "ptthito.com", "pttvan.org", "pttweb.cc", "pttyes.com", "ptwxz.com", "pubu.com.tw", "puffin.com", "puffinbrowser.com", "puffstore.com", "pugpig.com", "pullfolio.com", "punjab.gov.in", "punjab.gov.pk", "punyu.com", "pure18.com", "pureapk.com", "pureconcepts.net", "puredns.org", "pureinsight.org", "purepdf.com", "purevpn.com", "purplelotus.org", "pursuestar.com", "pushchinawall.com", "pussyspace.com", "putihome.org", "putlocker.com", "putty.org", "puuko.com", "pwned.com", "pximg.net", "python.com", "python.com.tw", "pythonhackers.com", "pythonic.life", "pytorch.org", "qanote.com", "qbittorrent.org", "qgirl.com.tw", "qhigh.com", "qi-gong.me", "qianbai.tw", "qiandao.today", "qianglie.com", "qiangwaikan.com", "qiangyou.org", "qianmo.tw", "qidian.ca", "qienkuen.org", "qiwen.lu", "qixianglu.cn", "qkshare.com", "qmp4.com", "qoos.com", "qpoe.com", "qq.co.za", "qstatus.com", "qtrac.eu", "qtweeter.com", "quannengshen.org", "questvisual.com", "quitccp.net", "quitccp.org", "quiz.directory", "quora.com", "quoracdn.net", "quran.com", "quranexplorer.com", "qusi8.net", "qvodzy.org", "qwant.com", "qx.net", "qxbbs.org", "qz.com", "r-pool.net", "r0.ru", "r10s.jp", "r18.com", "ra.gg", "radicalparty.org", "radiko.jp", "radio-canada.ca", "radio-en-ligne.fr", "radio.garden", "radioaustralia.net.au", "radiohilight.net", "radioline.co", "radiotime.com", "radiovaticana.org", "radiovncr.com", "radmin-vpn.com", "rael.org", "raggedbanner.com", "raidcall.com.tw", "rainbowplan.org", "raindrop.io", "raizoji.or.jp", "rakuten.co.jp", "rakuten.com.tw", "ramcity.com.au", "rangwang.biz", "rangzen.com", "rangzen.net", "rangzen.org", "ranxiang.com", "ranyunfei.com", "rapbull.net", "rapidmoviez.com", "rapidvpn.com", "rarbgprx.org", "raremovie.cc", "raremovie.net", "rateyourmusic.com", "rationalwiki.org", "ratx.com", "rawgit.com", "rawgithub.com", "razyboard.com", "rcinet.ca", "rd.com", "reabble.com", "read01.com", "read100.com", "readingtimes.com.tw", "readmoo.com", "readydown.com", "realcourage.org", "realitykings.com", "realraptalk.com", "realsexpass.com", "reason.com", "rebatesrule.net", "recaptcha.net", "recordhistory.org", "recovery.org.tw", "recoveryversion.com.tw", "recoveryversion.org", "red-lang.org", "redballoonsolidarity.org", "redbubble.com", "redchinacn.net", "redchinacn.org", "redd.it", "reddit.com", "reddithelp.com", "redditlist.com", "redditmedia.com", "redditspace.com", "redditstatic.com", "redhotlabs.com", "redtube.com", "referer.us", "reflectivecode.com", "reimu.net", "relaxbbs.com", "relay.com.tw", "releaseinternational.org", "religionnews.com", "religioustolerance.org", "renminbao.com", "renyurenquan.org", "resilio.com", "resistchina.org", "retweeteffect.com", "retweetist.com", "retweetrank.com", "reuters.com", "reutersmedia.net", "revleft.com", "revocationcheck.com", "revver.com", "rfa.org", "rfachina.com", "rfamobile.org", "rfaweb.org", "rferl.org", "rfi.fr", "rfi.my", "rigpa.org", "riku.me", "rileyguide.com", "riseup.net", "ritouki.jp", "ritter.vg", "rixcloud.com", "rixcloud.us", "rlcdn.com", "rlwlw.com", "rmbl.ws", "rmjdw.com", "rmjdw132.info", "roadshow.hk", "roboforex.com", "robustnessiskey.com", "rocket-inc.net", "rocket.chat", "rocketbbs.com", "rocksdb.org", "rojo.com", "rolfoundation.org", "rolia.net", "rolsociety.org", "ronjoneswriter.com", "roodo.com", "rosechina.net", "rotten.com", "rou.video", "roucdn.link", "rsdlmonitor.com", "rsf-chinese.org", "rsf.org", "rsgamen.org", "rsshub.app", "rssing.com", "rssmeme.com", "rtalabel.org", "rthk.hk", "rthk.org.hk", "rti.org.tw", "rti.tw", "rtycminnesota.org", "ruanyifeng.com", "rukor.org", "rule34.xxx", "rule34video.com", "rumble.com", "runbtx.com", "rushbee.com", "rusvpn.com", "ruten.com.tw", "rutracker.net", "rutracker.org", "rutube.ru", "ruyiseek.com", "rxhj.net", "s-cute.com", "s-dragon.org", "s-nbcnews.com", "s1heng.com", "s1s1s1.com", "s3-ap-northeast-1.amazonaws.com", "s3-ap-northeast-2.amazonaws.com", "s3-ap-southeast-1.amazonaws.com", "s3-ap-southeast-2.amazonaws.com", "s3-eu-central-1.amazonaws.com", "s3.amazonaws.com", "s3.ap-northeast-2.amazonaws.com", "s3.eu-central-1.amazonaws.com", "s3.us-east-1.amazonaws.com", "s4miniarchive.com", "s8forum.com", "saboom.com", "sacks.com", "sacom.hk", "sadistic-v.com", "sadpanda.us", "saeima.lv", "safechat.com", "safeguarddefenders.com", "safervpn.com", "sagernet.org", "saintyculture.com", "sakura-paris.org", "sakuralive.com", "sakya.org", "salvation.org.hk", "samair.ru", "sambhota.org", "sandscotaicentral.com", "sankakucomplex.com", "sankei.com", "sanmin.com.tw", "sans.edu", "sapikachu.net", "saveliuxiaobo.com", "savemedia.com", "savethedate.foo", "savethesounds.info", "savetibet.de", "savetibet.fr", "savetibet.nl", "savetibet.org", "savetibet.ru", "savetibetstore.org", "saveuighur.org", "savevid.com", "sbme.me", "sbs.com.au", "scasino.com", "schema.org", "sciencemag.org", "sciencenets.com", "scieron.com", "sclub.com.tw", "scmp.com", "scmpchinese.com", "scramble.io", "scribd.com", "scriptspot.com", "seapuff.com", "search.com", "search.xxx", "searchtruth.com", "searx.me", "seatguru.com", "seattlefdc.com", "secretchina.com", "secretgarden.no", "secretsline.biz", "secureservercdn.net", "securetunnel.com", "securityinabox.org", "securitykiss.com", "see.xxx", "seed4.me", "seehua.com", "seesmic.com", "seevpn.com", "seezone.net", "sehuatang.net", "sehuatang.org", "sejie.com", "seju.life", "sellclassics.com", "sendsmtp.com", "sendspace.com", "sensortower.com", "servehttp.com", "serveuser.com", "serveusers.com", "sesawe.net", "sesawe.org", "sethwklein.net", "setn.com", "settv.com.tw", "sevenload.com", "sex-11.com", "sex.com", "sex3.com", "sex8.cc", "sexandsubmission.com", "sexbot.com", "sexhu.com", "sexhuang.com", "sexidude.com", "sexinsex.net", "sextvx.com", "sf.net", "sfileydy.com", "sfshibao.com", "sftindia.org", "sftuk.org", "sgwritings.com", "sgzhan.com", "shadeyouvpn.com", "shadow.ma", "shadowsky.xyz", "shadowsocks-r.com", "shadowsocks.asia", "shadowsocks.be", "shadowsocks.com", "shadowsocks.com.hk", "shadowsocks.nu", "shadowsocks.org", "shafaqna.com", "shahit.biz", "shambalapost.com", "shambhalasun.com", "shangfang.org", "shanxivideo.com", "shapeservices.com", "share-videos.se", "sharebee.com", "sharecool.org", "sharpdaily.com.hk", "sharpdaily.hk", "sharpdaily.tw", "shat-tibet.com", "shattered.io", "sheet.new", "sheets.new", "sheikyermami.com", "shellfire.de", "shemalez.com", "shenshou.org", "shenyun.com", "shenyunperformingarts.org", "shenyunshop.com", "shenzhoufilm.com", "shenzhouzhengdao.org", "sherabgyaltsen.com", "shiatv.net", "shicheng.org", "shiksha.com", "shiksha.ws", "shipcamouflage.com", "shireyishunjian.com", "shitaotv.org", "shixiao.org", "shizhao.org", "shkspr.mobi", "shodanhq.com", "shooshtime.com", "shop2000.com.tw", "shopee.tw", "shopping.com", "showhaotu.com", "showtime.jp", "showwe.tw", "shutterstock.com", "shwchurch.org", "shwchurch3.com", "siddharthasintent.org", "sidelinesnews.com", "sidelinessportseatery.com", "sierrafriendsoftibet.org", "signal.org", "sijihuisuo.club", "sijihuisuo.com", "silkbook.com", "silvergatebank.com", "simbolostwitter.com", "simplecd.me", "simplecd.org", "simpleproductivityblog.com", "simpleswap.io", "simplex.chat", "sina.com", "sina.com.hk", "sinchew.com.my", "singaporepools.com.sg", "singfortibet.com", "singlelogin.me", "singlelogin.se", "singpao.com.hk", "singtao.ca", "singtao.com", "singtaousa.com", "sino-monthly.com", "sinoants.com", "sinoca.com", "sinocast.com", "sinocism.com", "sinoinsider.com", "sinomontreal.ca", "sinonet.ca", "sinopitt.info", "sinoquebec.com", "sipml5.org", "sis.xxx", "sis001.com", "sis001.us", "site.new", "site2unblock.com", "site90.net", "sitebro.tw", "sitekreator.com", "sitemaps.org", "sites.new", "six-degrees.io", "sixth.biz", "sjrt.org", "sjum.cn", "sketchappsources.com", "skimtube.com", "skk.moe", "skybet.com", "skyking.com.tw", "skykiwi.com", "skynet.be", "skype.com", "skyvegas.com", "skyxvpn.com", "sl-reverse.com", "slacker.com", "slandr.net", "slashine.onl", "slaytizle.com", "sleazydream.com", "sleazyfork.org", "slheng.com", "slickvpn.com", "slides.com", "slides.new", "slideshare.net", "slime.com.tw", "slinkset.com", "slutload.com", "slutmoonbeam.com", "slyip.com", "slyip.net", "sm-miracle.com", "sm3ha.ru", "smartdnsproxy.com", "smarthide.com", "smartmailcloud.com", "smashwords.com", "smchbooks.com", "smh.com.au", "smhric.org", "smith.edu", "smn.news", "smyxy.org", "snapchat.com", "snapseed.com", "snaptu.com", "sndcdn.com", "sneakme.net", "snow-plus.net", "snowlionpub.com", "so-net.net.tw", "soav.com", "sobees.com", "soc.mil", "social.edu.ci", "socialblade.com", "socks-proxy.net", "sockscap64.com", "sockslist.net", "socrec.org", "sod.co.jp", "softether-download.com", "softether.co.jp", "softether.org", "softfamous.com", "softlayer.net", "softonic.cn", "softonic.com", "softwarebychuck.com", "sogclub.com", "sogoo.org", "sogrady.me", "soh.tw", "sohcradio.com", "sohfrance.org", "soifind.com", "sokamonline.com", "sokmil.com", "solana.com", "solidaritetibet.org", "solidfiles.com", "solv.finance", "somee.com", "songjianjun.com", "sonicbbs.cc", "sonidodelaesperanza.org", "sony.com", "sopcast.com", "sopcast.org", "sophos.com", "sorazone.net", "sorting-algorithms.com", "sos.org", "sosad.fun", "sosreader.com", "sostibet.org", "sotwe.com", "sou-tong.org", "soubory.com", "soul-plus.net", "soulcaliburhentai.net", "soundcloud.com", "soundofhope.kr", "soundofhope.org", "soundon.fm", "soup.io", "soupofmedia.com", "sourceforge.net", "sourcewadio.com", "south-plus.net", "south-plus.org", "southmongolia.org", "southnews.com.tw", "sowers.org.hk", "sowiki.net", "soylent.com", "soylentnews.org", "spankbang.com", "spankingtube.com", "spankwire.com", "sparkpool.com", "spatial.io", "spb.com", "speakerdeck.com", "speedcat.me", "speedify.com", "spem.at", "spencertipping.com", "spendee.com", "spicevpn.com", "spideroak.com", "spiderpool.com", "spiegel.de", "spike.com", "spotflux.com", "spotify.com", "spreadsheet.new", "spreadshirt.es", "spreaker.com", "spring-plus.net", "spring4u.info", "springboardplatform.com", "springwood.me", "sprite.org", "sproutcore.com", "sproxy.info", "squirly.info", "squirrelvpn.com", "srocket.us", "ss-link.com", "ssglobal.co", "ssglobal.me", "ssh91.com", "ssl443.org", "sspanel.net", "sspanel.org", "sspro.ml", "ssr.tools", "ssrshare.com", "ssrshare.us", "ssrtool.com", "sss.camp", "sstm.moe", "sstmlt.moe", "sstmlt.net", "stackcommerce.net", "stackoverflow.com", "stacksocial.com", "stage64.hk", "standard.co.uk", "standupfortibet.org", "standwithhk.org", "stanford.edu", "starfishfx.com", "starp2p.com", "startpage.com", "startuplivingchina.com", "stat.gov.tw", "state.gov", "statearmor.org", "static-economist.com", "statically.io", "staticflickr.com", "statsig.com", "statueofdemocracy.org", "stboy.net", "stc.com.sa", "steamcommunity.com", "steampowered.com", "steamstatic.com", "steel-storm.com", "steemit.com", "steganos.com", "steganos.net", "stepchina.com", "stephaniered.com", "stgloballink.com", "stheadline.com", "sthoo.com", "stickam.com", "stickeraction.com", "stileproject.com", "stitcher.com", "sto.cc", "stoporganharvesting.org", "stoptibetcrisis.net", "storagenewsletter.com", "storify.com", "storj.io", "storm.mg", "stormmediagroup.com", "storry.tv", "stoweboyd.com", "straitstimes.com", "stranabg.com", "straplessdildo.com", "streamable.com", "streamate.com", "streamguys1.com", "streamingthe.net", "streema.com", "streetvoice.com", "striek.com", "strikingly.com", "strongvpn.com", "strongwindpress.com", "student.tw", "studentsforafreetibet.org", "studybuddhism.com", "stumbleupon.com", "stupidvideos.com", "stweetly.com", "subhd.tv", "substack.com", "successfn.com", "sueddeutsche.de", "sugarsync.com", "sugobbs.com", "sugumiru18.com", "suissl.com", "summify.com", "sumrando.com", "sun1911.com", "sundayguardianlive.com", "sunmedia.ca", "suno.ai", "suno.com", "sunporno.com", "sunskyforum.com", "sunta.com.tw", "sunvpn.net", "suoluo.org", "supchina.com", "superfreevpn.com", "superpages.com", "supervpn.net", "superzooi.com", "supjav.com", "suppig.net", "suprememastertv.com", "surfeasy.com", "surfeasy.com.au", "surfshark.com", "suroot.com", "surrenderat20.net", "suyangg.com", "svsfx.com", "swagbucks.com", "swapspace.co", "swbusdev.com", "swissinfo.ch", "swissvpn.net", "switch1.jp", "switchvpn.net", "sydneytoday.com", "sylfoundation.org", "synapse.org", "syncback.com", "synergyse.com", "syosetu.com", "sysresccd.org", "sytes.net", "syx86.com", "szbbs.net", "szetowah.org.hk", "t-g.com", "t.co", "t.me", "t35.com", "t35hosting.com", "t66y.com", "t91y.com", "taa-usa.org", "taaze.tw", "tablesgenerator.com", "tabtter.jp", "tacem.org", "taconet.com.tw", "taedp.org.tw", "tafm.org", "tagesschau.de", "tagwa.org.au", "tagwalk.com", "tahr.org.tw", "taipei.gov.tw", "taipeisociety.org", "taipeitimes.com", "taisounds.com", "taiwan-sex.com", "taiwanbible.com", "taiwancon.com", "taiwandaily.net", "taiwandc.org", "taiwanhot.net", "taiwanjobs.gov.tw", "taiwanjustice.com", "taiwanjustice.net", "taiwankiss.com", "taiwannation.com", "taiwannation.com.tw", "taiwanncf.org.tw", "taiwannews.com.tw", "taiwanonline.cc", "taiwantp.net", "taiwantt.org.tw", "taiwanus.net", "taiwanyes.com", "talk853.com", "talkatone.com", "talkboxapp.com", "talkcc.com", "talkonly.net", "tamiaode.tk", "tampabay.com", "tanc.org", "tangben.com", "tangren.us", "tanks.gg", "taoism.net", "taolun.info", "tapanwap.com", "tapatalk.com", "taragana.com", "tardigrade.io", "target.com", "tascn.com.au", "taup.net", "taup.org.tw", "taweet.com", "tbcollege.org", "tbi.org.hk", "tbjyt.org", "tbrc.org", "tbs-rainbow.org", "tbs.co.jp", "tbsec.org", "tbsn.org", "tbsseattle.org", "tbssqh.org", "tbswd.org", "tbtemple.org.uk", "tbthouston.org", "tccwonline.org", "tcewf.org", "tchrd.org", "tcnynj.org", "tcpspeed.co", "tcpspeed.com", "tcsofbc.org", "tcsovi.org", "tdesktop.com", "tdm.com.mo", "teachparentstech.org", "teamamericany.com", "techcrunch.com", "technews.tw", "techspot.com", "techviz.net", "teck.in", "teco-hk.org", "teco-mo.org", "teddysun.com", "teenfucks.org", "teeniefuck.net", "teensinasia.com", "teepr.com", "tehrantimes.com", "telecomspace.com", "telega.one", "telegra.ph", "telegram.dog", "telegram.me", "telegram.org", "telegram.space", "telegramdownload.com", "telegraph.co.uk", "telesco.pe", "tellapart.com", "tellme.pw", "tenacy.com", "tenor.com", "tensorflow.org", "tenzinpalmo.com", "terabox.com", "tew.org", "textnow.com", "textnow.me", "tfc-taiwan.org.tw", "tfhub.dev", "tfiflve.com", "tg-me.com", "tg.dev", "tgstat.com", "thaicn.com", "thb.gov.tw", "theage.com.au", "theatlantic.com", "theatrum-belli.com", "theaustralian.com.au", "theb.ai", "thebcomplex.com", "theblaze.com", "theblemish.com", "thebobs.com", "thebodyshop-usa.com", "thechasernews.co.uk", "thechinabeat.org", "thechinacollection.org", "thechinaproject.com", "thechinastory.org", "theconversation.com", "thedalailamamovie.com", "thediplomat.com", "thedw.us", "theepochtimes.com", "thefacebook.com", "thegay.com", "thegioitinhoc.vn", "thegly.com", "theguardian.com", "thehansindia.com", "thehindu.com", "thehots.info", "thehousenews.com", "thehun.net", "theinitium.com", "thenewslens.com", "thepiratebay.ee", "thepiratebay.org", "theporndude.com", "theportalwiki.com", "theprint.in", "therock.net.nz", "thesaturdaypaper.com.au", "thespeeder.com", "thestandard.com.hk", "thestandnews.com", "thestar.com", "thetatoken.org", "thetibetcenter.org", "thetibetconnection.org", "thetibetmuseum.org", "thetibetpost.com", "thetrotskymovie.com", "thetvdb.com", "thewgo.org", "thewirechina.com", "theync.com", "thinkgeek.com", "thinkhk.com", "thinkingtaiwan.com", "thinkwithgoogle.com", "thirdmill.org", "thisav.com", "thlib.org", "thomasbernhard.org", "thongdreams.com", "threadreaderapp.com", "threads.com", "threads.net", "threatchaos.com", "throughnightsfire.com", "thu.monster", "thuhole.com", "thumbzilla.com", "thywords.com", "thywords.com.tw", "tiananmenduizhi.com", "tiananmenmother.org", "tiananmenuniv.com", "tiananmenuniv.net", "tiandixing.org", "tianhuayuan.com", "tianlawoffice.com", "tianti.io", "tiantibooks.org", "tianyantong.org.cn", "tianzhu.org", "tibet-envoy.eu", "tibet-foundation.org", "tibet-house-trust.co.uk", "tibet-info.net", "tibet-initiative.de", "tibet-munich.de", "tibet.a.se", "tibet.at", "tibet.ca", "tibet.com", "tibet.fr", "tibet.net", "tibet.nu", "tibet.org", "tibet.org.tw", "tibet.sk", "tibet.to", "tibet3rdpole.org", "tibetaction.net", "tibetaid.org", "tibetalk.com", "tibetan-alliance.org", "tibetan.fr", "tibetanaidproject.org", "tibetanarts.org", "tibetanbuddhistinstitute.org", "tibetancommunity.org", "tibetancommunityuk.net", "tibetanculture.org", "tibetanentrepreneurs.org", "tibetanfeministcollective.org", "tibetanhealth.org", "tibetanjournal.com", "tibetanlanguage.org", "tibetanliberation.org", "tibetanpaintings.com", "tibetanphotoproject.com", "tibetanpoliticalreview.org", "tibetanreview.net", "tibetansports.org", "tibetanwomen.org", "tibetanyouth.org", "tibetanyouthcongress.org", "tibetcharity.dk", "tibetcharity.in", "tibetchild.org", "tibetcity.com", "tibetcollection.com", "tibetcorps.org", "tibetexpress.net", "tibetfocus.com", "tibetfund.org", "tibetgermany.com", "tibetgermany.de", "tibethaus.com", "tibetheritagefund.org", "tibethouse.jp", "tibethouse.org", "tibethouse.us", "tibetinfonet.net", "tibetjustice.org", "tibetkomite.dk", "tibetmuseum.org", "tibetnetwork.org", "tibetoffice.ch", "tibetoffice.com.au", "tibetoffice.eu", "tibetoffice.org", "tibetonline.com", "tibetonline.tv", "tibetoralhistory.org", "tibetpolicy.eu", "tibetrelieffund.co.uk", "tibetsites.com", "tibetsociety.com", "tibetsun.com", "tibetsupportgroup.org", "tibetswiss.ch", "tibettelegraph.com", "tibettimes.net", "tibettruth.com", "tibetwrites.org", "ticket.com.tw", "tigervpn.com", "tiktok.com", "tiktokcdn-eu.com", "tiktokcdn-us.com", "tiktokcdn.com", "tiktokv.com", "tiktokv.us", "tiktokw.us", "tiltbrush.com", "timdir.com", "time.com", "timesnownews.com", "timsah.com", "timtales.com", "tinc-vpn.org", "tiney.com", "tineye.com", "tingtalk.me", "tiny.cc", "tinychat.com", "tinypaste.com", "tinyurl.com", "tipas.net", "tipo.gov.tw", "tistory.com", "tkcs-collins.com", "tl.gd", "tma.co.jp", "tmagazine.com", "tmi.me", "tmpp.org", "tnaflix.com", "tnp.org", "tnt-ea.com", "to-porno.com", "togetter.com", "toh.info", "token.im", "tokenlon.im", "tokyo-247.com", "tokyo-hot.com", "tokyo-porn-tube.com", "tokyocn.com", "tomonews.net", "tomp3.cc", "tongil.or.kr", "tonyyan.net", "toodoc.com", "toonel.net", "top.tv", "top10vpn.com", "top81.ws", "topbtc.com", "topnews.in", "toppornsites.com", "topshareware.com", "topsy.com", "toptip.ca", "toptoon.net", "tora.to", "torcn.com", "torguard.net", "torlock.com", "torproject.org", "torrentgalaxy.to", "torrentkitty.tv", "torrentprivacy.com", "torrentproject.se", "torrenty.org", "torrentz.eu", "tortoisesvn.net", "torvpn.com", "totalvpn.com", "tou.tv", "toutiaoabc.com", "towngain.com", "toypark.in", "toythieves.com", "toytractorshow.com", "tparents.org", "tpi.org.tw", "tracfone.com", "tradingview.com", "traffichaus.com", "translate.goog", "transparency.org", "travelinkcard.com", "treemall.com.tw", "trendsmap.com", "trialofccp.org", "trickip.net", "trickip.org", "trimondi.de", "tron.network", "tronscan.org", "trouw.nl", "trt.net.tr", "trtc.com.tw", "truebuddha-md.org", "trulyergonomic.com", "truthontour.org", "truthsocial.com", "truveo.com", "tryheart.jp", "tsctv.net", "tsemtulku.com", "tsquare.tv", "tsu.org.tw", "tsunagarumon.com", "tt1069.com", "tttan.com", "ttv.com.tw", "ttvnw.net", "ttwstatic.com", "tu8964.com", "tubaholic.com", "tube.com", "tube8.com", "tube911.com", "tubecup.com", "tubegals.com", "tubeislam.com", "tubepornclassic.com", "tubestack.com", "tubewolf.com", "tuibeitu.net", "tuidang.net", "tuidang.org", "tuidang.se", "tuitwit.com", "tukaani.org", "tumblr.com", "tumutanzi.com", "tumview.com", "tunein.com", "tunnelbear.com", "tunnelblick.net", "tunnelr.com", "tunsafe.com", "tuo8.blue", "tuo8.cc", "tuo8.club", "tuo8.fit", "tuo8.hk", "tuo8.in", "tuo8.ninja", "tuo8.org", "tuo8.pw", "tuo8.red", "tuo8.space", "turansam.org", "turbobit.net", "turbohide.com", "turbotwitter.com", "turkistantimes.com", "turntable.fm", "tushycash.com", "tuvpn.com", "tuzaijidi.com", "tv.com", "tvants.com", "tvb.com", "tvboxnow.com", "tvbs.com.tw", "tvider.com", "tvmost.com.hk", "tvplayvideos.com", "tvunetworks.com", "tw-blog.com", "tw-npo.org", "tw01.org", "twaitter.com", "twapperkeeper.com", "twaud.io", "twavi.com", "twbbs.net.tw", "twbbs.org", "twbbs.tw", "twblogger.com", "twcomix.com", "tweepguide.com", "tweeplike.me", "tweepmag.com", "tweepml.org", "tweetbackup.com", "tweetboard.com", "tweetboner.biz", "tweetcs.com", "tweetdeck.com", "tweetedtimes.com", "tweetmylast.fm", "tweetphoto.com", "tweetrans.com", "tweetree.com", "tweettunnel.com", "tweetwally.com", "tweetymail.com", "tweez.net", "twelve.today", "twerkingbutt.com", "twftp.org", "twgreatdaily.com", "twibase.com", "twibble.de", "twibbon.com", "twibs.com", "twicountry.org", "twicsy.com", "twiends.com", "twifan.com", "twiffo.com", "twiggit.org", "twilightsex.com", "twilio.com", "twilog.org", "twimbow.com", "twimg.com", "twindexx.com", "twip.me", "twipple.jp", "twishort.com", "twistar.cc", "twister.net.co", "twisterio.com", "twisternow.com", "twistory.net", "twit2d.com", "twitch.tv", "twitchcdn.net", "twitgoo.com", "twitiq.com", "twitlonger.com", "twitmania.com", "twitoaster.com", "twitonmsn.com", "twitpic.com", "twitstat.com", "twittbot.net", "twitter.com", "twitter.jp", "twitter4j.org", "twittercounter.com", "twitterfeed.com", "twittergadget.com", "twitterkr.com", "twittermail.com", "twitterrific.com", "twittertim.es", "twitthat.com", "twitturk.com", "twitturly.com", "twitvid.com", "twitzap.com", "twiyia.com", "twkan.com", "twnorth.org.tw", "twreporter.org", "twskype.com", "twstar.net", "twt.tl", "twtkr.com", "twtrland.com", "twttr.com", "twurl.nl", "twyac.org", "tx.me", "txxx.com", "tycool.com", "typekit.net", "typepad.com", "typeset.io", "typora.io", "u15.info", "u9un.com", "ua5v.com", "ub0.cc", "ubddns.org", "uberproxy.net", "uc-japan.org", "ucam.org", "ucanews.com", "ucdc1998.org", "uchicago.edu", "uderzo.it", "udn.com", "udn.com.tw", "udnbkk.com", "udndata.com", "udomain.hk", "uforadio.com.tw", "ufreevpn.com", "ugo.com", "uhdwallpapers.org", "uhrp.org", "uighur.nl", "uighurbiz.net", "uk.to", "ukcdp.co.uk", "ukliferadio.co.uk", "uku.im", "ulifestyle.com.hk", "ulike.net", "ulop.net", "ultrasurf.us", "ultravpn.com", "ultravpn.fr", "ultraxs.com", "umich.edu", "unblock-us.com", "unblock.cn.com", "unblockdmm.com", "unblocker.yt", "unblocksit.es", "uncyclomedia.org", "uncyclopedia.hk", "uncyclopedia.tw", "underwoodammo.com", "unholyknight.com", "uni.cc", "unicode.org", "unification.net", "unification.org.tw", "unirule.cloud", "unitedsocialpress.com", "unix100.com", "unknownspace.org", "unlock-music.dev", "unmineable.com", "unodedos.com", "unpo.org", "unseen.is", "unstable.icu", "untraceable.us", "unwire.hk", "uocn.org", "upbit.com", "updatestar.com", "upghsbc.com", "upholdjustice.org", "upload4u.info", "uploaded.net", "uploaded.to", "uploadstation.com", "upmedia.mg", "upornia.com", "uproxy.org", "uptodown.com", "upwill.org", "ur7s.com", "uraban.me", "urbandictionary.com", "urbansurvival.com", "urchin.com", "url.com.tw", "urlborg.com", "urlparser.com", "us.to", "usacn.com", "usaip.eu", "usc.edu", "uscardforum.com", "uscg.mil", "uscnpm.org", "usefreevpn.com", "usembassy.gov", "usercontent.goog", "usfk.mil", "usma.edu", "usmc.mil", "usocctn.com", "uspto.gov", "ustibetcommittee.org", "ustream.tv", "usunitednews.com", "usus.cc", "utopianpal.com", "uu-gg.com", "uujiasu.com", "uukanshu.com", "uupool.cn", "uvwxyz.xyz", "uwants.com", "uwants.net", "uyghur-archive.com", "uyghur-j.org", "uyghur.co.uk", "uyghuraa.org", "uyghuramerican.org", "uyghurbiz.org", "uyghurcanadiansociety.org", "uyghurcongress.org", "uyghurensemble.co.uk", "uyghurpen.org", "uyghurpress.com", "uyghurstudies.org", "uyghurtribunal.com", "uygur.org", "uymaarip.com", "v2.help", "v2board.com", "v2ex.com", "v2fly.org", "v2mm.tech", "v2ray.com", "v2raya.org", "v2raycn.com", "valeursactuelles.com", "van001.com", "van698.com", "vanemu.cn", "vanilla-jp.com", "vanpeople.com", "vansky.com", "vaticannews.va", "vatn.org", "vcf-online.org", "vcfbuilder.org", "vegasred.com", "velkaepocha.sk", "venbbs.com", "venchina.com", "venetianmacao.com", "ventureswell.com", "veoh.com", "vercel.app", "vercel.com", "verizon.net", "vermonttibet.org", "vern.cc", "versavpn.com", "verybs.com", "vevo.com", "vewas.net", "vft.com.tw", "viber.com", "vica.info", "victimsofcommunism.org", "vid.me", "vidble.com", "videobam.com", "videodetective.com", "videomega.tv", "videomo.com", "videopediaworld.com", "videopress.com", "vidinfo.org", "vietdaikynguyen.com", "vijayatemple.org", "vilanet.me", "vilavpn.com", "vimeo.com", "vimeocdn.com", "vimperator.org", "vincnd.com", "vine.co", "vinniev.com", "vip-enterprise.com", "virginia.edu", "virtualrealporn.com", "visibletweets.com", "visualstudio.com", "vital247.org", "viu.com", "viu.tv", "vivahentai4u.net", "vivaldi.com", "vivatube.com", "vivthomas.com", "vizvaz.com", "vjav.com", "vjmedia.com.hk", "vllcs.org", "vmixcore.com", "vmpsoft.com", "vnet.link", "voa.mobi", "voacambodia.com", "voacantonese.com", "voachinese.com", "voachineseblog.com", "voagd.com", "voaindonesia.com", "voanews.com", "voatibetan.com", "voatibetanenglish.com", "vocaroo.com", "vocativ.com", "vocn.tv", "vocus.cc", "voi.id", "voicettank.org", "vot.org", "vovo2000.com", "vox.com", "voxer.com", "voy.com", "vpn.ac", "vpn.net", "vpn4all.com", "vpnaccount.org", "vpnaccounts.com", "vpnbook.com", "vpncomparison.org", "vpncoupons.com", "vpncup.com", "vpndada.com", "vpnfan.com", "vpnfire.com", "vpnfires.biz", "vpnforgame.net", "vpngate.jp", "vpngate.net", "vpngratis.net", "vpnhq.com", "vpnhub.com", "vpninja.net", "vpnintouch.com", "vpnintouch.net", "vpnjack.com", "vpnmaster.com", "vpnmentor.com", "vpnpick.com", "vpnpop.com", "vpnpronet.com", "vpnproxymaster.com", "vpnreactor.com", "vpnreviewz.com", "vpnsecure.me", "vpnshazam.com", "vpnshieldapp.com", "vpnsp.com", "vpntraffic.com", "vpntunnel.com", "vpnuk.info", "vpnunlimitedapp.com", "vpnvip.com", "vpnworldwide.com", "vporn.com", "vpser.net", "vpsxb.net", "vraiesagesse.net", "vrchat.com", "vrmtr.com", "vrporn.com", "vrsmash.com", "vtunnel.com", "vuku.cc", "vultryhw.com", "vyprvpn.com", "vzw.com", "w-pool.com", "w.wiki", "w3.org", "w3s.link", "waffle1999.com", "wahas.com", "waigaobu.com", "waikeung.org", "wailaike.net", "wainao.me", "waiwaier.com", "walletconnect.com", "wallhaven.cc", "wallmama.com", "wallornot.org", "wallpapercasa.com", "wallproxy.com", "wallsttv.com", "waltermartin.com", "waltermartin.org", "wan-press.org", "wanderinghorse.net", "wangafu.net", "wangjinbo.org", "wanglixiong.com", "wango.org", "wangruoshui.net", "wangruowang.org", "want-daily.com", "wanz-factory.com", "wapedia.mobi", "warroom.org", "waselpro.com", "washeng.net", "washingtonpost.com", "watch8x.com", "watchinese.com", "watchmygf.net", "watchout.tw", "wattpad.com", "wav.tv", "waveprotocol.org", "waybig.com", "waymo.com", "wd.bible", "wda.gov.tw", "wdf5.com", "wealth.com.tw", "wearehairy.com", "wearn.com", "weather.com.hk", "web.dev", "web2project.net", "webbang.net", "webevader.org", "webfreer.com", "webjb.org", "weblagu.com", "webmproject.org", "webpack.de", "webpkgcache.com", "webrtc.org", "webrush.net", "webs-tv.net", "website.new", "websitepulse.com", "websnapr.com", "webwarper.net", "webworkerdaily.com", "wechatlawsuit.com", "weebly.com", "wefightcensorship.org", "wefong.com", "weiboleak.com", "weihuo.org", "weijingsheng.org", "weiming.info", "weiquanwang.org", "weisuo.ws", "weitt.us", "welovecock.com", "welt.de", "wemigrate.org", "wengewang.com", "wengewang.org", "wenhui.ch", "wenxuecity.com", "wenyunchao.com", "wenzhao.ca", "westca.com", "westernshugdensociety.org", "westernwolves.com", "westkit.net", "westpoint.edu", "wetplace.com", "wetpussygames.com", "wexiaobo.org", "wezhiyong.org", "wezone.net", "wforum.com", "whatblocked.com", "whatbrowser.org", "whats.new", "whatsapp.com", "whatsapp.net", "whatsonweibo.com", "wheatseeds.org", "wheelockslatin.com", "whereiswerner.com", "wheretowatch.com", "whichav.com", "whichav.video", "whippedass.com", "whispersystems.org", "who.is", "whodns.xyz", "whoer.net", "whotalking.com", "whylover.com", "whyx.org", "widevine.com", "wikaba.com", "wikia.com", "wikia.org", "wikibooks.org", "wikidata.org", "wikileaks-forum.com", "wikileaks.ch", "wikileaks.com", "wikileaks.de", "wikileaks.eu", "wikileaks.lu", "wikileaks.org", "wikileaks.pl", "wikilivres.info", "wikimapia.org", "wikimedia.org", "wikinews.org", "wikipedia-on-ipfs.org", "wikipedia.org", "wikiquote.org", "wikisource.org", "wikiversity.org", "wikivoyage.org", "wikiwand.com", "wiktionary.org", "wildammo.com", "williamhill.com", "willw.net", "wilsoncenter.org", "windscribe.com", "wingamestore.com", "wingy.site", "winning11.com", "winwhispers.info", "wionews.com", "wire.com", "wiredbytes.com", "wiredpen.com", "wireguard.com", "wisdompubs.org", "wisevid.com", "wistia.com", "withgoogle.com", "withyoutube.com", "witnessleeteaching.com", "witopia.net", "wixsite.com", "wizcrafts.net", "wjbk.org", "wmflabs.org", "wmfusercontent.org", "wn.com", "wnacg.com", "wnacg.org", "wo.tc", "woeser.com", "wokar.org", "wolfax.com", "wombo.ai", "woolyss.com", "woopie.jp", "woopie.tv", "wordpress.com", "work2icu.org", "workatruna.com", "workerdemo.org.hk", "workerempowerment.org", "workers.dev", "workersthebig.net", "worldcat.org", "worldjournal.com", "worldpopulationreview.com", "worldvpn.net", "wow-life.net", "wow.com", "wowgirls.com", "wowhead.com", "wowlegacy.ml", "wowporn.com", "wowrk.com", "woxinghuiguo.com", "woyaolian.org", "wozy.in", "wp.com", "wpoforum.com", "wqyd.org", "wrchina.org", "wretch.cc", "writesonic.com", "wsimg.com", "wsj.com", "wsj.net", "wsjhk.com", "wtbn.org", "wtfpeople.com", "wuerkaixi.com", "wufafangwen.com", "wufi.org.tw", "wuguoguang.com", "wujie.net", "wujieliulan.com", "wukangrui.net", "wunderground.com", "wuw.red", "wuyanblog.com", "wwe.com", "wwitv.com", "www1.biz", "wwwhost.biz", "wxw.cat", "wxw.moe", "wzyboy.im", "x-art.com", "x-berry.com", "x-wall.org", "x.ai", "x.co", "x.com", "x.company", "x1949x.com", "x24hr.com", "x365x.com", "x3guide.com", "xaislam.com", "xanga.com", "xbabe.com", "xbookcn.com", "xbtce.com", "xcafe.in", "xchina.co", "xcity.jp", "xcritic.com", "xerotica.com", "xfiles.to", "xfinity.com", "xfxssr.me", "xgmyd.com", "xhamster.com", "xhcdn.com", "xianba.net", "xianchawang.net", "xianjian.tw", "xianqiao.net", "xiaobaiwu.com", "xiaochuncnjp.com", "xiaod.in", "xiaohexie.com", "xiaolan.me", "xiaoma.org", "xiaomi.eu", "xiaxiaoqiang.net", "xicons.org", "xiezhua.com", "xihua.es", "xinbao.de", "xing.com", "xinhuanet.org", "xinjiangpolicefiles.org", "xinmiao.com.hk", "xinsheng.net", "xinshijue.com", "xinyubbs.net", "xiongpian.com", "xiuren.org", "xizang-zhiye.org", "xjp.cc", "xjtravelguide.com", "xkiwi.tk", "xlfmtalk.com", "xlfmwz.info", "xm.com", "xml-training-guide.com", "xmonk.net", "xmovies.com", "xn--11xs86f.icu", "xn--4gq171p.com", "xn--90wwvt03e.com", "xn--9pr62r24a.com", "xn--czq75pvv1aj5c.org", "xn--i2ru8q2qg.com", "xn--ngstr-lra8j.com", "xn--noss43i.com", "xn--oiq.cc", "xnpool.com", "xnxx-cdn.com", "xnxx.com", "xpdo.net", "xpud.org", "xrentdvd.com", "xsden.info", "xsden.org", "xskywalker.com", "xt.com", "xt.pub", "xtube.com", "xuchao.net", "xuchao.org", "xuehua.us", "xuite.net", "xuzhiyong.net", "xvbelink.com", "xvideo.cc", "xvideos-cdn.com", "xvideos.com", "xvideos.es", "xvideos2.com", "xvinlink.com", "xxbbx.com", "xxlmovies.com", "xxuz.com", "xxx.com", "xxx.xxx", "xxxfuckmom.com", "xxxx.com.au", "xxxy.info", "xxxymovies.com", "xys.org", "xysblogs.org", "xyy69.com", "xyy69.info", "y2mate.com", "yadi.sk", "yahoo.co.jp", "yahoo.com", "yahoo.com.hk", "yahoo.com.tw", "yahoo.net", "yahooinc.com", "yahoosandbox.com", "yakbutterblues.com", "yam.com", "yam.org.tw", "yande.re", "yandex.com", "yandex.net", "yandex.ru", "yanghengjun.com", "yangjianli.com", "yangzhi.org", "yasni.co.uk", "yasukuni.or.jp", "yayabay.com", "ycombinator.com", "ydy.com", "yeahteentube.com", "yecl.net", "yeelou.com", "yeeyi.com", "yegle.net", "yes-news.com", "yes.xxx", "yes123.com.tw", "yesasia.com", "yesasia.com.hk", "yespornplease.com", "yeyeclub.com", "ygto.com", "yhcw.net", "yibada.com", "yibaochina.com", "yidio.com", "yigeni.com", "yilubbs.com", "yimg.com", "yingsuoss.com", "yinlei.org", "yipub.com", "yizhihongxing.com", "ylive.jp", "yobit.net", "yobt.com", "yobt.tv", "yodobashi.com", "yogichen.org", "yolasite.com", "yomiuri.co.jp", "yong.hu", "yorkbbs.ca", "you-get.org", "you.com", "youdontcare.com", "youjizz.com", "youlucky.com", "youmaker.com", "youngpornvideos.com", "youngspiration.hk", "youpai.org", "youporn.com", "youporngay.com", "your-freedom.net", "yourepeat.com", "yourlisten.com", "yourlust.com", "yourprivatevpn.com", "yourtrap.com", "yousendit.com", "youshun12.com", "youthnetradio.org", "youthwant.com.tw", "youtu.be", "youtube-nocookie.com", "youtube.com", "youtubecn.com", "youtubeeducation.com", "youtubegaming.com", "youtubekids.com", "youversion.com", "youwin.com", "youxu.info", "yt.be", "ytht.net", "ytimg.com", "ytn.co.kr", "yts.ag", "yts.am", "yts.lt", "yts.mx", "yuanming.net", "yuanzhengtang.org", "yulghun.com", "yunchao.net", "yunomi.tokyo", "yuntipub.com", "yuvutu.com", "yvesgeleyn.com", "ywpw.com", "yx51.net", "yyii.org", "yyjlymb.xyz", "yysub.net", "yzzk.com", "z-lib.fm", "z-lib.fo", "z-lib.gd", "z-lib.gl", "z-lib.io", "z-lib.org", "z-library.sk", "zacebook.com", "zalmos.com", "zamimg.com", "zannel.com", "zaobao.com.sg", "zaozon.com", "zapto.org", "zattoo.com", "zb.com", "zdnet.com.tw", "zello.com", "zengjinyan.org", "zenmate.com", "zenmate.com.ru", "zergpool.com", "zerohedge.com", "zeronet.io", "zeutch.com", "zfreet.com", "zgsddh.com", "zgzcjj.net", "zhanbin.net", "zhangboli.net", "zhangtianliang.com", "zhanlve.org", "zhenghui.org", "zhengjian.org", "zhengwunet.org", "zhenlibu.info", "zhenlibu1984.com", "zhenxiang.biz", "zhinengluyou.com", "zhizhu.top", "zhongguo.ca", "zhongguorenquan.org", "zhongguotese.net", "zhongzidi.com", "zhoushuguang.com", "zhreader.com", "zhuanxing.cn", "zhuatieba.com", "zhuichaguoji.org", "zhujiget.com", "zi.media", "zi5.me", "ziddu.com", "zillionk.com", "zim.vn", "zinio.com", "ziporn.com", "zippyshare.com", "zkaip.com", "zkiz.com", "zmedia.com.tw", "zmw.cn", "zodgame.us", "zodgame.xyz", "zoho.com", "zomobo.net", "zonaeuropa.com", "zonghexinwen.com", "zonghexinwen.net", "zoogvpn.com", "zoominfo.com", "zooqle.com", "zootool.com", "zoozle.net", "zophar.net", "zorrovpn.com", "zozotown.com", "zpn.im", "zsdxzk.com", "zspeeder.me", "zsrhao.com", "zuo.la", "zuobiao.me", "zuola.com", "zvereff.com", "zynamics.com", "zyns.com", "zyxel.com", "zzcartoon.com", "zzcloud.me", "zzux.com" ] ], [ [], [] ] ]; var lastRule = ''; function FindProxyForURL(url, host) { for (var i = 0; i < rules.length; i++) { ret = testHost(host, i); if (ret != undefined) return ret; } return 'DIRECT'; } function testHost(host, index) { for (var i = 0; i < rules[index].length; i++) { for (var j = 0; j < rules[index][i].length; j++) { lastRule = rules[index][i][j] if (host == lastRule || host.endsWith('.' + lastRule)) return i % 2 == 0 ? 'DIRECT' : proxy; } } lastRule = ''; } // REF: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith if (!String.prototype.endsWith) { String.prototype.endsWith = function(searchString, position) { var subjectString = this.toString(); if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; } position -= searchString.length; var lastIndex = subjectString.indexOf(searchString, position); return lastIndex !== -1 && lastIndex === position; }; } ================================================ FILE: v2rayN/ServiceLib/Sample/proxy_set_linux_sh ================================================ #!/bin/bash # Function to set proxy for GNOME set_gnome_proxy() { local MODE=$1 local PROXY_IP=$2 local PROXY_PORT=$3 local IGNORE_HOSTS=$4 # Set the proxy mode gsettings set org.gnome.system.proxy mode "$MODE" if [ "$MODE" == "manual" ]; then # List of protocols local PROTOCOLS=("http" "https" "ftp" "socks") # Loop through protocols to set the proxy for PROTOCOL in "${PROTOCOLS[@]}"; do gsettings set org.gnome.system.proxy.$PROTOCOL host "$PROXY_IP" gsettings set org.gnome.system.proxy.$PROTOCOL port "$PROXY_PORT" done # Set ignored hosts gsettings set org.gnome.system.proxy ignore-hosts "['$IGNORE_HOSTS']" echo "GNOME: Manual proxy settings applied." echo "Proxy IP: $PROXY_IP" echo "Proxy Port: $PROXY_PORT" echo "Ignored Hosts: $IGNORE_HOSTS" elif [ "$MODE" == "none" ]; then echo "GNOME: Proxy disabled." fi } # Function to set proxy for KDE set_kde_proxy() { local MODE=$1 local PROXY_IP=$2 local PROXY_PORT=$3 local IGNORE_HOSTS=$4 # Determine the correct kwriteconfig command based on KDE_SESSION_VERSION if [ "$KDE_SESSION_VERSION" == "6" ]; then KWRITECONFIG="kwriteconfig6" else KWRITECONFIG="kwriteconfig5" fi # KDE uses kwriteconfig to modify proxy settings if [ "$MODE" == "manual" ]; then # Set proxy for all protocols $KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key ProxyType 1 $KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key httpProxy "http://$PROXY_IP:$PROXY_PORT" $KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key httpsProxy "http://$PROXY_IP:$PROXY_PORT" $KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key ftpProxy "http://$PROXY_IP:$PROXY_PORT" $KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key socksProxy "http://$PROXY_IP:$PROXY_PORT" # Set ignored hosts $KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key NoProxyFor "$IGNORE_HOSTS" echo "KDE: Manual proxy settings applied." echo "Proxy IP: $PROXY_IP" echo "Proxy Port: $PROXY_PORT" echo "Ignored Hosts: $IGNORE_HOSTS" elif [ "$MODE" == "none" ]; then # Disable proxy $KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key ProxyType 0 echo "KDE: Proxy disabled." fi # Apply changes by restarting KDE's network settings dbus-send --type=signal /KIO/Scheduler org.kde.KIO.Scheduler.reparseSlaveConfiguration string:"" } # Detect the current desktop environment detect_desktop_environment() { if [[ "$XDG_CURRENT_DESKTOP" == *"GNOME"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"GNOME"* ]]; then echo "gnome" return fi if [[ "$XDG_CURRENT_DESKTOP" == *"XFCE"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"XFCE"* ]]; then echo "gnome" return fi if [[ "$XDG_CURRENT_DESKTOP" == *"X-Cinnamon"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"cinnamon"* ]]; then echo "gnome" return fi if [[ "$XDG_CURRENT_DESKTOP" == *"UKUI"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"ukui"* ]]; then echo "gnome" return fi if [[ "$XDG_CURRENT_DESKTOP" == *"DDE"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"dde"* ]]; then echo "gnome" return fi if [[ "$XDG_CURRENT_DESKTOP" == *"MATE"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"mate"* ]]; then echo "gnome" return fi local KDE_ENVIRONMENTS=("KDE" "plasma") for ENV in "${KDE_ENVIRONMENTS[@]}"; do if [ "$XDG_CURRENT_DESKTOP" == "$ENV" ] || [ "$XDG_SESSION_DESKTOP" == "$ENV" ]; then echo "kde" return fi done # Fallback to GNOME method if CLI utility is available. This solves the # proxy configuration issues on minimal installation systems, like setups # with only window managers, that borrow some parts from big DEs. if command -v gsettings >/dev/null 2>&1; then echo "gnome" return fi echo "unsupported" } # Main script logic if [ "$#" -lt 1 ]; then echo "Usage: $0 [proxy_ip proxy_port ignore_hosts]" echo " mode: 'none' or 'manual'" echo " If mode is 'manual', provide proxy IP, port, and ignore hosts." exit 1 fi # Get the mode MODE=$1 PROXY_IP=$2 PROXY_PORT=$3 IGNORE_HOSTS=$4 if ! [[ "$MODE" =~ ^(manual|none)$ ]]; then echo "Invalid mode. Use 'none' or 'manual'." >&2 exit 1 fi # Detect desktop environment DE=$(detect_desktop_environment) # Apply settings based on the desktop environment if [ "$DE" == "gnome" ]; then set_gnome_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS" elif [ "$DE" == "kde" ]; then set_gnome_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS" set_kde_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS" else echo "Unsupported desktop environment: $DE" >&2 exit 1 fi ================================================ FILE: v2rayN/ServiceLib/Sample/proxy_set_osx_sh ================================================ #!/bin/bash # Function to set proxy set_proxy() { PROXY_IP=$1 PROXY_PORT=$2 shift 2 BYPASS_DOMAINS=("$@") # If no bypass domains are provided, set it to empty by default if [ ${#BYPASS_DOMAINS[@]} -eq 0 ]; then BYPASS_DOMAINS=("") fi # Get all network service names SERVICES=$(networksetup -listallnetworkservices | grep -v '*') # Loop through each network service echo "$SERVICES" | while read -r SERVICE; do echo "Setting proxy for network service '$SERVICE'..." # Set HTTP proxy networksetup -setwebproxy "$SERVICE" "$PROXY_IP" "$PROXY_PORT" # Set HTTPS proxy networksetup -setsecurewebproxy "$SERVICE" "$PROXY_IP" "$PROXY_PORT" # Set SOCKS proxy networksetup -setsocksfirewallproxy "$SERVICE" "$PROXY_IP" "$PROXY_PORT" # Set bypass domains networksetup -setproxybypassdomains "$SERVICE" "${BYPASS_DOMAINS[@]}" echo "Proxy for network service '$SERVICE' has been set to $PROXY_IP:$PROXY_PORT" done echo "Proxy settings for all network services are complete!" } # Function to disable proxy clear_proxy() { # Get all network service names SERVICES=$(networksetup -listallnetworkservices | grep -v '*') # Loop through each network service echo "$SERVICES" | while read -r SERVICE; do echo "Disabling proxy and clearing bypass domains for network service '$SERVICE'..." # Disable HTTP proxy networksetup -setwebproxystate "$SERVICE" off # Disable HTTPS proxy networksetup -setsecurewebproxystate "$SERVICE" off # Disable SOCKS proxy networksetup -setsocksfirewallproxystate "$SERVICE" off echo "Proxy for network service '$SERVICE' has been disabled" done echo "Proxy for all network services has been disabled!" } # Main script logic if [ "$1" == "set" ]; then # Check if enough parameters are passed for setting proxy if [ "$#" -lt 3 ]; then echo "Usage: $0 set [Bypass Domain 1 Bypass Domain 2 ...]" exit 1 fi set_proxy "$2" "$3" "${@:4}" elif [ "$1" == "clear" ]; then clear_proxy else echo "Usage:" echo " To set proxy: $0 set [Bypass Domain 1 Bypass Domain 2 ...]" echo " To clear proxy: $0 clear" exit 1 fi ================================================ FILE: v2rayN/ServiceLib/Sample/singbox_fakeip_filter ================================================ { "domain": [ "amobile.music.tc.qq.com", "api-jooxtt.sanook.com", "api.joox.com", "aqqmusic.tc.qq.com", "dl.stream.qqmusic.qq.com", "ff.dorado.sdo.com", "heartbeat.belkin.com", "isure.stream.qqmusic.qq.com", "joox.com", "lens.l.google.com", "localhost.ptlogin2.qq.com", "localhost.sec.qq.com", "mesu.apple.com", "mobileoc.music.tc.qq.com", "music.taihe.com", "musicapi.taihe.com", "na.b.g-tun.com", "proxy.golang.org", "ps.res.netease.com", "shark007.net", "songsearch.kugou.com", "static.adtidy.org", "streamoc.music.tc.qq.com", "swcdn.apple.com", "swdist.apple.com", "swdownload.apple.com", "swquery.apple.com", "swscan.apple.com", "turn.cloudflare.com", "trackercdn.kugou.com", "xnotify.xboxlive.com" ], "domain_keyword": [ "ntp", "stun", "time" ], "domain_regex": [ "^[^.]+$", "^[^.]+\\.[^.]+\\.xboxlive\\.com$", "^localhost\\.[^.]+\\.weixin\\.qq\\.com$", "^mijia\\scloud$", "^xbox\\.[^.]+\\.microsoft\\.com$", "^xbox\\.[^.]+\\.[^.]+\\.microsoft\\.com$" ], "domain_suffix": [ "126.net", "3gppnetwork.org", "battle.net", "battlenet.com.cn", "cdn.nintendo.net", "cmbchina.com", "cmbimg.com", "ff14.sdo.com", "ffxiv.com", "finalfantasyxiv.com", "gcloudcs.com", "home.arpa", "invalid", "kuwo.cn", "lan", "linksys.com", "linksyssmartwifi.com", "local", "localdomain", "localhost", "market.xiaomi.com", "mcdn.bilivideo.cn", "media.dssott.com", "msftconnecttest.com", "msftncsi.com", "music.163.com", "music.migu.cn", "n0808.com", "nflxvideo.net", "oray.com", "orayimg.com", "router.asus.com", "sandai.net", "square-enix.com", "srv.nintendo.net", "steamcontent.com", "uu.163.com", "wargaming.net", "wggames.cn", "wotgame.cn", "wowsgame.cn", "xiami.com", "y.qq.com" ] } ================================================ FILE: v2rayN/ServiceLib/Sample/tun_singbox_dns ================================================ { "servers": [ { "tag": "remote", "type": "tcp", "server": "8.8.8.8", "detour": "proxy" }, { "tag": "local", "type": "udp", "server": "223.5.5.5" } ], "rules": [ { "rule_set": [ "geosite-google" ], "server": "remote", "strategy": "prefer_ipv4" }, { "rule_set": [ "geosite-cn" ], "server": "local", "strategy": "prefer_ipv4" } ], "final": "remote", "strategy": "prefer_ipv4" } ================================================ FILE: v2rayN/ServiceLib/Sample/tun_singbox_inbound ================================================ { "type": "tun", "tag": "tun-in", "interface_name": "singbox_tun", "address": [ "172.18.0.1/30", "fdfe:dcba:9876::1/126" ], "mtu": 9000, "auto_route": true, "strict_route": false, "stack": "system", "sniff": true } ================================================ FILE: v2rayN/ServiceLib/Sample/tun_singbox_rules ================================================ [ { "network": "udp", "port": [ 135, 137, 138, 139, 5353 ], "action": "reject" }, { "ip_cidr": [ "224.0.0.0/3", "ff00::/8" ], "action": "reject" } ] ================================================ FILE: v2rayN/ServiceLib/ServiceLib.csproj ================================================  Library true ResUI.resx True True Designer PublicResXFileCodeGenerator PublicResXFileCodeGenerator PublicResXFileCodeGenerator Designer ResUI.Designer.cs PublicResXFileCodeGenerator Designer PublicResXFileCodeGenerator Designer PublicResXFileCodeGenerator Designer PublicResXFileCodeGenerator ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs ================================================ namespace ServiceLib.Services.CoreConfig; /// /// Core configuration file processing class /// public class CoreConfigClashService { private Config _config; private static readonly string _tag = "CoreConfigClashService"; public CoreConfigClashService(Config config) { _config = config; } public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) { var ret = new RetResult(); if (node == null || fileName is null) { ret.Msg = ResUI.CheckServerSettings; return ret; } ret.Msg = ResUI.InitialConfiguration; try { if (node == null) { ret.Msg = ResUI.CheckServerSettings; return ret; } if (File.Exists(fileName)) { File.Delete(fileName); } var addressFileName = node.Address; if (addressFileName.IsNullOrEmpty()) { ret.Msg = ResUI.FailedGetDefaultConfiguration; return ret; } if (!File.Exists(addressFileName)) { addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName); } if (!File.Exists(addressFileName)) { ret.Msg = ResUI.FailedReadConfiguration + "1"; return ret; } var tagYamlStr1 = "!"; var tagYamlStr2 = "__strn__"; var tagYamlStr3 = "!!str"; var txtFile = File.ReadAllText(addressFileName); txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2); //YAML anchors if (txtFile.Contains("<<:") && txtFile.Contains('*') && txtFile.Contains('&')) { txtFile = YamlUtils.PreprocessYaml(txtFile); } var fileContent = YamlUtils.FromYaml>(txtFile); if (fileContent == null) { ret.Msg = ResUI.FailedConversionConfiguration; return ret; } //mixed-port fileContent["mixed-port"] = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); //log-level fileContent["log-level"] = GetLogLevel(_config.CoreBasicItem.Loglevel); //external-controller fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}"; fileContent.Remove("secret"); //allow-lan if (_config.Inbound.First().AllowLANConn) { fileContent["allow-lan"] = "true"; fileContent["bind-address"] = "*"; } else { fileContent["allow-lan"] = "false"; } //ipv6 fileContent["ipv6"] = _config.ClashUIItem.EnableIPv6; //mode if (!fileContent.ContainsKey("mode")) { fileContent["mode"] = ERuleMode.Rule.ToString().ToLower(); } else { if (_config.ClashUIItem.RuleMode != ERuleMode.Unchanged) { fileContent["mode"] = _config.ClashUIItem.RuleMode.ToString().ToLower(); } } //enable tun mode if (_config.TunModeItem.EnableTun) { var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml); if (tun.IsNotEmpty()) { var tunContent = YamlUtils.FromYaml>(tun); if (tunContent != null) { fileContent["tun"] = tunContent["tun"]; } } } //Mixin try { await MixinContent(fileContent, node); } catch (Exception ex) { Logging.SaveLog($"{_tag}-Mixin", ex); } var txtFileNew = YamlUtils.ToYaml(fileContent).Replace(tagYamlStr2, tagYamlStr3); await File.WriteAllTextAsync(fileName, txtFileNew); //check again if (!File.Exists(fileName)) { ret.Msg = ResUI.FailedReadConfiguration + "2"; return ret; } ClashApiManager.Instance.ProfileContent = fileContent; ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}"); ret.Success = true; return ret; } catch (Exception ex) { Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } } private async Task MixinContent(Dictionary fileContent, ProfileItem node) { if (!_config.ClashUIItem.EnableMixinContent) { return; } var path = Utils.GetConfigPath(Global.ClashMixinConfigFileName); if (!File.Exists(path)) { var mixin = EmbedUtils.GetEmbedText(Global.ClashMixinYaml); await File.AppendAllTextAsync(path, mixin); } var txtFile = await File.ReadAllTextAsync(Utils.GetConfigPath(Global.ClashMixinConfigFileName)); var mixinContent = YamlUtils.FromYaml>(txtFile); if (mixinContent == null) { return; } foreach (var item in mixinContent) { if (!_config.TunModeItem.EnableTun && item.Key == "tun") { continue; } if (item.Key.StartsWith("prepend-") || item.Key.StartsWith("append-") || item.Key.StartsWith("removed-")) { ModifyContentMerge(fileContent, item.Key, item.Value); } else { fileContent[item.Key] = item.Value; } } return; } private void ModifyContentMerge(Dictionary fileContent, string key, object value) { var blPrepend = false; var blRemoved = false; if (key.StartsWith("prepend-")) { blPrepend = true; key = key.Replace("prepend-", ""); } else if (key.StartsWith("append-")) { blPrepend = false; key = key.Replace("append-", ""); } else if (key.StartsWith("removed-")) { blRemoved = true; key = key.Replace("removed-", ""); } else { return; } if (!blRemoved && !fileContent.ContainsKey(key)) { fileContent.Add(key, value); return; } var lstOri = (List)fileContent[key]; var lstValue = (List)value; if (blRemoved) { foreach (var item in lstValue) { lstOri.RemoveAll(t => t.ToString().StartsWith(item.ToString())); } return; } if (blPrepend) { lstValue.Reverse(); lstValue.ForEach(item => lstOri.Insert(0, item)); } else { lstValue.ForEach(item => lstOri.Add(item)); } } private string GetLogLevel(string level) { if (level == "none") { return "silent"; } else { return level; } } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService(CoreConfigContext context) { private static readonly string _tag = "CoreConfigSingboxService"; private readonly Config _config = context.AppConfig; private readonly ProfileItem _node = context.Node; private SingboxConfig _coreConfig = new(); #region public gen function public RetResult GenerateClientConfigContent() { var ret = new RetResult(); try { if (_node == null || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) { ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } ret.Msg = ResUI.InitialConfiguration; var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); if (result.IsNullOrEmpty()) { ret.Msg = ResUI.FailedGetDefaultConfiguration; return ret; } _coreConfig = JsonUtils.Deserialize(result); if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } GenLog(); GenInbounds(); GenOutbounds(); GenRouting(); GenDns(); GenExperimental(); ConvertGeo2Ruleset(); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; ret.Data = ApplyFullConfigTemplate(); if (context.TunProtectSsPort is > 0 and <= 65535) { var ssInbound = new { type = "shadowsocks", tag = "tun-protect-ss", listen = Global.Loopback, listen_port = context.TunProtectSsPort, method = "none", password = "none", }; var directRule = new Rule4Sbox() { inbound = new List { ssInbound.tag }, outbound = Global.DirectTag, }; var singboxConfigNode = JsonUtils.ParseJson(ret.Data.ToString())!.AsObject(); var inboundsNode = singboxConfigNode["inbounds"]!.AsArray(); inboundsNode.Add(JsonUtils.SerializeToNode(ssInbound, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull })); var routeNode = singboxConfigNode["route"]?.AsObject(); var rulesNode = routeNode?["rules"]?.AsArray(); var protectRuleNode = JsonUtils.SerializeToNode(directRule, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); if (rulesNode != null) { rulesNode.Insert(0, protectRuleNode); } else { var newRulesNode = new JsonArray() { protectRuleNode }; if (routeNode is null) { var newRouteNode = new JsonObject() { ["rules"] = newRulesNode }; singboxConfigNode["route"] = newRouteNode; } else { routeNode["rules"] = newRulesNode; } } ret.Data = JsonUtils.Serialize(singboxConfigNode); } return ret; } catch (Exception ex) { Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } } public RetResult GenerateClientSpeedtestConfig(List selecteds) { var ret = new RetResult(); try { ret.Msg = ResUI.InitialConfiguration; var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) { ret.Msg = ResUI.FailedGetDefaultConfiguration; return ret; } _coreConfig = JsonUtils.Deserialize(result); if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } var (lstIpEndPoints, lstTcpConns) = Utils.GetActiveNetworkInfo(); GenLog(); GenMinimizedDns(); _coreConfig.inbounds.Clear(); _coreConfig.outbounds.RemoveAt(0); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); foreach (var it in selecteds) { if (!(Global.SingboxSupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType())) { continue; } if (!it.ConfigType.IsComplexType() && it.Port <= 0) { continue; } var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId); var item = context.AllProxiesMap.GetValueOrDefault(actIndexId); if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid()) { continue; } //find unused port var port = initPort; for (var k = initPort; k < Global.MaxPort; k++) { if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0) { continue; } if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0) { continue; } //found port = k; initPort = port + 1; break; } //Port In Used if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0) { continue; } it.Port = port; it.AllowTest = true; //inbound Inbound4Sbox inbound = new() { listen = Global.Loopback, listen_port = port, type = EInboundProtocol.mixed.ToString(), }; inbound.tag = inbound.type + inbound.listen_port.ToString(); _coreConfig.inbounds.Add(inbound); var tag = Global.ProxyTag + inbound.listen_port.ToString(); var serverList = new CoreConfigSingboxService(context with { Node = item }).BuildAllProxyOutbounds(tag); FillRangeProxy(serverList, _coreConfig, false); //rule Rule4Sbox rule = new() { inbound = new List { inbound.tag }, outbound = tag }; _coreConfig.route.rules.Add(rule); } ret.Success = true; ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) { Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } } public RetResult GenerateClientSpeedtestConfig(int port) { var ret = new RetResult(); try { if (_node == null || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) { ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } ret.Msg = ResUI.InitialConfiguration; var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); if (result.IsNullOrEmpty()) { ret.Msg = ResUI.FailedGetDefaultConfiguration; return ret; } _coreConfig = JsonUtils.Deserialize(result); if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } GenLog(); GenOutbounds(); GenMinimizedDns(); _coreConfig.route.rules.Clear(); _coreConfig.inbounds.Clear(); _coreConfig.inbounds.Add(new() { tag = $"{EInboundProtocol.mixed}{port}", listen = Global.Loopback, listen_port = port, type = EInboundProtocol.mixed.ToString(), }); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) { Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } } #endregion public gen function } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { private string ApplyFullConfigTemplate() { var fullConfigTemplate = context.FullConfigTemplate; if (fullConfigTemplate is not { Enabled: true }) { return JsonUtils.Serialize(_coreConfig); } var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config; if (fullConfigTemplateItem.IsNullOrEmpty()) { return JsonUtils.Serialize(_coreConfig); } var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem); if (fullConfigTemplateNode == null) { return JsonUtils.Serialize(_coreConfig); } // Process outbounds var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : []; foreach (var outbound in _coreConfig.outbounds) { if (outbound.type.ToLower() is "direct" or "block") { if (fullConfigTemplate.AddProxyOnly == true) { continue; } } else if (outbound.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty() && !Utils.IsPrivateNetwork(outbound.server ?? string.Empty)) { outbound.detour = fullConfigTemplate.ProxyDetour; } customOutboundsNode.Add(JsonUtils.DeepCopy(outbound)); } fullConfigTemplateNode["outbounds"] = customOutboundsNode; // Process endpoints if (_coreConfig.endpoints != null && _coreConfig.endpoints.Count > 0) { var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : []; foreach (var endpoint in _coreConfig.endpoints) { if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) { endpoint.detour = fullConfigTemplate.ProxyDetour; } customEndpointsNode.Add(JsonUtils.DeepCopy(endpoint)); } fullConfigTemplateNode["endpoints"] = customEndpointsNode; } return JsonUtils.Serialize(fullConfigTemplateNode); } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { private void GenDns() { try { var item = context.RawDnsItem; if (item is { Enabled: true }) { GenDnsCustom(); return; } GenDnsServers(); GenDnsRules(); _coreConfig.dns ??= new Dns4Sbox(); _coreConfig.dns.independent_cache = true; // final dns var routing = context.RoutingItem; var useDirectDns = false; if (routing != null) { var rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; if (rules?.LastOrDefault() is { } lastRule && lastRule.OutboundTag == Global.DirectTag) { var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0; var noProcess = lastRule.Process == null || lastRule.Process.Count == 0; var isAnyIp = lastRule.Ip == null || lastRule.Ip.Count == 0 || lastRule.Ip.Contains("0.0.0.0/0"); var isAnyPort = string.IsNullOrEmpty(lastRule.Port) || lastRule.Port == "0-65535"; var isAnyNetwork = string.IsNullOrEmpty(lastRule.Network) || lastRule.Network == "tcp,udp"; useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork; } } _coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag; var simpleDnsItem = context.SimpleDnsItem; if ((!useDirectDns) && simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false) { _coreConfig.dns.rules.Add(new() { server = Global.SingboxFakeDNSTag, query_type = new List { 1, 28 }, // A and AAAA rewrite_ttl = 1, }); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void GenDnsServers() { var simpleDnsItem = context.SimpleDnsItem; var finalDns = GenBootstrapDns(); var directDns = ParseDnsAddress(simpleDnsItem.DirectDNS ?? Global.DomainDirectDNSAddress.First()); directDns.tag = Global.SingboxDirectDNSTag; directDns.domain_resolver = Global.SingboxLocalDNSTag; var remoteDns = ParseDnsAddress(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First()); remoteDns.tag = Global.SingboxRemoteDNSTag; remoteDns.detour = Global.ProxyTag; remoteDns.domain_resolver = Global.SingboxLocalDNSTag; var hostsDns = new Server4Sbox { tag = Global.SingboxHostsDNSTag, type = "hosts", predefined = new(), }; if (simpleDnsItem.AddCommonHosts == true) { hostsDns.predefined = Global.PredefinedHosts; } if (simpleDnsItem.UseSystemHosts == true) { var systemHosts = Utils.GetSystemHosts(); if (systemHosts != null && systemHosts.Count > 0) { foreach (var host in systemHosts) { hostsDns.predefined.TryAdd(host.Key, new List { host.Value }); } } } foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts)) { // only allow full match // like example.com and full:example.com, // but not domain:example.com, keyword:example.com or regex:example.com etc. var testRule = new Rule4Sbox(); if (!ParseV2Domain(kvp.Key, testRule)) { continue; } if (testRule.domain_keyword?.Count > 0 && !kvp.Key.Contains(':')) { testRule.domain = testRule.domain_keyword; testRule.domain_keyword = null; } if (testRule.domain?.Count == 1) { hostsDns.predefined[testRule.domain.First()] = kvp.Value.Where(Utils.IsIpAddress).ToList(); } } foreach (var host in hostsDns.predefined) { if (finalDns.server == host.Key) { finalDns.domain_resolver = Global.SingboxHostsDNSTag; } if (remoteDns.server == host.Key) { remoteDns.domain_resolver = Global.SingboxHostsDNSTag; } if (directDns.server == host.Key) { directDns.domain_resolver = Global.SingboxHostsDNSTag; } } _coreConfig.dns ??= new Dns4Sbox(); _coreConfig.dns.servers ??= []; _coreConfig.dns.servers.Add(remoteDns); _coreConfig.dns.servers.Add(directDns); _coreConfig.dns.servers.Add(hostsDns); // fake ip if (simpleDnsItem.FakeIP == true) { var fakeip = new Server4Sbox { tag = Global.SingboxFakeDNSTag, type = "fakeip", inet4_range = "198.18.0.0/15", inet6_range = "fc00::/18", }; _coreConfig.dns.servers.Add(fakeip); } } private Server4Sbox GenBootstrapDns() { var finalDns = ParseDnsAddress(context.SimpleDnsItem?.BootstrapDNS ?? Global.DomainPureIPDNSAddress.First()); finalDns.tag = Global.SingboxLocalDNSTag; _coreConfig.dns ??= new Dns4Sbox(); _coreConfig.dns.servers ??= []; _coreConfig.dns.servers.Add(finalDns); return finalDns; } private void GenDnsRules() { var simpleDnsItem = context.SimpleDnsItem; _coreConfig.dns ??= new Dns4Sbox(); _coreConfig.dns.rules ??= []; _coreConfig.dns.rules.Add(new() { ip_accept_any = true, server = Global.SingboxHostsDNSTag }); if (context.ProtectDomainList.Count > 0) { _coreConfig.dns.rules.Add(new() { server = Global.SingboxDirectDNSTag, strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom), domain = context.ProtectDomainList.ToList(), }); } _coreConfig.dns.rules.AddRange(new[] { new Rule4Sbox { server = Global.SingboxRemoteDNSTag, strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy), clash_mode = ERuleMode.Global.ToString() }, new Rule4Sbox { server = Global.SingboxDirectDNSTag, strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom), clash_mode = ERuleMode.Direct.ToString() } }); foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts)) { var predefined = kvp.Value.First(); if (predefined.IsNullOrEmpty()) { continue; } var rule = new Rule4Sbox() { query_type = [1, 5, 28], // A, CNAME and AAAA action = "predefined", rcode = "NOERROR", }; if (!ParseV2Domain(kvp.Key, rule)) { continue; } // see: https://xtls.github.io/en/config/dns.html#dnsobject // The matching format (domain:, full:, etc.) is the same as the domain // in the commonly used Routing System. The difference is that without a prefix, // it defaults to using the full: prefix (similar to the common hosts file syntax). if (rule.domain_keyword?.Count > 0 && !kvp.Key.Contains(':')) { rule.domain = rule.domain_keyword; rule.domain_keyword = null; } // example.com #0 -> example.com with NOERROR if (predefined.StartsWith('#') && int.TryParse(predefined.AsSpan(1), out var rcode)) { rule.rcode = rcode switch { 0 => "NOERROR", 1 => "FORMERR", 2 => "SERVFAIL", 3 => "NXDOMAIN", 4 => "NOTIMP", 5 => "REFUSED", _ => "NOERROR", }; } else if (Utils.IsDomain(predefined)) { // example.com CNAME target.com -> example.com with CNAME target.com rule.answer = new List { $"*. IN CNAME {predefined}." }; } else if (Utils.IsIpAddress(predefined) && (rule.domain?.Count ?? 0) == 0) { // not full match, but an IP address, treat it as predefined answer if (Utils.IsIpv6(predefined)) { rule.answer = new List { $"*. IN AAAA {predefined}" }; } else { rule.answer = new List { $"*. IN A {predefined}" }; } } else { continue; } _coreConfig.dns.rules.Add(rule); } if (simpleDnsItem.BlockBindingQuery == true) { _coreConfig.dns.rules.Add(new() { query_type = [64, 65], action = "predefined", rcode = "NOERROR" }); } if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true) { var fakeipFilterRule = JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName)); fakeipFilterRule.invert = true; var rule4Fake = new Rule4Sbox { server = Global.SingboxFakeDNSTag, type = "logical", mode = "and", rewrite_ttl = 1, rules = [ new() { query_type = [1, 28], // A and AAAA }, fakeipFilterRule ] }; _coreConfig.dns.rules.Add(rule4Fake); } var routing = context.RoutingItem; if (routing == null) { return; } var rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; var expectedIPCidr = new List(); var expectedIPsRegions = new List(); var regionNames = new HashSet(); if (!string.IsNullOrEmpty(simpleDnsItem?.DirectExpectedIPs)) { var ipItems = simpleDnsItem.DirectExpectedIPs .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()) .Where(s => !string.IsNullOrEmpty(s)) .ToList(); foreach (var ip in ipItems) { if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase)) { var region = ip["geoip:".Length..]; if (!string.IsNullOrEmpty(region)) { expectedIPsRegions.Add(region); regionNames.Add(region); regionNames.Add($"geolocation-{region}"); regionNames.Add($"tld-{region}"); } } else { expectedIPCidr.Add(ip); } } } foreach (var item in rules) { if (!item.Enabled || item.Domain is null || item.Domain.Count == 0) { continue; } if (item.RuleType == ERuleType.Routing) { continue; } var rule = new Rule4Sbox(); var validDomains = item.Domain.Count(it => ParseV2Domain(it, rule)); if (validDomains <= 0) { continue; } if (item.OutboundTag == Global.DirectTag) { rule.server = Global.SingboxDirectDNSTag; rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom); if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0) { var geositeSet = new HashSet(rule.geosite); if (regionNames.Intersect(geositeSet).Any()) { if (expectedIPsRegions.Count > 0) { rule.geoip = expectedIPsRegions; } if (expectedIPCidr.Count > 0) { rule.ip_cidr = expectedIPCidr; } } } } else if (item.OutboundTag == Global.BlockTag) { rule.action = "predefined"; rule.rcode = "NXDOMAIN"; } else { if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false) { var rule4Fake = JsonUtils.DeepCopy(rule); rule4Fake.server = Global.SingboxFakeDNSTag; rule4Fake.query_type = new List { 1, 28 }; // A and AAAA rule4Fake.rewrite_ttl = 1; _coreConfig.dns.rules.Add(rule4Fake); } rule.server = Global.SingboxRemoteDNSTag; rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy); } _coreConfig.dns.rules.Add(rule); } } private void GenMinimizedDns() { GenDnsServers(); foreach (var server in _coreConfig.dns!.servers.Where(s => !string.IsNullOrEmpty(s.detour)).ToList()) { _coreConfig.dns.servers.Remove(server); } _coreConfig.dns ??= new(); _coreConfig.dns.rules ??= []; _coreConfig.dns.rules.Clear(); _coreConfig.dns.final = Global.SingboxDirectDNSTag; _coreConfig.route.default_domain_resolver = new() { server = Global.SingboxDirectDNSTag, }; } private void GenDnsCustom() { try { var item = context.RawDnsItem; var strDNS = string.Empty; if (context.IsTunEnabled) { strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS; } else { strDNS = string.IsNullOrEmpty(item?.NormalDNS) ? EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName) : item?.NormalDNS; } var dns4Sbox = JsonUtils.Deserialize(strDNS); if (dns4Sbox is null) { return; } _coreConfig.dns = dns4Sbox; if (dns4Sbox.servers?.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty()) { GenDnsProtectCustom(); } else { GenDnsProtectCustomLegacy(); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void GenDnsProtectCustom() { var dnsItem = context.RawDnsItem; var dns4Sbox = _coreConfig.dns ?? new(); dns4Sbox.servers ??= []; dns4Sbox.rules ??= []; var tag = Global.SingboxLocalDNSTag; dns4Sbox.rules.Insert(0, new() { server = tag, clash_mode = ERuleMode.Direct.ToString() }); dns4Sbox.rules.Insert(0, new() { server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote", clash_mode = ERuleMode.Global.ToString() }); var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress; var localDnsServer = ParseDnsAddress(finalDnsAddress); localDnsServer.tag = tag; dns4Sbox.servers.Add(localDnsServer); var protectDomainRule = BuildProtectDomainRule(); if (protectDomainRule != null) { dns4Sbox.rules.Insert(0, protectDomainRule); } _coreConfig.dns = dns4Sbox; } private void GenDnsProtectCustomLegacy() { GenDnsProtectCustom(); _coreConfig.dns?.servers?.RemoveAll(s => s.tag == Global.SingboxLocalDNSTag); var dnsItem = context.RawDnsItem; var localDnsServer = new Server4Sbox() { address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress, tag = Global.SingboxLocalDNSTag, detour = Global.DirectTag, }; _coreConfig.dns?.servers?.Add(localDnsServer); } private Rule4Sbox? BuildProtectDomainRule() { if (context.ProtectDomainList.Count == 0) { return null; } return new() { server = Global.SingboxLocalDNSTag, domain = context.ProtectDomainList.ToList(), }; } private static Server4Sbox? ParseDnsAddress(string address) { var addressFirst = address?.Split(address.Contains(',') ? ',' : ';').FirstOrDefault()?.Trim(); if (string.IsNullOrEmpty(addressFirst)) { return null; } var server = new Server4Sbox(); if (addressFirst is "local" or "localhost") { server.type = "local"; return server; } var (domain, scheme, port, path) = Utils.ParseUrl(addressFirst); if (scheme.Equals("dhcp", StringComparison.OrdinalIgnoreCase)) { server.type = "dhcp"; if ((!domain.IsNullOrEmpty()) && domain != "auto") { server.server = domain; } return server; } if (scheme.IsNullOrEmpty()) { // udp dns server.type = "udp"; } else { // server.type = scheme.ToLower(); // remove "+local" suffix // TODO: "+local" suffix decide server.detour = "direct" ? server.type = scheme.Replace("+local", "", StringComparison.OrdinalIgnoreCase).ToLower(); } server.server = domain; if (port != 0) { server.server_port = port; } if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(path) && path != "/") { server.path = path; } return server; } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { private void GenInbounds() { try { var listen = "0.0.0.0"; var listenPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); _coreConfig.inbounds = []; if (!context.IsTunEnabled || (context.IsTunEnabled && _node.Port != listenPort)) { var inbound = new Inbound4Sbox() { type = EInboundProtocol.mixed.ToString(), tag = EInboundProtocol.socks.ToString(), listen = Global.Loopback, }; _coreConfig.inbounds.Add(inbound); inbound.listen_port = listenPort; if (_config.Inbound.First().SecondLocalPortEnabled) { var inbound2 = BuildInbound(inbound, EInboundProtocol.socks2, true); _coreConfig.inbounds.Add(inbound2); } if (_config.Inbound.First().AllowLANConn) { if (_config.Inbound.First().NewPort4LAN) { var inbound3 = BuildInbound(inbound, EInboundProtocol.socks3, true); inbound3.listen = listen; _coreConfig.inbounds.Add(inbound3); //auth if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) { inbound3.users = new() { new() { username = _config.Inbound.First().User, password = _config.Inbound.First().Pass } }; } } else { inbound.listen = listen; } } } if (context.IsTunEnabled) { if (_config.TunModeItem.Mtu <= 0) { _config.TunModeItem.Mtu = Global.TunMtus.First(); } if (_config.TunModeItem.Stack.IsNullOrEmpty()) { _config.TunModeItem.Stack = Global.TunStacks.First(); } var tunInbound = JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { }; tunInbound.interface_name = Utils.IsMacOS() ? $"utun{new Random().Next(99)}" : "singbox_tun"; tunInbound.mtu = _config.TunModeItem.Mtu; tunInbound.auto_route = _config.TunModeItem.AutoRoute; tunInbound.strict_route = _config.TunModeItem.StrictRoute; tunInbound.stack = _config.TunModeItem.Stack; if (_config.TunModeItem.EnableIPv6Address == false) { tunInbound.address = ["172.18.0.1/30"]; } _coreConfig.inbounds.Add(tunInbound); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private Inbound4Sbox BuildInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks) { var inbound = JsonUtils.DeepCopy(inItem); inbound.tag = protocol.ToString(); inbound.listen_port = inItem.listen_port + (int)protocol; inbound.type = EInboundProtocol.mixed.ToString(); return inbound; } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { private void GenLog() { try { switch (_config.CoreBasicItem.Loglevel) { case "debug": case "info": case "error": _coreConfig.log.level = _config.CoreBasicItem.Loglevel; break; case "warning": _coreConfig.log.level = "warn"; break; default: break; } if (_config.CoreBasicItem.Loglevel == Global.None) { _coreConfig.log.disabled = true; } if (_config.CoreBasicItem.LogEnabled) { var dtNow = DateTime.Now; _coreConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt"); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { private void GenOutbounds() { var proxyOutbounds = BuildAllProxyOutbounds(); FillRangeProxy(proxyOutbounds, _coreConfig, true); } private List BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag, bool withSelector = true) { var proxyOutboundList = new List(); if (!_node.ConfigType.IsComplexType()) { var outbound = BuildProxyOutbound(baseTagName); proxyOutboundList.Add(outbound); } else { proxyOutboundList.AddRange(BuildGroupProxyOutbounds(baseTagName)); } if (withSelector) { var proxyTags = proxyOutboundList.Where(n => n.tag.StartsWith(Global.ProxyTag)).Select(n => n.tag).ToList(); if (proxyTags.Count > 1) { proxyOutboundList.InsertRange(0, BuildSelectorOutbounds(proxyTags, baseTagName)); } } return proxyOutboundList; } private BaseServer4Sbox BuildProxyOutbound(string baseTagName = Global.ProxyTag) { var outbound = BuildProxyServer(); outbound.tag = baseTagName; return outbound; } private List BuildGroupProxyOutbounds(string baseTagName = Global.ProxyTag) { var proxyOutboundList = new List(); switch (_node.ConfigType) { case EConfigType.PolicyGroup: proxyOutboundList = BuildOutboundsList(baseTagName); break; case EConfigType.ProxyChain: proxyOutboundList = BuildChainOutboundsList(baseTagName); break; } return proxyOutboundList; } private BaseServer4Sbox BuildProxyServer() { try { var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); if (_node.ConfigType == EConfigType.WireGuard) { var endpoint = JsonUtils.Deserialize(txtOutbound); FillEndpoint(endpoint); return endpoint; } else { var outbound = JsonUtils.Deserialize(txtOutbound); FillOutbound(outbound); return outbound; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } throw new InvalidOperationException(); } private void FillOutbound(Outbound4Sbox outbound) { try { var protocolExtra = _node.GetProtocolExtra(); outbound.server = _node.Address; outbound.server_port = _node.Port; outbound.type = Global.ProtocolTypes[_node.ConfigType]; switch (_node.ConfigType) { case EConfigType.VMess: { outbound.uuid = _node.Password; outbound.alter_id = int.TryParse(protocolExtra.AlterId, out var result) ? result : 0; if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity)) { outbound.security = protocolExtra.VmessSecurity; } else { outbound.security = Global.DefaultSecurity; } FillOutboundMux(outbound); FillOutboundTransport(outbound); break; } case EConfigType.Shadowsocks: { outbound.method = AppManager.Instance.GetShadowsocksSecurities(_node).Contains(protocolExtra.SsMethod) ? protocolExtra.SsMethod : Global.None; outbound.password = _node.Password; if (_node.Network == nameof(ETransport.tcp) && _node.HeaderType == Global.TcpHeaderHttp) { outbound.plugin = "obfs-local"; outbound.plugin_opts = $"obfs=http;obfs-host={_node.RequestHost};"; } else { var pluginArgs = string.Empty; if (_node.Network == nameof(ETransport.ws)) { pluginArgs += "mode=websocket;"; pluginArgs += $"host={_node.RequestHost};"; // https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172 // Equal signs and commas [and backslashes] must be escaped with a backslash. var path = _node.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,"); pluginArgs += $"path={path};"; } else if (_node.Network == nameof(ETransport.quic)) { pluginArgs += "mode=quic;"; } if (_node.StreamSecurity == Global.StreamSecurity) { pluginArgs += "tls;"; var certs = CertPemManager.ParsePemChain(_node.Cert); if (certs.Count > 0) { var cert = certs.First(); const string beginMarker = "-----BEGIN CERTIFICATE-----\n"; const string endMarker = "\n-----END CERTIFICATE-----"; var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim(); base64Content = base64Content.Replace("=", "\\="); pluginArgs += $"certRaw={base64Content};"; } } if (pluginArgs.Length > 0) { outbound.plugin = "v2ray-plugin"; pluginArgs += "mux=0;"; // pluginStr remove last ';' pluginArgs = pluginArgs[..^1]; outbound.plugin_opts = pluginArgs; } } FillOutboundMux(outbound); break; } case EConfigType.SOCKS: { outbound.version = "5"; if (_node.Username.IsNotEmpty() && _node.Password.IsNotEmpty()) { outbound.username = _node.Username; outbound.password = _node.Password; } break; } case EConfigType.HTTP: { if (_node.Username.IsNotEmpty() && _node.Password.IsNotEmpty()) { outbound.username = _node.Username; outbound.password = _node.Password; } break; } case EConfigType.VLESS: { outbound.uuid = _node.Password; outbound.packet_encoding = "xudp"; if (!protocolExtra.Flow.IsNullOrEmpty()) { outbound.flow = protocolExtra.Flow; } else { FillOutboundMux(outbound); } FillOutboundTransport(outbound); break; } case EConfigType.Trojan: { outbound.password = _node.Password; FillOutboundMux(outbound); FillOutboundTransport(outbound); break; } case EConfigType.Hysteria2: { outbound.password = _node.Password; if (!protocolExtra.SalamanderPass.IsNullOrEmpty()) { outbound.obfs = new() { type = "salamander", password = protocolExtra.SalamanderPass.TrimEx(), }; } outbound.up_mbps = protocolExtra?.UpMbps is { } su and >= 0 ? su : _config.HysteriaItem.UpMbps; outbound.down_mbps = protocolExtra?.DownMbps is { } sd and >= 0 ? sd : _config.HysteriaItem.DownMbps; var ports = protocolExtra?.Ports?.IsNullOrEmpty() == false ? protocolExtra.Ports : null; if ((!ports.IsNullOrEmpty()) && (ports.Contains(':') || ports.Contains('-') || ports.Contains(','))) { outbound.server_port = null; outbound.server_ports = ports.Split(',') .Select(p => p.Trim()) .Where(p => p.IsNotEmpty()) .Select(p => { var port = p.Replace('-', ':'); return port.Contains(':') ? port : $"{port}:{port}"; }) .ToList(); outbound.hop_interval = _config.HysteriaItem.HopInterval >= 5 ? $"{_config.HysteriaItem.HopInterval}s" : $"{Global.Hysteria2DefaultHopInt}s"; if (int.TryParse(protocolExtra.HopInterval, out var hiResult)) { outbound.hop_interval = hiResult >= 5 ? $"{hiResult}s" : outbound.hop_interval; } else if (protocolExtra.HopInterval?.Contains('-') ?? false) { // may be a range like 5-10 var parts = protocolExtra.HopInterval.Split('-'); if (parts.Length == 2 && int.TryParse(parts[0], out var hiL) && int.TryParse(parts[0], out var hiH)) { var hi = (hiL + hiH) / 2; outbound.hop_interval = hi >= 5 ? $"{hi}s" : outbound.hop_interval; } } } break; } case EConfigType.TUIC: { outbound.uuid = _node.Username; outbound.password = _node.Password; outbound.congestion_control = _node.HeaderType; break; } case EConfigType.Anytls: { outbound.password = _node.Password; break; } } FillOutboundTls(outbound); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void FillEndpoint(Endpoints4Sbox endpoint) { try { var protocolExtra = _node.GetProtocolExtra(); endpoint.address = Utils.String2List(protocolExtra.WgInterfaceAddress); endpoint.type = Global.ProtocolTypes[_node.ConfigType]; switch (_node.ConfigType) { case EConfigType.WireGuard: { var peer = new Peer4Sbox { public_key = protocolExtra.WgPublicKey, pre_shared_key = protocolExtra.WgPresharedKey, reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(), address = _node.Address, port = _node.Port, // TODO default ["0.0.0.0/0", "::/0"] allowed_ips = new() { "0.0.0.0/0", "::/0" }, }; endpoint.private_key = _node.Password; endpoint.mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(); endpoint.peers = [peer]; break; } } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void FillOutboundMux(Outbound4Sbox outbound) { try { var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty()) { var mux = new Multiplex4Sbox() { enabled = true, protocol = _config.Mux4SboxItem.Protocol, max_connections = _config.Mux4SboxItem.MaxConnections, padding = _config.Mux4SboxItem.Padding, }; outbound.multiplex = mux; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void FillOutboundTls(Outbound4Sbox outbound) { try { if (_node.StreamSecurity is not (Global.StreamSecurityReality or Global.StreamSecurity)) { return; } if (_node.ConfigType is EConfigType.Shadowsocks or EConfigType.SOCKS or EConfigType.WireGuard) { return; } var serverName = string.Empty; if (_node.Sni.IsNotEmpty()) { serverName = _node.Sni; } else if (_node.RequestHost.IsNotEmpty()) { serverName = Utils.String2List(_node.RequestHost)?.First(); } var tls = new Tls4Sbox() { enabled = true, record_fragment = _config.CoreBasicItem.EnableFragment ? true : null, server_name = serverName, insecure = Utils.ToBool(_node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : _node.AllowInsecure), alpn = _node.GetAlpn(), }; if (_node.Fingerprint.IsNotEmpty()) { tls.utls = new Utls4Sbox() { enabled = true, fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint }; } if (_node.StreamSecurity == Global.StreamSecurity) { var certs = CertPemManager.ParsePemChain(_node.Cert); if (certs.Count > 0) { tls.certificate = certs; tls.insecure = false; } } else if (_node.StreamSecurity == Global.StreamSecurityReality) { tls.reality = new Reality4Sbox() { enabled = true, public_key = _node.PublicKey, short_id = _node.ShortId }; tls.insecure = false; } var (ech, _) = ParseEchParam(_node.EchConfigList); if (ech is not null) { tls.ech = ech; } outbound.tls = tls; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void FillOutboundTransport(Outbound4Sbox outbound) { try { var transport = new Transport4Sbox(); switch (_node.GetNetwork()) { case nameof(ETransport.h2): transport.type = nameof(ETransport.http); transport.host = _node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(_node.RequestHost); transport.path = _node.Path.NullIfEmpty(); break; case nameof(ETransport.tcp): //http if (_node.HeaderType == Global.TcpHeaderHttp) { transport.type = nameof(ETransport.http); transport.host = _node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(_node.RequestHost); transport.path = _node.Path.NullIfEmpty(); } break; case nameof(ETransport.ws): transport.type = nameof(ETransport.ws); var wsPath = _node.Path; // Parse eh and ed parameters from path using regex if (!wsPath.IsNullOrEmpty()) { var edRegex = new Regex(@"[?&]ed=(\d+)"); var edMatch = edRegex.Match(wsPath); if (edMatch.Success && int.TryParse(edMatch.Groups[1].Value, out var edValue)) { transport.max_early_data = edValue; transport.early_data_header_name = "Sec-WebSocket-Protocol"; wsPath = edRegex.Replace(wsPath, ""); wsPath = wsPath.Replace("?&", "?"); if (wsPath.EndsWith('?')) { wsPath = wsPath.TrimEnd('?'); } } var ehRegex = new Regex(@"[?&]eh=([^&]+)"); var ehMatch = ehRegex.Match(wsPath); if (ehMatch.Success) { transport.early_data_header_name = Uri.UnescapeDataString(ehMatch.Groups[1].Value); } } transport.path = wsPath.NullIfEmpty(); if (_node.RequestHost.IsNotEmpty()) { transport.headers = new() { Host = _node.RequestHost }; } break; case nameof(ETransport.httpupgrade): transport.type = nameof(ETransport.httpupgrade); transport.path = _node.Path.NullIfEmpty(); transport.host = _node.RequestHost.NullIfEmpty(); break; case nameof(ETransport.quic): transport.type = nameof(ETransport.quic); break; case nameof(ETransport.grpc): transport.type = nameof(ETransport.grpc); transport.service_name = _node.Path; transport.idle_timeout = _config.GrpcItem.IdleTimeout?.ToString("##s"); transport.ping_timeout = _config.GrpcItem.HealthCheckTimeout?.ToString("##s"); transport.permit_without_stream = _config.GrpcItem.PermitWithoutStream; break; default: break; } if (transport.type != null) { outbound.transport = transport; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private List BuildSelectorOutbounds(List proxyTags, string baseTagName = Global.ProxyTag) { var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; var outUrltest = new Outbound4Sbox { type = "urltest", tag = $"{baseTagName}-auto", outbounds = proxyTags, interrupt_exist_connections = false, }; if (multipleLoad == EMultipleLoad.Fallback) { outUrltest.tolerance = 5000; } // Add selector outbound (manual selection) var outSelector = new Outbound4Sbox { type = "selector", tag = baseTagName, outbounds = JsonUtils.DeepCopy(proxyTags), interrupt_exist_connections = false, }; outSelector.outbounds.Insert(0, outUrltest.tag); return [outSelector, outUrltest]; } private List BuildOutboundsList(string baseTagName = Global.ProxyTag) { var nodes = new List(); foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) { if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) { nodes.Add(node); } } var resultOutbounds = new List(); for (var i = 0; i < nodes.Count; i++) { var node = nodes[i]; var currentTag = $"{baseTagName}-{i + 1}"; if (node.ConfigType.IsGroupType()) { var childProfiles = new CoreConfigSingboxService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); resultOutbounds.AddRange(childProfiles); continue; } var outbound = new CoreConfigSingboxService(context with { Node = node, }).BuildProxyOutbound(); outbound.tag = currentTag; resultOutbounds.Add(outbound); } return resultOutbounds; } private List BuildChainOutboundsList(string baseTagName = Global.ProxyTag) { var nodes = new List(); foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) { if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) { nodes.Add(node); } } // Based on actual network flow instead of data packets var nodesReverse = nodes.AsEnumerable().Reverse().ToList(); var resultOutbounds = new List(); for (var i = 0; i < nodesReverse.Count; i++) { var node = nodesReverse[i]; var currentTag = i == 0 ? baseTagName : $"chain-{baseTagName}-{i}"; var dialerProxyTag = i != nodesReverse.Count - 1 ? $"chain-{baseTagName}-{i + 1}" : null; if (node.ConfigType.IsGroupType()) { var childProfiles = new CoreConfigSingboxService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); if (!dialerProxyTag.IsNullOrEmpty()) { var chainEndNodes = childProfiles.Where(n => n?.detour.IsNullOrEmpty() ?? true); foreach (var chainEndNode in chainEndNodes) { chainEndNode.detour = dialerProxyTag; } } if (i != 0) { var chainStartNodes = childProfiles.Where(n => n.tag.StartsWith(currentTag)).ToList(); if (chainStartNodes.Count == 1) { foreach (var existedChainEndNode in resultOutbounds.Where(n => n.detour == currentTag)) { existedChainEndNode.detour = chainStartNodes.First().tag; } } else if (chainStartNodes.Count > 1) { var existedChainNodes = CloneOutbounds(resultOutbounds); resultOutbounds.Clear(); var j = 0; foreach (var chainStartNode in chainStartNodes) { var existedChainNodesClone = CloneOutbounds(existedChainNodes); foreach (var existedChainNode in existedChainNodesClone) { var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}"; existedChainNode.tag = cloneTag; } for (var k = 0; k < existedChainNodesClone.Count; k++) { var existedChainNode = existedChainNodesClone[k]; var previousDialerProxyTag = existedChainNode.detour; var nextTag = k + 1 < existedChainNodesClone.Count ? existedChainNodesClone[k + 1].tag : chainStartNode.tag; existedChainNode.detour = (previousDialerProxyTag == currentTag) ? chainStartNode.tag : nextTag; resultOutbounds.Add(existedChainNode); } j++; } } } resultOutbounds.AddRange(childProfiles); continue; } var outbound = new CoreConfigSingboxService(context with { Node = node, }).BuildProxyOutbound(); outbound.tag = currentTag; if (!dialerProxyTag.IsNullOrEmpty()) { outbound.detour = dialerProxyTag; } resultOutbounds.Add(outbound); } return resultOutbounds; } private static List CloneOutbounds(List source) { if (source is null || source.Count == 0) { return []; } var result = new List(source.Count); foreach (var item in source) { BaseServer4Sbox? clone = null; if (item is Outbound4Sbox outbound) { clone = JsonUtils.DeepCopy(outbound); } else if (item is Endpoints4Sbox endpoint) { clone = JsonUtils.DeepCopy(endpoint); } if (clone is not null) { result.Add(clone); } } return result; } private static void FillRangeProxy(List servers, SingboxConfig singboxConfig, bool prepend = true) { try { if (servers is null || servers.Count <= 0) { return; } var outbounds = servers.Where(s => s is Outbound4Sbox).Cast().ToList(); var endpoints = servers.Where(s => s is Endpoints4Sbox).Cast().ToList(); singboxConfig.endpoints ??= []; if (prepend) { singboxConfig.outbounds.InsertRange(0, outbounds); singboxConfig.endpoints.InsertRange(0, endpoints); } else { singboxConfig.outbounds.AddRange(outbounds); singboxConfig.endpoints.AddRange(endpoints); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private static (Ech4Sbox? ech, Server4Sbox? dnsServer) ParseEchParam(string? echConfig) { if (echConfig.IsNullOrEmpty()) { return (null, null); } if (!echConfig.Contains("://")) { return (new Ech4Sbox() { enabled = true, config = [$"-----BEGIN ECH CONFIGS-----\n" + $"{echConfig}\n" + $"-----END ECH CONFIGS-----"], }, null); } var idx = echConfig.IndexOf('+'); // NOTE: query_server_name, since sing-box 1.13.0 //var queryServerName = idx > 0 ? echConfig[..idx] : null; var echDnsServer = idx > 0 ? echConfig[(idx + 1)..] : echConfig; return (new Ech4Sbox() { enabled = true, query_server_name = null, }, ParseDnsAddress(echDnsServer)); } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { private void GenRouting() { try { _coreConfig.route.final = Global.ProxyTag; var simpleDnsItem = context.SimpleDnsItem; var defaultDomainResolverTag = Global.SingboxDirectDNSTag; var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom); var rawDNSItem = context.RawDnsItem; if (rawDNSItem is { Enabled: true }) { defaultDomainResolverTag = Global.SingboxLocalDNSTag; directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom; } _coreConfig.route.default_domain_resolver = new() { server = defaultDomainResolverTag, strategy = directDnsStrategy }; if (_config.TunModeItem.EnableTun) { _coreConfig.route.auto_detect_interface = true; var tunRules = JsonUtils.Deserialize>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName)); if (tunRules != null) { _coreConfig.route.rules.AddRange(tunRules); } var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe(); _coreConfig.route.rules.Add(new() { port = [53], action = "hijack-dns", process_name = lstDnsExe }); _coreConfig.route.rules.Add(new() { outbound = Global.DirectTag, process_name = lstDirectExe }); } if (_config.Inbound.First().SniffingEnabled) { _coreConfig.route.rules.Add(new() { action = "sniff" }); _coreConfig.route.rules.Add(new() { protocol = ["dns"], action = "hijack-dns" }); } else { _coreConfig.route.rules.Add(new() { port = [53], network = ["udp"], action = "hijack-dns" }); } var hostsDomains = new List(); if (rawDNSItem is not { Enabled: true }) { var userHostsMap = Utils.ParseHostsToDictionary(simpleDnsItem.Hosts); hostsDomains.AddRange(userHostsMap.Select(kvp => kvp.Key)); if (simpleDnsItem.UseSystemHosts == true) { var systemHostsMap = Utils.GetSystemHosts(); hostsDomains.AddRange(systemHostsMap.Select(kvp => kvp.Key)); } } if (hostsDomains.Count > 0) { var hostsResolveRule = new Rule4Sbox { action = "resolve", }; var hostsCounter = 0; foreach (var host in hostsDomains) { var domainRule = new Rule4Sbox(); if (!ParseV2Domain(host, domainRule)) { continue; } if (domainRule.domain_keyword?.Count > 0 && !host.Contains(':')) { domainRule.domain = domainRule.domain_keyword; domainRule.domain_keyword = null; } if (domainRule.domain?.Count > 0) { hostsResolveRule.domain ??= []; hostsResolveRule.domain.AddRange(domainRule.domain); hostsCounter++; } else if (domainRule.domain_keyword?.Count > 0) { hostsResolveRule.domain_keyword ??= []; hostsResolveRule.domain_keyword.AddRange(domainRule.domain_keyword); hostsCounter++; } else if (domainRule.domain_suffix?.Count > 0) { hostsResolveRule.domain_suffix ??= []; hostsResolveRule.domain_suffix.AddRange(domainRule.domain_suffix); hostsCounter++; } else if (domainRule.domain_regex?.Count > 0) { hostsResolveRule.domain_regex ??= []; hostsResolveRule.domain_regex.AddRange(domainRule.domain_regex); hostsCounter++; } else if (domainRule.geosite?.Count > 0) { hostsResolveRule.geosite ??= []; hostsResolveRule.geosite.AddRange(domainRule.geosite); hostsCounter++; } } if (hostsCounter > 0) { _coreConfig.route.rules.Add(hostsResolveRule); } } _coreConfig.route.rules.Add(new() { outbound = Global.DirectTag, clash_mode = ERuleMode.Direct.ToString() }); _coreConfig.route.rules.Add(new() { outbound = Global.ProxyTag, clash_mode = ERuleMode.Global.ToString() }); var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty(); var routing = context.RoutingItem; if (routing.DomainStrategy4Singbox.IsNotEmpty()) { domainStrategy = routing.DomainStrategy4Singbox; } var resolveRule = new Rule4Sbox { action = "resolve", strategy = domainStrategy }; if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand) { _coreConfig.route.rules.Add(resolveRule); } var ipRules = new List(); if (routing != null) { var rules = JsonUtils.Deserialize>(routing.RuleSet); foreach (var item1 in rules ?? []) { if (!item1.Enabled) { continue; } if (item1.RuleType == ERuleType.DNS) { continue; } GenRoutingUserRule(item1); if (item1.Ip?.Count > 0) { ipRules.Add(item1); } } } if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch) { _coreConfig.route.rules.Add(resolveRule); foreach (var item2 in ipRules) { GenRoutingUserRule(item2); } } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private static (List lstDnsExe, List lstDirectExe) BuildRoutingDirectExe() { var dnsExeSet = new HashSet(StringComparer.OrdinalIgnoreCase); var directExeSet = new HashSet(StringComparer.OrdinalIgnoreCase); var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo(); foreach (var coreConfig in coreInfoResult) { if (coreConfig.CoreType == ECoreType.v2rayN) { continue; } foreach (var baseExeName in coreConfig.CoreExes) { if (coreConfig.CoreType != ECoreType.sing_box) { dnsExeSet.Add(Utils.GetExeName(baseExeName)); } directExeSet.Add(Utils.GetExeName(baseExeName)); } } var lstDnsExe = new List(dnsExeSet); var lstDirectExe = new List(directExeSet); return (lstDnsExe, lstDirectExe); } private void GenRoutingUserRule(RulesItem? item) { try { if (item == null) { return; } item.OutboundTag = GenRoutingUserRuleOutbound(item.OutboundTag ?? Global.ProxyTag); var rules = _coreConfig.route.rules; var rule = new Rule4Sbox(); if (item.OutboundTag == "block") { rule.action = "reject"; } else { rule.outbound = item.OutboundTag; } if (item.Port.IsNotEmpty()) { var portRanges = item.Port.Split(',').Where(it => it.Contains('-')).Select(it => it.Replace("-", ":")).ToList(); var ports = item.Port.Split(',').Where(it => !it.Contains('-')).Select(it => it.ToInt()).ToList(); rule.port_range = portRanges.Count > 0 ? portRanges : null; rule.port = ports.Count > 0 ? ports : null; } if (item.Network.IsNotEmpty()) { rule.network = Utils.String2List(item.Network); } if (item.Protocol?.Count > 0) { rule.protocol = item.Protocol; } if (item.InboundTag?.Count >= 0) { rule.inbound = item.InboundTag; } var rule1 = JsonUtils.DeepCopy(rule); var rule2 = JsonUtils.DeepCopy(rule); var rule3 = JsonUtils.DeepCopy(rule); var hasDomainIp = false; if (item.Domain?.Count > 0) { var countDomain = 0; foreach (var it in item.Domain) { if (ParseV2Domain(it, rule1)) { countDomain++; } } if (countDomain > 0) { rules.Add(rule1); hasDomainIp = true; } } if (item.Ip?.Count > 0) { var countIp = 0; foreach (var it in item.Ip) { if (ParseV2Address(it, rule2)) { countIp++; } } if (countIp > 0) { rules.Add(rule2); hasDomainIp = true; } } if (item.Process?.Count > 0) { var ruleProcName = JsonUtils.DeepCopy(rule3); ruleProcName.process_name ??= []; var ruleProcPath = JsonUtils.DeepCopy(rule3); ruleProcPath.process_path ??= []; foreach (var process in item.Process) { // sing-box doesn't support this, fall back to process name match if (process is "self/" or "xray/") { ruleProcName.process_name.Add(Utils.GetExeName("sing-box")); continue; } if (process.Contains('/') || process.Contains('\\')) { var procPath = process; if (Utils.IsWindows()) { procPath = procPath.Replace('/', '\\'); } ruleProcPath.process_path.Add(procPath); continue; } // sing-box strictly matches the exe suffix on Windows var procName = Utils.GetExeName(process); ruleProcName.process_name.Add(procName); } if (ruleProcName.process_name.Count > 0) { rules.Add(ruleProcName); hasDomainIp = true; } if (ruleProcPath.process_path.Count > 0) { rules.Add(ruleProcPath); hasDomainIp = true; } } if (!hasDomainIp && (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null || rule.network != null)) { rules.Add(rule); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private static bool ParseV2Domain(string domain, Rule4Sbox rule) { if (domain.StartsWith('#') || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:")) { return false; } else if (domain.StartsWith("geosite:")) { rule.geosite ??= []; rule.geosite?.Add(domain.Substring(8)); } else if (domain.StartsWith("regexp:")) { rule.domain_regex ??= []; rule.domain_regex?.Add(domain.Replace(Global.RoutingRuleComma, ",").Substring(7)); } else if (domain.StartsWith("domain:")) { rule.domain_suffix ??= []; rule.domain_suffix?.Add(domain.Substring(7)); } else if (domain.StartsWith("full:")) { rule.domain ??= []; rule.domain?.Add(domain.Substring(5)); } else if (domain.StartsWith("keyword:")) { rule.domain_keyword ??= []; rule.domain_keyword?.Add(domain.Substring(8)); } else if (domain.StartsWith("dotless:")) { rule.domain_keyword ??= []; rule.domain_keyword?.Add(domain.Substring(8)); } else { rule.domain_keyword ??= []; rule.domain_keyword?.Add(domain); } return true; } private static bool ParseV2Address(string address, Rule4Sbox rule) { if (address.StartsWith("ext:") || address.StartsWith("ext-ip:")) { return false; } else if (address.Equals("geoip:private")) { rule.ip_is_private = true; } else if (address.StartsWith("geoip:")) { rule.geoip ??= new(); rule.geoip?.Add(address.Substring(6)); } else if (address.Equals("geoip:!private")) { rule.ip_is_private = false; } else if (address.StartsWith("geoip:!")) { rule.geoip ??= new(); rule.geoip?.Add(address.Substring(6)); rule.invert = true; } else { rule.ip_cidr ??= new(); rule.ip_cidr?.Add(address); } return true; } private string GenRoutingUserRuleOutbound(string outboundTag) { if (Global.OutboundTags.Contains(outboundTag)) { return outboundTag; } var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}"); if (node == null || (!Global.SingboxSupportConfigType.Contains(node.ConfigType) && !node.ConfigType.IsGroupType())) { return Global.ProxyTag; } var tag = $"{node.IndexId}-{Global.ProxyTag}"; if (_coreConfig.outbounds.Any(o => o.tag.StartsWith(tag)) || (_coreConfig.endpoints != null && _coreConfig.endpoints.Any(e => e.tag.StartsWith(tag)))) { return tag; } var proxyOutbounds = new CoreConfigSingboxService(context with { Node = node, }).BuildAllProxyOutbounds(tag); FillRangeProxy(proxyOutbounds, _coreConfig, false); return tag; } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { private void ConvertGeo2Ruleset() { static void AddRuleSets(List ruleSets, List? rule_set) { if (rule_set != null) { ruleSets.AddRange(rule_set); } } var geosite = "geosite"; var geoip = "geoip"; var ruleSets = new List(); //convert route geosite & geoip to ruleset foreach (var rule in _coreConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.geosite = null; AddRuleSets(ruleSets, rule.rule_set); } foreach (var rule in _coreConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); rule.geoip = null; AddRuleSets(ruleSets, rule.rule_set); } //convert dns geosite & geoip to ruleset foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.geosite = null; } foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); rule.geoip = null; } foreach (var dnsRule in _coreConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) { AddRuleSets(ruleSets, dnsRule.rule_set); } //rules in rules foreach (var item in _coreConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? []) { foreach (var item2 in item ?? []) { AddRuleSets(ruleSets, item2.rule_set); } } //load custom ruleset file List customRulesets = []; var routing = context.RoutingItem; if (routing.CustomRulesetPath4Singbox.IsNotEmpty()) { var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox); if (result.IsNotEmpty()) { customRulesets = (JsonUtils.Deserialize>(result) ?? []) .Where(t => t.tag != null) .Where(t => t.type != null) .Where(t => t.format != null) .ToList(); } } //Local srs files address var localSrss = Utils.GetBinPath("srss"); //Add ruleset srs _coreConfig.route.rule_set = []; foreach (var item in new HashSet(ruleSets)) { if (item.IsNullOrEmpty()) { continue; } var customRuleset = customRulesets.FirstOrDefault(t => t.tag != null && t.tag.Equals(item)); if (customRuleset is null) { var pathSrs = Path.Combine(localSrss, $"{item}.srs"); if (File.Exists(pathSrs)) { customRuleset = new() { type = "local", format = "binary", tag = item, path = pathSrs }; } else { var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl) ? Global.SingboxRulesetUrl : _config.ConstItem.SrsSourceUrl; customRuleset = new() { type = "remote", format = "binary", tag = item, url = string.Format(srsUrl, item.StartsWith(geosite) ? geosite : geoip, item), download_detour = Global.ProxyTag }; } } _coreConfig.route.rule_set.Add(customRuleset); } } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { private void GenExperimental() { //if (_config.guiItem.enableStatistics) { _coreConfig.experimental ??= new Experimental4Sbox(); _coreConfig.experimental.clash_api = new Clash_Api4Sbox() { external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}", }; } if (_config.CoreBasicItem.EnableCacheFile4Sbox) { _coreConfig.experimental ??= new Experimental4Sbox(); _coreConfig.experimental.cache_file = new CacheFile4Sbox() { enabled = true, path = Utils.GetBinPath("cache.db"), store_fakeip = context.SimpleDnsItem.FakeIP == true }; } } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService(CoreConfigContext context) { private static readonly string _tag = "CoreConfigV2rayService"; private readonly Config _config = context.AppConfig; private readonly ProfileItem _node = context.Node; private V2rayConfig _coreConfig = new(); #region public gen function public RetResult GenerateClientConfigContent() { var ret = new RetResult(); try { if (context.IsTunEnabled && context.TunProtectSsPort > 0 && context.ProxyRelaySsPort > 0) { return GenerateClientProxyRelayConfig(); } if (_node == null || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } if (_node.GetNetwork() is nameof(ETransport.quic)) { ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } ret.Msg = ResUI.InitialConfiguration; var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); if (result.IsNullOrEmpty()) { ret.Msg = ResUI.FailedGetDefaultConfiguration; return ret; } _coreConfig = JsonUtils.Deserialize(result); if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } GenLog(); GenInbounds(); GenOutbounds(); GenRouting(); GenDns(); GenStatistic(); var finalRule = BuildFinalRule(); if (!string.IsNullOrEmpty(finalRule?.balancerTag)) { _coreConfig.routing.rules.Add(finalRule); } ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; ret.Data = ApplyFullConfigTemplate(); return ret; } catch (Exception ex) { Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } } public RetResult GenerateClientSpeedtestConfig(List selecteds) { var ret = new RetResult(); try { ret.Msg = ResUI.InitialConfiguration; var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) { ret.Msg = ResUI.FailedGetDefaultConfiguration; return ret; } _coreConfig = JsonUtils.Deserialize(result); if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } var (lstIpEndPoints, lstTcpConns) = Utils.GetActiveNetworkInfo(); GenLog(); _coreConfig.inbounds.Clear(); _coreConfig.outbounds.Clear(); _coreConfig.routing.rules.Clear(); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); foreach (var it in selecteds) { if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType())) { continue; } if (!it.ConfigType.IsComplexType() && it.Port <= 0) { continue; } var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId); var item = context.AllProxiesMap.GetValueOrDefault(actIndexId); if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid()) { continue; } //find unused port var port = initPort; for (var k = initPort; k < Global.MaxPort; k++) { if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0) { continue; } if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0) { continue; } //found port = k; initPort = port + 1; break; } //Port In Used if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0) { continue; } it.Port = port; it.AllowTest = true; //inbound Inbounds4Ray inbound = new() { listen = Global.Loopback, port = port, protocol = EInboundProtocol.mixed.ToString(), }; inbound.tag = inbound.protocol + inbound.port.ToString(); _coreConfig.inbounds.Add(inbound); var tag = Global.ProxyTag + inbound.port.ToString(); var isBalancer = false; //outbound var proxyOutbounds = new CoreConfigV2rayService(context with { Node = item }).BuildAllProxyOutbounds(tag); _coreConfig.outbounds.AddRange(proxyOutbounds); if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1) { isBalancer = true; var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; GenObservatory(multipleLoad, tag); GenBalancer(multipleLoad, tag); } //rule RulesItem4Ray rule = new() { inboundTag = [inbound.tag], outboundTag = tag, type = "field" }; if (isBalancer) { rule.balancerTag = tag + Global.BalancerTagSuffix; rule.outboundTag = null; } _coreConfig.routing.rules.Add(rule); } //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); ret.Success = true; ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) { Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } } public RetResult GenerateClientSpeedtestConfig(int port) { var ret = new RetResult(); try { if (_node == null || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } if (_node.GetNetwork() is nameof(ETransport.quic)) { ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); if (result.IsNullOrEmpty()) { ret.Msg = ResUI.FailedGetDefaultConfiguration; return ret; } _coreConfig = JsonUtils.Deserialize(result); if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } GenLog(); GenOutbounds(); _coreConfig.routing.domainStrategy = Global.AsIs; _coreConfig.routing.rules.Clear(); _coreConfig.inbounds.Clear(); _coreConfig.inbounds.Add(new() { tag = $"{EInboundProtocol.socks}{port}", listen = Global.Loopback, port = port, protocol = EInboundProtocol.mixed.ToString(), }); _coreConfig.routing.rules.Add(BuildFinalRule()); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) { Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } } public RetResult GenerateClientProxyRelayConfig() { var ret = new RetResult(); try { if (_node == null || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } if (_node.GetNetwork() is nameof(ETransport.quic)) { ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); if (result.IsNullOrEmpty()) { ret.Msg = ResUI.FailedGetDefaultConfiguration; return ret; } _coreConfig = JsonUtils.Deserialize(result); if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } GenLog(); _coreConfig.outbounds.Clear(); GenOutbounds(); GenStatistic(); var protectNode = new ProfileItem() { CoreType = ECoreType.Xray, ConfigType = EConfigType.Shadowsocks, Address = Global.Loopback, Port = context.TunProtectSsPort, Password = Global.None, }; protectNode.SetProtocolExtra(protectNode.GetProtocolExtra() with { SsMethod = Global.None, }); foreach (var outbound in _coreConfig.outbounds .Where(o => o.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)) { outbound.streamSettings ??= new(); outbound.streamSettings.sockopt ??= new(); outbound.streamSettings.sockopt.dialerProxy = "tun-protect-ss"; } // ech protected foreach (var outbound in _coreConfig.outbounds .Where(outbound => outbound.streamSettings?.tlsSettings?.echConfigList?.IsNullOrEmpty() == false)) { outbound.streamSettings!.tlsSettings!.echSockopt ??= new(); outbound.streamSettings.tlsSettings.echSockopt.dialerProxy = "tun-protect-ss"; } _coreConfig.outbounds.Add(new CoreConfigV2rayService(context with { Node = protectNode, }).BuildProxyOutbound("tun-protect-ss")); _coreConfig.routing.rules ??= []; var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 }; _coreConfig.routing.rules.Add(new() { inboundTag = ["proxy-relay-ss"], outboundTag = hasBalancer ? null : Global.ProxyTag, balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix : null, type = "field" }); //_coreConfig.inbounds.Clear(); var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!; configNode["inbounds"]!.AsArray().Add(new { listen = Global.Loopback, port = context.ProxyRelaySsPort, protocol = "shadowsocks", settings = new { network = "tcp,udp", method = Global.None, password = Global.None, }, tag = "proxy-relay-ss", }); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; ret.Data = JsonUtils.Serialize(configNode); return ret; } catch (Exception ex) { Logging.SaveLog(_tag, ex); ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } } #endregion public gen function } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { private void GenObservatory(EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) { // Collect all existing subject selectors from both observatories var subjectSelectors = new List(); subjectSelectors.AddRange(_coreConfig.burstObservatory?.subjectSelector ?? []); subjectSelectors.AddRange(_coreConfig.observatory?.subjectSelector ?? []); // Case 1: exact match already exists -> nothing to do if (subjectSelectors.Any(baseTagName.StartsWith)) { return; } // Case 2: prefix match exists -> reuse it and move to the first position var matched = subjectSelectors.FirstOrDefault(s => s.StartsWith(baseTagName)); if (matched is not null) { baseTagName = matched; if (_coreConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true) { _coreConfig.burstObservatory.subjectSelector.Remove(baseTagName); _coreConfig.burstObservatory.subjectSelector.Insert(0, baseTagName); } if (_coreConfig.observatory?.subjectSelector?.Contains(baseTagName) == true) { _coreConfig.observatory.subjectSelector.Remove(baseTagName); _coreConfig.observatory.subjectSelector.Insert(0, baseTagName); } return; } // Case 3: need to create or insert based on multipleLoad type if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback) { if (_coreConfig.burstObservatory is null) { // Create new burst observatory with default ping config _coreConfig.burstObservatory = new BurstObservatory4Ray { subjectSelector = [baseTagName], pingConfig = new() { destination = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl, interval = "5m", timeout = "30s", sampling = 2, } }; } else { _coreConfig.burstObservatory.subjectSelector ??= new(); _coreConfig.burstObservatory.subjectSelector.Add(baseTagName); } } else if (multipleLoad is EMultipleLoad.LeastPing) { if (_coreConfig.observatory is null) { // Create new observatory with default probe config _coreConfig.observatory = new Observatory4Ray { subjectSelector = [baseTagName], probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl, probeInterval = "3m", enableConcurrency = true, }; } else { _coreConfig.observatory.subjectSelector ??= new(); _coreConfig.observatory.subjectSelector.Add(baseTagName); } } } private void GenBalancer(EMultipleLoad multipleLoad, string selector = Global.ProxyTag) { var strategyType = multipleLoad switch { EMultipleLoad.Random => "random", EMultipleLoad.RoundRobin => "roundRobin", EMultipleLoad.LeastPing => "leastPing", EMultipleLoad.LeastLoad => "leastLoad", _ => "roundRobin", }; var balancerTag = $"{selector}{Global.BalancerTagSuffix}"; var balancer = new BalancersItem4Ray { selector = [selector], strategy = new() { type = strategyType, settings = new() { expected = 1, }, }, tag = balancerTag, }; _coreConfig.routing.balancers ??= new(); _coreConfig.routing.balancers.Add(balancer); } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { private string ApplyFullConfigTemplate() { var fullConfigTemplate = context.FullConfigTemplate; if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty()) { return JsonUtils.Serialize(_coreConfig); } var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config); if (fullConfigTemplateNode == null) { return JsonUtils.Serialize(_coreConfig); } // Handle balancer and rules modifications (for multiple load scenarios) if (_coreConfig.routing?.balancers?.Count > 0) { var balancer = _coreConfig.routing.balancers.FirstOrDefault(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix, null); // Modify existing rules in custom config if (balancer != null) { var rulesNode = fullConfigTemplateNode["routing"]?["rules"]; if (rulesNode != null) { foreach (var rule in rulesNode.AsArray()) { if (rule["outboundTag"]?.GetValue() == Global.ProxyTag) { rule.AsObject().Remove("outboundTag"); rule["balancerTag"] = balancer.tag; } } } } // Ensure routing node exists if (fullConfigTemplateNode["routing"] == null) { fullConfigTemplateNode["routing"] = new JsonObject(); } // Handle balancers - append instead of override if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode) { if (JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers)) is JsonArray newBalancers) { foreach (var balancerNode in newBalancers) { customBalancersNode.Add(balancerNode?.DeepClone()); } } } else { fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers)); } } if (_coreConfig.observatory != null) { if (fullConfigTemplateNode["observatory"] == null) { fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.observatory)); } else { var subjectSelector = _coreConfig.observatory.subjectSelector; subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue()) ?? []); fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList())); } } if (_coreConfig.burstObservatory != null) { if (fullConfigTemplateNode["burstObservatory"] == null) { fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.burstObservatory)); } else { var subjectSelector = _coreConfig.burstObservatory.subjectSelector; subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue()) ?? []); fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList())); } } var customOutboundsNode = new JsonArray(); foreach (var outbound in _coreConfig.outbounds) { if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom") { if (fullConfigTemplate.AddProxyOnly == true) { continue; } } else if (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty() && (outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true)) { var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address ?? outbound.settings?.vnext?.FirstOrDefault()?.address ?? string.Empty; if (!Utils.IsPrivateNetwork(outboundAddress)) { outbound.streamSettings ??= new StreamSettings4Ray(); outbound.streamSettings.sockopt ??= new Sockopt4Ray(); outbound.streamSettings.sockopt.dialerProxy = fullConfigTemplate.ProxyDetour; } } customOutboundsNode.Add(JsonUtils.DeepCopy(outbound)); } if (fullConfigTemplateNode["outbounds"] is JsonArray templateOutbounds) { foreach (var outbound in templateOutbounds) { customOutboundsNode.Add(outbound?.DeepClone()); } } fullConfigTemplateNode["outbounds"] = customOutboundsNode; return JsonUtils.Serialize(fullConfigTemplateNode); } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { private void GenDns() { try { var item = context.RawDnsItem; if (item is { Enabled: true }) { GenDnsCustom(); if (_coreConfig.routing.domainStrategy != Global.IPIfNonMatch) { return; } // DNS routing var dnsObj = JsonUtils.SerializeToNode(_coreConfig.dns); if (dnsObj == null) { return; } dnsObj["tag"] = Global.DnsTag; _coreConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(dnsObj)); _coreConfig.routing.rules.Add(new RulesItem4Ray { type = "field", inboundTag = new List { Global.DnsTag }, outboundTag = Global.ProxyTag, }); return; } var simpleDnsItem = context.SimpleDnsItem; var dnsItem = _coreConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray(); var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs; //Outbound Freedom domainStrategy if (strategy4Freedom.IsNotEmpty() && strategy4Freedom != Global.AsIs) { var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); if (outbound != null) { outbound.settings = new() { domainStrategy = strategy4Freedom, userLevel = 0 }; } } var strategy4Proxy = simpleDnsItem?.Strategy4Proxy ?? Global.AsIs; //Outbound Proxy domainStrategy if (strategy4Proxy.IsNotEmpty() && strategy4Proxy != Global.AsIs) { var xraySupportConfigTypeNames = Global.XraySupportConfigType .Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x]) .ToHashSet(); _coreConfig.outbounds .Where(t => xraySupportConfigTypeNames.Contains(t.protocol)) .ToList() .ForEach(outbound => outbound.targetStrategy = strategy4Proxy); } FillDnsServers(dnsItem); FillDnsHosts(dnsItem); dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null; dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null; // DNS routing var directDnsTags = dnsItem.servers .Select(server => { var tagNode = (server as JsonObject)?["tag"]; return tagNode is JsonValue value && value.TryGetValue(out var tag) ? tag : null; }) .Where(tag => tag is not null && tag.StartsWith(Global.DirectDnsTag, StringComparison.Ordinal)) .Select(tag => tag!) .ToList(); if (directDnsTags.Count > 0) { _coreConfig.routing.rules.Add(new() { type = "field", inboundTag = directDnsTags, outboundTag = Global.DirectTag, }); } var finalRule = BuildFinalRule(); dnsItem.tag = Global.DnsTag; _coreConfig.routing.rules.Add(new() { type = "field", inboundTag = [Global.DnsTag], outboundTag = finalRule.outboundTag, balancerTag = finalRule.balancerTag, }); _coreConfig.dns = dnsItem; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void FillDnsServers(Dns4Ray dnsItem) { var simpleDNSItem = context.SimpleDnsItem; var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.First()); var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.First()); var directDomainList = new List(); var directGeositeList = new List(); var proxyDomainList = new List(); var proxyGeositeList = new List(); var expectedDomainList = new List(); var expectedIPs = new List(); var regionNames = new HashSet(); var bootstrapDNSAddress = ParseDnsAddresses(simpleDNSItem?.BootstrapDNS, Global.DomainPureIPDNSAddress.First()); var dnsServerDomains = new List(); foreach (var dns in directDNSAddress) { var (domain, _, _, _) = Utils.ParseUrl(dns); if (domain == "localhost") { continue; } if (Utils.IsDomain(domain)) { dnsServerDomains.Add($"full:{domain}"); } } foreach (var dns in remoteDNSAddress) { var (domain, _, _, _) = Utils.ParseUrl(dns); if (domain == "localhost") { continue; } if (Utils.IsDomain(domain)) { dnsServerDomains.Add($"full:{domain}"); } } dnsServerDomains = dnsServerDomains.Distinct().ToList(); if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs)) { expectedIPs = simpleDNSItem.DirectExpectedIPs .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()) .Where(s => !string.IsNullOrEmpty(s)) .ToList(); foreach (var ip in expectedIPs) { if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase)) { var region = ip["geoip:".Length..]; if (!string.IsNullOrEmpty(region)) { regionNames.Add($"geosite:{region}"); regionNames.Add($"geosite:geolocation-{region}"); regionNames.Add($"geosite:tld-{region}"); } } } } var routing = context.RoutingItem; List? rules = null; rules = JsonUtils.Deserialize>(routing?.RuleSet) ?? []; foreach (var item in rules) { if (!item.Enabled || item.Domain is null || item.Domain.Count == 0) { continue; } if (item.RuleType == ERuleType.Routing) { continue; } foreach (var domain in item.Domain) { if (domain.StartsWith('#')) { continue; } var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ","); if (item.OutboundTag == Global.DirectTag) { if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:")) { (regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain); } else { directDomainList.Add(normalizedDomain); } } else if (item.OutboundTag != Global.BlockTag) { if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:")) { proxyGeositeList.Add(normalizedDomain); } else { proxyDomainList.Add(normalizedDomain); } } } } if (context.ProtectDomainList.Count > 0) { directDomainList.AddRange(context.ProtectDomainList); } dnsItem.servers ??= []; var directDnsTagIndex = 1; AddDnsServers(remoteDNSAddress, proxyDomainList); AddDnsServers(directDNSAddress, directDomainList, true); AddDnsServers(remoteDNSAddress, proxyGeositeList); AddDnsServers(directDNSAddress, directGeositeList, true); AddDnsServers(directDNSAddress, expectedDomainList, true, expectedIPs); if (dnsServerDomains.Count > 0) { AddDnsServers(bootstrapDNSAddress, dnsServerDomains); } var useDirectDns = false; if (rules?.LastOrDefault() is { } lastRule && lastRule.OutboundTag == Global.DirectTag) { var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0; var noProcess = lastRule.Process == null || lastRule.Process.Count == 0; var isAnyIp = lastRule.Ip == null || lastRule.Ip.Count == 0 || lastRule.Ip.Contains("0.0.0.0/0"); var isAnyPort = string.IsNullOrEmpty(lastRule.Port) || lastRule.Port == "0-65535"; var isAnyNetwork = string.IsNullOrEmpty(lastRule.Network) || lastRule.Network == "tcp,udp"; useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork; } if (!useDirectDns) { dnsItem.servers.AddRange(remoteDNSAddress); } else { foreach (var dns in directDNSAddress) { var dnsServer = CreateDnsServer(dns, []); dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}"; dnsServer.skipFallback = false; dnsItem.servers.Add(JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull })); } } return; static List ParseDnsAddresses(string? dnsInput, string defaultAddress) { var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';') .Select(addr => addr.Trim()) .Where(addr => !string.IsNullOrEmpty(addr)) .Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr) .Distinct() .ToList() ?? [defaultAddress]; return addresses.Count > 0 ? addresses : new List { defaultAddress }; } static DnsServer4Ray CreateDnsServer(string dnsAddress, List domains, List? expectedIPs = null) { var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress); var domainFinal = dnsAddress; int? portFinal = null; if (scheme.IsNullOrEmpty() || scheme.StartsWith("udp", StringComparison.OrdinalIgnoreCase)) { domainFinal = domain; portFinal = port > 0 ? port : null; } else if (scheme.StartsWith("tcp", StringComparison.OrdinalIgnoreCase)) { domainFinal = scheme + "://" + domain; portFinal = port > 0 ? port : null; } var dnsServer = new DnsServer4Ray { address = domainFinal, port = portFinal, skipFallback = true, domains = domains.Count > 0 ? domains : null, expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null }; return dnsServer; } void AddDnsServers(List dnsAddresses, List domains, bool isDirectDns = false, List? expectedIPs = null) { if (domains.Count <= 0) { return; } foreach (var dnsAddress in dnsAddresses) { var dnsServer = CreateDnsServer(dnsAddress, domains, expectedIPs); if (isDirectDns) { dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}"; } var dnsServerNode = JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); dnsItem.servers.Add(dnsServerNode); } } } private void FillDnsHosts(Dns4Ray dnsItem) { var simpleDNSItem = context.SimpleDnsItem; if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty()) { return; } dnsItem.hosts ??= new Dictionary(); if (simpleDNSItem.AddCommonHosts == true) { dnsItem.hosts = Global.PredefinedHosts.ToDictionary( kvp => kvp.Key, kvp => (object)kvp.Value ); } if (simpleDNSItem.UseSystemHosts == true) { var systemHosts = Utils.GetSystemHosts(); var normalHost = dnsItem.hosts; if (normalHost != null && systemHosts?.Count > 0) { foreach (var host in systemHosts) { normalHost.TryAdd(host.Key, new List { host.Value }); } } } foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts)) { dnsItem.hosts[kvp.Key] = kvp.Value; } } private void GenDnsCustom() { try { var item = context.RawDnsItem; var normalDNS = item?.NormalDNS; var domainStrategy4Freedom = item?.DomainStrategy4Freedom; if (normalDNS.IsNullOrEmpty()) { normalDNS = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); } //Outbound Freedom domainStrategy if (domainStrategy4Freedom.IsNotEmpty()) { var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); if (outbound != null) { outbound.settings = new(); outbound.settings.domainStrategy = domainStrategy4Freedom; outbound.settings.userLevel = 0; } } var obj = JsonUtils.ParseJson(normalDNS); if (obj is null) { List servers = []; var arrDNS = normalDNS.Split(','); foreach (var str in arrDNS) { servers.Add(str); } obj = JsonUtils.ParseJson("{}"); obj["servers"] = JsonUtils.SerializeToNode(servers); } // Append to dns settings if (item.UseSystemHosts) { var systemHosts = Utils.GetSystemHosts(); if (systemHosts.Count > 0) { var normalHost1 = obj["hosts"]; if (normalHost1 != null) { foreach (var host in systemHosts) { if (normalHost1[host.Key] != null) { continue; } normalHost1[host.Key] = host.Value; } } } } var normalHost = obj["hosts"]; if (normalHost != null) { foreach (var hostProp in normalHost.AsObject().ToList()) { if (hostProp.Value is JsonValue value && value.TryGetValue(out var ip)) { normalHost[hostProp.Key] = new JsonArray(ip); } } } FillDnsDomainsCustom(obj); _coreConfig.dns = obj; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void FillDnsDomainsCustom(JsonNode dns) { var servers = dns["servers"]; if (servers == null) { return; } var domainList = context.ProtectDomainList; if (domainList.Count <= 0) { return; } var dnsItem = context.RawDnsItem; var dnsServer = new DnsServer4Ray() { address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress, skipFallback = true, domains = domainList.ToList(), }; servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer)); } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { private void GenInbounds() { try { var listen = "0.0.0.0"; _coreConfig.inbounds = []; var inbound = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks, true); _coreConfig.inbounds.Add(inbound); if (_config.Inbound.First().SecondLocalPortEnabled) { var inbound2 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks2, true); _coreConfig.inbounds.Add(inbound2); } if (_config.Inbound.First().AllowLANConn) { if (_config.Inbound.First().NewPort4LAN) { var inbound3 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks3, true); inbound3.listen = listen; _coreConfig.inbounds.Add(inbound3); //auth if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) { inbound3.settings.auth = "password"; inbound3.settings.accounts = new List { new() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } }; } } else { inbound.listen = listen; } } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private Inbounds4Ray BuildInbound(InItem inItem, EInboundProtocol protocol, bool bSocks) { var result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound); if (result.IsNullOrEmpty()) { return new(); } var inbound = JsonUtils.Deserialize(result); if (inbound == null) { return new(); } inbound.tag = protocol.ToString(); inbound.port = inItem.LocalPort + (int)protocol; inbound.protocol = EInboundProtocol.mixed.ToString(); inbound.settings.udp = inItem.UdpEnabled; inbound.sniffing.enabled = inItem.SniffingEnabled; inbound.sniffing.destOverride = inItem.DestOverride; inbound.sniffing.routeOnly = inItem.RouteOnly; return inbound; } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { private void GenLog() { try { if (_config.CoreBasicItem.LogEnabled) { var dtNow = DateTime.Now; _coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel; _coreConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt"); _coreConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt"); } else { _coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel; _coreConfig.log.access = null; _coreConfig.log.error = null; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { private void GenOutbounds() { var proxyOutboundList = BuildAllProxyOutbounds(); _coreConfig.outbounds.InsertRange(0, proxyOutboundList); if (proxyOutboundList.Count(n => n.tag.StartsWith(Global.ProxyTag)) > 1) { var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; GenObservatory(multipleLoad); GenBalancer(multipleLoad); } } private List BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag) { var proxyOutboundList = new List(); if (_node.ConfigType.IsGroupType()) { proxyOutboundList.AddRange(BuildGroupProxyOutbounds(baseTagName)); } else { proxyOutboundList.Add(BuildProxyOutbound(baseTagName)); } if (_config.CoreBasicItem.EnableFragment) { var fragmentOutbound = new Outbounds4Ray { protocol = "freedom", tag = $"frag-{baseTagName}", settings = new() { fragment = new() { packets = _config.Fragment4RayItem?.Packets, length = _config.Fragment4RayItem?.Length, interval = _config.Fragment4RayItem?.Interval } } }; var actOutboundWithTlsList = proxyOutboundList.Where(n => n.streamSettings?.security.IsNullOrEmpty() == false && (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)).ToList(); if (actOutboundWithTlsList.Count > 0) { proxyOutboundList.Add(fragmentOutbound); } foreach (var outbound in actOutboundWithTlsList) { outbound.streamSettings.sockopt = new() { dialerProxy = fragmentOutbound.tag }; } } return proxyOutboundList; } private List BuildGroupProxyOutbounds(string baseTagName = Global.ProxyTag) { var proxyOutboundList = new List(); switch (_node.ConfigType) { case EConfigType.PolicyGroup: proxyOutboundList.AddRange(BuildOutboundsList(baseTagName)); break; case EConfigType.ProxyChain: proxyOutboundList.AddRange(BuildChainOutboundsList(baseTagName)); break; } return proxyOutboundList; } private Outbounds4Ray BuildProxyOutbound(string baseTagName = Global.ProxyTag) { var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); var outbound = JsonUtils.Deserialize(txtOutbound); FillOutbound(outbound); outbound.tag = baseTagName; return outbound; } private void FillOutbound(Outbounds4Ray outbound) { try { var protocolExtra = _node.GetProtocolExtra(); var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; switch (_node.ConfigType) { case EConfigType.VMess: { VnextItem4Ray vnextItem; if (outbound.settings.vnext.Count <= 0) { vnextItem = new VnextItem4Ray(); outbound.settings.vnext.Add(vnextItem); } else { vnextItem = outbound.settings.vnext.First(); } vnextItem.address = _node.Address; vnextItem.port = _node.Port; UsersItem4Ray usersItem; if (vnextItem.users.Count <= 0) { usersItem = new UsersItem4Ray(); vnextItem.users.Add(usersItem); } else { usersItem = vnextItem.users.First(); } usersItem.id = _node.Password; usersItem.alterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0; usersItem.email = Global.UserEMail; if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity)) { usersItem.security = protocolExtra.VmessSecurity; } else { usersItem.security = Global.DefaultSecurity; } FillOutboundMux(outbound, muxEnabled, muxEnabled); outbound.settings.servers = null; break; } case EConfigType.Shadowsocks: { ServersItem4Ray serversItem; if (outbound.settings.servers.Count <= 0) { serversItem = new ServersItem4Ray(); outbound.settings.servers.Add(serversItem); } else { serversItem = outbound.settings.servers.First(); } serversItem.address = _node.Address; serversItem.port = _node.Port; serversItem.password = _node.Password; serversItem.method = AppManager.Instance.GetShadowsocksSecurities(_node).Contains(protocolExtra.SsMethod) ? protocolExtra.SsMethod : "none"; serversItem.ota = false; serversItem.level = 1; FillOutboundMux(outbound); outbound.settings.vnext = null; break; } case EConfigType.SOCKS: case EConfigType.HTTP: { ServersItem4Ray serversItem; if (outbound.settings.servers.Count <= 0) { serversItem = new ServersItem4Ray(); outbound.settings.servers.Add(serversItem); } else { serversItem = outbound.settings.servers.First(); } serversItem.address = _node.Address; serversItem.port = _node.Port; serversItem.method = null; serversItem.password = null; if (_node.Username.IsNotEmpty() && _node.Password.IsNotEmpty()) { SocksUsersItem4Ray socksUsersItem = new() { user = _node.Username ?? "", pass = _node.Password, level = 1 }; serversItem.users = new List() { socksUsersItem }; } FillOutboundMux(outbound); outbound.settings.vnext = null; break; } case EConfigType.VLESS: { VnextItem4Ray vnextItem; if (outbound.settings.vnext?.Count <= 0) { vnextItem = new VnextItem4Ray(); outbound.settings.vnext.Add(vnextItem); } else { vnextItem = outbound.settings.vnext.First(); } vnextItem.address = _node.Address; vnextItem.port = _node.Port; UsersItem4Ray usersItem; if (vnextItem.users.Count <= 0) { usersItem = new UsersItem4Ray(); vnextItem.users.Add(usersItem); } else { usersItem = vnextItem.users.First(); } usersItem.id = _node.Password; usersItem.email = Global.UserEMail; usersItem.encryption = protocolExtra.VlessEncryption; if (!protocolExtra.Flow.IsNullOrEmpty()) { usersItem.flow = protocolExtra.Flow; } else { FillOutboundMux(outbound, false, muxEnabled); } outbound.settings.servers = null; break; } case EConfigType.Trojan: { ServersItem4Ray serversItem; if (outbound.settings.servers.Count <= 0) { serversItem = new ServersItem4Ray(); outbound.settings.servers.Add(serversItem); } else { serversItem = outbound.settings.servers.First(); } serversItem.address = _node.Address; serversItem.port = _node.Port; serversItem.password = _node.Password; serversItem.ota = false; serversItem.level = 1; FillOutboundMux(outbound); outbound.settings.vnext = null; break; } case EConfigType.Hysteria2: { outbound.settings = new() { version = 2, address = _node.Address, port = _node.Port, }; outbound.settings.vnext = null; outbound.settings.servers = null; break; } case EConfigType.WireGuard: { var address = _node.Address; if (Utils.IsIpv6(address)) { address = $"[{address}]"; } var peer = new WireguardPeer4Ray { publicKey = protocolExtra.WgPublicKey ?? "", endpoint = address + ":" + _node.Port.ToString() }; var setting = new Outboundsettings4Ray { address = Utils.String2List(protocolExtra.WgInterfaceAddress), secretKey = _node.Password, reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(), mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(), peers = [peer] }; outbound.settings = setting; outbound.settings.vnext = null; outbound.settings.servers = null; break; } } outbound.protocol = Global.ProtocolTypes[_node.ConfigType]; if (_node.ConfigType == EConfigType.Hysteria2) { outbound.protocol = "hysteria"; } FillBoundStreamSettings(outbound); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void FillOutboundMux(Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false) { try { outbound.mux.enabled = false; outbound.mux.concurrency = -1; if (enabledTCP) { outbound.mux.enabled = true; outbound.mux.concurrency = _config.Mux4RayItem.Concurrency; } else if (enabledUDP) { outbound.mux.enabled = true; outbound.mux.xudpConcurrency = _config.Mux4RayItem.XudpConcurrency; outbound.mux.xudpProxyUDP443 = _config.Mux4RayItem.XudpProxyUDP443; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void FillBoundStreamSettings(Outbounds4Ray outbound) { try { var streamSettings = outbound.streamSettings; var network = _node.GetNetwork(); if (_node.ConfigType == EConfigType.Hysteria2) { network = "hysteria"; } streamSettings.network = network; var host = _node.RequestHost.TrimEx(); var path = _node.Path.TrimEx(); var sni = _node.Sni.TrimEx(); var useragent = ""; if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty()) { useragent = Global.UserAgentTexts.GetValueOrDefault(_config.CoreBasicItem.DefUserAgent, _config.CoreBasicItem.DefUserAgent); } //if tls if (_node.StreamSecurity == Global.StreamSecurity) { streamSettings.security = _node.StreamSecurity; TlsSettings4Ray tlsSettings = new() { allowInsecure = Utils.ToBool(_node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : _node.AllowInsecure), alpn = _node.GetAlpn(), fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint, echConfigList = _node.EchConfigList.NullIfEmpty(), echForceQuery = _node.EchForceQuery.NullIfEmpty() }; if (sni.IsNotEmpty()) { tlsSettings.serverName = sni; } else if (host.IsNotEmpty()) { tlsSettings.serverName = Utils.String2List(host)?.First(); } var certs = CertPemManager.ParsePemChain(_node.Cert); if (certs.Count > 0) { var certsettings = new List(); foreach (var cert in certs) { var certPerLine = cert.Split("\n").ToList(); certsettings.Add(new CertificateSettings4Ray { certificate = certPerLine, usage = "verify", }); } tlsSettings.certificates = certsettings; tlsSettings.disableSystemRoot = true; tlsSettings.allowInsecure = false; } else if (!_node.CertSha.IsNullOrEmpty()) { tlsSettings.pinnedPeerCertSha256 = _node.CertSha; tlsSettings.allowInsecure = false; } streamSettings.tlsSettings = tlsSettings; } //if Reality if (_node.StreamSecurity == Global.StreamSecurityReality) { streamSettings.security = _node.StreamSecurity; TlsSettings4Ray realitySettings = new() { fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint, serverName = sni, publicKey = _node.PublicKey, shortId = _node.ShortId, spiderX = _node.SpiderX, mldsa65Verify = _node.Mldsa65Verify, show = false, }; streamSettings.realitySettings = realitySettings; } //streamSettings switch (network) { case nameof(ETransport.kcp): KcpSettings4Ray kcpSettings = new() { mtu = _config.KcpItem.Mtu, tti = _config.KcpItem.Tti }; kcpSettings.uplinkCapacity = _config.KcpItem.UplinkCapacity; kcpSettings.downlinkCapacity = _config.KcpItem.DownlinkCapacity; kcpSettings.congestion = _config.KcpItem.Congestion; kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize; kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize; streamSettings.finalmask ??= new(); if (Global.KcpHeaderMaskMap.TryGetValue(_node.HeaderType, out var header)) { streamSettings.finalmask.udp = [ new Mask4Ray { type = header, settings = _node.HeaderType == "dns" && !host.IsNullOrEmpty() ? new MaskSettings4Ray { domain = host } : null } ]; } streamSettings.finalmask.udp ??= []; if (path.IsNullOrEmpty()) { streamSettings.finalmask.udp.Add(new Mask4Ray { type = "mkcp-original" }); } else { streamSettings.finalmask.udp.Add(new Mask4Ray { type = "mkcp-aes128gcm", settings = new MaskSettings4Ray { password = path } }); } streamSettings.kcpSettings = kcpSettings; break; //ws case nameof(ETransport.ws): WsSettings4Ray wsSettings = new(); wsSettings.headers = new Headers4Ray(); if (host.IsNotEmpty()) { wsSettings.host = host; } if (path.IsNotEmpty()) { wsSettings.path = path; } if (useragent.IsNotEmpty()) { wsSettings.headers.UserAgent = useragent; } streamSettings.wsSettings = wsSettings; break; //httpupgrade case nameof(ETransport.httpupgrade): HttpupgradeSettings4Ray httpupgradeSettings = new(); if (path.IsNotEmpty()) { httpupgradeSettings.path = path; } if (host.IsNotEmpty()) { httpupgradeSettings.host = host; } streamSettings.httpupgradeSettings = httpupgradeSettings; break; //xhttp case nameof(ETransport.xhttp): streamSettings.network = ETransport.xhttp.ToString(); XhttpSettings4Ray xhttpSettings = new(); if (path.IsNotEmpty()) { xhttpSettings.path = path; } if (host.IsNotEmpty()) { xhttpSettings.host = host; } if (_node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(_node.HeaderType)) { xhttpSettings.mode = _node.HeaderType; } if (_node.Extra.IsNotEmpty()) { xhttpSettings.extra = JsonUtils.ParseJson(_node.Extra); } streamSettings.xhttpSettings = xhttpSettings; FillOutboundMux(outbound); break; //h2 case nameof(ETransport.h2): HttpSettings4Ray httpSettings = new(); if (host.IsNotEmpty()) { httpSettings.host = Utils.String2List(host); } httpSettings.path = path; streamSettings.httpSettings = httpSettings; break; //quic case nameof(ETransport.quic): QuicSettings4Ray quicsettings = new() { security = host, key = path, header = new Header4Ray { type = _node.HeaderType } }; streamSettings.quicSettings = quicsettings; if (_node.StreamSecurity == Global.StreamSecurity) { if (sni.IsNotEmpty()) { streamSettings.tlsSettings.serverName = sni; } else { streamSettings.tlsSettings.serverName = _node.Address; } } break; case nameof(ETransport.grpc): GrpcSettings4Ray grpcSettings = new() { authority = host.NullIfEmpty(), serviceName = path, multiMode = _node.HeaderType == Global.GrpcMultiMode, idle_timeout = _config.GrpcItem.IdleTimeout, health_check_timeout = _config.GrpcItem.HealthCheckTimeout, permit_without_stream = _config.GrpcItem.PermitWithoutStream, initial_windows_size = _config.GrpcItem.InitialWindowsSize, }; streamSettings.grpcSettings = grpcSettings; break; case "hysteria": var protocolExtra = _node.GetProtocolExtra(); var ports = protocolExtra?.Ports; int? upMbps = protocolExtra?.UpMbps is { } su and >= 0 ? su : _config.HysteriaItem.UpMbps; int? downMbps = protocolExtra?.DownMbps is { } sd and >= 0 ? sd : _config.HysteriaItem.UpMbps; var hopInterval = !protocolExtra.HopInterval.IsNullOrEmpty() ? protocolExtra.HopInterval : (_config.HysteriaItem.HopInterval >= 5 ? _config.HysteriaItem.HopInterval : Global.Hysteria2DefaultHopInt).ToString(); HysteriaUdpHop4Ray? udpHop = null; if (!ports.IsNullOrEmpty() && (ports.Contains(':') || ports.Contains('-') || ports.Contains(','))) { udpHop = new HysteriaUdpHop4Ray { port = ports.Replace(':', '-'), interval = hopInterval, }; } streamSettings.hysteriaSettings = new() { version = 2, auth = _node.Password, up = upMbps > 0 ? $"{upMbps}mbps" : null, down = downMbps > 0 ? $"{downMbps}mbps" : null, udphop = udpHop, }; if (!protocolExtra.SalamanderPass.IsNullOrEmpty()) { streamSettings.finalmask ??= new(); streamSettings.finalmask.udp = [ new Mask4Ray { type = "salamander", settings = new MaskSettings4Ray { password = protocolExtra.SalamanderPass.TrimEx(), } } ]; } break; default: //tcp if (_node.HeaderType == Global.TcpHeaderHttp) { TcpSettings4Ray tcpSettings = new() { header = new Header4Ray { type = _node.HeaderType } }; //request Host var request = EmbedUtils.GetEmbedText(Global.V2raySampleHttpRequestFileName); var arrHost = host.Split(','); var host2 = string.Join(",".AppendQuotes(), arrHost); request = request.Replace("$requestHost$", $"{host2.AppendQuotes()}"); request = request.Replace("$requestUserAgent$", $"{useragent.AppendQuotes()}"); //Path var pathHttp = @"/"; if (path.IsNotEmpty()) { var arrPath = path.Split(','); pathHttp = string.Join(",".AppendQuotes(), arrPath); } request = request.Replace("$requestPath$", $"{pathHttp.AppendQuotes()}"); tcpSettings.header.request = JsonUtils.Deserialize(request); streamSettings.tcpSettings = tcpSettings; } break; } if (!_node.Finalmask.IsNullOrEmpty()) { streamSettings.finalmask = JsonUtils.Deserialize(_node.Finalmask); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private List BuildOutboundsList(string baseTagName = Global.ProxyTag) { var nodes = new List(); foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) { if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) { nodes.Add(node); } } var resultOutbounds = new List(); for (var i = 0; i < nodes.Count; i++) { var node = nodes[i]; var currentTag = $"{baseTagName}-{i + 1}"; if (node.ConfigType.IsGroupType()) { var childProfiles = new CoreConfigV2rayService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); resultOutbounds.AddRange(childProfiles); continue; } var outbound = new CoreConfigV2rayService(context with { Node = node, }).BuildProxyOutbound(); outbound.tag = currentTag; resultOutbounds.Add(outbound); } return resultOutbounds; } private List BuildChainOutboundsList(string baseTagName = Global.ProxyTag) { var nodes = new List(); foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) { if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) { nodes.Add(node); } } // Based on actual network flow instead of data packets var nodesReverse = nodes.AsEnumerable().Reverse().ToList(); var resultOutbounds = new List(); for (var i = 0; i < nodesReverse.Count; i++) { var node = nodesReverse[i]; var currentTag = i == 0 ? baseTagName : $"chain-{baseTagName}-{i}"; var dialerProxyTag = i != nodesReverse.Count - 1 ? $"chain-{baseTagName}-{i + 1}" : null; if (node.ConfigType.IsGroupType()) { var childProfiles = new CoreConfigV2rayService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); if (!dialerProxyTag.IsNullOrEmpty()) { var chainEndNodes = childProfiles.Where(n => n?.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true); foreach (var chainEndNode in chainEndNodes) { chainEndNode.streamSettings.sockopt = new() { dialerProxy = dialerProxyTag }; } } if (i != 0) { var chainStartNodes = childProfiles.Where(n => n.tag.StartsWith(currentTag)).ToList(); if (chainStartNodes.Count == 1) { foreach (var existedChainEndNode in resultOutbounds.Where(n => n.streamSettings?.sockopt?.dialerProxy == currentTag)) { existedChainEndNode.streamSettings.sockopt = new() { dialerProxy = chainStartNodes.First().tag }; } } else if (chainStartNodes.Count > 1) { var existedChainNodes = JsonUtils.DeepCopy(resultOutbounds); resultOutbounds.Clear(); var j = 0; foreach (var chainStartNode in chainStartNodes) { var existedChainNodesClone = JsonUtils.DeepCopy(existedChainNodes); foreach (var existedChainNode in existedChainNodesClone) { var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}"; existedChainNode.tag = cloneTag; } for (var k = 0; k < existedChainNodesClone.Count; k++) { var existedChainNode = existedChainNodesClone[k]; var previousDialerProxyTag = existedChainNode.streamSettings?.sockopt?.dialerProxy; var nextTag = k + 1 < existedChainNodesClone.Count ? existedChainNodesClone[k + 1].tag : chainStartNode.tag; existedChainNode.streamSettings.sockopt = new() { dialerProxy = (previousDialerProxyTag == currentTag) ? chainStartNode.tag : nextTag }; resultOutbounds.Add(existedChainNode); } j++; } } } resultOutbounds.AddRange(childProfiles); continue; } var outbound = new CoreConfigV2rayService(context with { Node = node, }).BuildProxyOutbound(); outbound.tag = currentTag; if (!dialerProxyTag.IsNullOrEmpty()) { outbound.streamSettings.sockopt = new() { dialerProxy = dialerProxyTag }; } resultOutbounds.Add(outbound); } return resultOutbounds; } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { private void GenRouting() { try { if (_coreConfig.routing?.rules != null) { _coreConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy; var routing = context.RoutingItem; if (routing != null) { if (routing.DomainStrategy.IsNotEmpty()) { _coreConfig.routing.domainStrategy = routing.DomainStrategy; } var rules = JsonUtils.Deserialize>(routing.RuleSet); foreach (var item in rules) { if (!item.Enabled) { continue; } if (item.RuleType == ERuleType.DNS) { continue; } var item2 = JsonUtils.Deserialize(JsonUtils.Serialize(item)); GenRoutingUserRule(item2); } } var balancerTagList = _coreConfig.routing.balancers ?.Select(p => p.tag) .ToList() ?? []; if (balancerTagList.Count > 0) { foreach (var rulesItem in _coreConfig.routing.rules.Where(r => balancerTagList.Contains(r.outboundTag + Global.BalancerTagSuffix))) { rulesItem.balancerTag = rulesItem.outboundTag + Global.BalancerTagSuffix; rulesItem.outboundTag = null; } } } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void GenRoutingUserRule(RulesItem4Ray? userRule) { try { if (userRule == null) { return; } userRule.outboundTag = GenRoutingUserRuleOutbound(userRule.outboundTag ?? Global.ProxyTag); if (userRule.port.IsNullOrEmpty()) { userRule.port = null; } if (userRule.network.IsNullOrEmpty()) { userRule.network = null; } if (userRule.domain?.Count == 0) { userRule.domain = null; } if (userRule.ip?.Count == 0) { userRule.ip = null; } if (userRule.protocol?.Count == 0) { userRule.protocol = null; } if (userRule.inboundTag?.Count == 0) { userRule.inboundTag = null; } if (userRule.process?.Count == 0) { userRule.process = null; } var hasDomainIp = false; if (userRule.domain?.Count > 0) { var it = JsonUtils.DeepCopy(userRule); it.ip = null; it.process = null; it.type = "field"; for (var k = it.domain.Count - 1; k >= 0; k--) { if (it.domain[k].StartsWith('#')) { it.domain.RemoveAt(k); } it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ","); } _coreConfig.routing.rules.Add(it); hasDomainIp = true; } if (userRule.ip?.Count > 0) { var it = JsonUtils.DeepCopy(userRule); it.domain = null; it.process = null; it.type = "field"; _coreConfig.routing.rules.Add(it); hasDomainIp = true; } if (userRule.process?.Count > 0) { var it = JsonUtils.DeepCopy(userRule); it.domain = null; it.ip = null; it.type = "field"; _coreConfig.routing.rules.Add(it); hasDomainIp = true; } if (!hasDomainIp) { if (userRule.port.IsNotEmpty() || userRule.protocol?.Count > 0 || userRule.inboundTag?.Count > 0 || userRule.network != null ) { var it = JsonUtils.DeepCopy(userRule); it.type = "field"; _coreConfig.routing.rules.Add(it); } } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private string GenRoutingUserRuleOutbound(string outboundTag) { if (Global.OutboundTags.Contains(outboundTag)) { return outboundTag; } var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}"); if (node == null || (!Global.XraySupportConfigType.Contains(node.ConfigType) && !node.ConfigType.IsGroupType())) { return Global.ProxyTag; } var tag = $"{node.IndexId}-{Global.ProxyTag}"; if (_coreConfig.outbounds.Any(p => p.tag.StartsWith(tag))) { return tag; } var proxyOutbounds = new CoreConfigV2rayService(context with { Node = node, }).BuildAllProxyOutbounds(tag); _coreConfig.outbounds.AddRange(proxyOutbounds); if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1) { var multipleLoad = node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; GenObservatory(multipleLoad, tag); GenBalancer(multipleLoad, tag); } return tag; } private RulesItem4Ray BuildFinalRule() { var finalRule = new RulesItem4Ray() { type = "field", network = "tcp,udp", outboundTag = Global.ProxyTag, }; var balancer = _coreConfig?.routing?.balancers?.FirstOrDefault(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix, null); var domainStrategy = _coreConfig.routing?.domainStrategy ?? Global.AsIs; if (balancer is not null) { finalRule.outboundTag = null; finalRule.balancerTag = balancer.tag; } if (domainStrategy == Global.IPIfNonMatch) { finalRule.network = null; finalRule.ip = ["0.0.0.0/0", "::/0"]; } return finalRule; } } ================================================ FILE: v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs ================================================ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { private void GenStatistic() { if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) { var tag = EInboundProtocol.api.ToString(); Metrics4Ray apiObj = new(); Policy4Ray policyObj = new(); SystemPolicy4Ray policySystemSetting = new(); _coreConfig.stats = new Stats4Ray(); apiObj.tag = tag; _coreConfig.metrics = apiObj; policySystemSetting.statsOutboundDownlink = true; policySystemSetting.statsOutboundUplink = true; policyObj.system = policySystemSetting; _coreConfig.policy = policyObj; if (!_coreConfig.inbounds.Exists(item => item.tag == tag)) { Inbounds4Ray apiInbound = new(); Inboundsettings4Ray apiInboundSettings = new(); apiInbound.tag = tag; apiInbound.listen = Global.Loopback; apiInbound.port = AppManager.Instance.StatePort; apiInbound.protocol = Global.InboundAPIProtocol; apiInboundSettings.address = Global.Loopback; apiInbound.settings = apiInboundSettings; _coreConfig.inbounds.Add(apiInbound); } if (!_coreConfig.routing.rules.Exists(item => item.outboundTag == tag)) { RulesItem4Ray apiRoutingRule = new() { inboundTag = new List { tag }, outboundTag = tag, type = "field" }; _coreConfig.routing.rules.Add(apiRoutingRule); } } } } ================================================ FILE: v2rayN/ServiceLib/Services/DownloadService.cs ================================================ using System.Net.Http.Headers; namespace ServiceLib.Services; /// ///Download /// public class DownloadService { public event EventHandler? UpdateCompleted; public event ErrorEventHandler? Error; private static readonly string _tag = "DownloadService"; public async Task DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Func updateFunc) { try { var progress = new Progress(); progress.ProgressChanged += (sender, value) => updateFunc?.Invoke(false, $"{value}"); await DownloaderHelper.Instance.DownloadDataAsync4Speed(webProxy, url, progress, downloadTimeout); } catch (Exception ex) { await updateFunc?.Invoke(false, ex.Message); if (ex.InnerException != null) { await updateFunc?.Invoke(false, ex.InnerException.Message); } } return 0; } public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout) { try { UpdateCompleted?.Invoke(this, new UpdateResult(false, $"{ResUI.Downloading} {url}")); var progress = new Progress(); progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new UpdateResult(value > 100, $"...{value}%")); var webProxy = await GetWebProxy(blProxy); await DownloaderHelper.Instance.DownloadFileAsync(webProxy, url, fileName, progress, downloadTimeout); } catch (Exception ex) { Logging.SaveLog(_tag, ex); Error?.Invoke(this, new ErrorEventArgs(ex)); if (ex.InnerException != null) { Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } } } public async Task UrlRedirectAsync(string url, bool blProxy) { var webRequestHandler = new SocketsHttpHandler { AllowAutoRedirect = false, Proxy = await GetWebProxy(blProxy) }; var client = new HttpClient(webRequestHandler); var response = await client.GetAsync(url); if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null) { return response.Headers.Location.ToString(); } else { Error?.Invoke(this, new ErrorEventArgs(new Exception("StatusCode error: " + response.StatusCode))); Logging.SaveLog("StatusCode error: " + url); return null; } } public async Task TryDownloadString(string url, bool blProxy, string userAgent) { try { var result1 = await DownloadStringAsync(url, blProxy, userAgent, 15); if (result1.IsNotEmpty()) { return result1; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); Error?.Invoke(this, new ErrorEventArgs(ex)); if (ex.InnerException != null) { Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } } try { var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent, 15); if (result2.IsNotEmpty()) { return result2; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); Error?.Invoke(this, new ErrorEventArgs(ex)); if (ex.InnerException != null) { Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } } return null; } /// /// DownloadString /// /// private async Task DownloadStringAsync(string url, bool blProxy, string userAgent, int timeout) { try { var webProxy = await GetWebProxy(blProxy); var client = new HttpClient(new SocketsHttpHandler() { Proxy = webProxy, UseProxy = webProxy != null }); if (userAgent.IsNullOrEmpty()) { userAgent = Utils.GetVersion(false); } client.DefaultRequestHeaders.UserAgent.TryParseAdd(userAgent); Uri uri = new(url); //Authorization Header if (uri.UserInfo.IsNotEmpty()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Utils.Base64Encode(uri.UserInfo)); } using var cts = new CancellationTokenSource(); var result = await client.GetStringAsync(url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token); return result; } catch (Exception ex) { Logging.SaveLog(_tag, ex); Error?.Invoke(this, new ErrorEventArgs(ex)); if (ex.InnerException != null) { Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } } return null; } /// /// DownloadString /// /// private async Task DownloadStringViaDownloader(string url, bool blProxy, string userAgent, int timeout) { try { var webProxy = await GetWebProxy(blProxy); if (userAgent.IsNullOrEmpty()) { userAgent = Utils.GetVersion(false); } var result = await DownloaderHelper.Instance.DownloadStringAsync(webProxy, url, userAgent, timeout); return result; } catch (Exception ex) { Logging.SaveLog(_tag, ex); Error?.Invoke(this, new ErrorEventArgs(ex)); if (ex.InnerException != null) { Error?.Invoke(this, new ErrorEventArgs(ex.InnerException)); } } return null; } private async Task GetWebProxy(bool blProxy) { if (!blProxy) { return null; } var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); if (await SocketCheck(Global.Loopback, port) == false) { return null; } return new WebProxy($"socks5://{Global.Loopback}:{port}"); } private async Task SocketCheck(string ip, int port) { try { IPEndPoint point = new(IPAddress.Parse(ip), port); using Socket? sock = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(point); return true; } catch (Exception) { return false; } } } ================================================ FILE: v2rayN/ServiceLib/Services/ProcessService.cs ================================================ namespace ServiceLib.Services; public class ProcessService : IDisposable { private readonly Process _process; private readonly Func? _updateFunc; private bool _isDisposed; public int Id => _process.Id; public IntPtr Handle => _process.Handle; public bool HasExited => _process.HasExited; public ProcessService( string fileName, string arguments, string workingDirectory, bool displayLog, bool redirectInput, Dictionary? environmentVars, Func? updateFunc) { _updateFunc = updateFunc; _process = new Process { StartInfo = new ProcessStartInfo { FileName = fileName, Arguments = arguments, WorkingDirectory = workingDirectory, UseShellExecute = false, RedirectStandardInput = redirectInput, RedirectStandardOutput = displayLog, RedirectStandardError = displayLog, CreateNoWindow = true, StandardOutputEncoding = displayLog ? Encoding.UTF8 : null, StandardErrorEncoding = displayLog ? Encoding.UTF8 : null, }, EnableRaisingEvents = true }; if (environmentVars != null) { foreach (var kv in environmentVars) { _process.StartInfo.Environment[kv.Key] = kv.Value; } } if (displayLog) { RegisterEventHandlers(); } } public async Task StartAsync(string pwd = null) { _process.Start(); if (_process.StartInfo.RedirectStandardOutput) { _process.BeginOutputReadLine(); _process.BeginErrorReadLine(); } if (_process.StartInfo.RedirectStandardInput) { await Task.Delay(10); await _process.StandardInput.WriteLineAsync(pwd); } } public async Task StopAsync() { if (_process.HasExited) { return; } try { if (_process.StartInfo.RedirectStandardOutput) { try { _process.CancelOutputRead(); } catch { } try { _process.CancelErrorRead(); } catch { } } try { if (Utils.IsNonWindows()) { _process.Kill(true); } } catch { } try { _process.Kill(); } catch { } await Task.Delay(100); } catch (Exception ex) { await _updateFunc?.Invoke(true, ex.Message); } } private void RegisterEventHandlers() { void dataHandler(object sender, DataReceivedEventArgs e) { if (e.Data.IsNotEmpty()) { _ = _updateFunc?.Invoke(false, e.Data + Environment.NewLine); } } _process.OutputDataReceived += dataHandler; _process.ErrorDataReceived += dataHandler; _process.Exited += (s, e) => { try { _process.OutputDataReceived -= dataHandler; _process.ErrorDataReceived -= dataHandler; } catch { } }; } public void Dispose() { if (_isDisposed) { return; } try { if (!_process.HasExited) { try { _process.CancelOutputRead(); } catch { } try { _process.CancelErrorRead(); } catch { } _process.Kill(); } _process.Dispose(); } catch (Exception ex) { _updateFunc?.Invoke(true, ex.Message); } _isDisposed = true; GC.SuppressFinalize(this); } } ================================================ FILE: v2rayN/ServiceLib/Services/SpeedtestService.cs ================================================ namespace ServiceLib.Services; public class SpeedtestService(Config config, Func updateFunc) { private static readonly string _tag = "SpeedtestService"; private readonly Config? _config = config; private readonly Func? _updateFunc = updateFunc; private static readonly ConcurrentBag _lstExitLoop = new(); public void RunLoop(ESpeedActionType actionType, List selecteds) { Task.Run(async () => { await RunAsync(actionType, selecteds); await ProfileExManager.Instance.SaveTo(); await UpdateFunc("", ResUI.SpeedtestingCompleted); }); } public void ExitLoop() { if (!_lstExitLoop.IsEmpty) { _ = UpdateFunc("", ResUI.SpeedtestingStop); _lstExitLoop.Clear(); } } private static bool ShouldStopTest(string exitLoopKey) { return !_lstExitLoop.Any(p => p == exitLoopKey); } private async Task RunAsync(ESpeedActionType actionType, List selecteds) { var exitLoopKey = Utils.GetGuid(false); _lstExitLoop.Add(exitLoopKey); var lstSelected = await GetClearItem(actionType, selecteds); switch (actionType) { case ESpeedActionType.Tcping: await RunTcpingAsync(lstSelected); break; case ESpeedActionType.Realping: await RunRealPingBatchAsync(lstSelected, exitLoopKey); break; case ESpeedActionType.Speedtest: await RunMixedTestAsync(lstSelected, 1, true, exitLoopKey); break; case ESpeedActionType.Mixedtest: await RunMixedTestAsync(lstSelected, _config.SpeedTestItem.MixedConcurrencyCount, true, exitLoopKey); break; } } private async Task> GetClearItem(ESpeedActionType actionType, List selecteds) { var lstSelected = new List(selecteds.Count); var ids = selecteds.Where(it => !it.IndexId.IsNullOrEmpty() && it.ConfigType != EConfigType.Custom && (it.ConfigType.IsComplexType() || it.Port > 0)) .Select(it => it.IndexId) .ToList(); var profileMap = await AppManager.Instance.GetProfileItemsByIndexIdsAsMap(ids); for (var i = 0; i < selecteds.Count; i++) { var it = selecteds[i]; if (it.ConfigType == EConfigType.Custom) { continue; } if (!it.ConfigType.IsComplexType() && it.Port <= 0) { continue; } var profile = profileMap.GetValueOrDefault(it.IndexId, it); lstSelected.Add(new ServerTestItem() { IndexId = it.IndexId, Address = it.Address, Port = it.Port, ConfigType = it.ConfigType, QueueNum = i, Profile = profile, CoreType = AppManager.Instance.GetCoreType(profile, it.ConfigType), }); } //clear test result foreach (var it in lstSelected) { switch (actionType) { case ESpeedActionType.Tcping: case ESpeedActionType.Realping: await UpdateFunc(it.IndexId, ResUI.Speedtesting, ""); ProfileExManager.Instance.SetTestDelay(it.IndexId, 0); break; case ESpeedActionType.Speedtest: await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingWait); ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0); break; case ESpeedActionType.Mixedtest: await UpdateFunc(it.IndexId, ResUI.Speedtesting, ResUI.SpeedtestingWait); ProfileExManager.Instance.SetTestDelay(it.IndexId, 0); ProfileExManager.Instance.SetTestSpeed(it.IndexId, 0); break; } } if (lstSelected.Count > 1 && (actionType == ESpeedActionType.Speedtest || actionType == ESpeedActionType.Mixedtest)) { NoticeManager.Instance.Enqueue(ResUI.SpeedtestingPressEscToExit); } return lstSelected; } private async Task RunTcpingAsync(List selecteds) { List tasks = []; foreach (var it in selecteds) { tasks.Add(Task.Run(async () => { try { var responseTime = await GetTcpingTime(it.Address, it.Port); ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime); await UpdateFunc(it.IndexId, responseTime.ToString()); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } })); } await Task.WhenAll(tasks); } private async Task RunRealPingBatchAsync(List lstSelected, string exitLoopKey, int pageSize = 0) { if (pageSize <= 0) { pageSize = lstSelected.Count < Global.SpeedTestPageSize ? lstSelected.Count : Global.SpeedTestPageSize; } var lstTest = GetTestBatchItem(lstSelected, pageSize); List lstFailed = new(); foreach (var lst in lstTest) { var ret = await RunRealPingAsync(lst, exitLoopKey); if (ret == false) { lstFailed.AddRange(lst); } await Task.Delay(100); } //Retest the failed part var pageSizeNext = pageSize / 2; if (lstFailed.Count > 0 && pageSizeNext > 0) { if (ShouldStopTest(exitLoopKey)) { await UpdateFunc("", ResUI.SpeedtestingSkip); return; } await UpdateFunc("", string.Format(ResUI.SpeedtestingTestFailedPart, lstFailed.Count)); if (pageSizeNext > _config.SpeedTestItem.MixedConcurrencyCount) { await RunRealPingBatchAsync(lstFailed, exitLoopKey, pageSizeNext); } else { await RunMixedTestAsync(lstSelected, _config.SpeedTestItem.MixedConcurrencyCount, false, exitLoopKey); } } } private async Task RunRealPingAsync(List selecteds, string exitLoopKey) { ProcessService processService = null; try { processService = await CoreManager.Instance.LoadCoreConfigSpeedtest(selecteds); if (processService is null) { return false; } await Task.Delay(1000); List tasks = new(); foreach (var it in selecteds) { if (!it.AllowTest) { await UpdateFunc(it.IndexId, ResUI.SpeedtestingSkip); continue; } if (ShouldStopTest(exitLoopKey)) { return false; } tasks.Add(Task.Run(async () => { await DoRealPing(it); })); } await Task.WhenAll(tasks); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } finally { if (processService != null) { await processService?.StopAsync(); } } return true; } private async Task RunMixedTestAsync(List selecteds, int concurrencyCount, bool blSpeedTest, string exitLoopKey) { using var concurrencySemaphore = new SemaphoreSlim(concurrencyCount); var downloadHandle = new DownloadService(); List tasks = new(); foreach (var it in selecteds) { if (ShouldStopTest(exitLoopKey)) { await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip); continue; } await concurrencySemaphore.WaitAsync(); tasks.Add(Task.Run(async () => { ProcessService processService = null; try { processService = await CoreManager.Instance.LoadCoreConfigSpeedtest(it); if (processService is null) { await UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore); return; } await Task.Delay(1000); var delay = await DoRealPing(it); if (blSpeedTest) { if (ShouldStopTest(exitLoopKey)) { await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip); return; } if (delay > 0) { await DoSpeedTest(downloadHandle, it); } else { await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip); } } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } finally { if (processService != null) { await processService?.StopAsync(); } concurrencySemaphore.Release(); } })); } await Task.WhenAll(tasks); } private async Task DoRealPing(ServerTestItem it) { var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); var responseTime = await ConnectionHandler.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10); ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime); await UpdateFunc(it.IndexId, responseTime.ToString()); return responseTime; } private async Task DoSpeedTest(DownloadService downloadHandle, ServerTestItem it) { await UpdateFunc(it.IndexId, "", ResUI.Speedtesting); var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); var url = _config.SpeedTestItem.SpeedTestUrl; var timeout = _config.SpeedTestItem.SpeedTestTimeout; await downloadHandle.DownloadDataAsync(url, webProxy, timeout, async (success, msg) => { decimal.TryParse(msg, out var dec); if (dec > 0) { ProfileExManager.Instance.SetTestSpeed(it.IndexId, dec); } await UpdateFunc(it.IndexId, "", msg); }); } private async Task GetTcpingTime(string url, int port) { var responseTime = -1; if (!IPAddress.TryParse(url, out var ipAddress)) { var ipHostInfo = await Dns.GetHostEntryAsync(url); ipAddress = ipHostInfo.AddressList.First(); } IPEndPoint endPoint = new(ipAddress, port); using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); var timer = Stopwatch.StartNew(); try { using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); await clientSocket.ConnectAsync(endPoint, cts.Token).ConfigureAwait(false); responseTime = (int)timer.ElapsedMilliseconds; } catch (OperationCanceledException) { } finally { timer.Stop(); } return responseTime; } private List> GetTestBatchItem(List lstSelected, int pageSize) { List> lstTest = new(); var lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList(); var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList(); for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) { lstTest.Add(lst1.Skip(num * pageSize).Take(pageSize).ToList()); } for (var num = 0; num < (int)Math.Ceiling(lst2.Count * 1.0 / pageSize); num++) { lstTest.Add(lst2.Skip(num * pageSize).Take(pageSize).ToList()); } return lstTest; } private async Task UpdateFunc(string indexId, string delay, string speed = "") { await _updateFunc?.Invoke(new() { IndexId = indexId, Delay = delay, Speed = speed }); if (indexId.IsNotEmpty() && speed.IsNotEmpty()) { ProfileExManager.Instance.SetTestMessage(indexId, speed); } } } ================================================ FILE: v2rayN/ServiceLib/Services/Statistics/StatisticsSingboxService.cs ================================================ using System.Net.WebSockets; namespace ServiceLib.Services.Statistics; public class StatisticsSingboxService { private readonly Config _config; private bool _exitFlag; private ClientWebSocket? webSocket; private readonly Func? _updateFunc; private string Url => $"ws://{Global.Loopback}:{AppManager.Instance.StatePort2}/traffic"; private static readonly string _tag = "StatisticsSingboxService"; public StatisticsSingboxService(Config config, Func updateFunc) { _config = config; _updateFunc = updateFunc; _exitFlag = false; _ = Task.Run(Run); } private async Task Init() { await Task.Delay(5000); try { if (webSocket == null) { webSocket = new ClientWebSocket(); await webSocket.ConnectAsync(new Uri(Url), CancellationToken.None); } } catch { } } public void Close() { try { _exitFlag = true; if (webSocket != null) { webSocket.Abort(); webSocket = null; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private async Task Run() { await Init(); while (!_exitFlag) { await Task.Delay(1000); try { if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box)) { continue; } if (webSocket != null) { if (webSocket.State is WebSocketState.Aborted or WebSocketState.Closed) { webSocket.Abort(); webSocket = null; await Init(); continue; } if (webSocket.State != WebSocketState.Open) { continue; } var buffer = new byte[1024]; var res = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); while (!res.CloseStatus.HasValue) { var result = Encoding.UTF8.GetString(buffer, 0, res.Count); if (result.IsNotEmpty()) { ParseOutput(result, out var up, out var down); await _updateFunc?.Invoke(new ServerSpeedItem() { ProxyUp = (long)(up / 1000), ProxyDown = (long)(down / 1000) }); } res = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); } } } catch { } } } private void ParseOutput(string source, out ulong up, out ulong down) { up = 0; down = 0; try { var trafficItem = JsonUtils.Deserialize(source); if (trafficItem != null) { up = trafficItem.Up; down = trafficItem.Down; } } catch { } } } ================================================ FILE: v2rayN/ServiceLib/Services/Statistics/StatisticsXrayService.cs ================================================ namespace ServiceLib.Services.Statistics; public class StatisticsXrayService { private const long linkBase = 1024; private ServerSpeedItem _serverSpeedItem = new(); private readonly Config _config; private bool _exitFlag; private readonly Func? _updateFunc; private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort}/debug/vars"; public StatisticsXrayService(Config config, Func updateFunc) { _config = config; _updateFunc = updateFunc; _exitFlag = false; _ = Task.Run(Run); } public void Close() { _exitFlag = true; } private async Task Run() { while (!_exitFlag) { await Task.Delay(1000); try { if (AppManager.Instance.RunningCoreType != ECoreType.Xray) { continue; } var result = await HttpClientHelper.Instance.TryGetAsync(Url); if (result != null) { var server = ParseOutput(result) ?? new ServerSpeedItem(); await _updateFunc?.Invoke(server); } } catch { // ignored } } } private ServerSpeedItem? ParseOutput(string result) { try { var source = JsonUtils.Deserialize(result); if (source?.stats?.outbound == null) { return null; } ServerSpeedItem server = new(); foreach (var key in source.stats.outbound.Keys.Cast()) { var value = source.stats.outbound[key]; if (value == null) { continue; } var state = JsonUtils.Deserialize(value.ToString()); if (key.StartsWith(Global.ProxyTag)) { server.ProxyUp += state.uplink / linkBase; server.ProxyDown += state.downlink / linkBase; } else if (key == Global.DirectTag) { server.DirectUp = state.uplink / linkBase; server.DirectDown = state.downlink / linkBase; } } if (server.DirectDown < _serverSpeedItem.DirectDown || server.ProxyDown < _serverSpeedItem.ProxyDown) { _serverSpeedItem = new(); return null; } ServerSpeedItem curItem = new() { ProxyUp = server.ProxyUp - _serverSpeedItem.ProxyUp, ProxyDown = server.ProxyDown - _serverSpeedItem.ProxyDown, DirectUp = server.DirectUp - _serverSpeedItem.DirectUp, DirectDown = server.DirectDown - _serverSpeedItem.DirectDown, }; _serverSpeedItem = server; return curItem; } catch { // ignored } return null; } } ================================================ FILE: v2rayN/ServiceLib/Services/UpdateService.cs ================================================ namespace ServiceLib.Services; public class UpdateService(Config config, Func updateFunc) { private readonly Config? _config = config; private readonly Func? _updateFunc = updateFunc; private readonly int _timeout = 30; private static readonly string _tag = "UpdateService"; public async Task CheckUpdateGuiN(bool preRelease) { var url = string.Empty; var fileName = string.Empty; DownloadService downloadHandle = new(); downloadHandle.UpdateCompleted += (sender2, args) => { if (args.Success) { _ = UpdateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully); _ = UpdateFunc(true, Utils.UrlEncode(fileName)); } else { _ = UpdateFunc(false, args.Msg); } }; downloadHandle.Error += (sender2, args) => { _ = UpdateFunc(false, args.GetException().Message); }; await UpdateFunc(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN)); var result = await CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease); if (result.Success) { await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN)); await UpdateFunc(false, result.Msg); url = result.Url.ToString(); fileName = Utils.GetTempPath(Utils.GetGuid()); await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout); } else { await UpdateFunc(false, result.Msg); } } public async Task CheckUpdateCore(ECoreType type, bool preRelease) { var url = string.Empty; var fileName = string.Empty; DownloadService downloadHandle = new(); downloadHandle.UpdateCompleted += (sender2, args) => { if (args.Success) { _ = UpdateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully); _ = UpdateFunc(false, ResUI.MsgUnpacking); try { _ = UpdateFunc(true, fileName); } catch (Exception ex) { _ = UpdateFunc(false, ex.Message); } } else { _ = UpdateFunc(false, args.Msg); } }; downloadHandle.Error += (sender2, args) => { _ = UpdateFunc(false, args.GetException().Message); }; await UpdateFunc(false, string.Format(ResUI.MsgStartUpdating, type)); var result = await CheckUpdateAsync(downloadHandle, type, preRelease); if (result.Success) { await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type)); await UpdateFunc(false, result.Msg); url = result.Url.ToString(); var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url); fileName = Utils.GetTempPath(Utils.GetGuid() + ext); await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout); } else { if (!result.Msg.IsNullOrEmpty()) { await UpdateFunc(false, result.Msg); } } } public async Task UpdateGeoFileAll() { await UpdateGeoFiles(); await UpdateOtherFiles(); await UpdateSrsFileAll(); await UpdateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo")); } #region CheckUpdate private private async Task CheckUpdateAsync(DownloadService downloadHandle, ECoreType type, bool preRelease) { try { var result = await GetRemoteVersion(downloadHandle, type, preRelease); if (!result.Success || result.Version is null) { return result; } return await ParseDownloadUrl(type, result); } catch (Exception ex) { Logging.SaveLog(_tag, ex); await UpdateFunc(false, ex.Message); return new UpdateResult(false, ex.Message); } } private async Task GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease) { var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type); var tagName = string.Empty; if (preRelease) { var url = coreInfo?.ReleaseApiUrl; var result = await downloadHandle.TryDownloadString(url, true, Global.AppName); if (result.IsNullOrEmpty()) { return new UpdateResult(false, ""); } var gitHubReleases = JsonUtils.Deserialize>(result); var gitHubRelease = preRelease ? gitHubReleases?.First() : gitHubReleases?.First(r => r.Prerelease == false); tagName = gitHubRelease?.TagName; //var body = gitHubRelease?.Body; } else { var url = Path.Combine(coreInfo.Url, "latest"); var lastUrl = await downloadHandle.UrlRedirectAsync(url, true); if (lastUrl == null) { return new UpdateResult(false, ""); } tagName = lastUrl?.Split("/tag/").LastOrDefault(); } return new UpdateResult(true, new SemanticVersion(tagName)); } private async Task GetCoreVersion(ECoreType type) { try { var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type); var filePath = string.Empty; foreach (var name in coreInfo.CoreExes) { var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString()); if (File.Exists(vName)) { filePath = vName; break; } } if (!File.Exists(filePath)) { var msg = string.Format(ResUI.NotFoundCore, @"", "", ""); //ShowMsg(true, msg); return new SemanticVersion(""); } var result = await Utils.GetCliWrapOutput(filePath, coreInfo.VersionArg); var echo = result ?? ""; var version = string.Empty; switch (type) { case ECoreType.v2fly: case ECoreType.Xray: case ECoreType.v2fly_v5: version = Regex.Match(echo, $"{coreInfo.Match} ([0-9.]+) \\(").Groups[1].Value; break; case ECoreType.mihomo: version = Regex.Match(echo, $"v[0-9.]+").Groups[0].Value; break; case ECoreType.sing_box: version = Regex.Match(echo, $"([0-9.]+)").Groups[1].Value; break; } return new SemanticVersion(version); } catch (Exception ex) { Logging.SaveLog(_tag, ex); await UpdateFunc(false, ex.Message); return new SemanticVersion(""); } } private async Task ParseDownloadUrl(ECoreType type, UpdateResult result) { try { var version = result.Version ?? new SemanticVersion(0, 0, 0); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type); var coreUrl = await GetUrlFromCore(coreInfo) ?? string.Empty; SemanticVersion curVersion; string message; string? url; switch (type) { case ECoreType.v2fly: case ECoreType.Xray: case ECoreType.v2fly_v5: { curVersion = await GetCoreVersion(type); message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v")); url = string.Format(coreUrl, version.ToVersionString("v")); break; } case ECoreType.mihomo: { curVersion = await GetCoreVersion(type); message = string.Format(ResUI.IsLatestCore, type, curVersion); url = string.Format(coreUrl, version.ToVersionString("v")); break; } case ECoreType.sing_box: { curVersion = await GetCoreVersion(type); message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v")); url = string.Format(coreUrl, version.ToVersionString("v"), version); break; } case ECoreType.v2rayN: { curVersion = new SemanticVersion(Utils.GetVersionInfo()); message = string.Format(ResUI.IsLatestN, type, curVersion); url = string.Format(coreUrl, version); break; } default: throw new ArgumentException("Type"); } if (curVersion >= version && version != new SemanticVersion(0, 0, 0)) { return new UpdateResult(false, message); } result.Url = url; return result; } catch (Exception ex) { Logging.SaveLog(_tag, ex); await UpdateFunc(false, ex.Message); return new UpdateResult(false, ex.Message); } } private async Task GetUrlFromCore(CoreInfo? coreInfo) { if (Utils.IsWindows()) { var url = RuntimeInformation.ProcessArchitecture switch { Architecture.Arm64 => coreInfo?.DownloadUrlWinArm64, Architecture.X64 => coreInfo?.DownloadUrlWin64, _ => null, }; if (coreInfo?.CoreType != ECoreType.v2rayN) { return url; } //Check for avalonia desktop windows version if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "libHarfBuzzSharp.dll"))) { return url?.Replace(".zip", "-desktop.zip"); } return url; } else if (Utils.IsLinux()) { return RuntimeInformation.ProcessArchitecture switch { Architecture.Arm64 => coreInfo?.DownloadUrlLinuxArm64, Architecture.X64 => coreInfo?.DownloadUrlLinux64, _ => null, }; } else if (Utils.IsMacOS()) { return RuntimeInformation.ProcessArchitecture switch { Architecture.Arm64 => coreInfo?.DownloadUrlOSXArm64, Architecture.X64 => coreInfo?.DownloadUrlOSX64, _ => null, }; } return await Task.FromResult(""); } #endregion CheckUpdate private #region Geo private private async Task UpdateGeoFiles() { var geoUrl = string.IsNullOrEmpty(_config?.ConstItem.GeoSourceUrl) ? Global.GeoUrl : _config.ConstItem.GeoSourceUrl; List files = ["geosite", "geoip"]; foreach (var geoName in files) { var fileName = $"{geoName}.dat"; var targetPath = Utils.GetBinPath($"{fileName}"); var url = string.Format(geoUrl, geoName); await DownloadGeoFile(url, fileName, targetPath); } } private async Task UpdateOtherFiles() { //If it is not in China area, no update is required if (_config.ConstItem.GeoSourceUrl.IsNotEmpty()) { return; } foreach (var url in Global.OtherGeoUrls) { var fileName = Path.GetFileName(url); var targetPath = Utils.GetBinPath($"{fileName}"); await DownloadGeoFile(url, fileName, targetPath); } } private async Task UpdateSrsFileAll() { var geoipFiles = new List(); var geoSiteFiles = new List(); // Collect from routing rules var routingItems = await AppManager.Instance.RoutingItems(); foreach (var routing in routingItems) { var rules = JsonUtils.Deserialize>(routing.RuleSet); foreach (var item in rules ?? []) { AddPrefixedItems(item.Ip, "geoip:", geoipFiles); AddPrefixedItems(item.Domain, "geosite:", geoSiteFiles); } } // Collect from DNS configuration var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); if (dnsItem != null) { ExtractDnsRuleSets(dnsItem.NormalDNS, geoipFiles, geoSiteFiles); ExtractDnsRuleSets(dnsItem.TunDNS, geoipFiles, geoSiteFiles); } // Append default items geoSiteFiles.AddRange(["google", "cn", "geolocation-cn", "category-ads-all"]); // Download files var path = Utils.GetBinPath("srss"); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } foreach (var item in geoipFiles.Distinct()) { await UpdateSrsFile("geoip", item); } foreach (var item in geoSiteFiles.Distinct()) { await UpdateSrsFile("geosite", item); } } private void AddPrefixedItems(List? items, string prefix, List output) { if (items == null) { return; } foreach (var item in items) { if (item.StartsWith(prefix)) { output.Add(item.Substring(prefix.Length)); } } } private void ExtractDnsRuleSets(string? dnsJson, List geoipFiles, List geoSiteFiles) { if (string.IsNullOrEmpty(dnsJson)) { return; } try { var dns = JsonUtils.Deserialize(dnsJson); if (dns?.rules != null) { foreach (var rule in dns.rules) { ExtractSrsRuleSets(rule, geoipFiles, geoSiteFiles); } } } catch { } } private void ExtractSrsRuleSets(Rule4Sbox? rule, List geoipFiles, List geoSiteFiles) { if (rule == null) { return; } AddPrefixedItems(rule.rule_set, "geosite-", geoSiteFiles); AddPrefixedItems(rule.rule_set, "geoip-", geoipFiles); // Handle nested rules recursively if (rule.rules != null) { foreach (var nestedRule in rule.rules) { ExtractSrsRuleSets(nestedRule, geoipFiles, geoSiteFiles); } } } private async Task UpdateSrsFile(string type, string srsName) { var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl) ? Global.SingboxRulesetUrl : _config.ConstItem.SrsSourceUrl; var fileName = $"{type}-{srsName}.srs"; var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName); var url = string.Format(srsUrl, type, $"{type}-{srsName}", srsName); await DownloadGeoFile(url, fileName, targetPath); } private async Task DownloadGeoFile(string url, string fileName, string targetPath) { var tmpFileName = Utils.GetTempPath(Utils.GetGuid()); DownloadService downloadHandle = new(); downloadHandle.UpdateCompleted += (sender2, args) => { if (args.Success) { _ = UpdateFunc(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, fileName)); try { if (File.Exists(tmpFileName)) { File.Copy(tmpFileName, targetPath, true); File.Delete(tmpFileName); //await UpdateFunc(true, ""); } } catch (Exception ex) { _ = UpdateFunc(false, ex.Message); } } else { _ = UpdateFunc(false, args.Msg); } }; downloadHandle.Error += (sender2, args) => { _ = UpdateFunc(false, args.GetException().Message); }; await downloadHandle.DownloadFileAsync(url, tmpFileName, true, _timeout); } #endregion Geo private private async Task UpdateFunc(bool notify, string msg) { await _updateFunc?.Invoke(notify, msg); } } ================================================ FILE: v2rayN/ServiceLib/Services/WindowsJobService.cs ================================================ namespace ServiceLib.Services; /// /// http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net /// public sealed class WindowsJobService : IDisposable { private nint handle = nint.Zero; public WindowsJobService() { handle = CreateJobObject(nint.Zero, null); var extendedInfoPtr = nint.Zero; var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION { LimitFlags = 0x2000 }; var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION { BasicLimitInformation = info }; try { var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); extendedInfoPtr = Marshal.AllocHGlobal(length); Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length)) { throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error())); } } finally { if (extendedInfoPtr != nint.Zero) { Marshal.FreeHGlobal(extendedInfoPtr); } } } public bool AddProcess(nint processHandle) { var succ = AssignProcessToJobObject(handle, processHandle); if (!succ) { Logging.SaveLog("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error()); } return succ; } public bool AddProcess(int processId) { return AddProcess(Process.GetProcessById(processId).Handle); } #region IDisposable private bool disposed; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposed) { return; } disposed = true; if (disposing) { // no managed objects to free } if (handle != nint.Zero) { CloseHandle(handle); handle = nint.Zero; } } ~WindowsJobService() { Dispose(false); } #endregion IDisposable #region Interop [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern nint CreateJobObject(nint a, string? lpName); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetInformationJobObject(nint hJob, JobObjectInfoType infoType, nint lpJobObjectInfo, uint cbJobObjectInfoLength); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AssignProcessToJobObject(nint job, nint process); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(nint hObject); #endregion Interop } [StructLayout(LayoutKind.Sequential)] internal struct IO_COUNTERS { public ulong ReadOperationCount; public ulong WriteOperationCount; public ulong OtherOperationCount; public ulong ReadTransferCount; public ulong WriteTransferCount; public ulong OtherTransferCount; } [StructLayout(LayoutKind.Sequential)] internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION { public long PerProcessUserTimeLimit; public long PerJobUserTimeLimit; public uint LimitFlags; public nuint MinimumWorkingSetSize; public nuint MaximumWorkingSetSize; public uint ActiveProcessLimit; public nuint Affinity; public uint PriorityClass; public uint SchedulingClass; } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public uint nLength; public nint lpSecurityDescriptor; public int bInheritHandle; } [StructLayout(LayoutKind.Sequential)] internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION { public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; public IO_COUNTERS IoInfo; public nuint ProcessMemoryLimit; public nuint JobMemoryLimit; public nuint PeakProcessMemoryUsed; public nuint PeakJobMemoryUsed; } public enum JobObjectInfoType { AssociateCompletionPortInformation = 7, BasicLimitInformation = 2, BasicUIRestrictions = 4, EndOfJobTimeInformation = 6, ExtendedLimitInformation = 9, SecurityLimitInformation = 5, GroupInformation = 11 } ================================================ FILE: v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class AddGroupServerViewModel : MyReactiveObject { [Reactive] public ProfileItem SelectedSource { get; set; } [Reactive] public ProfileItem SelectedChild { get; set; } [Reactive] public IList SelectedChildren { get; set; } [Reactive] public string? CoreType { get; set; } [Reactive] public string? PolicyGroupType { get; set; } [Reactive] public SubItem? SelectedSubItem { get; set; } [Reactive] public string? Filter { get; set; } public IObservableCollection SubItems { get; } = new ObservableCollectionExtended(); public IObservableCollection ChildItemsObs { get; } = new ObservableCollectionExtended(); public IObservableCollection AllProfilePreviewItemsObs { get; } = new ObservableCollectionExtended(); //public ReactiveCommand AddCmd { get; } public ReactiveCommand RemoveCmd { get; } public ReactiveCommand MoveTopCmd { get; } public ReactiveCommand MoveUpCmd { get; } public ReactiveCommand MoveDownCmd { get; } public ReactiveCommand MoveBottomCmd { get; } public ReactiveCommand SaveCmd { get; } public AddGroupServerViewModel(ProfileItem profileItem, Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; var canEditRemove = this.WhenAnyValue( x => x.SelectedChild, SelectedChild => SelectedChild != null && !SelectedChild.Remarks.IsNullOrEmpty()); RemoveCmd = ReactiveCommand.CreateFromTask(async () => { await ChildRemoveAsync(); }, canEditRemove); MoveTopCmd = ReactiveCommand.CreateFromTask(async () => { await MoveServer(EMove.Top); }, canEditRemove); MoveUpCmd = ReactiveCommand.CreateFromTask(async () => { await MoveServer(EMove.Up); }, canEditRemove); MoveDownCmd = ReactiveCommand.CreateFromTask(async () => { await MoveServer(EMove.Down); }, canEditRemove); MoveBottomCmd = ReactiveCommand.CreateFromTask(async () => { await MoveServer(EMove.Bottom); }, canEditRemove); SaveCmd = ReactiveCommand.CreateFromTask(async () => { await SaveServerAsync(); }); SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem); CoreType = (SelectedSource?.CoreType ?? ECoreType.Xray).ToString(); _ = Init(); } public async Task Init() { var protocolExtra = SelectedSource.GetProtocolExtra(); PolicyGroupType = (protocolExtra?.MultipleLoad ?? EMultipleLoad.LeastPing) switch { EMultipleLoad.LeastPing => ResUI.TbLeastPing, EMultipleLoad.Fallback => ResUI.TbFallback, EMultipleLoad.Random => ResUI.TbRandom, EMultipleLoad.RoundRobin => ResUI.TbRoundRobin, EMultipleLoad.LeastLoad => ResUI.TbLeastLoad, _ => ResUI.TbLeastPing, }; var subs = await AppManager.Instance.SubItems(); subs.Add(new SubItem()); SubItems.AddRange(subs); SelectedSubItem = SubItems.FirstOrDefault(s => s.Id == protocolExtra?.SubChildItems); Filter = protocolExtra?.Filter; var childIndexIds = Utils.String2List(protocolExtra?.ChildItems) ?? []; var childItemList = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childIndexIds); ChildItemsObs.AddRange(childItemList); } public async Task ChildRemoveAsync() { if (SelectedChild == null || SelectedChild.IndexId.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); return; } foreach (var it in SelectedChildren ?? [SelectedChild]) { if (it != null) { ChildItemsObs.Remove(it); } } await Task.CompletedTask; } public async Task MoveServer(EMove eMove) { if (SelectedChild == null || SelectedChild.IndexId.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); return; } var index = ChildItemsObs.IndexOf(SelectedChild); if (index < 0) { return; } var selectedChild = JsonUtils.DeepCopy(SelectedChild); switch (eMove) { case EMove.Top: if (index == 0) { return; } ChildItemsObs.RemoveAt(index); ChildItemsObs.Insert(0, selectedChild); break; case EMove.Up: if (index == 0) { return; } ChildItemsObs.RemoveAt(index); ChildItemsObs.Insert(index - 1, selectedChild); break; case EMove.Down: if (index == ChildItemsObs.Count - 1) { return; } ChildItemsObs.RemoveAt(index); ChildItemsObs.Insert(index + 1, selectedChild); break; case EMove.Bottom: if (index == ChildItemsObs.Count - 1) { return; } ChildItemsObs.RemoveAt(index); ChildItemsObs.Add(selectedChild); break; default: break; } await Task.CompletedTask; } private ProtocolExtraItem GetUpdatedProtocolExtra() { return SelectedSource.GetProtocolExtra() with { ChildItems = Utils.List2String(ChildItemsObs.Where(s => !s.IndexId.IsNullOrEmpty()).Select(s => s.IndexId).ToList()), MultipleLoad = PolicyGroupType switch { var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing, var s when s == ResUI.TbFallback => EMultipleLoad.Fallback, var s when s == ResUI.TbRandom => EMultipleLoad.Random, var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin, var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad, _ => EMultipleLoad.LeastPing, }, SubChildItems = SelectedSubItem?.Id, Filter = Filter, }; } public async Task UpdatePreviewList() { AllProfilePreviewItemsObs.Clear(); AllProfilePreviewItemsObs.AddRange(await GroupProfileManager.GetChildProfileItemsByProtocolExtra(GetUpdatedProtocolExtra())); } private async Task SaveServerAsync() { var remarks = SelectedSource.Remarks; if (remarks.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks); return; } if (ChildItemsObs.Count == 0 && SelectedSubItem?.Id.IsNullOrEmpty() == true) { NoticeManager.Instance.Enqueue(ResUI.PleaseAddAtLeastOneServer); return; } SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? ECoreType.Xray : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); if (SelectedSource.CoreType is not (ECoreType.Xray or ECoreType.sing_box) || SelectedSource.ConfigType is not (EConfigType.ProxyChain or EConfigType.PolicyGroup)) { return; } var protocolExtra = GetUpdatedProtocolExtra(); SelectedSource.SetProtocolExtra(protocolExtra); if (await ConfigHandler.AddServerCommon(_config, SelectedSource) == 0) { NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); _updateView?.Invoke(EViewAction.CloseWindow, null); } else { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); } } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/AddServer2ViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class AddServer2ViewModel : MyReactiveObject { [Reactive] public ProfileItem SelectedSource { get; set; } [Reactive] public string? CoreType { get; set; } public ReactiveCommand BrowseServerCmd { get; } public ReactiveCommand EditServerCmd { get; } public ReactiveCommand SaveServerCmd { get; } public bool IsModified { get; set; } public AddServer2ViewModel(ProfileItem profileItem, Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; BrowseServerCmd = ReactiveCommand.CreateFromTask(async () => { _updateView?.Invoke(EViewAction.BrowseServer, null); await Task.CompletedTask; }); EditServerCmd = ReactiveCommand.CreateFromTask(async () => { await EditServer(); }); SaveServerCmd = ReactiveCommand.CreateFromTask(async () => { await SaveServerAsync(); }); SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem); CoreType = SelectedSource?.CoreType?.ToString(); } private async Task SaveServerAsync() { var remarks = SelectedSource.Remarks; if (remarks.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks); return; } if (SelectedSource.Address.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.FillServerAddressCustom); return; } SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0) { NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); _updateView?.Invoke(EViewAction.CloseWindow, null); } else { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); } } public async Task BrowseServer(string fileName) { if (fileName.IsNullOrEmpty()) { return; } var item = await AppManager.Instance.GetProfileItem(SelectedSource.IndexId); item ??= SelectedSource; item.Address = fileName; if (await ConfigHandler.AddCustomServer(_config, item, false) == 0) { NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer); if (item.IndexId.IsNotEmpty()) { SelectedSource = JsonUtils.DeepCopy(item); } IsModified = true; } else { NoticeManager.Instance.Enqueue(ResUI.FailedImportedCustomServer); } } private async Task EditServer() { var address = SelectedSource.Address; if (address.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.FillServerAddressCustom); return; } address = Utils.GetConfigPath(address); if (File.Exists(address)) { ProcUtils.ProcessStart(address); } else { NoticeManager.Instance.Enqueue(ResUI.FailedReadConfiguration); } await Task.CompletedTask; } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class AddServerViewModel : MyReactiveObject { [Reactive] public ProfileItem SelectedSource { get; set; } [Reactive] public string? CoreType { get; set; } [Reactive] public string Cert { get; set; } [Reactive] public string CertTip { get; set; } [Reactive] public string CertSha { get; set; } [Reactive] public string SalamanderPass { get; set; } [Reactive] public int AlterId { get; set; } [Reactive] public string Ports { get; set; } [Reactive] public int? UpMbps { get; set; } [Reactive] public int? DownMbps { get; set; } [Reactive] public string HopInterval { get; set; } [Reactive] public string Flow { get; set; } [Reactive] public string VmessSecurity { get; set; } [Reactive] public string VlessEncryption { get; set; } [Reactive] public string SsMethod { get; set; } [Reactive] public string WgPublicKey { get; set; } //[Reactive] //public string WgPresharedKey { get; set; } [Reactive] public string WgInterfaceAddress { get; set; } [Reactive] public string WgReserved { get; set; } [Reactive] public int WgMtu { get; set; } public ReactiveCommand FetchCertCmd { get; } public ReactiveCommand FetchCertChainCmd { get; } public ReactiveCommand SaveCmd { get; } public AddServerViewModel(ProfileItem profileItem, Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; FetchCertCmd = ReactiveCommand.CreateFromTask(async () => { await FetchCert(); }); FetchCertChainCmd = ReactiveCommand.CreateFromTask(async () => { await FetchCertChain(); }); SaveCmd = ReactiveCommand.CreateFromTask(async () => { await SaveServerAsync(); }); this.WhenAnyValue(x => x.Cert) .Subscribe(_ => UpdateCertTip()); this.WhenAnyValue(x => x.CertSha) .Subscribe(_ => UpdateCertTip()); this.WhenAnyValue(x => x.Cert) .Subscribe(_ => UpdateCertSha()); if (profileItem.IndexId.IsNullOrEmpty()) { profileItem.Network = Global.DefaultNetwork; profileItem.HeaderType = Global.None; profileItem.RequestHost = ""; profileItem.StreamSecurity = ""; SelectedSource = profileItem; } else { SelectedSource = JsonUtils.DeepCopy(profileItem); } CoreType = SelectedSource?.CoreType?.ToString(); Cert = SelectedSource?.Cert?.ToString() ?? string.Empty; CertSha = SelectedSource?.CertSha?.ToString() ?? string.Empty; var protocolExtra = SelectedSource?.GetProtocolExtra(); Ports = protocolExtra?.Ports ?? string.Empty; AlterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0; Flow = protocolExtra?.Flow ?? string.Empty; SalamanderPass = protocolExtra?.SalamanderPass ?? string.Empty; UpMbps = protocolExtra?.UpMbps; DownMbps = protocolExtra?.DownMbps; HopInterval = protocolExtra?.HopInterval.IsNullOrEmpty() ?? true ? Global.Hysteria2DefaultHopInt.ToString() : protocolExtra.HopInterval; VmessSecurity = protocolExtra?.VmessSecurity?.IsNullOrEmpty() == false ? protocolExtra.VmessSecurity : Global.DefaultSecurity; VlessEncryption = protocolExtra?.VlessEncryption.IsNullOrEmpty() == false ? protocolExtra.VlessEncryption : Global.None; SsMethod = protocolExtra?.SsMethod ?? string.Empty; WgPublicKey = protocolExtra?.WgPublicKey ?? string.Empty; WgInterfaceAddress = protocolExtra?.WgInterfaceAddress ?? string.Empty; WgReserved = protocolExtra?.WgReserved ?? string.Empty; WgMtu = protocolExtra?.WgMtu ?? 1280; } private async Task SaveServerAsync() { if (SelectedSource.Remarks.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks); return; } if (SelectedSource.Address.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.FillServerAddress); return; } var port = SelectedSource.Port.ToString(); if (port.IsNullOrEmpty() || !Utils.IsNumeric(port) || SelectedSource.Port <= 0 || SelectedSource.Port >= Global.MaxPort) { NoticeManager.Instance.Enqueue(ResUI.FillCorrectServerPort); return; } if (SelectedSource.ConfigType == EConfigType.Shadowsocks) { if (SelectedSource.Password.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.FillPassword); return; } if (SsMethod.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectEncryption); return; } } if (SelectedSource.ConfigType is not EConfigType.SOCKS and not EConfigType.HTTP) { if (SelectedSource.Password.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.FillUUID); return; } } SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert; SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha; SelectedSource.SetProtocolExtra(SelectedSource.GetProtocolExtra() with { Ports = Ports.NullIfEmpty(), AlterId = AlterId > 0 ? AlterId.ToString() : null, Flow = Flow.NullIfEmpty(), SalamanderPass = SalamanderPass.NullIfEmpty(), UpMbps = UpMbps, DownMbps = DownMbps, HopInterval = HopInterval.NullIfEmpty(), VmessSecurity = VmessSecurity.NullIfEmpty(), VlessEncryption = VlessEncryption.NullIfEmpty(), SsMethod = SsMethod.NullIfEmpty(), WgPublicKey = WgPublicKey.NullIfEmpty(), WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(), WgReserved = WgReserved.NullIfEmpty(), WgMtu = WgMtu >= 576 ? WgMtu : null, }); if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) { NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); _updateView?.Invoke(EViewAction.CloseWindow, null); } else { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); } } private void UpdateCertTip(string? errorMessage = null) { CertTip = errorMessage.IsNullOrEmpty() ? ((Cert.IsNullOrEmpty() && CertSha.IsNullOrEmpty()) ? ResUI.CertNotSet : ResUI.CertSet) : errorMessage; } private void UpdateCertSha() { if (Cert.IsNullOrEmpty()) { return; } var certList = CertPemManager.ParsePemChain(Cert); if (certList.Count == 0) { return; } List shaList = []; foreach (var cert in certList) { var sha = CertPemManager.GetCertSha256Thumbprint(cert); if (sha.IsNullOrEmpty()) { return; } shaList.Add(sha); } CertSha = string.Join(',', shaList); } private async Task FetchCert() { if (SelectedSource.StreamSecurity != Global.StreamSecurity) { return; } var domain = SelectedSource.Address; var serverName = SelectedSource.Sni; if (serverName.IsNullOrEmpty()) { serverName = SelectedSource.RequestHost; } if (serverName.IsNullOrEmpty()) { serverName = SelectedSource.Address; } if (SelectedSource.Port > 0) { domain += $":{SelectedSource.Port}"; } (Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName); UpdateCertTip(certError); } private async Task FetchCertChain() { if (SelectedSource.StreamSecurity != Global.StreamSecurity) { return; } var domain = SelectedSource.Address; var serverName = SelectedSource.Sni; if (serverName.IsNullOrEmpty()) { serverName = SelectedSource.RequestHost; } if (serverName.IsNullOrEmpty()) { serverName = SelectedSource.Address; } if (SelectedSource.Port > 0) { domain += $":{SelectedSource.Port}"; } var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName); Cert = CertPemManager.ConcatenatePemChain(certs); UpdateCertTip(certError); } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/BackupAndRestoreViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class BackupAndRestoreViewModel : MyReactiveObject { private readonly string _guiConfigs = "guiConfigs"; private static string BackupFileName => $"backup_{DateTime.Now:yyyyMMddHHmmss}.zip"; public ReactiveCommand RemoteBackupCmd { get; } public ReactiveCommand RemoteRestoreCmd { get; } public ReactiveCommand WebDavCheckCmd { get; } [Reactive] public WebDavItem SelectedSource { get; set; } [Reactive] public string OperationMsg { get; set; } public BackupAndRestoreViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () => { await WebDavCheck(); }); RemoteBackupCmd = ReactiveCommand.CreateFromTask(async () => { await RemoteBackup(); }); RemoteRestoreCmd = ReactiveCommand.CreateFromTask(async () => { await RemoteRestore(); }); SelectedSource = JsonUtils.DeepCopy(_config.WebDavItem); } private void DisplayOperationMsg(string msg = "") { OperationMsg = msg; } private async Task WebDavCheck() { DisplayOperationMsg(); _config.WebDavItem = SelectedSource; _ = await ConfigHandler.SaveConfig(_config); var result = await WebDavManager.Instance.CheckConnection(); if (result) { DisplayOperationMsg(ResUI.OperationSuccess); } else { DisplayOperationMsg(WebDavManager.Instance.GetLastError()); } } private async Task RemoteBackup() { DisplayOperationMsg(); var fileName = Utils.GetBackupPath(BackupFileName); var result = await CreateZipFileFromDirectory(fileName); if (result) { var result2 = await WebDavManager.Instance.PutFile(fileName); if (result2) { DisplayOperationMsg(ResUI.OperationSuccess); return; } } DisplayOperationMsg(WebDavManager.Instance.GetLastError()); } private async Task RemoteRestore() { DisplayOperationMsg(); var fileName = Utils.GetTempPath(Utils.GetGuid()); var result = await WebDavManager.Instance.GetRawFile(fileName); if (result) { await LocalRestore(fileName); return; } DisplayOperationMsg(WebDavManager.Instance.GetLastError()); } public async Task LocalBackup(string fileName) { DisplayOperationMsg(); var result = await CreateZipFileFromDirectory(fileName); if (result) { DisplayOperationMsg(ResUI.OperationSuccess); } else { DisplayOperationMsg(WebDavManager.Instance.GetLastError()); } return result; } public async Task LocalRestore(string fileName) { DisplayOperationMsg(); if (fileName.IsNullOrEmpty()) { return; } //exist if (!File.Exists(fileName)) { return; } //check var lstFiles = FileUtils.GetFilesFromZip(fileName); if (lstFiles is null || !lstFiles.Any(t => t.Contains(_guiConfigs))) { DisplayOperationMsg(ResUI.LocalRestoreInvalidZipTips); return; } //backup first var fileBackup = Utils.GetBackupPath(BackupFileName); var result = await CreateZipFileFromDirectory(fileBackup); if (result) { await AppManager.Instance.AppExitAsync(false); await SQLiteHelper.Instance.DisposeDbConnectionAsync(); var toPath = Utils.GetConfigPath(); FileUtils.ZipExtractToFile(fileName, toPath, ""); if (Utils.IsWindows()) { ProcUtils.RebootAsAdmin(false); } else { if (Utils.UpgradeAppExists(out var upgradeFileName)) { _ = ProcUtils.ProcessStart(upgradeFileName, Global.RebootAs, Utils.StartupPath()); } } AppManager.Instance.Shutdown(true); } else { DisplayOperationMsg(WebDavManager.Instance.GetLastError()); } } private async Task CreateZipFileFromDirectory(string fileName) { if (fileName.IsNullOrEmpty()) { return false; } var configDir = Utils.GetConfigPath(); var configDirZipTemp = Utils.GetTempPath($"v2rayN_{DateTime.Now:yyyyMMddHHmmss}"); var configDirTemp = Path.Combine(configDirZipTemp, _guiConfigs); FileUtils.CopyDirectory(configDir, configDirTemp, false, true, ""); var ret = FileUtils.CreateFromDirectory(configDirZipTemp, fileName); Directory.Delete(configDirZipTemp, true); return await Task.FromResult(ret); } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class CheckUpdateViewModel : MyReactiveObject { private const string _geo = "GeoFiles"; private readonly string _v2rayN = ECoreType.v2rayN.ToString(); private List _lstUpdated = []; private static readonly string _tag = "CheckUpdateViewModel"; public IObservableCollection CheckUpdateModels { get; } = new ObservableCollectionExtended(); public ReactiveCommand CheckUpdateCmd { get; } [Reactive] public bool EnableCheckPreReleaseUpdate { get; set; } public CheckUpdateViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; CheckUpdateCmd = ReactiveCommand.CreateFromTask(CheckUpdate); CheckUpdateCmd.ThrownExceptions.Subscribe(ex => { Logging.SaveLog(_tag, ex); _ = UpdateView(_v2rayN, ex.Message); }); EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate; this.WhenAnyValue( x => x.EnableCheckPreReleaseUpdate, y => y == true) .Subscribe(c => _config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate); RefreshCheckUpdateItems(); } private void RefreshCheckUpdateItems() { CheckUpdateModels.Clear(); if (RuntimeInformation.ProcessArchitecture != Architecture.X86) { CheckUpdateModels.Add(GetCheckUpdateModel(_v2rayN)); //Not Windows and under Win10 if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10)) { CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.Xray.ToString())); CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString())); CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString())); } } CheckUpdateModels.Add(GetCheckUpdateModel(_geo)); } private CheckUpdateModel GetCheckUpdateModel(string coreType) { if (coreType == _v2rayN && Utils.IsPackagedInstall()) { return new() { IsSelected = false, CoreType = coreType, Remarks = ResUI.menuCheckUpdate + " (Not Support)", }; } return new() { IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType) ?? true, CoreType = coreType, Remarks = ResUI.menuCheckUpdate, }; } private async Task SaveSelectedCoreTypes() { _config.CheckUpdateItem.SelectedCoreTypes = CheckUpdateModels.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList(); await ConfigHandler.SaveConfig(_config); } private async Task CheckUpdate() { await Task.Run(CheckUpdateTask); } private async Task CheckUpdateTask() { _lstUpdated.Clear(); _lstUpdated = CheckUpdateModels.Where(x => x.IsSelected == true) .Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList(); await SaveSelectedCoreTypes(); for (var k = CheckUpdateModels.Count - 1; k >= 0; k--) { var item = CheckUpdateModels[k]; if (item.IsSelected != true) { continue; } await UpdateView(item.CoreType, "..."); if (item.CoreType == _geo) { await CheckUpdateGeo(); } else if (item.CoreType == _v2rayN) { if (Utils.IsPackagedInstall()) { await UpdateView(_v2rayN, "Not Support"); continue; } await CheckUpdateN(EnableCheckPreReleaseUpdate); } else if (item.CoreType == ECoreType.Xray.ToString()) { await CheckUpdateCore(item, EnableCheckPreReleaseUpdate); } else { await CheckUpdateCore(item, false); } } await UpdateFinished(); } private void UpdatedPlusPlus(string coreType, string fileName) { var item = _lstUpdated.FirstOrDefault(x => x.CoreType == coreType); if (item == null) { return; } item.IsFinished = true; if (!fileName.IsNullOrEmpty()) { item.FileName = fileName; } } private async Task CheckUpdateGeo() { async Task _updateUI(bool success, string msg) { await UpdateView(_geo, msg); if (success) { UpdatedPlusPlus(_geo, ""); } } await new UpdateService(_config, _updateUI).UpdateGeoFileAll() .ContinueWith(t => UpdatedPlusPlus(_geo, "")); } private async Task CheckUpdateN(bool preRelease) { async Task _updateUI(bool success, string msg) { await UpdateView(_v2rayN, msg); if (success) { await UpdateView(_v2rayN, ResUI.OperationSuccess); UpdatedPlusPlus(_v2rayN, msg); } } await new UpdateService(_config, _updateUI).CheckUpdateGuiN(preRelease) .ContinueWith(t => UpdatedPlusPlus(_v2rayN, "")); } private async Task CheckUpdateCore(CheckUpdateModel model, bool preRelease) { async Task _updateUI(bool success, string msg) { await UpdateView(model.CoreType, msg); if (success) { await UpdateView(model.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore); UpdatedPlusPlus(model.CoreType, msg); } } var type = (ECoreType)Enum.Parse(typeof(ECoreType), model.CoreType); await new UpdateService(_config, _updateUI).CheckUpdateCore(type, preRelease) .ContinueWith(t => UpdatedPlusPlus(model.CoreType, "")); } private async Task UpdateFinished() { if (_lstUpdated.Count > 0 && _lstUpdated.Count(x => x.IsFinished == true) == _lstUpdated.Count) { await UpdateFinishedSub(false); await Task.Delay(2000); await UpgradeCore(); if (_lstUpdated.Any(x => x.CoreType == _v2rayN && x.IsFinished == true)) { await Task.Delay(1000); await UpgradeN(); } await Task.Delay(1000); await UpdateFinishedSub(true); } } private async Task UpdateFinishedSub(bool blReload) { RxSchedulers.MainThreadScheduler.Schedule(blReload, (scheduler, blReload) => { _ = UpdateFinishedResult(blReload); return Disposable.Empty; }); await Task.CompletedTask; } public async Task UpdateFinishedResult(bool blReload) { if (blReload) { AppEvents.ReloadRequested.Publish(); } else { await CoreManager.Instance.CoreStop(); } } private async Task UpgradeN() { try { var fileName = _lstUpdated.FirstOrDefault(x => x.CoreType == _v2rayN)?.FileName; if (fileName.IsNullOrEmpty()) { return; } if (!Utils.UpgradeAppExists(out var upgradeFileName)) { await UpdateView(_v2rayN, ResUI.UpgradeAppNotExistTip); NoticeManager.Instance.SendMessageAndEnqueue(ResUI.UpgradeAppNotExistTip); Logging.SaveLog("UpgradeApp does not exist"); return; } var id = ProcUtils.ProcessStart(upgradeFileName, fileName, Utils.StartupPath()); if (id > 0) { await AppManager.Instance.AppExitAsync(true); } } catch (Exception ex) { await UpdateView(_v2rayN, ex.Message); } } private async Task UpgradeCore() { foreach (var item in _lstUpdated) { if (item.FileName.IsNullOrEmpty()) { continue; } var fileName = item.FileName; if (!File.Exists(fileName)) { continue; } var toPath = Utils.GetBinPath("", item.CoreType); if (fileName.Contains(".tar.gz")) { FileUtils.DecompressTarFile(fileName, toPath); var dir = new DirectoryInfo(toPath); if (dir.Exists) { foreach (var subDir in dir.GetDirectories()) { FileUtils.CopyDirectory(subDir.FullName, toPath, false, true); subDir.Delete(true); } } } else if (fileName.Contains(".gz")) { FileUtils.DecompressFile(fileName, toPath, item.CoreType); } else { FileUtils.ZipExtractToFile(fileName, toPath, "geo"); } if (Utils.IsNonWindows()) { var filesList = new DirectoryInfo(toPath).GetFiles().Select(u => u.FullName).ToList(); foreach (var file in filesList) { await Utils.SetLinuxChmod(Path.Combine(toPath, item.CoreType.ToLower())); } } await UpdateView(item.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfully); if (File.Exists(fileName)) { File.Delete(fileName); } } } private async Task UpdateView(string coreType, string msg) { var item = new CheckUpdateModel() { CoreType = coreType, Remarks = msg, }; RxSchedulers.MainThreadScheduler.Schedule(item, (scheduler, model) => { _ = UpdateViewResult(model); return Disposable.Empty; }); await Task.CompletedTask; } public async Task UpdateViewResult(CheckUpdateModel model) { var found = CheckUpdateModels.FirstOrDefault(t => t.CoreType == model.CoreType); if (found == null) { return; } found.Remarks = model.Remarks; await Task.CompletedTask; } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class ClashConnectionsViewModel : MyReactiveObject { public IObservableCollection ConnectionItems { get; } = new ObservableCollectionExtended(); [Reactive] public ClashConnectionModel SelectedSource { get; set; } public ReactiveCommand ConnectionCloseCmd { get; } public ReactiveCommand ConnectionCloseAllCmd { get; } [Reactive] public string HostFilter { get; set; } [Reactive] public bool AutoRefresh { get; set; } public ClashConnectionsViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh; var canEditRemove = this.WhenAnyValue( x => x.SelectedSource, selectedSource => selectedSource != null && selectedSource.Id.IsNotEmpty()); this.WhenAnyValue( x => x.AutoRefresh, y => y == true) .Subscribe(c => { _config.ClashUIItem.ConnectionsAutoRefresh = AutoRefresh; }); ConnectionCloseCmd = ReactiveCommand.CreateFromTask(async () => { await ClashConnectionClose(false); }, canEditRemove); ConnectionCloseAllCmd = ReactiveCommand.CreateFromTask(async () => { await ClashConnectionClose(true); }); _ = Init(); } private async Task Init() { await DelayTestTask(); } private async Task GetClashConnections() { var ret = await ClashApiManager.Instance.GetClashConnectionsAsync(); if (ret == null) { return; } RxSchedulers.MainThreadScheduler.Schedule(ret?.connections, (scheduler, model) => { _ = RefreshConnections(model); return Disposable.Empty; }); } public async Task RefreshConnections(List? connections) { ConnectionItems.Clear(); var dtNow = DateTime.Now; var lstModel = new List(); foreach (var item in connections ?? new()) { var host = $"{(item.metadata.host.IsNullOrEmpty() ? item.metadata.destinationIP : item.metadata.host)}:{item.metadata.destinationPort}"; if (HostFilter.IsNotEmpty() && !host.Contains(HostFilter)) { continue; } var model = new ClashConnectionModel { Id = item.id, Network = item.metadata.network, Type = item.metadata.type, Host = host, Time = (dtNow - item.start).TotalSeconds < 0 ? 1 : (dtNow - item.start).TotalSeconds, Elapsed = (dtNow - item.start).ToString(@"hh\:mm\:ss"), Chain = $"{item.rule} , {string.Join("->", item.chains ?? new())}" }; lstModel.Add(model); } if (lstModel.Count <= 0) { return; } ConnectionItems.AddRange(lstModel); await Task.CompletedTask; } public async Task ClashConnectionClose(bool all) { var id = string.Empty; if (!all) { var item = SelectedSource; if (item is null) { return; } id = item.Id; } else { ConnectionItems.Clear(); } await ClashApiManager.Instance.ClashConnectionClose(id); await GetClashConnections(); } public async Task DelayTestTask() { _ = Task.Run(async () => { var numOfExecuted = 1; while (true) { await Task.Delay(1000 * 5); numOfExecuted++; if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box))) { continue; } if (_config.ClashUIItem.ConnectionsRefreshInterval <= 0) { continue; } if (numOfExecuted % _config.ClashUIItem.ConnectionsRefreshInterval != 0) { continue; } await GetClashConnections(); } }); await Task.CompletedTask; } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs ================================================ using System.Reactive.Concurrency; using static ServiceLib.Models.ClashProviders; using static ServiceLib.Models.ClashProxies; namespace ServiceLib.ViewModels; public class ClashProxiesViewModel : MyReactiveObject { private Dictionary? _proxies; private Dictionary? _providers; private readonly int _delayTimeout = 99999999; public IObservableCollection ProxyGroups { get; } = new ObservableCollectionExtended(); public IObservableCollection ProxyDetails { get; } = new ObservableCollectionExtended(); [Reactive] public ClashProxyModel SelectedGroup { get; set; } [Reactive] public ClashProxyModel SelectedDetail { get; set; } public ReactiveCommand ProxiesReloadCmd { get; } public ReactiveCommand ProxiesDelayTestCmd { get; } public ReactiveCommand ProxiesDelayTestPartCmd { get; } public ReactiveCommand ProxiesSelectActivityCmd { get; } [Reactive] public int RuleModeSelected { get; set; } [Reactive] public int SortingSelected { get; set; } [Reactive] public bool AutoRefresh { get; set; } public ClashProxiesViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () => { await ProxiesReload(); }); ProxiesDelayTestCmd = ReactiveCommand.CreateFromTask(async () => { await ProxiesDelayTest(true); }); ProxiesDelayTestPartCmd = ReactiveCommand.CreateFromTask(async () => { await ProxiesDelayTest(false); }); ProxiesSelectActivityCmd = ReactiveCommand.CreateFromTask(async () => { await SetActiveProxy(); }); SelectedGroup = new(); SelectedDetail = new(); AutoRefresh = _config.ClashUIItem.ProxiesAutoRefresh; SortingSelected = _config.ClashUIItem.ProxiesSorting; RuleModeSelected = (int)_config.ClashUIItem.RuleMode; #region WhenAnyValue && ReactiveCommand this.WhenAnyValue( x => x.SelectedGroup, y => y != null && y.Name.IsNotEmpty()) .Subscribe(c => RefreshProxyDetails(c)); this.WhenAnyValue( x => x.RuleModeSelected, y => y >= 0) .Subscribe(async c => await DoRuleModeSelected(c)); this.WhenAnyValue( x => x.SortingSelected, y => y >= 0) .Subscribe(c => DoSortingSelected(c)); this.WhenAnyValue( x => x.AutoRefresh, y => y == true) .Subscribe(c => { _config.ClashUIItem.ProxiesAutoRefresh = AutoRefresh; }); #endregion WhenAnyValue && ReactiveCommand #region AppEvents AppEvents.ProxiesReloadRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await ProxiesReload()); #endregion AppEvents _ = Init(); } private async Task Init() { await DelayTestTask(); } private async Task DoRuleModeSelected(bool c) { if (!c) { return; } if (_config.ClashUIItem.RuleMode == (ERuleMode)RuleModeSelected) { return; } await SetRuleModeCheck((ERuleMode)RuleModeSelected); } public async Task SetRuleModeCheck(ERuleMode mode) { if (_config.ClashUIItem.RuleMode == mode) { return; } await SetRuleMode(mode); } private void DoSortingSelected(bool c) { if (!c) { return; } if (SortingSelected != _config.ClashUIItem.ProxiesSorting) { _config.ClashUIItem.ProxiesSorting = SortingSelected; } RefreshProxyDetails(c); } public async Task ProxiesReload() { await GetClashProxies(true); await ProxiesDelayTest(); } #region proxy function private async Task SetRuleMode(ERuleMode mode) { _config.ClashUIItem.RuleMode = mode; if (mode != ERuleMode.Unchanged) { Dictionary headers = new() { { "mode", mode.ToString().ToLower() } }; await ClashApiManager.Instance.ClashConfigUpdate(headers); } } private async Task GetClashProxies(bool refreshUI) { var ret = await ClashApiManager.Instance.GetClashProxiesAsync(); if (ret?.Item1 == null || ret.Item2 == null) { return; } _proxies = ret.Item1.proxies; _providers = ret?.Item2.providers; if (refreshUI) { RxSchedulers.MainThreadScheduler.Schedule(() => _ = RefreshProxyGroups()); } } public async Task RefreshProxyGroups() { if (_proxies == null) { return; } var selectedName = SelectedGroup?.Name; ProxyGroups.Clear(); var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups(); if (proxyGroups != null && proxyGroups.Count > 0) { foreach (var it in proxyGroups) { if (it.name.IsNullOrEmpty() || !_proxies.ContainsKey(it.name)) { continue; } var item = _proxies[it.name]; if (!Global.allowSelectType.Contains(item.type.ToLower())) { continue; } ProxyGroups.Add(new ClashProxyModel() { Now = item.now, Name = item.name, Type = item.type }); } } //from api foreach (var kv in _proxies) { if (!Global.allowSelectType.Contains(kv.Value.type.ToLower())) { continue; } var item = ProxyGroups.FirstOrDefault(t => t.Name == kv.Key); if (item != null && item.Name.IsNotEmpty()) { continue; } ProxyGroups.Add(new ClashProxyModel() { Now = kv.Value.now, Name = kv.Key, Type = kv.Value.type }); } if (ProxyGroups != null && ProxyGroups.Count > 0) { if (selectedName != null && ProxyGroups.Any(t => t.Name == selectedName)) { SelectedGroup = ProxyGroups.FirstOrDefault(t => t.Name == selectedName); } else { SelectedGroup = ProxyGroups.First(); } } else { SelectedGroup = new(); } await Task.CompletedTask; } private void RefreshProxyDetails(bool c) { ProxyDetails.Clear(); if (!c) { return; } var name = SelectedGroup?.Name; if (name.IsNullOrEmpty()) { return; } if (_proxies == null) { return; } _proxies.TryGetValue(name, out var proxy); if (proxy?.all == null) { return; } var lstDetails = new List(); foreach (var item in proxy.all) { var proxy2 = TryGetProxy(item); if (proxy2 == null) { continue; } var delay = proxy2.history?.Count > 0 ? proxy2.history.Last().delay : -1; lstDetails.Add(new ClashProxyModel() { IsActive = item == proxy.now, Name = item, Type = proxy2.type, Delay = delay <= 0 ? _delayTimeout : delay, DelayName = delay <= 0 ? string.Empty : $"{delay}ms", }); } //sort switch (SortingSelected) { case 0: lstDetails = lstDetails.OrderBy(t => t.Delay).ToList(); break; case 1: lstDetails = lstDetails.OrderBy(t => t.Name).ToList(); break; default: break; } ProxyDetails.AddRange(lstDetails); } private ProxiesItem? TryGetProxy(string name) { if (_proxies == null) { return null; } _proxies.TryGetValue(name, out var proxy2); if (proxy2 != null) { return proxy2; } //from providers if (_providers != null) { foreach (var kv in _providers) { if (Global.proxyVehicleType.Contains(kv.Value.vehicleType.ToLower())) { var proxy3 = kv.Value.proxies.FirstOrDefault(t => t.name == name); if (proxy3 != null) { return proxy3; } } } } return null; } public async Task SetActiveProxy() { if (SelectedGroup == null || SelectedGroup.Name.IsNullOrEmpty()) { return; } if (SelectedDetail == null || SelectedDetail.Name.IsNullOrEmpty()) { return; } var name = SelectedGroup.Name; if (name.IsNullOrEmpty()) { return; } var nameNode = SelectedDetail.Name; if (nameNode.IsNullOrEmpty()) { return; } var selectedProxy = TryGetProxy(name); if (selectedProxy == null || selectedProxy.type != "Selector") { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); return; } await ClashApiManager.Instance.ClashSetActiveProxy(name, nameNode); selectedProxy.now = nameNode; var group = ProxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name); if (group != null) { group.Now = nameNode; var group2 = JsonUtils.DeepCopy(group); ProxyGroups.Replace(group, group2); SelectedGroup = group2; } NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); } private async Task ProxiesDelayTest(bool blAll = true) { ClashApiManager.Instance.ClashProxiesDelayTest(blAll, ProxyDetails.ToList(), async (item, result) => { if (item == null || result.IsNullOrEmpty()) { return; } var model = new SpeedTestResult() { IndexId = item.Name, Delay = result }; RxSchedulers.MainThreadScheduler.Schedule(model, (scheduler, model) => { _ = ProxiesDelayTestResult(model); return Disposable.Empty; }); await Task.CompletedTask; }); await Task.CompletedTask; } public async Task ProxiesDelayTestResult(SpeedTestResult result) { var detail = ProxyDetails.FirstOrDefault(it => it.Name == result.IndexId); if (detail == null) { return; } var dicResult = JsonUtils.Deserialize>(result.Delay); if (dicResult != null && dicResult.TryGetValue("delay", out var value)) { detail.Delay = Convert.ToInt32(value.ToString()); detail.DelayName = $"{detail.Delay}ms"; } else if (dicResult != null && dicResult.TryGetValue("message", out var value1)) { detail.Delay = _delayTimeout; detail.DelayName = $"{value1}"; } else { detail.Delay = _delayTimeout; detail.DelayName = string.Empty; } await Task.CompletedTask; } #endregion proxy function #region task public async Task DelayTestTask() { _ = Task.Run(async () => { var numOfExecuted = 1; while (true) { await Task.Delay(1000 * 60); numOfExecuted++; if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box))) { continue; } if (_config.ClashUIItem.ProxiesAutoDelayTestInterval <= 0) { continue; } if (numOfExecuted % _config.ClashUIItem.ProxiesAutoDelayTestInterval != 0) { continue; } await ProxiesDelayTest(); } }); await Task.CompletedTask; } #endregion task } ================================================ FILE: v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class DNSSettingViewModel : MyReactiveObject { [Reactive] public bool? UseSystemHosts { get; set; } [Reactive] public bool? AddCommonHosts { get; set; } [Reactive] public bool? FakeIP { get; set; } [Reactive] public bool? BlockBindingQuery { get; set; } [Reactive] public string? DirectDNS { get; set; } [Reactive] public string? RemoteDNS { get; set; } [Reactive] public string? BootstrapDNS { get; set; } [Reactive] public string? Strategy4Freedom { get; set; } [Reactive] public string? Strategy4Proxy { get; set; } [Reactive] public string? Hosts { get; set; } [Reactive] public string? DirectExpectedIPs { get; set; } [Reactive] public bool? ParallelQuery { get; set; } [Reactive] public bool? ServeStale { get; set; } [Reactive] public bool UseSystemHostsCompatible { get; set; } [Reactive] public string DomainStrategy4FreedomCompatible { get; set; } [Reactive] public string DomainDNSAddressCompatible { get; set; } [Reactive] public string NormalDNSCompatible { get; set; } [Reactive] public string DomainStrategy4Freedom2Compatible { get; set; } [Reactive] public string DomainDNSAddress2Compatible { get; set; } [Reactive] public string NormalDNS2Compatible { get; set; } [Reactive] public string TunDNS2Compatible { get; set; } [Reactive] public bool RayCustomDNSEnableCompatible { get; set; } [Reactive] public bool SBCustomDNSEnableCompatible { get; set; } [ObservableAsProperty] public bool IsSimpleDNSEnabled { get; } public ReactiveCommand SaveCmd { get; } public ReactiveCommand ImportDefConfig4V2rayCompatibleCmd { get; } public ReactiveCommand ImportDefConfig4SingboxCompatibleCmd { get; } public DNSSettingViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync); ImportDefConfig4V2rayCompatibleCmd = ReactiveCommand.CreateFromTask(async () => { NormalDNSCompatible = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName); await Task.CompletedTask; }); ImportDefConfig4SingboxCompatibleCmd = ReactiveCommand.CreateFromTask(async () => { NormalDNS2Compatible = EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName); TunDNS2Compatible = EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName); await Task.CompletedTask; }); this.WhenAnyValue(x => x.RayCustomDNSEnableCompatible, x => x.SBCustomDNSEnableCompatible) .Select(x => !(x.Item1 && x.Item2)) .ToPropertyEx(this, x => x.IsSimpleDNSEnabled); _ = Init(); } private async Task Init() { _config = AppManager.Instance.Config; var item = _config.SimpleDNSItem; UseSystemHosts = item.UseSystemHosts; AddCommonHosts = item.AddCommonHosts; FakeIP = item.FakeIP; BlockBindingQuery = item.BlockBindingQuery; DirectDNS = item.DirectDNS; RemoteDNS = item.RemoteDNS; BootstrapDNS = item.BootstrapDNS; Strategy4Freedom = item.Strategy4Freedom; Strategy4Proxy = item.Strategy4Proxy; Hosts = item.Hosts; DirectExpectedIPs = item.DirectExpectedIPs; ParallelQuery = item.ParallelQuery; ServeStale = item.ServeStale; var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray); RayCustomDNSEnableCompatible = item1.Enabled; UseSystemHostsCompatible = item1.UseSystemHosts; DomainStrategy4FreedomCompatible = item1?.DomainStrategy4Freedom ?? string.Empty; DomainDNSAddressCompatible = item1?.DomainDNSAddress ?? string.Empty; NormalDNSCompatible = item1?.NormalDNS ?? string.Empty; var item2 = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); SBCustomDNSEnableCompatible = item2.Enabled; DomainStrategy4Freedom2Compatible = item2?.DomainStrategy4Freedom ?? string.Empty; DomainDNSAddress2Compatible = item2?.DomainDNSAddress ?? string.Empty; NormalDNS2Compatible = item2?.NormalDNS ?? string.Empty; TunDNS2Compatible = item2?.TunDNS ?? string.Empty; } private async Task SaveSettingAsync() { _config.SimpleDNSItem.UseSystemHosts = UseSystemHosts; _config.SimpleDNSItem.AddCommonHosts = AddCommonHosts; _config.SimpleDNSItem.FakeIP = FakeIP; _config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery; _config.SimpleDNSItem.DirectDNS = DirectDNS; _config.SimpleDNSItem.RemoteDNS = RemoteDNS; _config.SimpleDNSItem.BootstrapDNS = BootstrapDNS; _config.SimpleDNSItem.Strategy4Freedom = Strategy4Freedom; _config.SimpleDNSItem.Strategy4Proxy = Strategy4Proxy; _config.SimpleDNSItem.Hosts = Hosts; _config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs; _config.SimpleDNSItem.ParallelQuery = ParallelQuery; _config.SimpleDNSItem.ServeStale = ServeStale; if (NormalDNSCompatible.IsNotEmpty()) { var obj = JsonUtils.ParseJson(NormalDNSCompatible); if (obj != null && obj["servers"] != null) { } else { if (NormalDNSCompatible.Contains('{') || NormalDNSCompatible.Contains('}')) { NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText); return; } } } if (NormalDNS2Compatible.IsNotEmpty()) { var obj2 = JsonUtils.Deserialize(NormalDNS2Compatible); if (obj2 == null) { NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText); return; } } if (TunDNS2Compatible.IsNotEmpty()) { var obj2 = JsonUtils.Deserialize(TunDNS2Compatible); if (obj2 == null) { NoticeManager.Instance.Enqueue(ResUI.FillCorrectDNSText); return; } } var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray); item1.Enabled = RayCustomDNSEnableCompatible; item1.DomainStrategy4Freedom = DomainStrategy4FreedomCompatible; item1.DomainDNSAddress = DomainDNSAddressCompatible; item1.UseSystemHosts = UseSystemHostsCompatible; item1.NormalDNS = NormalDNSCompatible; await ConfigHandler.SaveDNSItems(_config, item1); var item2 = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); item2.Enabled = SBCustomDNSEnableCompatible; item2.DomainStrategy4Freedom = DomainStrategy4Freedom2Compatible; item2.DomainDNSAddress = DomainDNSAddress2Compatible; item2.NormalDNS = JsonUtils.Serialize(JsonUtils.ParseJson(NormalDNS2Compatible)); item2.TunDNS = JsonUtils.Serialize(JsonUtils.ParseJson(TunDNS2Compatible)); await ConfigHandler.SaveDNSItems(_config, item2); await ConfigHandler.SaveConfig(_config); if (_updateView != null) { await _updateView(EViewAction.CloseWindow, null); } } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/FullConfigTemplateViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class FullConfigTemplateViewModel : MyReactiveObject { #region Reactive [Reactive] public bool EnableFullConfigTemplate4Ray { get; set; } [Reactive] public bool EnableFullConfigTemplate4Singbox { get; set; } [Reactive] public string FullConfigTemplate4Ray { get; set; } [Reactive] public string FullConfigTemplate4Singbox { get; set; } [Reactive] public string FullTunConfigTemplate4Singbox { get; set; } [Reactive] public bool AddProxyOnly4Ray { get; set; } [Reactive] public bool AddProxyOnly4Singbox { get; set; } [Reactive] public string ProxyDetour4Ray { get; set; } [Reactive] public string ProxyDetour4Singbox { get; set; } public ReactiveCommand SaveCmd { get; } #endregion Reactive public FullConfigTemplateViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; SaveCmd = ReactiveCommand.CreateFromTask(async () => { await SaveSettingAsync(); }); _ = Init(); } private async Task Init() { var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray); EnableFullConfigTemplate4Ray = item?.Enabled ?? false; FullConfigTemplate4Ray = item?.Config ?? string.Empty; AddProxyOnly4Ray = item?.AddProxyOnly ?? false; ProxyDetour4Ray = item?.ProxyDetour ?? string.Empty; var item2 = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box); EnableFullConfigTemplate4Singbox = item2?.Enabled ?? false; FullConfigTemplate4Singbox = item2?.Config ?? string.Empty; FullTunConfigTemplate4Singbox = item2?.TunConfig ?? string.Empty; AddProxyOnly4Singbox = item2?.AddProxyOnly ?? false; ProxyDetour4Singbox = item2?.ProxyDetour ?? string.Empty; } private async Task SaveSettingAsync() { if (!await SaveXrayConfigAsync()) { return; } if (!await SaveSingboxConfigAsync()) { return; } NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); _ = _updateView?.Invoke(EViewAction.CloseWindow, null); } private async Task SaveXrayConfigAsync() { var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray); item.Enabled = EnableFullConfigTemplate4Ray; item.Config = null; item.Config = FullConfigTemplate4Ray; item.AddProxyOnly = AddProxyOnly4Ray; item.ProxyDetour = ProxyDetour4Ray; await ConfigHandler.SaveFullConfigTemplate(_config, item); return true; } private async Task SaveSingboxConfigAsync() { var item = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box); item.Enabled = EnableFullConfigTemplate4Singbox; item.Config = null; item.TunConfig = null; item.Config = FullConfigTemplate4Singbox; item.TunConfig = FullTunConfigTemplate4Singbox; item.AddProxyOnly = AddProxyOnly4Singbox; item.ProxyDetour = ProxyDetour4Singbox; await ConfigHandler.SaveFullConfigTemplate(_config, item); return true; } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/GlobalHotkeySettingViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class GlobalHotkeySettingViewModel : MyReactiveObject { private readonly List _globalHotkeys; public ReactiveCommand SaveCmd { get; } public GlobalHotkeySettingViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; _globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys); SaveCmd = ReactiveCommand.CreateFromTask(async () => { await SaveSettingAsync(); }); } public KeyEventItem GetKeyEventItem(EGlobalHotkey eg) { var item = _globalHotkeys.FirstOrDefault((it) => it.EGlobalHotkey == eg); if (item != null) { return item; } item = new() { EGlobalHotkey = eg, Control = false, Alt = false, Shift = false, KeyCode = null }; _globalHotkeys.Add(item); return item; } public void ResetKeyEventItem() { _globalHotkeys.Clear(); } private async Task SaveSettingAsync() { _config.GlobalHotkeys = _globalHotkeys; if (await ConfigHandler.SaveConfig(_config) == 0) { _updateView?.Invoke(EViewAction.CloseWindow, null); } else { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); } } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs ================================================ using System.Reactive.Concurrency; namespace ServiceLib.ViewModels; public class MainWindowViewModel : MyReactiveObject { #region Menu //servers public ReactiveCommand AddVmessServerCmd { get; } public ReactiveCommand AddVlessServerCmd { get; } public ReactiveCommand AddShadowsocksServerCmd { get; } public ReactiveCommand AddSocksServerCmd { get; } public ReactiveCommand AddHttpServerCmd { get; } public ReactiveCommand AddTrojanServerCmd { get; } public ReactiveCommand AddHysteria2ServerCmd { get; } public ReactiveCommand AddTuicServerCmd { get; } public ReactiveCommand AddWireguardServerCmd { get; } public ReactiveCommand AddAnytlsServerCmd { get; } public ReactiveCommand AddCustomServerCmd { get; } public ReactiveCommand AddPolicyGroupServerCmd { get; } public ReactiveCommand AddProxyChainServerCmd { get; } public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } public ReactiveCommand AddServerViaImageCmd { get; } //Subscription public ReactiveCommand SubSettingCmd { get; } public ReactiveCommand SubUpdateCmd { get; } public ReactiveCommand SubUpdateViaProxyCmd { get; } public ReactiveCommand SubGroupUpdateCmd { get; } public ReactiveCommand SubGroupUpdateViaProxyCmd { get; } //Setting public ReactiveCommand OptionSettingCmd { get; } public ReactiveCommand RoutingSettingCmd { get; } public ReactiveCommand DNSSettingCmd { get; } public ReactiveCommand FullConfigTemplateCmd { get; } public ReactiveCommand GlobalHotkeySettingCmd { get; } public ReactiveCommand RebootAsAdminCmd { get; } public ReactiveCommand ClearServerStatisticsCmd { get; } public ReactiveCommand OpenTheFileLocationCmd { get; } //Presets public ReactiveCommand RegionalPresetDefaultCmd { get; } public ReactiveCommand RegionalPresetRussiaCmd { get; } public ReactiveCommand RegionalPresetIranCmd { get; } public ReactiveCommand ReloadCmd { get; } [Reactive] public bool BlReloadEnabled { get; set; } [Reactive] public bool ShowClashUI { get; set; } [Reactive] public int TabMainSelectedIndex { get; set; } [Reactive] public bool BlIsWindows { get; set; } #endregion Menu #region Init public MainWindowViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; BlIsWindows = Utils.IsWindows(); #region WhenAnyValue && ReactiveCommand //servers AddVmessServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.VMess); }); AddVlessServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.VLESS); }); AddShadowsocksServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.Shadowsocks); }); AddSocksServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.SOCKS); }); AddHttpServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.HTTP); }); AddTrojanServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.Trojan); }); AddHysteria2ServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.Hysteria2); }); AddTuicServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.TUIC); }); AddWireguardServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.WireGuard); }); AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.Anytls); }); AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.Custom); }); AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.PolicyGroup); }); AddProxyChainServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.ProxyChain); }); AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerViaClipboardAsync(null); }); AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerViaScanAsync(); }); AddServerViaImageCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerViaImageAsync(); }); //Subscription SubSettingCmd = ReactiveCommand.CreateFromTask(async () => { await SubSettingAsync(); }); SubUpdateCmd = ReactiveCommand.CreateFromTask(async () => { await UpdateSubscriptionProcess("", false); }); SubUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () => { await UpdateSubscriptionProcess("", true); }); SubGroupUpdateCmd = ReactiveCommand.CreateFromTask(async () => { await UpdateSubscriptionProcess(_config.SubIndexId, false); }); SubGroupUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () => { await UpdateSubscriptionProcess(_config.SubIndexId, true); }); //Setting OptionSettingCmd = ReactiveCommand.CreateFromTask(async () => { await OptionSettingAsync(); }); RoutingSettingCmd = ReactiveCommand.CreateFromTask(async () => { await RoutingSettingAsync(); }); DNSSettingCmd = ReactiveCommand.CreateFromTask(async () => { await DNSSettingAsync(); }); FullConfigTemplateCmd = ReactiveCommand.CreateFromTask(async () => { await FullConfigTemplateAsync(); }); GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () => { if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true) { NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); } }); RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () => { await AppManager.Instance.RebootAsAdmin(); }); ClearServerStatisticsCmd = ReactiveCommand.CreateFromTask(async () => { await ClearServerStatistics(); }); OpenTheFileLocationCmd = ReactiveCommand.CreateFromTask(async () => { await OpenTheFileLocation(); }); ReloadCmd = ReactiveCommand.CreateFromTask(async () => { await Reload(); }); RegionalPresetDefaultCmd = ReactiveCommand.CreateFromTask(async () => { await ApplyRegionalPreset(EPresetType.Default); }); RegionalPresetRussiaCmd = ReactiveCommand.CreateFromTask(async () => { await ApplyRegionalPreset(EPresetType.Russia); }); RegionalPresetIranCmd = ReactiveCommand.CreateFromTask(async () => { await ApplyRegionalPreset(EPresetType.Iran); }); #endregion WhenAnyValue && ReactiveCommand #region AppEvents AppEvents.ReloadRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await Reload()); AppEvents.AddServerViaScanRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await AddServerViaScanAsync()); AppEvents.AddServerViaClipboardRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await AddServerViaClipboardAsync(null)); AppEvents.SubscriptionsUpdateRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy)); #endregion AppEvents _ = Init(); } private async Task Init() { AppManager.Instance.ShowInTaskbar = true; //await ConfigHandler.InitBuiltinRouting(_config); await ConfigHandler.InitBuiltinDNS(_config); await ConfigHandler.InitBuiltinFullConfigTemplate(_config); await ProfileExManager.Instance.Init(); await CoreManager.Instance.Init(_config, UpdateHandler); TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler); if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) { await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler); } await RefreshServers(); await Reload(); } #endregion Init #region Actions private async Task UpdateHandler(bool notify, string msg) { NoticeManager.Instance.SendMessage(msg); if (notify) { NoticeManager.Instance.Enqueue(msg); } await Task.CompletedTask; } private async Task UpdateTaskHandler(bool success, string msg) { NoticeManager.Instance.SendMessageEx(msg); if (success) { var indexIdOld = _config.IndexId; await RefreshServers(); if (indexIdOld != _config.IndexId) { await Reload(); } if (_config.UiItem.EnableAutoAdjustMainLvColWidth) { AppEvents.AdjustMainLvColWidthRequested.Publish(); } } } private async Task UpdateStatisticsHandler(ServerSpeedItem update) { if (!AppManager.Instance.ShowInTaskbar) { return; } AppEvents.DispatcherStatisticsRequested.Publish(update); await Task.CompletedTask; } #endregion Actions #region Servers && Groups private async Task RefreshServers() { AppEvents.ProfilesRefreshRequested.Publish(); await Task.Delay(200); } private void RefreshSubscriptions() { AppEvents.SubscriptionsRefreshRequested.Publish(); } #endregion Servers && Groups #region Add Servers public async Task AddServerAsync(EConfigType eConfigType) { ProfileItem item = new() { Subid = _config.SubIndexId, ConfigType = eConfigType, IsSub = false, }; bool? ret = false; if (eConfigType == EConfigType.Custom) { ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item); } else if (eConfigType.IsGroupType()) { ret = await _updateView?.Invoke(EViewAction.AddGroupServerWindow, item); } else { ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item); } if (ret == true) { await RefreshServers(); if (item.IndexId == _config.IndexId) { await Reload(); } } } public async Task AddServerViaClipboardAsync(string? clipboardData) { if (clipboardData == null) { await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null); return; } var ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false); if (ret > 0) { RefreshSubscriptions(); await RefreshServers(); NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret)); } else { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); } } public async Task AddServerViaScanAsync() { _updateView?.Invoke(EViewAction.ScanScreenTask, null); await Task.CompletedTask; } public async Task ScanScreenResult(byte[]? bytes) { var result = QRCodeUtils.ParseBarcode(bytes); await AddScanResultAsync(result); } public async Task AddServerViaImageAsync() { _updateView?.Invoke(EViewAction.ScanImageTask, null); await Task.CompletedTask; } public async Task ScanImageResult(string fileName) { if (fileName.IsNullOrEmpty()) { return; } var result = QRCodeUtils.ParseBarcode(fileName); await AddScanResultAsync(result); } private async Task AddScanResultAsync(string? result) { if (result.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.NoValidQRcodeFound); } else { var ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false); if (ret > 0) { RefreshSubscriptions(); await RefreshServers(); NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan); } else { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); } } } #endregion Add Servers #region Subscription private async Task SubSettingAsync() { if (await _updateView?.Invoke(EViewAction.SubSettingWindow, null) == true) { RefreshSubscriptions(); } } public async Task UpdateSubscriptionProcess(string subId, bool blProxy) { await Task.Run(async () => await SubscriptionHandler.UpdateProcess(_config, subId, blProxy, UpdateTaskHandler)); } #endregion Subscription #region Setting private async Task OptionSettingAsync() { var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null); if (ret == true) { AppEvents.InboundDisplayRequested.Publish(); await Reload(); } } private async Task RoutingSettingAsync() { var ret = await _updateView?.Invoke(EViewAction.RoutingSettingWindow, null); if (ret == true) { await ConfigHandler.InitBuiltinRouting(_config); AppEvents.RoutingsMenuRefreshRequested.Publish(); await Reload(); } } private async Task DNSSettingAsync() { var ret = await _updateView?.Invoke(EViewAction.DNSSettingWindow, null); if (ret == true) { await Reload(); } } private async Task FullConfigTemplateAsync() { var ret = await _updateView?.Invoke(EViewAction.FullConfigTemplateWindow, null); if (ret == true) { await Reload(); } } private async Task ClearServerStatistics() { await StatisticsManager.Instance.ClearAllServerStatistics(); await RefreshServers(); } private async Task OpenTheFileLocation() { var path = Utils.StartupPath(); if (Utils.IsWindows()) { ProcUtils.ProcessStart(path); } else if (Utils.IsLinux()) { ProcUtils.ProcessStart("xdg-open", path); } else if (Utils.IsMacOS()) { ProcUtils.ProcessStart("open", path); } await Task.CompletedTask; } #endregion Setting #region core job private bool _hasNextReloadJob = false; private readonly SemaphoreSlim _reloadSemaphore = new(1, 1); public async Task Reload() { //If there are unfinished reload job, marked with next job. if (!await _reloadSemaphore.WaitAsync(0)) { _hasNextReloadJob = true; return; } try { SetReloadEnabled(false); var profileItem = await ConfigHandler.GetDefaultServer(_config); if (profileItem == null) { NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings); return; } var allResult = await CoreConfigContextBuilder.BuildAll(_config, profileItem); if (NoticeManager.Instance.NotifyValidatorResult(allResult.CombinedValidatorResult) && !allResult.Success) { return; } await Task.Run(async () => { await LoadCore(allResult.ResolvedMainContext, allResult.PreSocksResult?.Context); await SysProxyHandler.UpdateSysProxy(_config, false); await Task.Delay(1000); }); AppEvents.TestServerRequested.Publish(); var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box); if (showClashUI) { AppEvents.ProxiesReloadRequested.Publish(); } ReloadResult(showClashUI); } finally { SetReloadEnabled(true); _reloadSemaphore.Release(); //If there is a next reload job, execute it. if (_hasNextReloadJob) { _hasNextReloadJob = false; await Reload(); } } } private void ReloadResult(bool showClashUI) { RxSchedulers.MainThreadScheduler.Schedule(() => { ShowClashUI = showClashUI; TabMainSelectedIndex = showClashUI ? TabMainSelectedIndex : 0; }); } private void SetReloadEnabled(bool enabled) { RxSchedulers.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled); } private async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext) { await CoreManager.Instance.LoadCore(mainContext, preContext); } #endregion core job #region Presets public async Task ApplyRegionalPreset(EPresetType type) { await ConfigHandler.ApplyRegionalPreset(_config, type); await ConfigHandler.InitRouting(_config); AppEvents.RoutingsMenuRefreshRequested.Publish(); await ConfigHandler.SaveConfig(_config); await new UpdateService(_config, UpdateTaskHandler).UpdateGeoFileAll(); await Reload(); } #endregion Presets } ================================================ FILE: v2rayN/ServiceLib/ViewModels/MsgViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class MsgViewModel : MyReactiveObject { private readonly ConcurrentQueue _queueMsg = new(); private volatile bool _lastMsgFilterNotAvailable; private int _showLock = 0; // 0 = unlocked, 1 = locked public int NumMaxMsg { get; } = 500; [Reactive] public string MsgFilter { get; set; } [Reactive] public bool AutoRefresh { get; set; } public MsgViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty; AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true; this.WhenAnyValue( x => x.MsgFilter) .Subscribe(c => DoMsgFilter()); this.WhenAnyValue( x => x.AutoRefresh, y => y == true) .Subscribe(c => _config.MsgUIItem.AutoRefresh = AutoRefresh); AppEvents.SendMsgViewRequested .AsObservable() //.ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(content => _ = AppendQueueMsg(content)); } private async Task AppendQueueMsg(string msg) { if (AutoRefresh == false) { return; } EnqueueQueueMsg(msg); if (!AppManager.Instance.ShowInTaskbar) { return; } if (Interlocked.CompareExchange(ref _showLock, 1, 0) != 0) { return; } try { await Task.Delay(500).ConfigureAwait(false); var sb = new StringBuilder(); while (_queueMsg.TryDequeue(out var line)) { sb.Append(line); } await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString()); } finally { Interlocked.Exchange(ref _showLock, 0); } } private void EnqueueQueueMsg(string msg) { //filter msg if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) { try { if (!Regex.IsMatch(msg, MsgFilter)) { return; } } catch (Exception ex) { EnqueueWithLimit(ex.Message); _lastMsgFilterNotAvailable = true; } } EnqueueWithLimit(msg); if (!msg.EndsWith(Environment.NewLine)) { EnqueueWithLimit(Environment.NewLine); } } private void EnqueueWithLimit(string item) { _queueMsg.Enqueue(item); while (_queueMsg.Count > NumMaxMsg) { _queueMsg.TryDequeue(out _); } } //public void ClearMsg() //{ // _queueMsg.Clear(); //} private void DoMsgFilter() { _config.MsgUIItem.MainMsgFilter = MsgFilter; _lastMsgFilterNotAvailable = false; } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class OptionSettingViewModel : MyReactiveObject { #region Core [Reactive] public int localPort { get; set; } [Reactive] public bool SecondLocalPortEnabled { get; set; } [Reactive] public bool udpEnabled { get; set; } [Reactive] public bool sniffingEnabled { get; set; } public IList destOverride { get; set; } [Reactive] public bool routeOnly { get; set; } [Reactive] public bool allowLANConn { get; set; } [Reactive] public bool newPort4LAN { get; set; } [Reactive] public string user { get; set; } [Reactive] public string pass { get; set; } [Reactive] public bool muxEnabled { get; set; } [Reactive] public bool logEnabled { get; set; } [Reactive] public string loglevel { get; set; } [Reactive] public bool defAllowInsecure { get; set; } [Reactive] public string defFingerprint { get; set; } [Reactive] public string defUserAgent { get; set; } [Reactive] public string mux4SboxProtocol { get; set; } [Reactive] public bool enableCacheFile4Sbox { get; set; } [Reactive] public int? hyUpMbps { get; set; } [Reactive] public int? hyDownMbps { get; set; } [Reactive] public bool enableFragment { get; set; } #endregion Core #region Core KCP //[Reactive] public int Kcpmtu { get; set; } //[Reactive] public int Kcptti { get; set; } //[Reactive] public int KcpuplinkCapacity { get; set; } //[Reactive] public int KcpdownlinkCapacity { get; set; } //[Reactive] public int KcpreadBufferSize { get; set; } //[Reactive] public int KcpwriteBufferSize { get; set; } //[Reactive] public bool Kcpcongestion { get; set; } #endregion Core KCP #region UI [Reactive] public bool AutoRun { get; set; } [Reactive] public bool EnableStatistics { get; set; } [Reactive] public bool KeepOlderDedupl { get; set; } [Reactive] public bool DisplayRealTimeSpeed { get; set; } [Reactive] public bool EnableAutoAdjustMainLvColWidth { get; set; } [Reactive] public bool AutoHideStartup { get; set; } [Reactive] public bool Hide2TrayWhenClose { get; set; } [Reactive] public bool MacOSShowInDock { get; set; } [Reactive] public bool EnableDragDropSort { get; set; } [Reactive] public bool DoubleClick2Activate { get; set; } [Reactive] public int AutoUpdateInterval { get; set; } [Reactive] public int TrayMenuServersLimit { get; set; } [Reactive] public string CurrentFontFamily { get; set; } [Reactive] public int SpeedTestTimeout { get; set; } [Reactive] public string SpeedTestUrl { get; set; } [Reactive] public string SpeedPingTestUrl { get; set; } [Reactive] public int MixedConcurrencyCount { get; set; } [Reactive] public bool EnableHWA { get; set; } [Reactive] public string SubConvertUrl { get; set; } [Reactive] public int MainGirdOrientation { get; set; } [Reactive] public string GeoFileSourceUrl { get; set; } [Reactive] public string SrsFileSourceUrl { get; set; } [Reactive] public string RoutingRulesSourceUrl { get; set; } [Reactive] public string IPAPIUrl { get; set; } #endregion UI #region UI visibility [Reactive] public bool BlIsWindows { get; set; } [Reactive] public bool BlIsLinux { get; set; } [Reactive] public bool BlIsIsMacOS { get; set; } [Reactive] public bool BlIsNonWindows { get; set; } #endregion UI visibility #region System proxy [Reactive] public bool notProxyLocalAddress { get; set; } [Reactive] public string systemProxyAdvancedProtocol { get; set; } [Reactive] public string systemProxyExceptions { get; set; } [Reactive] public string CustomSystemProxyPacPath { get; set; } [Reactive] public string CustomSystemProxyScriptPath { get; set; } #endregion System proxy #region Tun mode [Reactive] public bool TunAutoRoute { get; set; } [Reactive] public bool TunStrictRoute { get; set; } [Reactive] public string TunStack { get; set; } [Reactive] public int TunMtu { get; set; } [Reactive] public bool TunEnableIPv6Address { get; set; } #endregion Tun mode #region CoreType [Reactive] public string CoreType1 { get; set; } [Reactive] public string CoreType2 { get; set; } [Reactive] public string CoreType3 { get; set; } [Reactive] public string CoreType4 { get; set; } [Reactive] public string CoreType5 { get; set; } [Reactive] public string CoreType6 { get; set; } [Reactive] public string CoreType7 { get; set; } [Reactive] public string CoreType9 { get; set; } #endregion CoreType public ReactiveCommand SaveCmd { get; } public OptionSettingViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; BlIsWindows = Utils.IsWindows(); BlIsLinux = Utils.IsLinux(); BlIsIsMacOS = Utils.IsMacOS(); BlIsNonWindows = Utils.IsNonWindows(); SaveCmd = ReactiveCommand.CreateFromTask(async () => { await SaveSettingAsync(); }); _ = Init(); } private async Task Init() { await _updateView?.Invoke(EViewAction.InitSettingFont, null); #region Core var inbound = _config.Inbound.First(); localPort = inbound.LocalPort; SecondLocalPortEnabled = inbound.SecondLocalPortEnabled; udpEnabled = inbound.UdpEnabled; sniffingEnabled = inbound.SniffingEnabled; routeOnly = inbound.RouteOnly; allowLANConn = inbound.AllowLANConn; newPort4LAN = inbound.NewPort4LAN; user = inbound.User; pass = inbound.Pass; muxEnabled = _config.CoreBasicItem.MuxEnabled; logEnabled = _config.CoreBasicItem.LogEnabled; loglevel = _config.CoreBasicItem.Loglevel; defAllowInsecure = _config.CoreBasicItem.DefAllowInsecure; defFingerprint = _config.CoreBasicItem.DefFingerprint; defUserAgent = _config.CoreBasicItem.DefUserAgent; mux4SboxProtocol = _config.Mux4SboxItem.Protocol; enableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox; hyUpMbps = _config.HysteriaItem.UpMbps; hyDownMbps = _config.HysteriaItem.DownMbps; enableFragment = _config.CoreBasicItem.EnableFragment; #endregion Core #region Core KCP //Kcpmtu = _config.kcpItem.mtu; //Kcptti = _config.kcpItem.tti; //KcpuplinkCapacity = _config.kcpItem.uplinkCapacity; //KcpdownlinkCapacity = _config.kcpItem.downlinkCapacity; //KcpreadBufferSize = _config.kcpItem.readBufferSize; //KcpwriteBufferSize = _config.kcpItem.writeBufferSize; //Kcpcongestion = _config.kcpItem.congestion; #endregion Core KCP #region UI AutoRun = _config.GuiItem.AutoRun; EnableStatistics = _config.GuiItem.EnableStatistics; DisplayRealTimeSpeed = _config.GuiItem.DisplayRealTimeSpeed; KeepOlderDedupl = _config.GuiItem.KeepOlderDedupl; EnableAutoAdjustMainLvColWidth = _config.UiItem.EnableAutoAdjustMainLvColWidth; AutoHideStartup = _config.UiItem.AutoHideStartup; Hide2TrayWhenClose = _config.UiItem.Hide2TrayWhenClose; MacOSShowInDock = _config.UiItem.MacOSShowInDock; EnableDragDropSort = _config.UiItem.EnableDragDropSort; DoubleClick2Activate = _config.UiItem.DoubleClick2Activate; AutoUpdateInterval = _config.GuiItem.AutoUpdateInterval; TrayMenuServersLimit = _config.GuiItem.TrayMenuServersLimit; CurrentFontFamily = _config.UiItem.CurrentFontFamily; SpeedTestTimeout = _config.SpeedTestItem.SpeedTestTimeout; SpeedTestUrl = _config.SpeedTestItem.SpeedTestUrl; MixedConcurrencyCount = _config.SpeedTestItem.MixedConcurrencyCount; SpeedPingTestUrl = _config.SpeedTestItem.SpeedPingTestUrl; EnableHWA = _config.GuiItem.EnableHWA; SubConvertUrl = _config.ConstItem.SubConvertUrl; MainGirdOrientation = (int)_config.UiItem.MainGirdOrientation; GeoFileSourceUrl = _config.ConstItem.GeoSourceUrl; SrsFileSourceUrl = _config.ConstItem.SrsSourceUrl; RoutingRulesSourceUrl = _config.ConstItem.RouteRulesTemplateSourceUrl; IPAPIUrl = _config.SpeedTestItem.IPAPIUrl; #endregion UI #region System proxy notProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress; systemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol; systemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions; CustomSystemProxyPacPath = _config.SystemProxyItem.CustomSystemProxyPacPath; CustomSystemProxyScriptPath = _config.SystemProxyItem.CustomSystemProxyScriptPath; #endregion System proxy #region Tun mode TunAutoRoute = _config.TunModeItem.AutoRoute; TunStrictRoute = _config.TunModeItem.StrictRoute; TunStack = _config.TunModeItem.Stack; TunMtu = _config.TunModeItem.Mtu; TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address; #endregion Tun mode await InitCoreType(); } private async Task InitCoreType() { if (_config.CoreTypeItem == null) { _config.CoreTypeItem = new List(); } foreach (EConfigType it in Enum.GetValues(typeof(EConfigType))) { if (_config.CoreTypeItem.FindIndex(t => t.ConfigType == it) >= 0) { continue; } _config.CoreTypeItem.Add(new CoreTypeItem() { ConfigType = it, CoreType = ECoreType.Xray }); } _config.CoreTypeItem.ForEach(it => { var type = it.CoreType.ToString(); switch ((int)it.ConfigType) { case 1: CoreType1 = type; break; case 2: CoreType2 = type; break; case 3: CoreType3 = type; break; case 4: CoreType4 = type; break; case 5: CoreType5 = type; break; case 6: CoreType6 = type; break; case 7: CoreType7 = type; break; case 9: CoreType9 = type; break; } }); await Task.CompletedTask; } private async Task SaveSettingAsync() { if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString()) || localPort <= 0 || localPort >= Global.MaxPort) { NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort); return; } var needReboot = EnableStatistics != _config.GuiItem.EnableStatistics || DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed || EnableDragDropSort != _config.UiItem.EnableDragDropSort || EnableHWA != _config.GuiItem.EnableHWA || CurrentFontFamily != _config.UiItem.CurrentFontFamily || MainGirdOrientation != (int)_config.UiItem.MainGirdOrientation; //if (Utile.IsNullOrEmpty(Kcpmtu.ToString()) || !Utile.IsNumeric(Kcpmtu.ToString()) // || Utile.IsNullOrEmpty(Kcptti.ToString()) || !Utile.IsNumeric(Kcptti.ToString()) // || Utile.IsNullOrEmpty(KcpuplinkCapacity.ToString()) || !Utile.IsNumeric(KcpuplinkCapacity.ToString()) // || Utile.IsNullOrEmpty(KcpdownlinkCapacity.ToString()) || !Utile.IsNumeric(KcpdownlinkCapacity.ToString()) // || Utile.IsNullOrEmpty(KcpreadBufferSize.ToString()) || !Utile.IsNumeric(KcpreadBufferSize.ToString()) // || Utile.IsNullOrEmpty(KcpwriteBufferSize.ToString()) || !Utile.IsNumeric(KcpwriteBufferSize.ToString())) //{ // NoticeHandler.Instance.Enqueue(ResUI.FillKcpParameters); // return; //} //Core _config.Inbound.First().LocalPort = localPort; _config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled; _config.Inbound.First().UdpEnabled = udpEnabled; _config.Inbound.First().SniffingEnabled = sniffingEnabled; _config.Inbound.First().DestOverride = destOverride?.ToList(); _config.Inbound.First().RouteOnly = routeOnly; _config.Inbound.First().AllowLANConn = allowLANConn; _config.Inbound.First().NewPort4LAN = newPort4LAN; _config.Inbound.First().User = user; _config.Inbound.First().Pass = pass; if (_config.Inbound.Count > 1) { _config.Inbound.RemoveAt(1); } _config.CoreBasicItem.LogEnabled = logEnabled; _config.CoreBasicItem.Loglevel = loglevel; _config.CoreBasicItem.MuxEnabled = muxEnabled; _config.CoreBasicItem.DefAllowInsecure = defAllowInsecure; _config.CoreBasicItem.DefFingerprint = defFingerprint; _config.CoreBasicItem.DefUserAgent = defUserAgent; _config.Mux4SboxItem.Protocol = mux4SboxProtocol; _config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox; _config.HysteriaItem.UpMbps = hyUpMbps ?? 0; _config.HysteriaItem.DownMbps = hyDownMbps ?? 0; _config.CoreBasicItem.EnableFragment = enableFragment; _config.GuiItem.AutoRun = AutoRun; _config.GuiItem.EnableStatistics = EnableStatistics; _config.GuiItem.DisplayRealTimeSpeed = DisplayRealTimeSpeed; _config.GuiItem.KeepOlderDedupl = KeepOlderDedupl; _config.UiItem.EnableAutoAdjustMainLvColWidth = EnableAutoAdjustMainLvColWidth; _config.UiItem.AutoHideStartup = AutoHideStartup; _config.UiItem.Hide2TrayWhenClose = Hide2TrayWhenClose; _config.UiItem.MacOSShowInDock = MacOSShowInDock; _config.GuiItem.AutoUpdateInterval = AutoUpdateInterval; _config.UiItem.EnableDragDropSort = EnableDragDropSort; _config.UiItem.DoubleClick2Activate = DoubleClick2Activate; _config.GuiItem.TrayMenuServersLimit = TrayMenuServersLimit; _config.UiItem.CurrentFontFamily = CurrentFontFamily; _config.SpeedTestItem.SpeedTestTimeout = SpeedTestTimeout; _config.SpeedTestItem.MixedConcurrencyCount = MixedConcurrencyCount; _config.SpeedTestItem.SpeedTestUrl = SpeedTestUrl; _config.SpeedTestItem.SpeedPingTestUrl = SpeedPingTestUrl; _config.GuiItem.EnableHWA = EnableHWA; _config.ConstItem.SubConvertUrl = SubConvertUrl; _config.UiItem.MainGirdOrientation = (EGirdOrientation)MainGirdOrientation; _config.ConstItem.GeoSourceUrl = GeoFileSourceUrl; _config.ConstItem.SrsSourceUrl = SrsFileSourceUrl; _config.ConstItem.RouteRulesTemplateSourceUrl = RoutingRulesSourceUrl; _config.SpeedTestItem.IPAPIUrl = IPAPIUrl; //systemProxy _config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions; _config.SystemProxyItem.NotProxyLocalAddress = notProxyLocalAddress; _config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol; _config.SystemProxyItem.CustomSystemProxyPacPath = CustomSystemProxyPacPath; _config.SystemProxyItem.CustomSystemProxyScriptPath = CustomSystemProxyScriptPath; //tun mode _config.TunModeItem.AutoRoute = TunAutoRoute; _config.TunModeItem.StrictRoute = TunStrictRoute; _config.TunModeItem.Stack = TunStack; _config.TunModeItem.Mtu = TunMtu; _config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address; //coreType await SaveCoreType(); if (await ConfigHandler.SaveConfig(_config) == 0) { await AutoStartupHandler.UpdateTask(_config); AppManager.Instance.Reset(); NoticeManager.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess); _updateView?.Invoke(EViewAction.CloseWindow, null); } else { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); } } private async Task SaveCoreType() { for (var k = 1; k <= _config.CoreTypeItem.Count; k++) { var item = _config.CoreTypeItem[k - 1]; var type = string.Empty; switch ((int)item.ConfigType) { case 1: type = CoreType1; break; case 2: type = CoreType2; break; case 3: type = CoreType3; break; case 4: type = CoreType4; break; case 5: type = CoreType5; break; case 6: type = CoreType6; break; case 7: type = CoreType7; break; case 9: type = CoreType9; break; default: continue; } item.CoreType = (ECoreType)Enum.Parse(typeof(ECoreType), type); } await Task.CompletedTask; } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class ProfilesSelectViewModel : MyReactiveObject { #region private prop private string _serverFilter = string.Empty; private Dictionary _dicHeaderSort = new(); private string _subIndexId = string.Empty; // ConfigType filter state: default include-mode with all types selected private List _filterConfigTypes = new(); private bool _filterExclude = false; #endregion private prop #region ObservableCollection public IObservableCollection ProfileItems { get; } = new ObservableCollectionExtended(); public IObservableCollection SubItems { get; } = new ObservableCollectionExtended(); [Reactive] public ProfileItemModel SelectedProfile { get; set; } public IList SelectedProfiles { get; set; } [Reactive] public SubItem SelectedSub { get; set; } [Reactive] public string ServerFilter { get; set; } // Include/Exclude filter for ConfigType public List FilterConfigTypes { get => _filterConfigTypes; set => this.RaiseAndSetIfChanged(ref _filterConfigTypes, value); } [Reactive] public bool FilterExclude { get => _filterExclude; set => this.RaiseAndSetIfChanged(ref _filterExclude, value); } #endregion ObservableCollection #region Init public ProfilesSelectViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; _subIndexId = _config.SubIndexId ?? string.Empty; #region WhenAnyValue && ReactiveCommand this.WhenAnyValue( x => x.SelectedSub, y => y != null && !y.Remarks.IsNullOrEmpty() && _subIndexId != y.Id) .Subscribe(async c => await SubSelectedChangedAsync(c)); this.WhenAnyValue( x => x.ServerFilter, y => y != null && _serverFilter != y) .Subscribe(async c => await ServerFilterChanged(c)); // React to ConfigType filter changes this.WhenAnyValue(x => x.FilterExclude) .Skip(1) .Subscribe(async _ => await RefreshServersBiz()); this.WhenAnyValue(x => x.FilterConfigTypes) .Skip(1) .Subscribe(async _ => await RefreshServersBiz()); #endregion WhenAnyValue && ReactiveCommand _ = Init(); } private async Task Init() { SelectedProfile = new(); SelectedSub = new(); // Default: include mode with all ConfigTypes selected try { FilterExclude = false; FilterConfigTypes = Enum.GetValues(typeof(EConfigType)).Cast().ToList(); } catch { FilterConfigTypes = new(); } await RefreshSubscriptions(); await RefreshServers(); } #endregion Init #region Actions public bool CanOk() { return SelectedProfile != null && !SelectedProfile.IndexId.IsNullOrEmpty(); } public bool SelectFinish() { if (!CanOk()) { return false; } _updateView?.Invoke(EViewAction.CloseWindow, null); return true; } #endregion Actions #region Servers && Groups private async Task SubSelectedChangedAsync(bool c) { if (!c) { return; } _subIndexId = SelectedSub?.Id; await RefreshServers(); await _updateView?.Invoke(EViewAction.ProfilesFocus, null); } private async Task ServerFilterChanged(bool c) { if (!c) { return; } _serverFilter = ServerFilter; if (_serverFilter.IsNullOrEmpty()) { await RefreshServers(); } } public async Task RefreshServers() { await RefreshServersBiz(); } private async Task RefreshServersBiz() { var lstModel = await GetProfileItemsEx(_subIndexId, _serverFilter); ProfileItems.Clear(); ProfileItems.AddRange(lstModel); if (lstModel.Count > 0) { var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); if (selected != null) { SelectedProfile = selected; } else { SelectedProfile = lstModel.First(); } } await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null); } public async Task RefreshSubscriptions() { SubItems.Clear(); SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers }); foreach (var item in await AppManager.Instance.SubItems()) { SubItems.Add(item); } SelectedSub = (_config.SubIndexId.IsNotEmpty() ? SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId) : null) ?? SubItems.LastOrDefault(); } private async Task?> GetProfileItemsEx(string subid, string filter) { var lstModel = await AppManager.Instance.ProfileModels(_subIndexId, filter); lstModel = (from t in lstModel select new ProfileItemModel { IndexId = t.IndexId, ConfigType = t.ConfigType, Remarks = t.Remarks, Address = t.Address, Port = t.Port, //Security = t.Security, Network = t.Network, StreamSecurity = t.StreamSecurity, Subid = t.Subid, SubRemarks = t.SubRemarks, IsActive = t.IndexId == _config.IndexId, }).OrderBy(t => t.Sort).ToList(); // Apply ConfigType filter (include or exclude) if (FilterConfigTypes != null && FilterConfigTypes.Count > 0) { if (FilterExclude) { lstModel = lstModel.Where(t => !FilterConfigTypes.Contains(t.ConfigType)).ToList(); } else { lstModel = lstModel.Where(t => FilterConfigTypes.Contains(t.ConfigType)).ToList(); } } return lstModel; } public async Task GetProfileItem() { if (string.IsNullOrEmpty(SelectedProfile?.IndexId)) { return null; } var indexId = SelectedProfile.IndexId; var item = await AppManager.Instance.GetProfileItem(indexId); if (item is null) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); return null; } return item; } public async Task?> GetProfileItems() { if (SelectedProfiles == null || SelectedProfiles.Count == 0) { return null; } var lst = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(SelectedProfiles.Select(sp => sp?.IndexId)); if (lst.Count == 0) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); return null; } return lst; } public void SortServer(string colName) { if (colName.IsNullOrEmpty()) { return; } var prop = typeof(ProfileItemModel).GetProperty(colName); if (prop == null) { return; } _dicHeaderSort.TryAdd(colName, true); var asc = _dicHeaderSort[colName]; var comparer = Comparer.Create((a, b) => { if (ReferenceEquals(a, b)) { return 0; } if (a is null) { return -1; } if (b is null) { return 1; } if (a.GetType() == b.GetType() && a is IComparable ca) { return ca.CompareTo(b); } return string.Compare(a.ToString(), b.ToString(), StringComparison.OrdinalIgnoreCase); }); object? KeySelector(ProfileItemModel x) { return prop.GetValue(x); } IEnumerable sorted = asc ? ProfileItems.OrderBy(KeySelector, comparer) : ProfileItems.OrderByDescending(KeySelector, comparer); var list = sorted.ToList(); ProfileItems.Clear(); ProfileItems.AddRange(list); _dicHeaderSort[colName] = !asc; return; } #endregion Servers && Groups #region Public API // External setter for ConfigType filter public void SetConfigTypeFilter(IEnumerable types, bool exclude = false) { FilterConfigTypes = types?.Distinct().ToList() ?? new List(); FilterExclude = exclude; } #endregion Public API } ================================================ FILE: v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class ProfilesViewModel : MyReactiveObject { #region private prop private List _lstProfile; private string _serverFilter = string.Empty; private Dictionary _dicHeaderSort = new(); private SpeedtestService? _speedtestService; private string? _pendingSelectIndexId; #endregion private prop #region ObservableCollection public IObservableCollection ProfileItems { get; } = new ObservableCollectionExtended(); public IObservableCollection SubItems { get; } = new ObservableCollectionExtended(); [Reactive] public ProfileItemModel SelectedProfile { get; set; } public IList SelectedProfiles { get; set; } [Reactive] public SubItem SelectedSub { get; set; } [Reactive] public SubItem SelectedMoveToGroup { get; set; } [Reactive] public string ServerFilter { get; set; } #endregion ObservableCollection #region Menu //servers delete public ReactiveCommand EditServerCmd { get; } public ReactiveCommand RemoveServerCmd { get; } public ReactiveCommand RemoveDuplicateServerCmd { get; } public ReactiveCommand CopyServerCmd { get; } public ReactiveCommand SetDefaultServerCmd { get; } public ReactiveCommand ShareServerCmd { get; } public ReactiveCommand GenGroupAllServerCmd { get; } public ReactiveCommand GenGroupRegionServerCmd { get; } //servers move public ReactiveCommand MoveTopCmd { get; } public ReactiveCommand MoveUpCmd { get; } public ReactiveCommand MoveDownCmd { get; } public ReactiveCommand MoveBottomCmd { get; } public ReactiveCommand MoveToGroupCmd { get; } //servers ping public ReactiveCommand MixedTestServerCmd { get; } public ReactiveCommand TcpingServerCmd { get; } public ReactiveCommand RealPingServerCmd { get; } public ReactiveCommand SpeedServerCmd { get; } public ReactiveCommand SortServerResultCmd { get; } public ReactiveCommand RemoveInvalidServerResultCmd { get; } public ReactiveCommand FastRealPingCmd { get; } //servers export public ReactiveCommand Export2ClientConfigCmd { get; } public ReactiveCommand Export2ClientConfigClipboardCmd { get; } public ReactiveCommand Export2ShareUrlCmd { get; } public ReactiveCommand Export2ShareUrlBase64Cmd { get; } public ReactiveCommand AddSubCmd { get; } public ReactiveCommand EditSubCmd { get; } public ReactiveCommand DeleteSubCmd { get; } #endregion Menu #region Init public ProfilesViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; #region WhenAnyValue && ReactiveCommand var canEditRemove = this.WhenAnyValue( x => x.SelectedProfile, selectedSource => selectedSource != null && !selectedSource.IndexId.IsNullOrEmpty()); this.WhenAnyValue( x => x.SelectedSub, y => y != null && !y.Remarks.IsNullOrEmpty() && _config.SubIndexId != y.Id) .Subscribe(async c => await SubSelectedChangedAsync(c)); this.WhenAnyValue( x => x.SelectedMoveToGroup, y => y != null && !y.Remarks.IsNullOrEmpty()) .Subscribe(async c => await MoveToGroup(c)); this.WhenAnyValue( x => x.ServerFilter, y => y != null && _serverFilter != y) .Subscribe(async c => await ServerFilterChanged(c)); //servers delete EditServerCmd = ReactiveCommand.CreateFromTask(async () => { await EditServerAsync(); }, canEditRemove); RemoveServerCmd = ReactiveCommand.CreateFromTask(async () => { await RemoveServerAsync(); }, canEditRemove); RemoveDuplicateServerCmd = ReactiveCommand.CreateFromTask(async () => { await RemoveDuplicateServer(); }); CopyServerCmd = ReactiveCommand.CreateFromTask(async () => { await CopyServer(); }, canEditRemove); SetDefaultServerCmd = ReactiveCommand.CreateFromTask(async () => { await SetDefaultServer(); }, canEditRemove); ShareServerCmd = ReactiveCommand.CreateFromTask(async () => { await ShareServerAsync(); }, canEditRemove); GenGroupAllServerCmd = ReactiveCommand.CreateFromTask(async () => { await GenGroupAllServer(); }, canEditRemove); GenGroupRegionServerCmd = ReactiveCommand.CreateFromTask(async () => { await GenGroupRegionServer(); }, canEditRemove); //servers move MoveTopCmd = ReactiveCommand.CreateFromTask(async () => { await MoveServer(EMove.Top); }, canEditRemove); MoveUpCmd = ReactiveCommand.CreateFromTask(async () => { await MoveServer(EMove.Up); }, canEditRemove); MoveDownCmd = ReactiveCommand.CreateFromTask(async () => { await MoveServer(EMove.Down); }, canEditRemove); MoveBottomCmd = ReactiveCommand.CreateFromTask(async () => { await MoveServer(EMove.Bottom); }, canEditRemove); MoveToGroupCmd = ReactiveCommand.CreateFromTask(async sub => { SelectedMoveToGroup = sub; }); //servers ping FastRealPingCmd = ReactiveCommand.CreateFromTask(async () => { await ServerSpeedtest(ESpeedActionType.FastRealping); }); MixedTestServerCmd = ReactiveCommand.CreateFromTask(async () => { await ServerSpeedtest(ESpeedActionType.Mixedtest); }); TcpingServerCmd = ReactiveCommand.CreateFromTask(async () => { await ServerSpeedtest(ESpeedActionType.Tcping); }, canEditRemove); RealPingServerCmd = ReactiveCommand.CreateFromTask(async () => { await ServerSpeedtest(ESpeedActionType.Realping); }, canEditRemove); SpeedServerCmd = ReactiveCommand.CreateFromTask(async () => { await ServerSpeedtest(ESpeedActionType.Speedtest); }, canEditRemove); SortServerResultCmd = ReactiveCommand.CreateFromTask(async () => { await SortServer(EServerColName.DelayVal.ToString()); }); RemoveInvalidServerResultCmd = ReactiveCommand.CreateFromTask(async () => { await RemoveInvalidServerResult(); }); //servers export Export2ClientConfigCmd = ReactiveCommand.CreateFromTask(async () => { await Export2ClientConfigAsync(false); }, canEditRemove); Export2ClientConfigClipboardCmd = ReactiveCommand.CreateFromTask(async () => { await Export2ClientConfigAsync(true); }, canEditRemove); Export2ShareUrlCmd = ReactiveCommand.CreateFromTask(async () => { await Export2ShareUrlAsync(false); }, canEditRemove); Export2ShareUrlBase64Cmd = ReactiveCommand.CreateFromTask(async () => { await Export2ShareUrlAsync(true); }, canEditRemove); //Subscription AddSubCmd = ReactiveCommand.CreateFromTask(async () => { await EditSubAsync(true); }); EditSubCmd = ReactiveCommand.CreateFromTask(async () => { await EditSubAsync(false); }); DeleteSubCmd = ReactiveCommand.CreateFromTask(async () => { await DeleteSubAsync(); }); #endregion WhenAnyValue && ReactiveCommand #region AppEvents AppEvents.ProfilesRefreshRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await RefreshServersBiz()); AppEvents.SubscriptionsRefreshRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await RefreshSubscriptions()); AppEvents.DispatcherStatisticsRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async result => await UpdateStatistics(result)); AppEvents.SetDefaultServerRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async indexId => await SetDefaultServer(indexId)); #endregion AppEvents _ = Init(); } private async Task Init() { SelectedProfile = new(); SelectedSub = new(); SelectedMoveToGroup = new(); await RefreshSubscriptions(); //await RefreshServers(); } #endregion Init #region Actions private void Reload() { AppEvents.ReloadRequested.Publish(); } public async Task SetSpeedTestResult(SpeedTestResult result) { if (result.IndexId.IsNullOrEmpty()) { NoticeManager.Instance.SendMessageEx(result.Delay); NoticeManager.Instance.Enqueue(result.Delay); return; } var item = ProfileItems.FirstOrDefault(it => it.IndexId == result.IndexId); if (item == null) { return; } if (result.Delay.IsNotEmpty()) { item.Delay = result.Delay.ToInt(); item.DelayVal = result.Delay ?? string.Empty; } if (result.Speed.IsNotEmpty()) { item.SpeedVal = result.Speed ?? string.Empty; } await Task.CompletedTask; } public async Task UpdateStatistics(ServerSpeedItem update) { if (!_config.GuiItem.EnableStatistics || (update.ProxyUp + update.ProxyDown) <= 0 || DateTime.Now.Second % 3 != 0) { return; } try { var item = ProfileItems.FirstOrDefault(it => it.IndexId == update.IndexId); if (item != null) { item.TodayDown = Utils.HumanFy(update.TodayDown); item.TodayUp = Utils.HumanFy(update.TodayUp); item.TotalDown = Utils.HumanFy(update.TotalDown); item.TotalUp = Utils.HumanFy(update.TotalUp); } } catch { } await Task.CompletedTask; } #endregion Actions #region Servers && Groups private async Task SubSelectedChangedAsync(bool c) { if (!c) { return; } _config.SubIndexId = SelectedSub?.Id; await RefreshServers(); await _updateView?.Invoke(EViewAction.ProfilesFocus, null); } private async Task ServerFilterChanged(bool c) { if (!c) { return; } _serverFilter = ServerFilter; if (_serverFilter.IsNullOrEmpty()) { await RefreshServers(); } } public async Task RefreshServers() { AppEvents.ProfilesRefreshRequested.Publish(); await Task.Delay(200); } private async Task RefreshServersBiz() { var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter); _lstProfile = JsonUtils.Deserialize>(JsonUtils.Serialize(lstModel)) ?? []; ProfileItems.Clear(); ProfileItems.AddRange(lstModel); if (lstModel.Count > 0) { ProfileItemModel? selected = null; if (!_pendingSelectIndexId.IsNullOrEmpty()) { selected = lstModel.FirstOrDefault(t => t.IndexId == _pendingSelectIndexId); _pendingSelectIndexId = null; } selected ??= lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); SelectedProfile = selected ?? lstModel.First(); } await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null); } private async Task RefreshSubscriptions() { SubItems.Clear(); SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers }); foreach (var item in await AppManager.Instance.SubItems()) { SubItems.Add(item); } SelectedSub = (_config.SubIndexId.IsNotEmpty() ? SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId) : null) ?? SubItems.LastOrDefault(); } private async Task?> GetProfileItemsEx(string subid, string filter) { var lstModel = await AppManager.Instance.ProfileModels(_config.SubIndexId, filter); await ConfigHandler.SetDefaultServer(_config, lstModel); var lstServerStat = (_config.GuiItem.EnableStatistics ? StatisticsManager.Instance.ServerStat : null) ?? []; var lstProfileExs = await ProfileExManager.Instance.GetProfileExs(); lstModel = (from t in lstModel join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b from t22 in t2b.DefaultIfEmpty() join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b from t33 in t3b.DefaultIfEmpty() select new ProfileItemModel { IndexId = t.IndexId, ConfigType = t.ConfigType, Remarks = t.Remarks, Address = t.Address, Port = t.Port, //Security = t.Security, Network = t.Network, StreamSecurity = t.StreamSecurity, Subid = t.Subid, SubRemarks = t.SubRemarks, IsActive = t.IndexId == _config.IndexId, Sort = t33?.Sort ?? 0, Delay = t33?.Delay ?? 0, Speed = t33?.Speed ?? 0, DelayVal = t33?.Delay != 0 ? $"{t33?.Delay}" : string.Empty, SpeedVal = t33?.Speed > 0 ? $"{t33?.Speed}" : t33?.Message ?? string.Empty, TodayDown = t22 == null ? "" : Utils.HumanFy(t22.TodayDown), TodayUp = t22 == null ? "" : Utils.HumanFy(t22.TodayUp), TotalDown = t22 == null ? "" : Utils.HumanFy(t22.TotalDown), TotalUp = t22 == null ? "" : Utils.HumanFy(t22.TotalUp) }).OrderBy(t => t.Sort).ToList(); return lstModel; } #endregion Servers && Groups #region Add Servers private async Task?> GetProfileItems(bool latest) { var lstSelected = new List(); if (SelectedProfiles == null || SelectedProfiles.Count <= 0) { return null; } var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort); if (latest) { lstSelected.AddRange(await AppManager.Instance.GetProfileItemsOrderedByIndexIds(orderProfiles.Select(sp => sp?.IndexId))); } else { lstSelected = JsonUtils.Deserialize>(JsonUtils.Serialize(orderProfiles)); } return lstSelected; } public async Task EditServerAsync() { if (string.IsNullOrEmpty(SelectedProfile?.IndexId)) { return; } var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId); if (item is null) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); return; } var eConfigType = item.ConfigType; bool? ret = false; if (eConfigType == EConfigType.Custom) { ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item); } else if (eConfigType.IsGroupType()) { ret = await _updateView?.Invoke(EViewAction.AddGroupServerWindow, item); } else { ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item); } if (ret == true) { await RefreshServers(); if (item.IndexId == _config.IndexId) { Reload(); } } } public async Task RemoveServerAsync() { var lstSelected = await GetProfileItems(true); if (lstSelected == null) { return; } if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) { return; } var exists = lstSelected.Exists(t => t.IndexId == _config.IndexId); await ConfigHandler.RemoveServers(_config, lstSelected); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); if (lstSelected.Count == ProfileItems.Count) { ProfileItems.Clear(); } await RefreshServers(); if (exists) { Reload(); } } private async Task RemoveDuplicateServer() { if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) { return; } var tuple = await ConfigHandler.DedupServerList(_config, _config.SubIndexId); if (tuple.Item1 > 0 || tuple.Item2 > 0) { await RefreshServers(); Reload(); } NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, tuple.Item1, tuple.Item2)); } private async Task CopyServer() { var lstSelected = await GetProfileItems(false); if (lstSelected == null) { return; } if (await ConfigHandler.CopyServer(_config, lstSelected) == 0) { await RefreshServers(); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); } } public async Task SetDefaultServer() { if (string.IsNullOrEmpty(SelectedProfile?.IndexId)) { return; } await SetDefaultServer(SelectedProfile.IndexId); } private async Task SetDefaultServer(string? indexId) { if (indexId.IsNullOrEmpty()) { return; } if (indexId == _config.IndexId) { return; } var item = await AppManager.Instance.GetProfileItem(indexId); if (item is null) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); return; } if (await ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0) { await RefreshServers(); Reload(); } } public async Task ShareServerAsync() { var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId); if (item is null) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); return; } var url = FmtHandler.GetShareUri(item); if (url.IsNullOrEmpty()) { return; } await _updateView?.Invoke(EViewAction.ShareServer, url); } private async Task GenGroupAllServer() { var ret = await ConfigHandler.AddGroupAllServer(_config, SelectedSub); if (ret.Success != true) { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); return; } _pendingSelectIndexId = ret.Data?.ToString(); await RefreshServers(); } private async Task GenGroupRegionServer() { var ret = await ConfigHandler.AddGroupRegionServer(_config, SelectedSub); if (ret.Success != true) { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); return; } var indexIdList = ret.Data as List; _pendingSelectIndexId = indexIdList?.FirstOrDefault(); await RefreshServers(); } public async Task SortServer(string colName) { if (colName.IsNullOrEmpty()) { return; } _dicHeaderSort.TryAdd(colName, true); _dicHeaderSort.TryGetValue(colName, out var asc); if (await ConfigHandler.SortServers(_config, _config.SubIndexId, colName, asc) != 0) { return; } _dicHeaderSort[colName] = !asc; await RefreshServers(); } public async Task RemoveInvalidServerResult() { var count = await ConfigHandler.RemoveInvalidServerResult(_config, _config.SubIndexId); await RefreshServers(); NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveInvalidServerResultTip, count)); } //move server private async Task MoveToGroup(bool c) { if (!c) { return; } var lstSelected = await GetProfileItems(true); if (lstSelected == null) { return; } await ConfigHandler.MoveToGroup(_config, lstSelected, SelectedMoveToGroup.Id); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); await RefreshServers(); SelectedMoveToGroup = null; SelectedMoveToGroup = new(); } public async Task MoveServer(EMove eMove) { var item = _lstProfile.FirstOrDefault(t => t.IndexId == SelectedProfile.IndexId); if (item is null) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); return; } var index = _lstProfile.IndexOf(item); if (index < 0) { return; } if (await ConfigHandler.MoveServer(_config, _lstProfile, index, eMove) == 0) { await RefreshServers(); } } public async Task MoveServerTo(int startIndex, ProfileItemModel targetItem) { var targetIndex = ProfileItems.IndexOf(targetItem); if (startIndex >= 0 && targetIndex >= 0 && startIndex != targetIndex) { if (await ConfigHandler.MoveServer(_config, _lstProfile, startIndex, EMove.Position, targetIndex) == 0) { await RefreshServers(); } } } public async Task ServerSpeedtest(ESpeedActionType actionType) { if (actionType == ESpeedActionType.Mixedtest) { SelectedProfiles = ProfileItems; } else if (actionType == ESpeedActionType.FastRealping) { SelectedProfiles = ProfileItems; actionType = ESpeedActionType.Realping; } var lstSelected = await GetProfileItems(false); if (lstSelected == null) { return; } _speedtestService ??= new SpeedtestService(_config, async (SpeedTestResult result) => { RxSchedulers.MainThreadScheduler.Schedule(result, (scheduler, result) => { _ = SetSpeedTestResult(result); return Disposable.Empty; }); await Task.CompletedTask; }); _speedtestService?.RunLoop(actionType, lstSelected); } public void ServerSpeedtestStop() { _speedtestService?.ExitLoop(); } private async Task Export2ClientConfigAsync(bool blClipboard) { var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId); if (item is null) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); return; } var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item); if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success) { return; } if (blClipboard) { var result = await CoreConfigHandler.GenerateClientConfig(context, null); if (result.Success != true) { NoticeManager.Instance.Enqueue(result.Msg); } else { await _updateView?.Invoke(EViewAction.SetClipboardData, result.Data); NoticeManager.Instance.SendMessage(ResUI.OperationSuccess); } } else { await _updateView?.Invoke(EViewAction.SaveFileDialog, item); } } public async Task Export2ClientConfigResult(string fileName, ProfileItem item) { if (fileName.IsNullOrEmpty()) { return; } var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item); if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success) { return; } var result = await CoreConfigHandler.GenerateClientConfig(context, fileName); if (result.Success != true) { NoticeManager.Instance.Enqueue(result.Msg); } else { NoticeManager.Instance.SendMessageAndEnqueue(string.Format(ResUI.SaveClientConfigurationIn, fileName)); } } public async Task Export2ShareUrlAsync(bool blEncode) { var lstSelected = await GetProfileItems(true); if (lstSelected == null) { return; } StringBuilder sb = new(); foreach (var it in lstSelected) { var url = FmtHandler.GetShareUri(it); if (url.IsNullOrEmpty()) { continue; } sb.Append(url); sb.AppendLine(); } if (sb.Length > 0) { if (blEncode) { await _updateView?.Invoke(EViewAction.SetClipboardData, Utils.Base64Encode(sb.ToString())); } else { await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString()); } NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully); } } #endregion Add Servers #region Subscription private async Task EditSubAsync(bool blNew) { SubItem item; if (blNew) { item = new(); } else { item = await AppManager.Instance.GetSubItem(_config.SubIndexId); if (item is null) { return; } } if (await _updateView?.Invoke(EViewAction.SubEditWindow, item) == true) { await RefreshSubscriptions(); await SubSelectedChangedAsync(true); } } private async Task DeleteSubAsync() { var item = await AppManager.Instance.GetSubItem(_config.SubIndexId); if (item is null) { return; } if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) { return; } await ConfigHandler.DeleteSubItem(_config, item.Id); await RefreshSubscriptions(); await SubSelectedChangedAsync(true); } #endregion Subscription } ================================================ FILE: v2rayN/ServiceLib/ViewModels/RoutingRuleDetailsViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class RoutingRuleDetailsViewModel : MyReactiveObject { public IList ProtocolItems { get; set; } public IList InboundTagItems { get; set; } [Reactive] public RulesItem SelectedSource { get; set; } [Reactive] public string Domain { get; set; } [Reactive] public string IP { get; set; } [Reactive] public string Process { get; set; } [Reactive] public string? RuleType { get; set; } [Reactive] public bool AutoSort { get; set; } public ReactiveCommand SaveCmd { get; } public RoutingRuleDetailsViewModel(RulesItem rulesItem, Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; SaveCmd = ReactiveCommand.CreateFromTask(async () => { await SaveRulesAsync(); }); if (rulesItem.Id.IsNullOrEmpty()) { rulesItem.Id = Utils.GetGuid(false); rulesItem.OutboundTag = Global.ProxyTag; rulesItem.Enabled = true; SelectedSource = rulesItem; } else { SelectedSource = rulesItem; } Domain = Utils.List2String(SelectedSource.Domain, true); IP = Utils.List2String(SelectedSource.Ip, true); Process = Utils.List2String(SelectedSource.Process, true); RuleType = SelectedSource.RuleType?.ToString(); } private async Task SaveRulesAsync() { Domain = Utils.Convert2Comma(Domain); IP = Utils.Convert2Comma(IP); Process = Utils.Convert2Comma(Process); if (AutoSort) { SelectedSource.Domain = Utils.String2ListSorted(Domain); SelectedSource.Ip = Utils.String2ListSorted(IP); SelectedSource.Process = Utils.String2ListSorted(Process); } else { SelectedSource.Domain = Utils.String2List(Domain); SelectedSource.Ip = Utils.String2List(IP); SelectedSource.Process = Utils.String2List(Process); } SelectedSource.Protocol = ProtocolItems?.ToList(); SelectedSource.InboundTag = InboundTagItems?.ToList(); SelectedSource.RuleType = RuleType.IsNullOrEmpty() ? null : (ERuleType)Enum.Parse(typeof(ERuleType), RuleType); var hasRule = SelectedSource.Domain?.Count > 0 || SelectedSource.Ip?.Count > 0 || SelectedSource.Protocol?.Count > 0 || SelectedSource.Process?.Count > 0 || SelectedSource.Port.IsNotEmpty() || SelectedSource.Network.IsNotEmpty(); if (!hasRule) { NoticeManager.Instance.Enqueue(string.Format(ResUI.RoutingRuleDetailRequiredTips, "Network/Port/Protocol/Domain/IP/Process")); return; } //NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); await _updateView?.Invoke(EViewAction.CloseWindow, null); } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/RoutingRuleSettingViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class RoutingRuleSettingViewModel : MyReactiveObject { private List _rules; [Reactive] public RoutingItem SelectedRouting { get; set; } public IObservableCollection RulesItems { get; } = new ObservableCollectionExtended(); [Reactive] public RulesItemModel SelectedSource { get; set; } public IList SelectedSources { get; set; } public ReactiveCommand RuleAddCmd { get; } public ReactiveCommand ImportRulesFromFileCmd { get; } public ReactiveCommand ImportRulesFromClipboardCmd { get; } public ReactiveCommand ImportRulesFromUrlCmd { get; } public ReactiveCommand RuleRemoveCmd { get; } public ReactiveCommand RuleExportSelectedCmd { get; } public ReactiveCommand MoveTopCmd { get; } public ReactiveCommand MoveUpCmd { get; } public ReactiveCommand MoveDownCmd { get; } public ReactiveCommand MoveBottomCmd { get; } public ReactiveCommand SaveCmd { get; } public RoutingRuleSettingViewModel(RoutingItem routingItem, Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; var canEditRemove = this.WhenAnyValue( x => x.SelectedSource, selectedSource => selectedSource != null && !selectedSource.OutboundTag.IsNullOrEmpty()); RuleAddCmd = ReactiveCommand.CreateFromTask(async () => { await RuleEditAsync(true); }); ImportRulesFromFileCmd = ReactiveCommand.CreateFromTask(async () => { await _updateView?.Invoke(EViewAction.ImportRulesFromFile, null); }); ImportRulesFromClipboardCmd = ReactiveCommand.CreateFromTask(async () => { await ImportRulesFromClipboardAsync(null); }); ImportRulesFromUrlCmd = ReactiveCommand.CreateFromTask(async () => { await ImportRulesFromUrl(); }); RuleRemoveCmd = ReactiveCommand.CreateFromTask(async () => { await RuleRemoveAsync(); }, canEditRemove); RuleExportSelectedCmd = ReactiveCommand.CreateFromTask(async () => { await RuleExportSelectedAsync(); }, canEditRemove); MoveTopCmd = ReactiveCommand.CreateFromTask(async () => { await MoveRule(EMove.Top); }, canEditRemove); MoveUpCmd = ReactiveCommand.CreateFromTask(async () => { await MoveRule(EMove.Up); }, canEditRemove); MoveDownCmd = ReactiveCommand.CreateFromTask(async () => { await MoveRule(EMove.Down); }, canEditRemove); MoveBottomCmd = ReactiveCommand.CreateFromTask(async () => { await MoveRule(EMove.Bottom); }, canEditRemove); SaveCmd = ReactiveCommand.CreateFromTask(async () => { await SaveRoutingAsync(); }); SelectedSource = new(); SelectedRouting = routingItem; _rules = routingItem.Id.IsNullOrEmpty() ? new() : JsonUtils.Deserialize>(SelectedRouting.RuleSet); RefreshRulesItems(); } public void RefreshRulesItems() { RulesItems.Clear(); foreach (var item in _rules) { var it = new RulesItemModel() { Id = item.Id, RuleTypeName = item.RuleType?.ToString(), OutboundTag = item.OutboundTag, Port = item.Port, Network = item.Network, Protocols = Utils.List2String(item.Protocol), InboundTags = Utils.List2String(item.InboundTag), Domains = Utils.List2String((item.Domain ?? []).Concat(item.Ip ?? []).ToList().Concat(item.Process ?? []).ToList()), Enabled = item.Enabled, Remarks = item.Remarks, }; RulesItems.Add(it); } } public async Task RuleEditAsync(bool blNew) { RulesItem? item; if (blNew) { item = new(); } else { item = _rules.FirstOrDefault(t => t.Id == SelectedSource?.Id); if (item is null) { return; } } if (await _updateView?.Invoke(EViewAction.RoutingRuleDetailsWindow, item) == true) { if (blNew) { _rules.Insert(0, item); } RefreshRulesItems(); } } public async Task RuleRemoveAsync() { if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules); return; } if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) { return; } foreach (var it in SelectedSources ?? [SelectedSource]) { var item = _rules.FirstOrDefault(t => t.Id == it?.Id); if (item != null) { _rules.Remove(item); } } RefreshRulesItems(); } public async Task RuleExportSelectedAsync() { if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules); return; } var lst = new List(); var sources = SelectedSources ?? [SelectedSource]; foreach (var it in _rules) { if (sources.Any(t => t.Id == it?.Id)) { var item2 = JsonUtils.DeepCopy(it); item2.Id = null; lst.Add(item2 ?? new()); } } if (lst.Count > 0) { var options = new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; await _updateView?.Invoke(EViewAction.SetClipboardData, JsonUtils.Serialize(lst, options)); } } public async Task MoveRule(EMove eMove) { if (SelectedSource is null || SelectedSource.OutboundTag.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules); return; } var item = _rules.FirstOrDefault(t => t.Id == SelectedSource?.Id); if (item == null) { return; } var index = _rules.IndexOf(item); if (await ConfigHandler.MoveRoutingRule(_rules, index, eMove) == 0) { RefreshRulesItems(); } } private async Task SaveRoutingAsync() { var remarks = SelectedRouting.Remarks; if (remarks.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks); return; } var item = SelectedRouting; foreach (var it in _rules) { it.Id = Utils.GetGuid(false); } item.RuleNum = _rules.Count; item.RuleSet = JsonUtils.Serialize(_rules, false); if (await ConfigHandler.SaveRoutingItem(_config, item) == 0) { NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); _updateView?.Invoke(EViewAction.CloseWindow, null); } else { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); } } #region Import rules public async Task ImportRulesFromFileAsync(string fileName) { if (fileName.IsNullOrEmpty()) { return; } var result = EmbedUtils.LoadResource(fileName); if (result.IsNullOrEmpty()) { return; } var ret = await AddBatchRoutingRulesAsync(SelectedRouting, result); if (ret == 0) { RefreshRulesItems(); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); } } public async Task ImportRulesFromClipboardAsync(string? clipboardData) { if (clipboardData == null) { await _updateView?.Invoke(EViewAction.ImportRulesFromClipboard, null); return; } var ret = await AddBatchRoutingRulesAsync(SelectedRouting, clipboardData); if (ret == 0) { RefreshRulesItems(); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); } } private async Task ImportRulesFromUrl() { var url = SelectedRouting.Url; if (url.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.MsgNeedUrl); return; } var downloadHandle = new DownloadService(); var result = await downloadHandle.TryDownloadString(url, true, ""); var ret = await AddBatchRoutingRulesAsync(SelectedRouting, result); if (ret == 0) { RefreshRulesItems(); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); } } private async Task AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData) { var blReplace = false; if (await _updateView?.Invoke(EViewAction.AddBatchRoutingRulesYesNo, null) == false) { blReplace = true; } if (clipboardData.IsNullOrEmpty()) { return -1; } var lstRules = JsonUtils.Deserialize>(clipboardData); if (lstRules == null) { return -1; } foreach (var rule in lstRules) { rule.Id = Utils.GetGuid(false); } if (blReplace) { _rules = lstRules; } else { _rules.AddRange(lstRules); } return 0; } #endregion Import rules } ================================================ FILE: v2rayN/ServiceLib/ViewModels/RoutingSettingViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class RoutingSettingViewModel : MyReactiveObject { #region Reactive public IObservableCollection RoutingItems { get; } = new ObservableCollectionExtended(); [Reactive] public RoutingItemModel SelectedSource { get; set; } public IList SelectedSources { get; set; } [Reactive] public string DomainStrategy { get; set; } [Reactive] public string DomainStrategy4Singbox { get; set; } public ReactiveCommand RoutingAdvancedAddCmd { get; } public ReactiveCommand RoutingAdvancedRemoveCmd { get; } public ReactiveCommand RoutingAdvancedSetDefaultCmd { get; } public ReactiveCommand RoutingAdvancedImportRulesCmd { get; } public ReactiveCommand SaveCmd { get; } public bool IsModified { get; set; } #endregion Reactive public RoutingSettingViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; var canEditRemove = this.WhenAnyValue( x => x.SelectedSource, selectedSource => selectedSource != null && !selectedSource.Remarks.IsNullOrEmpty()); RoutingAdvancedAddCmd = ReactiveCommand.CreateFromTask(async () => { await RoutingAdvancedEditAsync(true); }); RoutingAdvancedRemoveCmd = ReactiveCommand.CreateFromTask(async () => { await RoutingAdvancedRemoveAsync(); }, canEditRemove); RoutingAdvancedSetDefaultCmd = ReactiveCommand.CreateFromTask(async () => { await RoutingAdvancedSetDefault(); }, canEditRemove); RoutingAdvancedImportRulesCmd = ReactiveCommand.CreateFromTask(async () => { await RoutingAdvancedImportRules(); }); SaveCmd = ReactiveCommand.CreateFromTask(async () => { await SaveRoutingAsync(); }); _ = Init(); } private async Task Init() { SelectedSource = new(); DomainStrategy = _config.RoutingBasicItem.DomainStrategy; DomainStrategy4Singbox = _config.RoutingBasicItem.DomainStrategy4Singbox; await ConfigHandler.InitBuiltinRouting(_config); await RefreshRoutingItems(); } #region Refresh Save public async Task RefreshRoutingItems() { RoutingItems.Clear(); var routings = await AppManager.Instance.RoutingItems(); foreach (var item in routings) { var it = new RoutingItemModel() { IsActive = item.IsActive, RuleNum = item.RuleNum, Id = item.Id, Remarks = item.Remarks, Url = item.Url, CustomIcon = item.CustomIcon, CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox, Sort = item.Sort, }; RoutingItems.Add(it); } } private async Task SaveRoutingAsync() { _config.RoutingBasicItem.DomainStrategy = DomainStrategy; _config.RoutingBasicItem.DomainStrategy4Singbox = DomainStrategy4Singbox; if (await ConfigHandler.SaveConfig(_config) == 0) { NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); _updateView?.Invoke(EViewAction.CloseWindow, null); } else { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); } } #endregion Refresh Save public async Task RoutingAdvancedEditAsync(bool blNew) { RoutingItem item; if (blNew) { item = new(); } else { item = await AppManager.Instance.GetRoutingItem(SelectedSource?.Id); if (item is null) { return; } } if (await _updateView?.Invoke(EViewAction.RoutingRuleSettingWindow, item) == true) { await RefreshRoutingItems(); IsModified = true; } } public async Task RoutingAdvancedRemoveAsync() { if (SelectedSource is null || SelectedSource.Remarks.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules); return; } if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) { return; } foreach (var it in SelectedSources ?? [SelectedSource]) { var item = await AppManager.Instance.GetRoutingItem(it?.Id); if (item != null) { await ConfigHandler.RemoveRoutingItem(item); } } await RefreshRoutingItems(); IsModified = true; } public async Task RoutingAdvancedSetDefault() { var item = await AppManager.Instance.GetRoutingItem(SelectedSource?.Id); if (item is null) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules); return; } if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) { await RefreshRoutingItems(); IsModified = true; } } private async Task RoutingAdvancedImportRules() { if (await ConfigHandler.InitRouting(_config, true) == 0) { await RefreshRoutingItems(); IsModified = true; } } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class StatusBarViewModel : MyReactiveObject { private static readonly Lazy _instance = new(() => new(null)); public static StatusBarViewModel Instance => _instance.Value; #region ObservableCollection public IObservableCollection RoutingItems { get; } = new ObservableCollectionExtended(); public IObservableCollection Servers { get; } = new ObservableCollectionExtended(); [Reactive] public RoutingItem SelectedRouting { get; set; } [Reactive] public ComboItem SelectedServer { get; set; } [Reactive] public bool BlServers { get; set; } #endregion ObservableCollection public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } public ReactiveCommand SubUpdateCmd { get; } public ReactiveCommand SubUpdateViaProxyCmd { get; } public ReactiveCommand CopyProxyCmdToClipboardCmd { get; } public ReactiveCommand NotifyLeftClickCmd { get; } public ReactiveCommand ShowWindowCmd { get; } public ReactiveCommand HideWindowCmd { get; } #region System Proxy [Reactive] public bool BlSystemProxyClear { get; set; } [Reactive] public bool BlSystemProxySet { get; set; } [Reactive] public bool BlSystemProxyNothing { get; set; } [Reactive] public bool BlSystemProxyPac { get; set; } public ReactiveCommand SystemProxyClearCmd { get; } public ReactiveCommand SystemProxySetCmd { get; } public ReactiveCommand SystemProxyNothingCmd { get; } public ReactiveCommand SystemProxyPacCmd { get; } [Reactive] public bool BlRouting { get; set; } [Reactive] public int SystemProxySelected { get; set; } [Reactive] public bool BlSystemProxyPacVisible { get; set; } #endregion System Proxy #region UI [Reactive] public string InboundDisplay { get; set; } [Reactive] public string InboundLanDisplay { get; set; } [Reactive] public string RunningServerDisplay { get; set; } [Reactive] public string RunningServerToolTipText { get; set; } [Reactive] public string RunningInfoDisplay { get; set; } [Reactive] public string SpeedProxyDisplay { get; set; } [Reactive] public string SpeedDirectDisplay { get; set; } [Reactive] public bool EnableTun { get; set; } [Reactive] public bool BlIsNonWindows { get; set; } #endregion UI public StatusBarViewModel(Func>? updateView) { _config = AppManager.Instance.Config; SelectedRouting = new(); SelectedServer = new(); RunningServerToolTipText = "-"; BlSystemProxyPacVisible = Utils.IsWindows(); BlIsNonWindows = Utils.IsNonWindows(); if (_config.TunModeItem.EnableTun && AllowEnableTun()) { EnableTun = true; } else { _config.TunModeItem.EnableTun = EnableTun = false; } #region WhenAnyValue && ReactiveCommand this.WhenAnyValue( x => x.SelectedRouting, y => y != null && !y.Remarks.IsNullOrEmpty()) .Subscribe(async c => await RoutingSelectedChangedAsync(c)); this.WhenAnyValue( x => x.SelectedServer, y => y != null && !y.Text.IsNullOrEmpty()) .Subscribe(ServerSelectedChanged); SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; this.WhenAnyValue( x => x.SystemProxySelected, y => y >= 0) .Subscribe(async c => await DoSystemProxySelected(c)); this.WhenAnyValue( x => x.EnableTun, y => y == true) .Subscribe(async c => await DoEnableTun(c)); CopyProxyCmdToClipboardCmd = ReactiveCommand.CreateFromTask(async () => { await CopyProxyCmdToClipboard(); }); NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () => { AppEvents.ShowHideWindowRequested.Publish(null); await Task.CompletedTask; }); ShowWindowCmd = ReactiveCommand.CreateFromTask(async () => { AppEvents.ShowHideWindowRequested.Publish(true); await Task.CompletedTask; }); HideWindowCmd = ReactiveCommand.CreateFromTask(async () => { AppEvents.ShowHideWindowRequested.Publish(false); await Task.CompletedTask; }); AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerViaClipboard(); }); AddServerViaScanCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerViaScan(); }); SubUpdateCmd = ReactiveCommand.CreateFromTask(async () => { await UpdateSubscriptionProcess(false); }); SubUpdateViaProxyCmd = ReactiveCommand.CreateFromTask(async () => { await UpdateSubscriptionProcess(true); }); //System proxy SystemProxyClearCmd = ReactiveCommand.CreateFromTask(async () => { await SetListenerType(ESysProxyType.ForcedClear); }); SystemProxySetCmd = ReactiveCommand.CreateFromTask(async () => { await SetListenerType(ESysProxyType.ForcedChange); }); SystemProxyNothingCmd = ReactiveCommand.CreateFromTask(async () => { await SetListenerType(ESysProxyType.Unchanged); }); SystemProxyPacCmd = ReactiveCommand.CreateFromTask(async () => { await SetListenerType(ESysProxyType.Pac); }); #endregion WhenAnyValue && ReactiveCommand #region AppEvents if (updateView != null) { InitUpdateView(updateView); } AppEvents.DispatcherStatisticsRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async result => await UpdateStatistics(result)); AppEvents.RoutingsMenuRefreshRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await RefreshRoutingsMenu()); AppEvents.TestServerRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await TestServerAvailability()); AppEvents.InboundDisplayRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await InboundDisplayStatus()); AppEvents.SysProxyChangeRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async result => await SetListenerType(result)); #endregion AppEvents _ = Init(); } private async Task Init() { await ConfigHandler.InitBuiltinRouting(_config); await RefreshRoutingsMenu(); await InboundDisplayStatus(); await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true); } public void InitUpdateView(Func>? updateView) { _updateView = updateView; if (_updateView != null) { AppEvents.ProfilesRefreshRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await RefreshServersBiz()); //.DisposeWith(_disposables); } } private async Task CopyProxyCmdToClipboard() { var cmd = Utils.IsWindows() ? "set" : "export"; var address = $"{Global.Loopback}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}"; var sb = new StringBuilder(); sb.AppendLine($"{cmd} http_proxy={Global.HttpProtocol}{address}"); sb.AppendLine($"{cmd} https_proxy={Global.HttpProtocol}{address}"); sb.AppendLine($"{cmd} all_proxy={Global.Socks5Protocol}{address}"); sb.AppendLine(""); sb.AppendLine($"{cmd} HTTP_PROXY={Global.HttpProtocol}{address}"); sb.AppendLine($"{cmd} HTTPS_PROXY={Global.HttpProtocol}{address}"); sb.AppendLine($"{cmd} ALL_PROXY={Global.Socks5Protocol}{address}"); await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString()); } private async Task AddServerViaClipboard() { AppEvents.AddServerViaClipboardRequested.Publish(); await Task.Delay(1000); } private async Task AddServerViaScan() { AppEvents.AddServerViaScanRequested.Publish(); await Task.Delay(1000); } private async Task UpdateSubscriptionProcess(bool blProxy) { AppEvents.SubscriptionsUpdateRequested.Publish(blProxy); await Task.Delay(1000); } private async Task RefreshServersBiz() { await RefreshServersMenu(); //display running server var running = await ConfigHandler.GetDefaultServer(_config); if (running != null) { RunningServerDisplay = RunningServerToolTipText = running.GetSummary(); } else { RunningServerDisplay = RunningServerToolTipText = ResUI.CheckServerSettings; } } private async Task RefreshServersMenu() { var lstModel = await AppManager.Instance.ProfileModels(_config.SubIndexId, ""); Servers.Clear(); if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit) { BlServers = false; return; } BlServers = true; for (var k = 0; k < lstModel.Count; k++) { var it = lstModel[k]; var name = it.GetSummary(); var item = new ComboItem() { ID = it.IndexId, Text = name }; Servers.Add(item); if (_config.IndexId == it.IndexId) { SelectedServer = item; } } } private void ServerSelectedChanged(bool c) { if (!c) { return; } if (SelectedServer == null) { return; } if (SelectedServer.ID.IsNullOrEmpty()) { return; } AppEvents.SetDefaultServerRequested.Publish(SelectedServer.ID); } public async Task TestServerAvailability() { var item = await ConfigHandler.GetDefaultServer(_config); if (item == null) { return; } await TestServerAvailabilitySub(ResUI.Speedtesting); var msg = await Task.Run(ConnectionHandler.RunAvailabilityCheck); NoticeManager.Instance.SendMessageEx(msg); await TestServerAvailabilitySub(msg); } private async Task TestServerAvailabilitySub(string msg) { RxSchedulers.MainThreadScheduler.Schedule(msg, (scheduler, msg) => { _ = TestServerAvailabilityResult(msg); return Disposable.Empty; }); await Task.CompletedTask; } public async Task TestServerAvailabilityResult(string msg) { RunningInfoDisplay = msg; await Task.CompletedTask; } #region System proxy and Routings private async Task SetListenerType(ESysProxyType type) { if (_config.SystemProxyItem.SysProxyType == type) { return; } _config.SystemProxyItem.SysProxyType = type; await ChangeSystemProxyAsync(type, true); NoticeManager.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType}"); SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; await ConfigHandler.SaveConfig(_config); } public async Task ChangeSystemProxyAsync(ESysProxyType type, bool blChange) { await SysProxyHandler.UpdateSysProxy(_config, false); BlSystemProxyClear = type == ESysProxyType.ForcedClear; BlSystemProxySet = type == ESysProxyType.ForcedChange; BlSystemProxyNothing = type == ESysProxyType.Unchanged; BlSystemProxyPac = type == ESysProxyType.Pac; if (blChange) { _updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null); } } private async Task RefreshRoutingsMenu() { RoutingItems.Clear(); BlRouting = true; var routings = await AppManager.Instance.RoutingItems(); foreach (var item in routings) { RoutingItems.Add(item); if (item.IsActive) { SelectedRouting = item; } } } private async Task RoutingSelectedChangedAsync(bool c) { if (!c) { return; } if (SelectedRouting == null) { return; } var item = await AppManager.Instance.GetRoutingItem(SelectedRouting?.Id); if (item is null) { return; } if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) { NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting); AppEvents.ReloadRequested.Publish(); _updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null); } } private async Task DoSystemProxySelected(bool c) { if (!c) { return; } if (_config.SystemProxyItem.SysProxyType == (ESysProxyType)SystemProxySelected) { return; } await SetListenerType((ESysProxyType)SystemProxySelected); } private async Task DoEnableTun(bool c) { if (_config.TunModeItem.EnableTun == EnableTun) { return; } _config.TunModeItem.EnableTun = EnableTun; if (EnableTun && AllowEnableTun() == false) { // When running as a non-administrator, reboot to administrator mode if (Utils.IsWindows()) { _config.TunModeItem.EnableTun = false; await AppManager.Instance.RebootAsAdmin(); return; } else { bool? passwordResult = await _updateView?.Invoke(EViewAction.PasswordInput, null); if (passwordResult == false) { _config.TunModeItem.EnableTun = false; return; } } } await ConfigHandler.SaveConfig(_config); AppEvents.ReloadRequested.Publish(); } private bool AllowEnableTun() { if (Utils.IsWindows()) { return Utils.IsAdministrator(); } else if (Utils.IsLinux()) { return AppManager.Instance.LinuxSudoPwd.IsNotEmpty(); } else if (Utils.IsMacOS()) { return AppManager.Instance.LinuxSudoPwd.IsNotEmpty(); } return false; } #endregion System proxy and Routings #region UI private async Task InboundDisplayStatus() { StringBuilder sb = new(); sb.Append($"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}"); if (_config.Inbound.First().SecondLocalPortEnabled) { sb.Append($",{AppManager.Instance.GetLocalPort(EInboundProtocol.socks2)}"); } sb.Append(']'); InboundDisplay = $"{ResUI.LabLocal}:{sb}"; if (_config.Inbound.First().AllowLANConn) { var lan = _config.Inbound.First().NewPort4LAN ? $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks3)}]" : $"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}]"; InboundLanDisplay = $"{ResUI.LabLAN}:{lan}"; } else { InboundLanDisplay = $"{ResUI.LabLAN}:{Global.None}"; } await Task.CompletedTask; } public async Task UpdateStatistics(ServerSpeedItem update) { if (!_config.GuiItem.DisplayRealTimeSpeed) { return; } try { if (AppManager.Instance.IsRunningCore(ECoreType.sing_box)) { SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); SpeedDirectDisplay = string.Empty; } else { SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, Global.ProxyTag, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); SpeedDirectDisplay = string.Format(ResUI.SpeedDisplayText, Global.DirectTag, Utils.HumanFy(update.DirectUp), Utils.HumanFy(update.DirectDown)); } } catch { } await Task.CompletedTask; } #endregion UI } ================================================ FILE: v2rayN/ServiceLib/ViewModels/SubEditViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class SubEditViewModel : MyReactiveObject { [Reactive] public SubItem SelectedSource { get; set; } public ReactiveCommand SaveCmd { get; } public SubEditViewModel(SubItem subItem, Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; SaveCmd = ReactiveCommand.CreateFromTask(async () => { await SaveSubAsync(); }); SelectedSource = subItem.Id.IsNullOrEmpty() ? subItem : JsonUtils.DeepCopy(subItem); } private async Task SaveSubAsync() { var remarks = SelectedSource.Remarks; if (remarks.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks); return; } var url = SelectedSource.Url; if (url.IsNotEmpty()) { var uri = Utils.TryUri(url); if (uri == null) { NoticeManager.Instance.Enqueue(ResUI.InvalidUrlTip); return; } //Do not allow http protocol if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost)) { NoticeManager.Instance.Enqueue(ResUI.InsecureUrlProtocol); //return; } } if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0) { NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); _updateView?.Invoke(EViewAction.CloseWindow, null); } else { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); } } } ================================================ FILE: v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs ================================================ namespace ServiceLib.ViewModels; public class SubSettingViewModel : MyReactiveObject { public IObservableCollection SubItems { get; } = new ObservableCollectionExtended(); [Reactive] public SubItem SelectedSource { get; set; } public IList SelectedSources { get; set; } public ReactiveCommand SubAddCmd { get; } public ReactiveCommand SubDeleteCmd { get; } public ReactiveCommand SubEditCmd { get; } public ReactiveCommand SubShareCmd { get; } public bool IsModified { get; set; } public SubSettingViewModel(Func>? updateView) { _config = AppManager.Instance.Config; _updateView = updateView; var canEditRemove = this.WhenAnyValue( x => x.SelectedSource, selectedSource => selectedSource != null && !selectedSource.Id.IsNullOrEmpty()); SubAddCmd = ReactiveCommand.CreateFromTask(async () => { await EditSubAsync(true); }); SubDeleteCmd = ReactiveCommand.CreateFromTask(async () => { await DeleteSubAsync(); }, canEditRemove); SubEditCmd = ReactiveCommand.CreateFromTask(async () => { await EditSubAsync(false); }, canEditRemove); SubShareCmd = ReactiveCommand.CreateFromTask(async () => { await _updateView?.Invoke(EViewAction.ShareSub, SelectedSource?.Url); }, canEditRemove); _ = Init(); } private async Task Init() { SelectedSource = new(); await RefreshSubItems(); } public async Task RefreshSubItems() { SubItems.Clear(); SubItems.AddRange(await AppManager.Instance.SubItems()); } public async Task EditSubAsync(bool blNew) { SubItem item; if (blNew) { item = new(); } else { item = await AppManager.Instance.GetSubItem(SelectedSource?.Id); if (item is null) { return; } } if (await _updateView?.Invoke(EViewAction.SubEditWindow, item) == true) { await RefreshSubItems(); IsModified = true; } } private async Task DeleteSubAsync() { if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false) { return; } foreach (var it in SelectedSources ?? [SelectedSource]) { await ConfigHandler.DeleteSubItem(_config, it.Id); } await RefreshSubItems(); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); IsModified = true; } } ================================================ FILE: v2rayN/v2rayN/App.xaml ================================================ 32 12 13 11 ================================================ FILE: v2rayN/v2rayN/App.xaml.cs ================================================ namespace v2rayN; /// /// Interaction logic for App.xaml /// public partial class App : Application { public static EventWaitHandle ProgramStarted; public App() { DispatcherUnhandledException += App_DispatcherUnhandledException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; } /// /// Open only one process /// /// protected override void OnStartup(StartupEventArgs e) { var exePathKey = Utils.GetMd5(Utils.GetExePath()); var rebootas = (e.Args ?? Array.Empty()).Any(t => t == Global.RebootAs); ProgramStarted = new EventWaitHandle(false, EventResetMode.AutoReset, exePathKey, out var bCreatedNew); if (!rebootas && !bCreatedNew) { ProgramStarted.Set(); Environment.Exit(0); return; } if (!AppManager.Instance.InitApp()) { UI.Show($"Loading GUI configuration file is abnormal,please restart the application{Environment.NewLine}加载GUI配置文件异常,请重启应用"); Environment.Exit(0); return; } AppManager.Instance.InitComponents(); RxAppBuilder.CreateReactiveUIBuilder() .WithWpf() .BuildApp(); base.OnStartup(e); } private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { Logging.SaveLog("App_DispatcherUnhandledException", e.Exception); e.Handled = true; } private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { if (e.ExceptionObject != null) { Logging.SaveLog("CurrentDomain_UnhandledException", (Exception)e.ExceptionObject); } } private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) { Logging.SaveLog("TaskScheduler_UnobservedTaskException", e.Exception); } protected override void OnExit(ExitEventArgs e) { Logging.SaveLog("OnExit"); base.OnExit(e); Process.GetCurrentProcess().Kill(); } } ================================================ FILE: v2rayN/v2rayN/AssemblyInfo.cs ================================================ [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located //(used if a resource is not found in the page, // or application resource dictionaries) ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located //(used if a resource is not found in the page, // app, or any theme specific resource dictionaries) )] ================================================ FILE: v2rayN/v2rayN/Base/MyDGTextColumn.cs ================================================ using System.Windows.Controls; namespace v2rayN.Base; internal class MyDGTextColumn : DataGridTextColumn { public string ExName { get; set; } } ================================================ FILE: v2rayN/v2rayN/Base/WindowBase.cs ================================================ namespace v2rayN.Base; public class WindowBase : ReactiveWindow where TViewModel : class { public WindowBase() { Loaded += OnLoaded; } protected virtual void OnLoaded(object? sender, RoutedEventArgs e) { try { var sizeItem = ConfigHandler.GetWindowSizeItem(AppManager.Instance.Config, GetType().Name); if (sizeItem == null) { return; } Width = Math.Min(sizeItem.Width, SystemParameters.WorkArea.Width); Height = Math.Min(sizeItem.Height, SystemParameters.WorkArea.Height); Left = SystemParameters.WorkArea.Left + ((SystemParameters.WorkArea.Width - Width) / 2); Top = SystemParameters.WorkArea.Top + ((SystemParameters.WorkArea.Height - Height) / 2); } catch { } } protected override void OnClosed(EventArgs e) { base.OnClosed(e); try { ConfigHandler.SaveWindowSizeItem(AppManager.Instance.Config, GetType().Name, Width, Height); } catch { } } } ================================================ FILE: v2rayN/v2rayN/Common/QRCodeWindowsUtils.cs ================================================ using System.Drawing; using System.Windows.Media; using System.Windows.Media.Imaging; namespace v2rayN.Common; public class QRCodeWindowsUtils { public static ImageSource? GetQRCode(string? strContent) { if (strContent is null) { return null; } try { var qrCodeImage = QRCodeUtils.GenQRCode(strContent); return qrCodeImage is null ? null : ByteToImage(qrCodeImage); } catch (Exception ex) { Logging.SaveLog("GetQRCode", ex); return null; } } public static byte[]? CaptureScreen(Window window) { try { GetDpi(window, out var dpiX, out var dpiY); var left = (int)SystemParameters.WorkArea.Left; var top = (int)SystemParameters.WorkArea.Top; var width = (int)(SystemParameters.WorkArea.Width / dpiX); var height = (int)(SystemParameters.WorkArea.Height / dpiY); using var fullImage = new Bitmap(width, height); using var g = Graphics.FromImage(fullImage); g.CopyFromScreen(left, top, 0, 0, fullImage.Size, CopyPixelOperation.SourceCopy); //fullImage.Save("test1.png", ImageFormat.Png); return ImageToByte(fullImage); } catch { return null; } } private static void GetDpi(Window window, out float x, out float y) { var hWnd = new WindowInteropHelper(window).EnsureHandle(); var g = Graphics.FromHwnd(hWnd); x = 96 / g.DpiX; y = 96 / g.DpiY; } private static ImageSource? ByteToImage(IEnumerable imageData) { return new ImageSourceConverter().ConvertFrom(imageData) as BitmapSource; } private static byte[]? ImageToByte(Image img) { return new ImageConverter().ConvertTo(img, typeof(byte[])) as byte[]; } } ================================================ FILE: v2rayN/v2rayN/Common/UI.cs ================================================ using Microsoft.Win32; namespace v2rayN.Common; internal class UI { private static readonly string caption = Global.AppName; public static void Show(string msg) { MessageBox.Show(msg, caption, MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK); } public static MessageBoxResult ShowYesNo(string msg) { return MessageBox.Show(msg, caption, MessageBoxButton.YesNo, MessageBoxImage.Question); } public static bool? OpenFileDialog(out string fileName, string filter) { fileName = string.Empty; var fileDialog = new OpenFileDialog { Multiselect = false, Filter = filter }; if (fileDialog.ShowDialog() != true) { return false; } fileName = fileDialog.FileName; return true; } public static bool? SaveFileDialog(out string fileName, string filter) { fileName = string.Empty; SaveFileDialog fileDialog = new() { Filter = filter, FilterIndex = 2, RestoreDirectory = true }; if (fileDialog.ShowDialog() != true) { return false; } fileName = fileDialog.FileName; return true; } } ================================================ FILE: v2rayN/v2rayN/Common/WindowsUtils.cs ================================================ using System.Drawing; using System.Windows.Media; using System.Windows.Media.Imaging; using Microsoft.Win32; namespace v2rayN.Common; internal static class WindowsUtils { private static readonly string _tag = "WindowsUtils"; public static string? GetClipboardData() { var strData = string.Empty; try { var data = Clipboard.GetDataObject(); if (data?.GetDataPresent(DataFormats.UnicodeText) == true) { strData = data.GetData(DataFormats.UnicodeText)?.ToString(); } return strData; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } return strData; } public static void SetClipboardData(string strData) { try { Clipboard.SetText(strData); } catch { } } [DllImport("dwmapi.dll")] public static extern int DwmSetWindowAttribute(nint hwnd, DWMWINDOWATTRIBUTE attribute, ref int attributeValue, uint attributeSize); public static ImageSource IconToImageSource(Icon icon) { return Imaging.CreateBitmapSourceFromHIcon( icon.Handle, new Int32Rect(0, 0, icon.Width, icon.Height), BitmapSizeOptions.FromEmptyOptions()); } public static void SetDarkBorder(Window window, string? theme) { var isDark = theme switch { nameof(ETheme.Dark) => true, nameof(ETheme.Light) => false, _ => IsDarkTheme(), }; SetDarkBorder(window, isDark); } private static void SetDarkBorder(Window window, bool dark) { // Make sure the handle is created before the window is shown var hWnd = new WindowInteropHelper(window).EnsureHandle(); var attribute = dark ? 1 : 0; var attributeSize = (uint)Marshal.SizeOf(attribute); DwmSetWindowAttribute(hWnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, ref attribute, attributeSize); DwmSetWindowAttribute(hWnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, ref attribute, attributeSize); } private static bool IsDarkTheme() { using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"); var obj = key?.GetValue("AppsUseLightTheme"); var value = obj?.ToString().ToInt(); return value == 0; } #region Windows API [Flags] public enum DWMWINDOWATTRIBUTE : uint { DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19, DWMWA_USE_IMMERSIVE_DARK_MODE = 20, } #endregion Windows API } ================================================ FILE: v2rayN/v2rayN/Converters/DelayColorConverter.cs ================================================ using System.Windows.Media; namespace v2rayN.Converters; public class DelayColorConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var delay = value.ToString().ToInt(); return delay switch { <= 0 => new SolidColorBrush(Colors.Red), <= 500 => new SolidColorBrush(Colors.Green), _ => new SolidColorBrush(Colors.IndianRed) }; } public object? ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return null; } } ================================================ FILE: v2rayN/v2rayN/Converters/InverseBooleanConverter.cs ================================================ namespace v2rayN.Converters; [ValueConversion(typeof(bool), typeof(bool))] public class InverseBooleanConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (targetType != typeof(bool)) { throw new InvalidOperationException("The target must be a boolean"); } return !(bool)value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } ================================================ FILE: v2rayN/v2rayN/Converters/MaterialDesignFonts.cs ================================================ using System.Windows.Media; namespace v2rayN.Converters; public class MaterialDesignFonts { public static FontFamily MyFont { get; } static MaterialDesignFonts() { try { var fontFamily = AppManager.Instance.Config.UiItem.CurrentFontFamily; if (fontFamily.IsNotEmpty()) { var fontPath = Utils.GetFontsPath(); MyFont = new FontFamily(new Uri(@$"file:///{fontPath}\"), $"./#{fontFamily}"); } } catch { } MyFont ??= new FontFamily("Microsoft YaHei"); } } ================================================ FILE: v2rayN/v2rayN/FodyWeavers.xml ================================================  ================================================ FILE: v2rayN/v2rayN/GlobalUsings.cs ================================================ global using System; global using System.Collections; global using System.Collections.Generic; global using System.ComponentModel; global using System.Diagnostics; global using System.Globalization; global using System.IO; global using System.Linq; global using System.Reactive.Disposables.Fluent; global using System.Reactive.Linq; global using System.Runtime.InteropServices; global using System.Text; global using System.Threading; global using System.Threading.Tasks; global using System.Windows; global using System.Windows.Data; global using System.Windows.Input; global using System.Windows.Interop; global using System.Windows.Threading; global using DynamicData; global using DynamicData.Binding; global using ReactiveUI; global using ReactiveUI.Builder; global using ReactiveUI.Fody.Helpers; global using ServiceLib; global using ServiceLib.Base; global using ServiceLib.Common; global using ServiceLib.Enums; global using ServiceLib.Events; global using ServiceLib.Handler; global using ServiceLib.Manager; global using ServiceLib.Models; global using ServiceLib.Resx; global using ServiceLib.ViewModels; global using v2rayN.Common; ================================================ FILE: v2rayN/v2rayN/Manager/HotkeyManager.cs ================================================ namespace v2rayN.Manager; public sealed class HotkeyManager { private static readonly Lazy _instance = new(() => new()); public static HotkeyManager Instance = _instance.Value; private const int WmHotkey = 0x0312; private readonly Dictionary> _hotkeyTriggerDic = new(); public bool IsPause { get; set; } = false; public event Action? UpdateViewEvent; public event Action? HotkeyTriggerEvent; public HotkeyManager() { ComponentDispatcher.ThreadPreprocessMessage += OnThreadPreProcessMessage; Init(); } private void Init() { _hotkeyTriggerDic.Clear(); foreach (var item in AppManager.Instance.Config.GlobalHotkeys) { if (item.KeyCode != null && (Key)item.KeyCode != Key.None) { var key = KeyInterop.VirtualKeyFromKey((Key)item.KeyCode); var modifiers = KeyModifiers.None; if (item.Control) { modifiers |= KeyModifiers.Ctrl; } if (item.Shift) { modifiers |= KeyModifiers.Shift; } if (item.Alt) { modifiers |= KeyModifiers.Alt; } key = (key << 16) | (int)modifiers; if (!_hotkeyTriggerDic.ContainsKey(key)) { _hotkeyTriggerDic.Add(key, new() { item.EGlobalHotkey }); } else { if (!_hotkeyTriggerDic[key].Contains(item.EGlobalHotkey)) { _hotkeyTriggerDic[key].Add(item.EGlobalHotkey); } } } } } public void Load() { foreach (var _hotkeyCode in _hotkeyTriggerDic.Keys) { var hotkeyInfo = GetHotkeyInfo(_hotkeyCode); var isSuccess = false; var msg = string.Empty; Application.Current?.Dispatcher.Invoke(() => { isSuccess = RegisterHotKey(nint.Zero, _hotkeyCode, hotkeyInfo.fsModifiers, hotkeyInfo.vKey); }); foreach (var name in hotkeyInfo.Names) { if (isSuccess) { msg = string.Format(ResUI.RegisterGlobalHotkeySuccessfully, $"{name}({hotkeyInfo.hotkeyStr})"); } else { var errInfo = new Win32Exception(Marshal.GetLastWin32Error()).Message; msg = string.Format(ResUI.RegisterGlobalHotkeyFailed, $"{name}({hotkeyInfo.hotkeyStr})", errInfo); } UpdateViewEvent?.Invoke(false, msg); } } } public void ReLoad() { foreach (var hotkey in _hotkeyTriggerDic.Keys) { Application.Current?.Dispatcher.Invoke(() => { UnregisterHotKey(nint.Zero, hotkey); }); } Init(); Load(); } private (int fsModifiers, int vKey, string hotkeyStr, List Names) GetHotkeyInfo(int hotkeyCode) { var fsModifiers = hotkeyCode & 0xffff; var vKey = (hotkeyCode >> 16) & 0xffff; var hotkeyStr = new StringBuilder(); var names = new List(); var modify = (KeyModifiers)fsModifiers; var key = KeyInterop.KeyFromVirtualKey(vKey); if ((modify & KeyModifiers.Ctrl) == KeyModifiers.Ctrl) { hotkeyStr.Append($"{KeyModifiers.Ctrl}+"); } if ((modify & KeyModifiers.Alt) == KeyModifiers.Alt) { hotkeyStr.Append($"{KeyModifiers.Alt}+"); } if ((modify & KeyModifiers.Shift) == KeyModifiers.Shift) { hotkeyStr.Append($"{KeyModifiers.Shift}+"); } hotkeyStr.Append(key.ToString()); foreach (var name in _hotkeyTriggerDic[hotkeyCode]) { names.Add(name.ToString()); } return (fsModifiers, vKey, hotkeyStr.ToString(), names); } private void OnThreadPreProcessMessage(ref MSG msg, ref bool handled) { if (msg.message != WmHotkey || !_hotkeyTriggerDic.ContainsKey((int)msg.lParam)) { return; } handled = true; var hotKeyCode = (int)msg.lParam; if (IsPause) { Application.Current?.Dispatcher.Invoke(() => { if (Keyboard.FocusedElement is UIElement element) { var keyEventArgs = new KeyEventArgs(Keyboard.PrimaryDevice, PresentationSource.FromVisual(element), 0, KeyInterop.KeyFromVirtualKey(GetHotkeyInfo(hotKeyCode).vKey)) { RoutedEvent = UIElement.KeyDownEvent }; element.RaiseEvent(keyEventArgs); } }); } else { foreach (var keyEvent in _hotkeyTriggerDic[(int)msg.lParam]) { HotkeyTriggerEvent?.Invoke(keyEvent); } } } [DllImport("user32.dll", SetLastError = true)] private static extern bool RegisterHotKey(nint hWnd, int id, int fsModifiers, int vlc); [DllImport("user32.dll", SetLastError = true)] private static extern bool UnregisterHotKey(nint hWnd, int id); [Flags] private enum KeyModifiers { None = 0x0000, Alt = 0x0001, Ctrl = 0x0002, Shift = 0x0004, Win = 0x0008, NoRepeat = 0x4000 } } ================================================ FILE: v2rayN/v2rayN/Manager/WindowsManager.cs ================================================ using System.Drawing; using System.Windows.Media.Imaging; namespace v2rayN.Manager; public sealed class WindowsManager { private static readonly Lazy instance = new(() => new()); public static WindowsManager Instance => instance.Value; private static readonly string _tag = "WindowsHandler"; public async Task GetNotifyIcon(Config config) { try { var index = (int)config.SystemProxyItem.SysProxyType; //Load from routing setting var createdIcon = await GetNotifyIcon4Routing(config); if (createdIcon != null) { return createdIcon; } //Load from local file var fileName = Utils.GetPath($"NotifyIcon{index + 1}.ico"); if (File.Exists(fileName)) { return new Icon(fileName); } return index switch { 0 => Properties.Resources.NotifyIcon1, 1 => Properties.Resources.NotifyIcon2, 2 => Properties.Resources.NotifyIcon3, 3 => Properties.Resources.NotifyIcon4, _ => Properties.Resources.NotifyIcon1, // default }; } catch (Exception ex) { Logging.SaveLog(_tag, ex); return Properties.Resources.NotifyIcon1; } } public System.Windows.Media.ImageSource GetAppIcon(Config config) { var index = (int)config.SystemProxyItem.SysProxyType + 1; return BitmapFrame.Create(new Uri($"pack://application:,,,/Resources/NotifyIcon{index}.ico", UriKind.RelativeOrAbsolute)); } private async Task GetNotifyIcon4Routing(Config config) { try { var item = await ConfigHandler.GetDefaultRouting(config); if (item == null || item.CustomIcon.IsNullOrEmpty() || !File.Exists(item.CustomIcon)) { return null; } var color = ColorTranslator.FromHtml("#3399CC"); var index = (int)config.SystemProxyItem.SysProxyType; if (index > 0) { color = (new[] { Color.Red, Color.Purple, Color.DarkGreen, Color.Orange, Color.DarkSlateBlue, Color.RoyalBlue })[index - 1]; } var width = 128; var height = 128; Bitmap bitmap = new(width, height); var graphics = Graphics.FromImage(bitmap); SolidBrush drawBrush = new(color); graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; //graphics.FillRectangle(drawBrush, new Rectangle(0, 0, width, height)); graphics.DrawImage(new Bitmap(item.CustomIcon), 0, 0, width, height); graphics.FillEllipse(drawBrush, width / 2, width / 2, width / 2, width / 2); var createdIcon = Icon.FromHandle(bitmap.GetHicon()); drawBrush.Dispose(); graphics.Dispose(); bitmap.Dispose(); return createdIcon; } catch (Exception ex) { Logging.SaveLog(_tag, ex); return null; } } public void RegisterGlobalHotkey(Config config, Action handler, Action? update) { HotkeyManager.Instance.UpdateViewEvent += update; HotkeyManager.Instance.HotkeyTriggerEvent += handler; HotkeyManager.Instance.Load(); } } ================================================ FILE: v2rayN/v2rayN/Properties/Resources.Designer.cs ================================================ //------------------------------------------------------------------------------ // // 此代码由工具生成。 // 运行时版本:4.0.30319.42000 // // 对此文件的更改可能会导致不正确的行为,并且如果 // 重新生成代码,这些更改将会丢失。 // //------------------------------------------------------------------------------ namespace v2rayN.Properties { using System; /// /// 一个强类型的资源类,用于查找本地化的字符串等。 /// // 此类是由 StronglyTypedResourceBuilder // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen // (以 /str 作为命令选项),或重新生成 VS 项目。 [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } /// /// 返回此类使用的缓存的 ResourceManager 实例。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("v2rayN.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } /// /// 重写当前线程的 CurrentUICulture 属性,对 /// 使用此强类型资源类的所有资源查找执行重写。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap about { get { object obj = ResourceManager.GetObject("about", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap active { get { object obj = ResourceManager.GetObject("active", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap checkupdate { get { object obj = ResourceManager.GetObject("checkupdate", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap help { get { object obj = ResourceManager.GetObject("help", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap minimize { get { object obj = ResourceManager.GetObject("minimize", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap notify { get { object obj = ResourceManager.GetObject("notify", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找类似于 (图标) 的 System.Drawing.Icon 类型的本地化资源。 /// internal static System.Drawing.Icon NotifyIcon1 { get { object obj = ResourceManager.GetObject("NotifyIcon1", resourceCulture); return ((System.Drawing.Icon)(obj)); } } /// /// 查找类似于 (图标) 的 System.Drawing.Icon 类型的本地化资源。 /// internal static System.Drawing.Icon NotifyIcon2 { get { object obj = ResourceManager.GetObject("NotifyIcon2", resourceCulture); return ((System.Drawing.Icon)(obj)); } } /// /// 查找类似于 (图标) 的 System.Drawing.Icon 类型的本地化资源。 /// internal static System.Drawing.Icon NotifyIcon3 { get { object obj = ResourceManager.GetObject("NotifyIcon3", resourceCulture); return ((System.Drawing.Icon)(obj)); } } /// /// 查找类似于 (图标) 的 System.Drawing.Icon 类型的本地化资源。 /// internal static System.Drawing.Icon NotifyIcon4 { get { object obj = ResourceManager.GetObject("NotifyIcon4", resourceCulture); return ((System.Drawing.Icon)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap option { get { object obj = ResourceManager.GetObject("option", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap promotion { get { object obj = ResourceManager.GetObject("promotion", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap restart { get { object obj = ResourceManager.GetObject("restart", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap server { get { object obj = ResourceManager.GetObject("server", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap share { get { object obj = ResourceManager.GetObject("share", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap speedtest { get { object obj = ResourceManager.GetObject("speedtest", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap sub { get { object obj = ResourceManager.GetObject("sub", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } } } ================================================ FILE: v2rayN/v2rayN/Properties/Resources.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ..\Resources\NotifyIcon1.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\Resources\NotifyIcon2.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\Resources\NotifyIcon3.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\Resources\NotifyIcon4.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ================================================ FILE: v2rayN/v2rayN/ViewModels/ThemeSettingViewModel.cs ================================================ using MaterialDesignColors; using MaterialDesignColors.ColorManipulation; using MaterialDesignThemes.Wpf; using Microsoft.Win32; namespace v2rayN.ViewModels; public class ThemeSettingViewModel : MyReactiveObject { private readonly PaletteHelper _paletteHelper = new(); private IObservableCollection _swatches = new ObservableCollectionExtended(); public IObservableCollection Swatches => _swatches; [Reactive] public Swatch SelectedSwatch { get; set; } [Reactive] public string CurrentTheme { get; set; } [Reactive] public int CurrentFontSize { get; set; } [Reactive] public string CurrentLanguage { get; set; } public ThemeSettingViewModel() { _config = AppManager.Instance.Config; RegisterSystemColorSet(_config, ModifyTheme); BindingUI(); RestoreUI(); } private void RestoreUI() { ModifyTheme(); ModifyFontSize(); if (!_config.UiItem.ColorPrimaryName.IsNullOrEmpty()) { var swatch = new SwatchesProvider().Swatches.FirstOrDefault(t => t.Name == _config.UiItem.ColorPrimaryName); if (swatch != null && swatch.ExemplarHue != null && swatch.ExemplarHue?.Color != null) { ChangePrimaryColor(swatch.ExemplarHue.Color); } } } private void BindingUI() { _swatches.AddRange(new SwatchesProvider().Swatches); if (!_config.UiItem.ColorPrimaryName.IsNullOrEmpty()) { SelectedSwatch = _swatches.FirstOrDefault(t => t.Name == _config.UiItem.ColorPrimaryName); } CurrentTheme = _config.UiItem.CurrentTheme; CurrentFontSize = _config.UiItem.CurrentFontSize; CurrentLanguage = _config.UiItem.CurrentLanguage; this.WhenAnyValue( x => x.CurrentTheme, y => y != null && !y.IsNullOrEmpty()) .Subscribe(c => { if (_config.UiItem.CurrentTheme != CurrentTheme) { _config.UiItem.CurrentTheme = CurrentTheme; ModifyTheme(); ConfigHandler.SaveConfig(_config); } }); this.WhenAnyValue( x => x.SelectedSwatch, y => y != null && !y.Name.IsNullOrEmpty()) .Subscribe(c => { if (SelectedSwatch == null || SelectedSwatch.Name.IsNullOrEmpty() || SelectedSwatch.ExemplarHue == null || SelectedSwatch.ExemplarHue?.Color == null) { return; } if (_config.UiItem.ColorPrimaryName != SelectedSwatch?.Name) { _config.UiItem.ColorPrimaryName = SelectedSwatch?.Name; ChangePrimaryColor(SelectedSwatch.ExemplarHue.Color); ConfigHandler.SaveConfig(_config); } }); this.WhenAnyValue( x => x.CurrentFontSize, y => y > 0) .Subscribe(c => { if (_config.UiItem.CurrentFontSize != CurrentFontSize) { _config.UiItem.CurrentFontSize = CurrentFontSize; ModifyFontSize(); ConfigHandler.SaveConfig(_config); } }); this.WhenAnyValue( x => x.CurrentLanguage, y => y != null && !y.IsNullOrEmpty()) .Subscribe(c => { if (CurrentLanguage.IsNotEmpty() && _config.UiItem.CurrentLanguage != CurrentLanguage) { _config.UiItem.CurrentLanguage = CurrentLanguage; Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage); ConfigHandler.SaveConfig(_config); NoticeManager.Instance.Enqueue(ResUI.NeedRebootTips); } }); } public void ModifyTheme() { var baseTheme = CurrentTheme switch { nameof(ETheme.Dark) => BaseTheme.Dark, nameof(ETheme.Light) => BaseTheme.Light, _ => BaseTheme.Inherit, }; var theme = _paletteHelper.GetTheme(); theme.SetBaseTheme(baseTheme); _paletteHelper.SetTheme(theme); WindowsUtils.SetDarkBorder(Application.Current.MainWindow, CurrentTheme); } private void ModifyFontSize() { double size = (long)CurrentFontSize; if (size < Global.MinFontSize) { return; } Application.Current.Resources["StdFontSize"] = size; Application.Current.Resources["StdFontSize1"] = size + 1; Application.Current.Resources["StdFontSize-1"] = size - 1; } public void ChangePrimaryColor(System.Windows.Media.Color color) { var theme = _paletteHelper.GetTheme(); theme.PrimaryLight = new ColorPair(color.Lighten()); theme.PrimaryMid = new ColorPair(color); theme.PrimaryDark = new ColorPair(color.Darken()); _paletteHelper.SetTheme(theme); } public static void RegisterSystemColorSet(Config config, Action updateFunc) { SystemEvents.UserPreferenceChanged += (s, e) => { if ((e.Category == UserPreferenceCategory.Color || e.Category == UserPreferenceCategory.General) && config.UiItem.CurrentTheme == nameof(ETheme.FollowSystem)) { updateFunc?.Invoke(); } }; } } ================================================ FILE: v2rayN/v2rayN/Views/AddGroupServerWindow.xaml ================================================ ================================================ FILE: v2rayN/v2rayN/Views/ClashConnectionsView.xaml.cs ================================================ using System.Windows.Controls; using v2rayN.Base; namespace v2rayN.Views; /// /// Interaction logic for ConnectionsView.xaml /// public partial class ClashConnectionsView { private static Config _config; private static readonly string _tag = "ClashConnectionsView"; public ClashConnectionsView() { InitializeComponent(); _config = AppManager.Instance.Config; ViewModel = new ClashConnectionsViewModel(UpdateViewHandler); btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click; this.WhenActivated(disposables => { this.OneWayBind(ViewModel, vm => vm.ConnectionItems, v => v.lstConnections.ItemsSource).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource, v => v.lstConnections.SelectedItem).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ConnectionCloseCmd, v => v.menuConnectionClose).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.menuConnectionCloseAll).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); AppEvents.AppExitRequested .AsObservable() .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(_ => StorageUI()) .DisposeWith(disposables); }); RestoreUI(); } private async Task UpdateViewHandler(EViewAction action, object? obj) { return await Task.FromResult(true); } private void BtnAutofitColumnWidth_Click(object sender, RoutedEventArgs e) { AutofitColumnWidth(); } private void AutofitColumnWidth() { try { foreach (var it in lstConnections.Columns) { it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void btnClose_Click(object sender, System.Windows.RoutedEventArgs e) { ViewModel?.ClashConnectionClose(false); } #region UI private void RestoreUI() { try { var lvColumnItem = _config.ClashUIItem?.ConnectionsColumnItem?.OrderBy(t => t.Index).ToList(); if (lvColumnItem == null) { return; } var displayIndex = 0; foreach (var item in lvColumnItem) { foreach (var col in lstConnections.Columns.Cast()) { if (col.ExName == item.Name) { if (item.Width > 0) { col.Width = item.Width; } col.DisplayIndex = displayIndex++; break; } } } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } private void StorageUI() { try { List lvColumnItem = new(); foreach (var col in lstConnections.Columns.Cast()) { var name = col.ExName; if (string.IsNullOrWhiteSpace(name)) { continue; } lvColumnItem.Add(new() { Name = name, Width = (int)col.ActualWidth, Index = col.DisplayIndex }); } _config.ClashUIItem.ConnectionsColumnItem = lvColumnItem; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } } #endregion UI } ================================================ FILE: v2rayN/v2rayN/Views/ClashProxiesView.xaml ================================================ ================================================ FILE: v2rayN/v2rayN/Views/ClashProxiesView.xaml.cs ================================================ namespace v2rayN.Views; /// /// Interaction logic for ProxiesView.xaml /// public partial class ClashProxiesView { public ClashProxiesView() { InitializeComponent(); ViewModel = new ClashProxiesViewModel(UpdateViewHandler); lstProxyDetails.PreviewMouseDoubleClick += lstProxyDetails_PreviewMouseDoubleClick; this.WhenActivated(disposables => { this.OneWayBind(ViewModel, vm => vm.ProxyGroups, v => v.lstProxyGroups.ItemsSource).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedGroup, v => v.lstProxyGroups.SelectedItem).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.ProxyDetails, v => v.lstProxyDetails.ItemsSource).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedDetail, v => v.lstProxyDetails.SelectedItem).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ProxiesReloadCmd, v => v.menuProxiesReload).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ProxiesDelayTestCmd, v => v.menuProxiesDelaytest).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ProxiesDelayTestPartCmd, v => v.menuProxiesDelaytestPart).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ProxiesSelectActivityCmd, v => v.menuProxiesSelectActivity).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.RuleModeSelected, v => v.cmbRulemode.SelectedIndex).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); }); } private async Task UpdateViewHandler(EViewAction action, object? obj) { return await Task.FromResult(true); } private void ProxiesView_KeyDown(object sender, KeyEventArgs e) { switch (e.Key) { case Key.F5: ViewModel?.ProxiesReload(); break; case Key.Enter: ViewModel?.SetActiveProxy(); break; } } private void lstProxyDetails_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e) { ViewModel?.SetActiveProxy(); } } ================================================ FILE: v2rayN/v2rayN/Views/DNSSettingWindow.xaml ================================================