mirror of
				https://github.com/2dust/v2rayN.git
				synced 2025-10-30 20:12:52 +00:00 
			
		
		
		
	Compare commits
	
		
			No commits in common. "master" and "6.44" have entirely different histories.
		
	
	
		
	
		
					 461 changed files with 25730 additions and 56954 deletions
				
			
		
							
								
								
									
										170
									
								
								.editorconfig
									
									
									
									
									
								
							
							
						
						
									
										170
									
								
								.editorconfig
									
									
									
									
									
								
							|  | @ -1,170 +0,0 @@ | ||||||
| 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 |  | ||||||
							
								
								
									
										52
									
								
								.github/ISSUE_TEMPLATE/01_bug_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										52
									
								
								.github/ISSUE_TEMPLATE/01_bug_report.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -3,26 +3,6 @@ description: 在提出问题前请先自行排除服务器端问题和升级到 | ||||||
| title: "[Bug]: " | title: "[Bug]: " | ||||||
| labels: ["bug"] | labels: ["bug"] | ||||||
| body: | body: | ||||||
|   - type: markdown |  | ||||||
|     attributes: |  | ||||||
|       value: | |  | ||||||
|         ### 报告 Bug 前请务必确认以下事项: |  | ||||||
|         > ** ** |  | ||||||
|         > **✓ 已自行排除服务器端问题。** |  | ||||||
|         > **✓ 已升级到最新客户端版本。** |  | ||||||
|         > **✓ 已通过搜索确认没有人提出过相同问题。** |  | ||||||
|         > **✓ 已确认自己的电脑系统环境是受支持的。** |  | ||||||
| 
 |  | ||||||
|         --- |  | ||||||
| 
 |  | ||||||
|   - type: input |  | ||||||
|     id: "os-version" |  | ||||||
|     attributes: |  | ||||||
|       label: "操作系统和版本" |  | ||||||
|       description: "操作系统和版本" |  | ||||||
|     validations: |  | ||||||
|       required: true |  | ||||||
| 
 |  | ||||||
|   - type: input |   - type: input | ||||||
|     id: "expectation" |     id: "expectation" | ||||||
|     attributes: |     attributes: | ||||||
|  | @ -30,7 +10,6 @@ body: | ||||||
|       description: "描述你认为应该发生什么" |       description: "描述你认为应该发生什么" | ||||||
|     validations: |     validations: | ||||||
|       required: true |       required: true | ||||||
| 
 |  | ||||||
|   - type: textarea |   - type: textarea | ||||||
|     id: "describe-the-bug" |     id: "describe-the-bug" | ||||||
|     attributes: |     attributes: | ||||||
|  | @ -38,34 +17,22 @@ body: | ||||||
|       description: "描述实际发生了什么" |       description: "描述实际发生了什么" | ||||||
|     validations: |     validations: | ||||||
|       required: true |       required: true | ||||||
| 
 |  | ||||||
|   - type: textarea |   - type: textarea | ||||||
|     id: "reproduction-method" |     id: "reproduction-method" | ||||||
|     attributes: |     attributes: | ||||||
|       label: "复现方法" |       label: "复现方法" | ||||||
|       description: "在BUG出现前执行了哪些操作" |       description: "在BUG出现前执行了哪些操作" | ||||||
|       placeholder: "标序号" |       placeholder: 标序号 | ||||||
|     validations: |     validations: | ||||||
|       required: true |       required: true | ||||||
| 
 |  | ||||||
|   - type: textarea |   - type: textarea | ||||||
|     id: "gui-log" |     id: "log" | ||||||
|     attributes: |     attributes: | ||||||
|       label: "软件日志" |       label: "日志信息" | ||||||
|       description: "位置在软件当前目录下的guiLogs" |       description: "位置在软件当前目录下的guiLogs" | ||||||
|       placeholder: "在日志开始和结束位置粘贴冒号后的内容到这:" |       placeholder: 在日志开始和结束位置粘贴冒号后的内容:``` | ||||||
|     validations: |     validations: | ||||||
|       required: true |       required: true | ||||||
| 
 |  | ||||||
|   - type: textarea |  | ||||||
|     id: "core-log" |  | ||||||
|     attributes: |  | ||||||
|       label: "内核日志" |  | ||||||
|       description: "位置在软件主界面的信息框内" |  | ||||||
|       placeholder: "在信息框内鼠标右键复制全部信息粘贴在这:" |  | ||||||
|     validations: |  | ||||||
|       required: true |  | ||||||
| 
 |  | ||||||
|   - type: textarea |   - type: textarea | ||||||
|     id: "more" |     id: "more" | ||||||
|     attributes: |     attributes: | ||||||
|  | @ -73,7 +40,6 @@ body: | ||||||
|       description: "可选" |       description: "可选" | ||||||
|     validations: |     validations: | ||||||
|       required: false |       required: false | ||||||
| 
 |  | ||||||
|   - type: checkboxes |   - type: checkboxes | ||||||
|     id: "latest-version" |     id: "latest-version" | ||||||
|     attributes: |     attributes: | ||||||
|  | @ -82,7 +48,6 @@ body: | ||||||
|       options: |       options: | ||||||
|         - label: 是 |         - label: 是 | ||||||
|           required: true |           required: true | ||||||
| 
 |  | ||||||
|   - type: checkboxes |   - type: checkboxes | ||||||
|     id: "issues" |     id: "issues" | ||||||
|     attributes: |     attributes: | ||||||
|  | @ -91,12 +56,3 @@ body: | ||||||
|       options: |       options: | ||||||
|         - label: 是 |         - label: 是 | ||||||
|           required: true |           required: true | ||||||
| 
 |  | ||||||
|   - type: checkboxes |  | ||||||
|     id: "system-version" |  | ||||||
|     attributes: |  | ||||||
|       label: "我确认系统版本是受支持的" |  | ||||||
|       description: "否则请切换后尝试" |  | ||||||
|       options: |  | ||||||
|         - label: 是 |  | ||||||
|           required: true |  | ||||||
|  |  | ||||||
							
								
								
									
										9
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -1,9 +0,0 @@ | ||||||
| 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: 查看常见问题和使用文档。 |  | ||||||
							
								
								
									
										11
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -1,11 +0,0 @@ | ||||||
| version: 2 |  | ||||||
| updates: |  | ||||||
|   - package-ecosystem: "github-actions" |  | ||||||
|     directory: "/" |  | ||||||
|     schedule: |  | ||||||
|       interval: "daily" |  | ||||||
| 
 |  | ||||||
|   - package-ecosystem: "nuget" |  | ||||||
|     directory: "/" |  | ||||||
|     schedule: |  | ||||||
|       interval: "daily" |  | ||||||
							
								
								
									
										69
									
								
								.github/workflows/build-all.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										69
									
								
								.github/workflows/build-all.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -1,69 +0,0 @@ | ||||||
| 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 }}\" |  | ||||||
|               } |  | ||||||
|             }" |  | ||||||
							
								
								
									
										151
									
								
								.github/workflows/build-linux.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										151
									
								
								.github/workflows/build-linux.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -1,151 +0,0 @@ | ||||||
| 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@v5.0.0 |  | ||||||
|       with: |  | ||||||
|         submodules: 'recursive' |  | ||||||
|         fetch-depth: '0' |  | ||||||
| 
 |  | ||||||
|     - name: Setup .NET |  | ||||||
|       uses: actions/setup-dotnet@v5.0.0 |  | ||||||
|       with: |  | ||||||
|         dotnet-version: '8.0.x' |  | ||||||
| 
 |  | ||||||
|     - name: Build |  | ||||||
|       run: | |  | ||||||
|         cd v2rayN |  | ||||||
|         dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-x64   --self-contained=true -o "$OutputPath64" |  | ||||||
|         dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-arm64 --self-contained=true -o "$OutputPathArm64" |  | ||||||
|         dotnet publish ./AmazTool/AmazTool.csproj             -c Release -r linux-x64   --self-contained=true -p:PublishTrimmed=true -o "$OutputPath64" |  | ||||||
|         dotnet publish ./AmazTool/AmazTool.csproj             -c Release -r linux-arm64 --self-contained=true -p:PublishTrimmed=true -o "$OutputPathArm64" |  | ||||||
| 
 |  | ||||||
|     - name: Upload build artifacts |  | ||||||
|       uses: actions/upload-artifact@v5.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: quay.io/almalinuxorg/10-base:latest |  | ||||||
|       options: --platform=linux/amd64/v2 |  | ||||||
|     env: |  | ||||||
|       RELEASE_TAG: ${{ github.event.inputs.release_tag != '' && github.event.inputs.release_tag || github.ref_name }} |  | ||||||
| 
 |  | ||||||
|     steps: |  | ||||||
|     - name: Prepare tools (Red Hat) |  | ||||||
|       run: | |  | ||||||
|         dnf -y makecache |  | ||||||
|         dnf -y install epel-release |  | ||||||
|         dnf -y install sudo git rpm-build rpmdevtools dnf-plugins-core rsync findutils tar gzip unzip which |  | ||||||
| 
 |  | ||||||
|     - name: Checkout repo (for scripts) |  | ||||||
|       uses: actions/checkout@v5.0.0 |  | ||||||
|       with: |  | ||||||
|         submodules: 'recursive' |  | ||||||
|         fetch-depth: '0' |  | ||||||
| 
 |  | ||||||
|     - name: Restore build artifacts |  | ||||||
|       uses: actions/download-artifact@v6 |  | ||||||
|       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@v5.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 |  | ||||||
							
								
								
									
										87
									
								
								.github/workflows/build-osx.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										87
									
								
								.github/workflows/build-osx.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -1,87 +0,0 @@ | ||||||
| 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@v5.0.0 |  | ||||||
|       with: |  | ||||||
|         submodules: 'recursive' |  | ||||||
|         fetch-depth: '0' |  | ||||||
| 
 |  | ||||||
|     - name: Setup |  | ||||||
|       uses: actions/setup-dotnet@v5.0.0 |  | ||||||
|       with: |  | ||||||
|         dotnet-version: '8.0.x' |  | ||||||
| 
 |  | ||||||
|     - name: Build |  | ||||||
|       run: | |  | ||||||
|         cd v2rayN  |  | ||||||
|         dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-x64   --self-contained=true -o $OutputPath64 |  | ||||||
|         dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-arm64 --self-contained=true -o $OutputPathArm64 |  | ||||||
|         dotnet publish ./AmazTool/AmazTool.csproj             -c Release -r osx-x64   --self-contained=true -p:PublishTrimmed=true -o $OutputPath64 |  | ||||||
|         dotnet publish ./AmazTool/AmazTool.csproj             -c Release -r osx-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64 |  | ||||||
| 
 |  | ||||||
|     - name: Upload build artifacts |  | ||||||
|       uses: actions/upload-artifact@v5.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 |  | ||||||
							
								
								
									
										71
									
								
								.github/workflows/build-windows-desktop.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										71
									
								
								.github/workflows/build-windows-desktop.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -1,71 +0,0 @@ | ||||||
| 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@v5.0.0 |  | ||||||
|       with: |  | ||||||
|         submodules: 'recursive' |  | ||||||
|         fetch-depth: '0' |  | ||||||
| 
 |  | ||||||
|     - name: Setup |  | ||||||
|       uses: actions/setup-dotnet@v5.0.0 |  | ||||||
|       with: |  | ||||||
|         dotnet-version: '8.0.x' |  | ||||||
| 
 |  | ||||||
|     - name: Build |  | ||||||
|       run: | |  | ||||||
|         cd v2rayN  |  | ||||||
|         dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-x64   --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPath64 |  | ||||||
|         dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64 |  | ||||||
|         dotnet publish ./AmazTool/AmazTool.csproj             -c Release -r win-x64   --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64 |  | ||||||
|         dotnet publish ./AmazTool/AmazTool.csproj             -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64 |  | ||||||
| 
 |  | ||||||
|     - name: Upload build artifacts |  | ||||||
|       uses: actions/upload-artifact@v5.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 |  | ||||||
							
								
								
									
										71
									
								
								.github/workflows/build-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										71
									
								
								.github/workflows/build-windows.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -1,71 +0,0 @@ | ||||||
| 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" |  | ||||||
|   OutputPath64Sc:  "${{ github.workspace }}/v2rayN/Release/windows-64-SelfContained" |  | ||||||
| 
 |  | ||||||
| jobs: |  | ||||||
|   build: |  | ||||||
|     strategy: |  | ||||||
|       matrix: |  | ||||||
|         configuration: [Release] |  | ||||||
| 
 |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
| 
 |  | ||||||
|     steps: |  | ||||||
|     - name: Checkout |  | ||||||
|       uses: actions/checkout@v5.0.0 |  | ||||||
| 
 |  | ||||||
|     - name: Setup |  | ||||||
|       uses: actions/setup-dotnet@v5.0.0 |  | ||||||
|       with: |  | ||||||
|         dotnet-version: '8.0.x' |  | ||||||
| 
 |  | ||||||
|     - name: Build |  | ||||||
|       run: | |  | ||||||
|         cd v2rayN  |  | ||||||
|         dotnet publish ./v2rayN/v2rayN.csproj     -c Release -r win-x64   --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPath64 |  | ||||||
|         dotnet publish ./v2rayN/v2rayN.csproj     -c Release -r win-arm64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64 |  | ||||||
|         dotnet publish ./v2rayN/v2rayN.csproj     -c Release -r win-x64   --self-contained=true  -p:EnableWindowsTargeting=true -o $OutputPath64Sc |  | ||||||
|         dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64   --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPath64 |  | ||||||
|         dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64 |  | ||||||
|         dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64   --self-contained=true  -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64Sc |  | ||||||
| 
 |  | ||||||
|    |  | ||||||
|     - name: Upload build artifacts |  | ||||||
|       uses: actions/upload-artifact@v5.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 |  | ||||||
|         ./package-release-zip.sh "windows-64-SelfContained" $OutputPath64Sc |  | ||||||
|      |  | ||||||
|     - 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 |  | ||||||
							
								
								
									
										60
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | name: release | ||||||
|  | 
 | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: [ "master" ] | ||||||
|  |   pull_request: | ||||||
|  |     branches: [ "master" ] | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   build: | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         configuration: [Release] | ||||||
|  | 
 | ||||||
|  |     runs-on: windows-latest  | ||||||
|  | 
 | ||||||
|  |     steps: | ||||||
|  |     - name: Checkout | ||||||
|  |       uses: actions/checkout@v4 | ||||||
|  | 
 | ||||||
|  |     # - name: 删除工作流运行 | ||||||
|  |     #   uses: Mattraks/delete-workflow-runs@v2 | ||||||
|  |     #   with: | ||||||
|  |     #     token: ${{ github.token }} | ||||||
|  |     #     repository: ${{ github.repository }} | ||||||
|  |     #     retain_days: 0 | ||||||
|  |     #     keep_minimum_runs: 1 | ||||||
|  | 
 | ||||||
|  |     - name: Build | ||||||
|  |       run:  cd v2rayN &&  | ||||||
|  |         .\build.ps1 | ||||||
|  |          | ||||||
|  |     # - name: Package | ||||||
|  |     #   shell: pwsh | ||||||
|  |     #   run: | | ||||||
|  |     #     7z a -mx9 ..\v2rayN.7z $env:Wap_Project_Directory | ||||||
|  | 
 | ||||||
|  |     - name: Upload build artifacts | ||||||
|  |       uses: actions/upload-artifact@v4 | ||||||
|  |       with: | ||||||
|  |         name: v2rayN | ||||||
|  |         path: | | ||||||
|  |           .\v2rayN\v2rayN.zip | ||||||
|  | 
 | ||||||
|  |     # - name: Release | ||||||
|  |     #   uses: softprops/action-gh-release@v1 | ||||||
|  |     #   env: | ||||||
|  |     #       GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} | ||||||
|  |     #   with: | ||||||
|  |     #     prerelease: ${{ contains(github.ref, '-') }} | ||||||
|  |     #     draft: false | ||||||
|  |     #     files: | | ||||||
|  |     #         .\v2rayN\v2rayN.zip | ||||||
|  |     #     body: | | ||||||
|  |     #         [](https://t.me/netch_channel) [](https://t.me/netch_group) | ||||||
|  |     #         ## Changelogs | ||||||
|  |     #         * This is an automated deployment of GitHub Actions, the change log should be updated manually soon | ||||||
|  |              | ||||||
|  |     #         ## 更新日志 | ||||||
|  |     #         * 这是 GitHub Actions 自动化部署,更新日志应该很快会手动更新 | ||||||
							
								
								
									
										39
									
								
								.github/workflows/winget-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										39
									
								
								.github/workflows/winget-publish.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -1,39 +0,0 @@ | ||||||
| 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 |  | ||||||
							
								
								
									
										416
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										416
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -1,401 +1,19 @@ | ||||||
| ## Ignore Visual Studio temporary files, build results, and | ################################################################################ | ||||||
| ## files generated by popular Visual Studio add-ons. | # 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。 | ||||||
| ## | ################################################################################ | ||||||
| ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore |  | ||||||
| 
 | 
 | ||||||
| # User-specific files | /v2rayN/.vs/ | ||||||
| *.rsuser | /v2rayN/v2rayN/bin/Debug/app.publish | ||||||
| *.suo | /v2rayN/v2rayN/bin/Debug | ||||||
|  | /v2rayN/v2rayN/bin/Release | ||||||
|  | /v2rayN/v2rayN/obj/ | ||||||
|  | /v2rayN/.vs/v2rayN/DesignTimeBuild | ||||||
|  | /v2rayN/packages | ||||||
|  | .vs/ProjectSettings.json | ||||||
|  | .vs/slnx.sqlite | ||||||
|  | .vs/VSWorkspaceState.json | ||||||
|  | /v2rayN/v2rayUpgrade/bin/Debug | ||||||
|  | /v2rayN/v2rayUpgrade/bin/Release | ||||||
|  | /v2rayN/v2rayUpgrade/obj/ | ||||||
| *.user | *.user | ||||||
| *.userosscache | /.vs/v2rayN | ||||||
| *.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 |  | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							|  | @ -1,3 +0,0 @@ | ||||||
| [submodule "v2rayN/GlobalHotKeys"] |  | ||||||
| 	path = v2rayN/GlobalHotKeys |  | ||||||
| 	url = https://github.com/2dust/GlobalHotKeys |  | ||||||
							
								
								
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							|  | @ -632,7 +632,7 @@ 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. | the "copyright" line and a pointer to where the full notice is found. | ||||||
| 
 | 
 | ||||||
|     <one line to give the program's name and a brief idea of what it does.> |     <one line to give the program's name and a brief idea of what it does.> | ||||||
|     Copyright (C) 2019-Present  2dust |     Copyright (C) <year>  <name of author> | ||||||
| 
 | 
 | ||||||
|     This program is free software: you can redistribute it and/or modify |     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 |     it under the terms of the GNU General Public License as published by | ||||||
|  | @ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. | ||||||
|   If the program does terminal interaction, make it output a short |   If the program does terminal interaction, make it output a short | ||||||
| notice like this when it starts in an interactive mode: | notice like this when it starts in an interactive mode: | ||||||
| 
 | 
 | ||||||
|     v2rayN  Copyright (C) 2019-Present  2dust |     <program>  Copyright (C) <year>  <name of author> | ||||||
|     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||||||
|     This is free software, and you are welcome to redistribute it |     This is free software, and you are welcome to redistribute it | ||||||
|     under certain conditions; type `show c' for details. |     under certain conditions; type `show c' for details. | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								README.md
									
									
									
									
									
								
							|  | @ -1,18 +1,23 @@ | ||||||
| # v2rayN | # v2rayN | ||||||
|  | A GUI client for Windows, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core) and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores) | ||||||
| 
 | 
 | ||||||
| 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) |  | ||||||
| 
 | 
 | ||||||
| [](https://github.com/2dust/v2rayN/commits/master) | [](https://github.com/2dust/v2rayN/commits/master) | ||||||
| [](https://www.codefactor.io/repository/github/2dust/v2rayn) | [](https://www.codefactor.io/repository/github/2dust/v2rayn) | ||||||
| [](https://github.com/2dust/v2rayN/releases) | [](https://github.com/2dust/v2rayN/releases) | ||||||
| [](https://t.me/v2rayn) | [](https://t.me/v2rayn) | ||||||
| 
 | 
 | ||||||
| ## How to use |  | ||||||
| 
 | 
 | ||||||
| Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details. | ## How to use | ||||||
|  | - If you are new to this, please download v2rayN-With-Core.zip from [releases](https://github.com/2dust/v2rayN/releases) | ||||||
|  | - Otherwise please download v2rayN.zip (you will also need to download cores in the bin directory) | ||||||
|  | - Run v2rayN.exe | ||||||
|  | 
 | ||||||
|  | ## Requirements   | ||||||
|  | - (6.35 and above)[Microsoft .NET 8.0 Desktop Runtime ](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) | ||||||
|  | - (6.33 and below)[Microsoft .NET 6.0 Desktop Runtime ](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) | ||||||
|  | - [Supported cores](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| ## Telegram Channel | ## Telegram Channel | ||||||
| 
 |  | ||||||
| [github_2dust](https://t.me/github_2dust) | [github_2dust](https://t.me/github_2dust) | ||||||
|  |  | ||||||
|  | @ -1,69 +0,0 @@ | ||||||
| #!/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) |  | ||||||
| Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others |  | ||||||
| EOF |  | ||||||
| 
 |  | ||||||
| cat >"${PackagePath}/DEBIAN/postinst" <<-EOF |  | ||||||
| if [ ! -s /usr/share/applications/v2rayN.desktop ]; then |  | ||||||
|     cat >/usr/share/applications/v2rayN.desktop<<-END |  | ||||||
| [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; |  | ||||||
| END |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| update-desktop-database |  | ||||||
| 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" |  | ||||||
|  | @ -1,58 +0,0 @@ | ||||||
| #!/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 |  | ||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |  | ||||||
| <plist version="1.0"> |  | ||||||
| <dict> |  | ||||||
|   <key>CFBundleDevelopmentRegion</key> |  | ||||||
|   <string>English</string> |  | ||||||
|   <key>CFBundleDisplayName</key> |  | ||||||
|   <string>v2rayN</string> |  | ||||||
|   <key>CFBundleExecutable</key> |  | ||||||
|   <string>v2rayN</string> |  | ||||||
|   <key>CFBundleIconFile</key> |  | ||||||
|   <string>AppIcon</string> |  | ||||||
|   <key>CFBundleIconName</key> |  | ||||||
|   <string>AppIcon</string> |  | ||||||
|   <key>CFBundleIdentifier</key> |  | ||||||
|   <string>2dust.v2rayN</string> |  | ||||||
|   <key>CFBundleName</key> |  | ||||||
|   <string>v2rayN</string> |  | ||||||
|   <key>CFBundlePackageType</key> |  | ||||||
|   <string>APPL</string> |  | ||||||
|   <key>CFBundleShortVersionString</key> |  | ||||||
|   <string>${Version}</string> |  | ||||||
|   <key>CSResourcesFileMapped</key> |  | ||||||
|   <true/> |  | ||||||
|   <key>NSHighResolutionCapable</key> |  | ||||||
|   <true/> |  | ||||||
| </dict> |  | ||||||
| </plist> |  | ||||||
| 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" |  | ||||||
|  | @ -1,15 +0,0 @@ | ||||||
| #!/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 |  | ||||||
							
								
								
									
										830
									
								
								package-rhel.sh
									
									
									
									
									
								
							
							
						
						
									
										830
									
								
								package-rhel.sh
									
									
									
									
									
								
							|  | @ -1,830 +0,0 @@ | ||||||
| #!/usr/bin/env bash |  | ||||||
| set -euo pipefail |  | ||||||
| 
 |  | ||||||
| # == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian == |  | ||||||
| if [[ -r /etc/os-release ]]; then |  | ||||||
|   . /etc/os-release |  | ||||||
|   case "$ID" in |  | ||||||
|     rhel|rocky|almalinux|fedora|centos|ubuntu|debian) |  | ||||||
|       echo "[OK] Detected supported system: $NAME $VERSION_ID" |  | ||||||
|       ;; |  | ||||||
|     *) |  | ||||||
|       echo "[ERROR] Unsupported system: $NAME ($ID)." |  | ||||||
|       echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS or Ubuntu/Debian." |  | ||||||
|       exit 1 |  | ||||||
|       ;; |  | ||||||
|   esac |  | ||||||
| else |  | ||||||
|   echo "[ERROR] Cannot detect system (missing /etc/os-release)." |  | ||||||
|   exit 1 |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| # ======================== Kernel version check (require >= 6.11) ======================= |  | ||||||
| MIN_KERNEL_MAJOR=6 |  | ||||||
| MIN_KERNEL_MINOR=11 |  | ||||||
| KERNEL_FULL=$(uname -r) |  | ||||||
| KERNEL_MAJOR=$(echo "$KERNEL_FULL" | cut -d. -f1) |  | ||||||
| KERNEL_MINOR=$(echo "$KERNEL_FULL" | cut -d. -f2) |  | ||||||
| 
 |  | ||||||
| echo "[INFO] Detected kernel version: $KERNEL_FULL" |  | ||||||
| 
 |  | ||||||
| if (( KERNEL_MAJOR < MIN_KERNEL_MAJOR )) || { (( KERNEL_MAJOR == MIN_KERNEL_MAJOR )) && (( KERNEL_MINOR < MIN_KERNEL_MINOR )); }; then |  | ||||||
|   echo "[ERROR] Kernel $KERNEL_FULL is too old. Requires Linux >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}." |  | ||||||
|   echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+, Debian 13+)." |  | ||||||
|   exit 1 |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| echo "[OK] Kernel version >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}." |  | ||||||
| 
 |  | ||||||
| # ===== 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 |  | ||||||
| AUTOSTART=0              # 1 = enable system-wide autostart (/etc/xdg/autostart) |  | ||||||
| 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;; |  | ||||||
|     --autostart)     AUTOSTART=1; shift;; |  | ||||||
|     --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 "[ERROR] 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 |  | ||||||
| 
 |  | ||||||
| # ===== Environment check + Dependencies ======================================== |  | ||||||
| host_arch="$(uname -m)" |  | ||||||
| [[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; } |  | ||||||
| 
 |  | ||||||
| install_ok=0 |  | ||||||
| case "$ID" in |  | ||||||
|   # ------------------------------ RHEL family (UNCHANGED) ------------------------------ |  | ||||||
|   rhel|rocky|almalinux|centos) |  | ||||||
|     if command -v dnf >/dev/null 2>&1; then |  | ||||||
|       sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \ |  | ||||||
|       sudo dnf -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync |  | ||||||
|       install_ok=1 |  | ||||||
|     elif command -v yum >/dev/null 2>&1; then |  | ||||||
|       sudo yum -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \ |  | ||||||
|       sudo yum -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync |  | ||||||
|       install_ok=1 |  | ||||||
|     fi |  | ||||||
|     ;; |  | ||||||
|   # ------------------------------ Ubuntu ---------------------------------------------- |  | ||||||
|   ubuntu) |  | ||||||
|     sudo apt-get update |  | ||||||
|     # Ensure 'universe' (Ubuntu) to get 'rpm' |  | ||||||
|     if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then |  | ||||||
|       sudo apt-get -y install software-properties-common || true |  | ||||||
|       sudo add-apt-repository -y universe || true |  | ||||||
|       sudo apt-get update |  | ||||||
|     fi |  | ||||||
|     # Base tools + rpm (provides rpmbuild) |  | ||||||
|     sudo apt-get -y install curl unzip tar rsync rpm || true |  | ||||||
|     # Cross-arch binutils so strip matches target arch + objdump for brp scripts |  | ||||||
|     sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true |  | ||||||
|     # rpmbuild presence check |  | ||||||
|     if ! command -v rpmbuild >/dev/null 2>&1; then |  | ||||||
|       echo "[ERROR] 'rpmbuild' not found after installing 'rpm'." |  | ||||||
|       echo "        Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)." |  | ||||||
|       exit 1 |  | ||||||
|     fi |  | ||||||
|     # .NET SDK 8 (best effort via apt) |  | ||||||
|     if ! command -v dotnet >/dev/null 2>&1; then |  | ||||||
|       sudo apt-get -y install dotnet-sdk-8.0 || true |  | ||||||
|       sudo apt-get -y install dotnet-sdk-8 || true |  | ||||||
|       sudo apt-get -y install dotnet-sdk || true |  | ||||||
|     fi |  | ||||||
|     install_ok=1 |  | ||||||
|     ;; |  | ||||||
|   # ------------------------------ Debian (KEEP, with local dotnet install) ------------ |  | ||||||
|   debian) |  | ||||||
|     sudo apt-get update |  | ||||||
|     # Base tools + rpm (provides rpmbuild on Debian) + objdump/strip |  | ||||||
|     sudo apt-get -y install curl unzip tar rsync rpm binutils || true |  | ||||||
|     # rpmbuild presence check |  | ||||||
|     if ! command -v rpmbuild >/dev/null 2>&1; then |  | ||||||
|       echo "[ERROR] 'rpmbuild' not found after installing 'rpm'." |  | ||||||
|       echo "        Please ensure 'rpm' is available from Debian repos." |  | ||||||
|       exit 1 |  | ||||||
|     fi |  | ||||||
|     # Try apt for dotnet; fallback to official installer into $HOME/.dotnet |  | ||||||
|     if ! command -v dotnet >/dev/null 2>&1; then |  | ||||||
|       echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..." |  | ||||||
|       tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN |  | ||||||
|       curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh" |  | ||||||
|       bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet" |  | ||||||
|       export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH" |  | ||||||
|       export DOTNET_ROOT="$HOME/.dotnet" |  | ||||||
|       if ! command -v dotnet >/dev/null 2>&1; then |  | ||||||
|         echo "[ERROR] dotnet installation failed." |  | ||||||
|         exit 1 |  | ||||||
|       fi |  | ||||||
|     fi |  | ||||||
|     install_ok=1 |  | ||||||
|     ;; |  | ||||||
| esac |  | ||||||
| 
 |  | ||||||
| if [[ "$install_ok" -ne 1 ]]; then |  | ||||||
|   echo "[WARN] 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 RPM-based distros)" |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| command -v curl >/dev/null |  | ||||||
| 
 |  | ||||||
| # Root directory = the script's location |  | ||||||
| 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; } |  | ||||||
| 
 |  | ||||||
| # ===== Resolve GUI version & auto checkout ============================================ |  | ||||||
| VERSION="" |  | ||||||
| 
 |  | ||||||
| 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/tty; then |  | ||||||
|       case "${sel:-}" in |  | ||||||
|         2) ch="prerelease" ;; |  | ||||||
|         3) ch="keep" ;; |  | ||||||
|         *) ch="latest" ;; |  | ||||||
|       esac |  | ||||||
|     else |  | ||||||
|       ch="latest" |  | ||||||
|     fi |  | ||||||
|   else |  | ||||||
|     ch="latest" |  | ||||||
|   fi |  | ||||||
|   echo "$ch" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| get_latest_tag_latest() { |  | ||||||
|   # Resolve /releases/latest → tag_name |  | ||||||
|   curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases/latest" \ |  | ||||||
|     | grep -Eo '"tag_name":\s*"v?[^"]+"' \ |  | ||||||
|     | head -n1 \ |  | ||||||
|     | sed -E 's/.*"tag_name":\s*"v?([^"]+)".*/\1/' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| get_latest_tag_prerelease() { |  | ||||||
|   # Resolve newest prerelease=true tag; prefer jq, fallback to sed/grep (no awk) |  | ||||||
|   local json tag |  | ||||||
|   json="$(curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20")" || return 1 |  | ||||||
| 
 |  | ||||||
|   # 1) Use jq if present |  | ||||||
|   if command -v jq >/dev/null 2>&1; then |  | ||||||
|     tag="$(printf '%s' "$json" \ |  | ||||||
|       | jq -r '[.[] | select(.prerelease==true)][0].tag_name' 2>/dev/null \ |  | ||||||
|       | sed 's/^v//')" || true |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   # 2) Fallback to sed/grep only |  | ||||||
|   if [[ -z "${tag:-}" || "${tag:-}" == "null" ]]; then |  | ||||||
|     tag="$(printf '%s' "$json" \ |  | ||||||
|       | tr '\n' ' ' \ |  | ||||||
|       | sed 's/},[[:space:]]*{/\n/g' \ |  | ||||||
|       | grep -m1 -E '"prerelease"[[:space:]]*:[[:space:]]*true' \ |  | ||||||
|       | grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"v?[^"]+"' \ |  | ||||||
|       | head -n1 \ |  | ||||||
|       | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/')" || true |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   [[ -n "${tag:-}" && "${tag:-}" != "null" ]] || return 1 |  | ||||||
|   printf '%s\n' "$tag" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| git_try_checkout() { |  | ||||||
|   # Try a series of refs and checkout when found. |  | ||||||
|   local want="$1" ref="" |  | ||||||
|   if git rev-parse --git-dir >/dev/null 2>&1; then |  | ||||||
|     git fetch --tags --force --prune --depth=1 || true |  | ||||||
|     if git rev-parse "refs/tags/v${want}" >/dev/null 2>&1; then |  | ||||||
|       ref="v${want}" |  | ||||||
|     elif git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then |  | ||||||
|       ref="${want}" |  | ||||||
|     elif git rev-parse --verify "${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 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| if git rev-parse --git-dir >/dev/null 2>&1; then |  | ||||||
|   if [[ -n "${VERSION_ARG:-}" ]]; then |  | ||||||
|     echo "[*] Trying to switch v2rayN repo to version: ${VERSION_ARG}" |  | ||||||
|     if git_try_checkout "${VERSION_ARG#v}"; then |  | ||||||
|       VERSION="${VERSION_ARG#v}" |  | ||||||
|     else |  | ||||||
|       echo "[WARN] Tag '${VERSION_ARG}' not found." |  | ||||||
|       ch="$(choose_channel)" |  | ||||||
|       if [[ "$ch" == "keep" ]]; then |  | ||||||
|         echo "[*] Keep current repository state (no checkout)." |  | ||||||
|         if git describe --tags --abbrev=0 >/dev/null 2>&1; then |  | ||||||
|           VERSION="$(git describe --tags --abbrev=0)" |  | ||||||
|         else |  | ||||||
|           VERSION="0.0.0+git" |  | ||||||
|         fi |  | ||||||
|         VERSION="${VERSION#v}" |  | ||||||
|       else |  | ||||||
|         echo "[*] Resolving ${ch} tag from GitHub releases..." |  | ||||||
|         tag="" |  | ||||||
|         if [[ "$ch" == "prerelease" ]]; then |  | ||||||
|           tag="$(get_latest_tag_prerelease || true)" |  | ||||||
|           if [[ -z "$tag" ]]; then |  | ||||||
|             echo "[WARN] Failed to resolve prerelease tag, falling back to latest." |  | ||||||
|             tag="$(get_latest_tag_latest || true)" |  | ||||||
|           fi |  | ||||||
|         else |  | ||||||
|           tag="$(get_latest_tag_latest || true)" |  | ||||||
|         fi |  | ||||||
|         [[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; } |  | ||||||
|         echo "[*] Latest tag for '${ch}': ${tag}" |  | ||||||
|         git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; } |  | ||||||
|         VERSION="${tag#v}" |  | ||||||
|       fi |  | ||||||
|     fi |  | ||||||
|   else |  | ||||||
|     ch="$(choose_channel)" |  | ||||||
|     if [[ "$ch" == "keep" ]]; then |  | ||||||
|       echo "[*] Keep current repository state (no checkout)." |  | ||||||
|       if git describe --tags --abbrev=0 >/dev/null 2>&1; then |  | ||||||
|         VERSION="$(git describe --tags --abbrev=0)" |  | ||||||
|       else |  | ||||||
|         VERSION="0.0.0+git" |  | ||||||
|       fi |  | ||||||
|       VERSION="${VERSION#v}" |  | ||||||
|     else |  | ||||||
|       echo "[*] Resolving ${ch} tag from GitHub releases..." |  | ||||||
|       tag="" |  | ||||||
|       if [[ "$ch" == "prerelease" ]]; then |  | ||||||
|         tag="$(get_latest_tag_prerelease || true)" |  | ||||||
|         if [[ -z "$tag" ]]; then |  | ||||||
|           echo "[WARN] Failed to resolve prerelease tag, falling back to latest." |  | ||||||
|           tag="$(get_latest_tag_latest || true)" |  | ||||||
|         fi |  | ||||||
|       else |  | ||||||
|         tag="$(get_latest_tag_latest || true)" |  | ||||||
|       fi |  | ||||||
|       [[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; } |  | ||||||
|       echo "[*] Latest tag for '${ch}': ${tag}" |  | ||||||
|       git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; } |  | ||||||
|       VERSION="${tag#v}" |  | ||||||
|     fi |  | ||||||
|   fi |  | ||||||
| else |  | ||||||
|   echo "[WARN] Current directory is not a git repo; cannot checkout version. Proceeding on current tree." |  | ||||||
|   VERSION="${VERSION_ARG:-}" |  | ||||||
|   if [[ -z "$VERSION" ]]; then |  | ||||||
|     if git describe --tags --abbrev=0 >/dev/null 2>&1; then |  | ||||||
|       VERSION="$(git describe --tags --abbrev=0)" |  | ||||||
|     else |  | ||||||
|       VERSION="0.0.0+git" |  | ||||||
|     fi |  | ||||||
|   fi |  | ||||||
|   VERSION="${VERSION#v}" |  | ||||||
| fi |  | ||||||
| echo "[*] GUI version resolved as: ${VERSION}" |  | ||||||
| 
 |  | ||||||
| # ===== Helpers for core/rules download (use RID_DIR for arch sync) ===================== |  | ||||||
| download_xray() { |  | ||||||
|   # Download Xray core and install to outdir/xray |  | ||||||
|   local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip" |  | ||||||
|   mkdir -p "$outdir" |  | ||||||
|   if [[ -n "${XRAY_VER:-}" ]]; then ver="${XRAY_VER}"; fi |  | ||||||
|   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 core and install to outdir/sing-box |  | ||||||
|   local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin |  | ||||||
|   mkdir -p "$outdir" |  | ||||||
|   if [[ -n "${SING_VER:-}" ]]; then ver="${SING_VER}"; fi |  | ||||||
|   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" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| # ---- NEW: download_mihomo (REQUIRED in --netcore mode) ---- |  | ||||||
| download_mihomo() { |  | ||||||
|   # Download mihomo into outroot/bin/mihomo/mihomo |  | ||||||
|   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/bin/mihomo/mihomo" |  | ||||||
|   else |  | ||||||
|     url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64/bin/mihomo/mihomo" |  | ||||||
|   fi |  | ||||||
|   echo "[+] Download mihomo: $url" |  | ||||||
|   mkdir -p "$outroot/bin/mihomo" |  | ||||||
|   curl -fL "$url" -o "$outroot/bin/mihomo/mihomo" |  | ||||||
|   chmod +x "$outroot/bin/mihomo/mihomo" || true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| # Move geo files to a unified path: 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 file exists under bin/xray/, move it up to bin/ |  | ||||||
|     if [[ -f "$outroot/bin/xray/$n" ]]; then |  | ||||||
|       mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n" |  | ||||||
|     fi |  | ||||||
|     # If file already in bin/, leave it as-is |  | ||||||
|     if [[ -f "$outroot/bin/$n" ]]; then |  | ||||||
|       : |  | ||||||
|     fi |  | ||||||
|   done |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| # Download geo/rule assets; then unify to bin/ |  | ||||||
| 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-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 |  | ||||||
|   # keep mihomo |  | ||||||
|   # 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 "[ERROR] 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 \ |  | ||||||
|     -p:IncludeNativeLibrariesForSelfExtract=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 USE_TOPDIR_DEFINE |  | ||||||
|   if [[ "$ID" =~ ^(rhel|rocky|almalinux|centos)$ ]]; then |  | ||||||
|     rpmdev-setuptree |  | ||||||
|     TOPDIR="${HOME}/rpmbuild" |  | ||||||
|     SPECDIR="${TOPDIR}/SPECS" |  | ||||||
|     SOURCEDIR="${TOPDIR}/SOURCES" |  | ||||||
|     USE_TOPDIR_DEFINE=0 |  | ||||||
|   else |  | ||||||
|     TOPDIR="${WORKDIR}/rpmbuild" |  | ||||||
|     SPECDIR="${TOPDIR}/SPECS}" |  | ||||||
|     SOURCEDIR="${TOPDIR}/SOURCES" |  | ||||||
|     mkdir -p "${SPECDIR}" "${SOURCEDIR}" "${TOPDIR}/BUILD" "${TOPDIR}/RPMS" "${TOPDIR}/SRPMS" |  | ||||||
|     USE_TOPDIR_DEFINE=1 |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   # 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 |  | ||||||
|   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." |  | ||||||
|       if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then |  | ||||||
|         download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)" |  | ||||||
|       fi |  | ||||||
|       if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then |  | ||||||
|         download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)" |  | ||||||
|       fi |  | ||||||
|       download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)" |  | ||||||
|     fi |  | ||||||
|   else |  | ||||||
|     echo "[*] --netcore specified: use separate core + rules." |  | ||||||
|     if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then |  | ||||||
|       download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)" |  | ||||||
|     fi |  | ||||||
|     if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then |  | ||||||
|       download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)" |  | ||||||
|     fi |  | ||||||
|     download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)" |  | ||||||
|     # ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ---- |  | ||||||
|     download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)" |  | ||||||
|   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:       freetype, 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 |  | ||||||
| 
 |  | ||||||
| %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 |  | ||||||
| 
 |  | ||||||
|   # Autostart injection (inside %install) and %files entry |  | ||||||
|   if [[ "$AUTOSTART" -eq 1 ]]; then |  | ||||||
|     awk ' |  | ||||||
|       BEGIN{ins=0} |  | ||||||
|       /^%post$/ && !ins { |  | ||||||
|         print "# --- Autostart (.desktop) ---" |  | ||||||
|         print "install -dm0755 %{buildroot}/etc/xdg/autostart" |  | ||||||
|         print "cat > %{buildroot}/etc/xdg/autostart/v2rayn.desktop << '\''EOF'\''" |  | ||||||
|         print "[Desktop Entry]" |  | ||||||
|         print "Type=Application" |  | ||||||
|         print "Name=v2rayN (Autostart)" |  | ||||||
|         print "Exec=v2rayn" |  | ||||||
|         print "X-GNOME-Autostart-enabled=true" |  | ||||||
|         print "NoDisplay=false" |  | ||||||
|         print "EOF" |  | ||||||
|         ins=1 |  | ||||||
|       } |  | ||||||
|       {print} |  | ||||||
|     ' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE" |  | ||||||
| 
 |  | ||||||
|     awk ' |  | ||||||
|       BEGIN{infiles=0; done=0} |  | ||||||
|       /^%files$/        {infiles=1} |  | ||||||
|       infiles && done==0 && $0 ~ /%{_datadir}\/icons\/hicolor\/256x256\/apps\/v2rayn\.png/ { |  | ||||||
|         print |  | ||||||
|         print "%config(noreplace) /etc/xdg/autostart/v2rayn.desktop" |  | ||||||
|         done=1 |  | ||||||
|         next |  | ||||||
|       } |  | ||||||
|       {print} |  | ||||||
|     ' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE" |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   # Replace placeholders |  | ||||||
|   sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE" |  | ||||||
|   sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE" |  | ||||||
| 
 |  | ||||||
|   # ----- Select proper 'strip' per target arch on Ubuntu only (cross-binutils) ----- |  | ||||||
|   # NOTE: We define only __strip to point to the target-arch strip. |  | ||||||
|   #       DO NOT override __brp_strip (it must stay the brp script path). |  | ||||||
|   local STRIP_ARGS=() |  | ||||||
|   if [[ "$ID" == "ubuntu" ]]; then |  | ||||||
|     local STRIP_BIN="" |  | ||||||
|     if [[ "$short" == "x64" ]]; then |  | ||||||
|       STRIP_BIN="/usr/bin/x86_64-linux-gnu-strip" |  | ||||||
|     else |  | ||||||
|       STRIP_BIN="/usr/bin/aarch64-linux-gnu-strip" |  | ||||||
|     fi |  | ||||||
|     if [[ -x "$STRIP_BIN" ]]; then |  | ||||||
|       STRIP_ARGS=( --define "__strip $STRIP_BIN" ) |  | ||||||
|     fi |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   # Build RPM for this arch (force rpm --target to match compile arch) |  | ||||||
|   if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then |  | ||||||
|     rpmbuild -ba "$SPECFILE" --define "_topdir $TOPDIR" --target "$rpm_target" "${STRIP_ARGS[@]}" |  | ||||||
|   else |  | ||||||
|     rpmbuild -ba "$SPECFILE" --target "$rpm_target" "${STRIP_ARGS[@]}" |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   # Copy temporary rpmbuild to ~/rpmbuild on Debian/Ubuntu path |  | ||||||
|   if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then |  | ||||||
|     mkdir -p "$HOME/rpmbuild" |  | ||||||
|     rsync -a "$TOPDIR"/ "$HOME/rpmbuild"/ |  | ||||||
|     TOPDIR="$HOME/rpmbuild" |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   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 |  | ||||||
|   "") |  | ||||||
|     # No --arch: use host architecture |  | ||||||
|     if [[ "$host_arch" == "aarch64" ]]; then |  | ||||||
|       build_for_arch arm64 |  | ||||||
|     else |  | ||||||
|       build_for_arch x64 |  | ||||||
|     fi |  | ||||||
|     ;; |  | ||||||
|   x64|amd64) |  | ||||||
|     build_for_arch x64 |  | ||||||
|     ;; |  | ||||||
|   arm64|aarch64) |  | ||||||
|     build_for_arch arm64 |  | ||||||
|     ;; |  | ||||||
|   all) |  | ||||||
|     BUILT_ALL=1 |  | ||||||
|     # Build x64 and arm64 separately; each package contains its own arch-only binaries. |  | ||||||
|     build_for_arch x64 |  | ||||||
|     build_for_arch arm64 |  | ||||||
|     ;; |  | ||||||
|   *) |  | ||||||
|     echo "[ERROR] Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all." |  | ||||||
|     exit 1 |  | ||||||
|     ;; |  | ||||||
| esac |  | ||||||
| 
 |  | ||||||
| # ===== Final summary if building both arches ========================================== |  | ||||||
| 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 "[WARN] No RPMs detected in summary (check build logs above)." |  | ||||||
|   fi |  | ||||||
|   echo "===================================================================" |  | ||||||
| fi |  | ||||||
							
								
								
									
										0
									
								
								.gitattributes → v2rayN/.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										0
									
								
								.gitattributes → v2rayN/.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
								
								
									
										363
									
								
								v2rayN/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								v2rayN/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,363 @@ | ||||||
|  | ## 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/master/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/ | ||||||
|  | [Oo]ut/ | ||||||
|  | [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 | ||||||
|  | *.sbr | ||||||
|  | *.tlb | ||||||
|  | *.tli | ||||||
|  | *.tlh | ||||||
|  | *.tmp | ||||||
|  | *.tmp_proj | ||||||
|  | *_wpftmp.csproj | ||||||
|  | *.log | ||||||
|  | *.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 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/ | ||||||
|  | 
 | ||||||
|  | # 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 | ||||||
|  | @ -1,20 +0,0 @@ | ||||||
| <Project Sdk="Microsoft.NET.Sdk"> |  | ||||||
| 
 |  | ||||||
| 	<PropertyGroup> |  | ||||||
| 		<OutputType>Exe</OutputType> |  | ||||||
| 	</PropertyGroup> |  | ||||||
| 
 |  | ||||||
| 	<ItemGroup> |  | ||||||
| 		<EmbeddedResource Update="Resx\Resource.resx"> |  | ||||||
| 			<Generator>ResXFileCodeGenerator</Generator> |  | ||||||
| 			<LastGenOutput>Resource.Designer.cs</LastGenOutput> |  | ||||||
| 		</EmbeddedResource> |  | ||||||
| 
 |  | ||||||
| 		<Compile Update="Resx\Resource.Designer.cs"> |  | ||||||
| 			<DesignTime>True</DesignTime> |  | ||||||
| 			<AutoGen>True</AutoGen> |  | ||||||
| 			<DependentUpon>Resource.resx</DependentUpon> |  | ||||||
| 		</Compile> |  | ||||||
| 	</ItemGroup> |  | ||||||
| 
 |  | ||||||
| </Project> |  | ||||||
|  | @ -1,87 +0,0 @@ | ||||||
| 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(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Display help information and usage guidelines |  | ||||||
|     /// </summary> |  | ||||||
|     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); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Handle application restart |  | ||||||
|     /// </summary> |  | ||||||
|     private static void HandleRebootAsync() |  | ||||||
|     { |  | ||||||
|         Console.WriteLine("Restarting application..."); |  | ||||||
|         Thread.Sleep(1000); |  | ||||||
|         Utils.StartV2RayN(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Handle application upgrade with the provided data |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="upgradeData">Data for the upgrade process</param> |  | ||||||
|     private static void HandleUpgrade(string upgradeData) |  | ||||||
|     { |  | ||||||
|         Console.WriteLine("Upgrading application..."); |  | ||||||
|         UpgradeApp.Upgrade(upgradeData); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										171
									
								
								v2rayN/AmazTool/Resx/Resource.Designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										171
									
								
								v2rayN/AmazTool/Resx/Resource.Designer.cs
									
									
									
										generated
									
									
									
								
							|  | @ -1,171 +0,0 @@ | ||||||
| //------------------------------------------------------------------------------ |  | ||||||
| // <auto-generated> |  | ||||||
| //     此代码由工具生成。 |  | ||||||
| //     运行时版本:4.0.30319.42000 |  | ||||||
| // |  | ||||||
| //     对此文件的更改可能会导致不正确的行为,并且如果 |  | ||||||
| //     重新生成代码,这些更改将会丢失。 |  | ||||||
| // </auto-generated> |  | ||||||
| //------------------------------------------------------------------------------ |  | ||||||
| 
 |  | ||||||
| namespace AmazTool.Resx { |  | ||||||
|     using System; |  | ||||||
|      |  | ||||||
|      |  | ||||||
|     /// <summary> |  | ||||||
|     ///   一个强类型的资源类,用于查找本地化的字符串等。 |  | ||||||
|     /// </summary> |  | ||||||
|     // 此类是由 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() { |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   返回此类使用的缓存的 ResourceManager 实例。 |  | ||||||
|         /// </summary> |  | ||||||
|         [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; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   重写当前线程的 CurrentUICulture 属性,对 |  | ||||||
|         ///   使用此强类型资源类的所有资源查找执行重写。 |  | ||||||
|         /// </summary> |  | ||||||
|         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |  | ||||||
|         internal static global::System.Globalization.CultureInfo Culture { |  | ||||||
|             get { |  | ||||||
|                 return resourceCulture; |  | ||||||
|             } |  | ||||||
|             set { |  | ||||||
|                 resourceCulture = value; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Failed to terminate the v2rayN. Close it manually, or the upgrade may fail. 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string FailedTerminateProcess { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("FailedTerminateProcess", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Failed to extract the update package. 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string FailedUnzipping { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("FailedUnzipping", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Upgrade failed. 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string FailedUpgrade { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("FailedUpgrade", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Please run it from the main application. 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string Guidelines { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("Guidelines", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Information 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string Information { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("Information", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 In progress, please wait... 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string InProgress { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("InProgress", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Start v2rayN, please wait... 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string Restartv2rayN { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("Restartv2rayN", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Start extracting the update package... 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string StartUnzipping { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("StartUnzipping", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Successfully extracted the update package. 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string SuccessUnzipping { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("SuccessUnzipping", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Upgrade success. 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string SuccessUpgrade { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("SuccessUpgrade", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Try to terminate the v2rayN process... 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string TryTerminateProcess { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("TryTerminateProcess", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         /// <summary> |  | ||||||
|         ///   查找类似 Upgrade failed, file not found. 的本地化字符串。 |  | ||||||
|         /// </summary> |  | ||||||
|         internal static string UpgradeFileNotFound { |  | ||||||
|             get { |  | ||||||
|                 return ResourceManager.GetString("UpgradeFileNotFound", resourceCulture); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,128 +0,0 @@ | ||||||
| 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,51 +0,0 @@ | ||||||
| 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); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,33 +0,0 @@ | ||||||
| <Project> |  | ||||||
| 
 |  | ||||||
|     <PropertyGroup> |  | ||||||
|         <Version>7.15.6</Version> |  | ||||||
|     </PropertyGroup> |  | ||||||
| 
 |  | ||||||
|     <PropertyGroup> |  | ||||||
|         <TargetFramework>net8.0</TargetFramework> |  | ||||||
|         <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> |  | ||||||
|         <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> |  | ||||||
|         <NoWarn>CA1031;CS1591;NU1507;CA1416;IDE0058</NoWarn> |  | ||||||
|         <Nullable>annotations</Nullable> |  | ||||||
|         <ImplicitUsings>enable</ImplicitUsings> |  | ||||||
|         <Authors>2dust</Authors> |  | ||||||
|         <PackageLicenseExpression>GPL-3.0</PackageLicenseExpression> |  | ||||||
|         <Copyright>Copyright © 2017-$([System.DateTime]::UtcNow.Year) $(Authors)</Copyright> |  | ||||||
|         <InvariantGlobalization>false</InvariantGlobalization> |  | ||||||
|     </PropertyGroup> |  | ||||||
| 
 |  | ||||||
|     <PropertyGroup Condition="'$(Configuration)' == 'Release'"> |  | ||||||
|         <DebugType>embedded</DebugType> |  | ||||||
|         <EventSourceSupport>false</EventSourceSupport> |  | ||||||
|         <StackTraceSupport>false</StackTraceSupport> |  | ||||||
|         <MetricsSupport>false</MetricsSupport> |  | ||||||
|         <MetadataUpdaterSupport>false</MetadataUpdaterSupport> |  | ||||||
|         <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization> |  | ||||||
|         <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding> |  | ||||||
| 
 |  | ||||||
|         <UseSystemResourceKeys>true</UseSystemResourceKeys> |  | ||||||
|         <PublishSingleFile>true</PublishSingleFile> |  | ||||||
|         <PublishReadyToRun>false</PublishReadyToRun> |  | ||||||
|     </PropertyGroup> |  | ||||||
| </Project> |  | ||||||
|  | @ -1,32 +0,0 @@ | ||||||
| <Project> |  | ||||||
|   <PropertyGroup> |  | ||||||
|     <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> |  | ||||||
|     <CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled> |  | ||||||
|     <CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled> |  | ||||||
|   </PropertyGroup> |  | ||||||
|   <ItemGroup> |  | ||||||
|     <PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" /> |  | ||||||
|     <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.8" /> |  | ||||||
|     <PackageVersion Include="Avalonia.Desktop" Version="11.3.8" /> |  | ||||||
|     <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.8" /> |  | ||||||
|     <PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" /> |  | ||||||
|     <PackageVersion Include="CliWrap" Version="3.9.0" /> |  | ||||||
|     <PackageVersion Include="Downloader" Version="4.0.3" /> |  | ||||||
|     <PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.2" /> |  | ||||||
|     <PackageVersion Include="MaterialDesignThemes" Version="5.3.0" /> |  | ||||||
|     <PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" /> |  | ||||||
|     <PackageVersion Include="QRCoder" Version="1.7.0" /> |  | ||||||
|     <PackageVersion Include="ReactiveUI" Version="22.2.1" /> |  | ||||||
|     <PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" /> |  | ||||||
|     <PackageVersion Include="ReactiveUI.WPF" Version="22.2.1" /> |  | ||||||
|     <PackageVersion Include="Semi.Avalonia" Version="11.3.7" /> |  | ||||||
|     <PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" /> |  | ||||||
|     <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7" /> |  | ||||||
|     <PackageVersion Include="NLog" Version="6.0.5" /> |  | ||||||
|     <PackageVersion Include="sqlite-net-pcl" Version="1.9.172" /> |  | ||||||
|     <PackageVersion Include="TaskScheduler" Version="2.12.2" /> |  | ||||||
|     <PackageVersion Include="WebDav.Client" Version="2.9.0" /> |  | ||||||
|     <PackageVersion Include="YamlDotNet" Version="16.3.0" /> |  | ||||||
|     <PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" /> |  | ||||||
|   </ItemGroup> |  | ||||||
| </Project> |  | ||||||
|  | @ -1 +0,0 @@ | ||||||
| Subproject commit ffb2850df0991495d0918e13cc5701737f26175a |  | ||||||
							
								
								
									
										102
									
								
								v2rayN/PacLib/PacHandler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								v2rayN/PacLib/PacHandler.cs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,102 @@ | ||||||
|  | using System; | ||||||
|  | using System.IO; | ||||||
|  | using System.Net.Sockets; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | 
 | ||||||
|  | namespace PacLib; | ||||||
|  | 
 | ||||||
|  | public class PacHandler | ||||||
|  | { | ||||||
|  |     private static string _configPath; | ||||||
|  |     private static int _httpPort; | ||||||
|  |     private static int _pacPort; | ||||||
|  |     private static TcpListener? _tcpListener; | ||||||
|  |     private static string _pacText; | ||||||
|  |     private static bool _isRunning; | ||||||
|  |     private static bool _needRestart = true; | ||||||
|  | 
 | ||||||
|  |     public static void Start(string configPath, int httpPort, int pacPort) | ||||||
|  |     { | ||||||
|  |         _needRestart = (configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning); | ||||||
|  | 
 | ||||||
|  |         _configPath = configPath; | ||||||
|  |         _httpPort = httpPort; | ||||||
|  |         _pacPort = pacPort; | ||||||
|  | 
 | ||||||
|  |         InitText(); | ||||||
|  | 
 | ||||||
|  |         if (_needRestart) | ||||||
|  |         { | ||||||
|  |             Stop(); | ||||||
|  |             RunListener(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static void InitText() | ||||||
|  |     { | ||||||
|  |         var path = Path.Combine(_configPath, "pac.txt"); | ||||||
|  |         if (!File.Exists(path)) | ||||||
|  |         { | ||||||
|  |             File.AppendAllText(path, Resources.ResourceManager.GetString("pac")); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         _pacText = File.ReadAllText(path).Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static 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 = _tcpListener.AcceptTcpClient(); | ||||||
|  |                     await Task.Run(() => | ||||||
|  |                       { | ||||||
|  |                           var stream = client.GetStream(); | ||||||
|  |                           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); | ||||||
|  |                           var content = Encoding.UTF8.GetBytes(sb.ToString()); | ||||||
|  |                           stream.Write(content, 0, content.Length); | ||||||
|  |                           stream.Flush(); | ||||||
|  |                       }); | ||||||
|  |                 } | ||||||
|  |                 catch (Exception e) | ||||||
|  |                 { | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, TaskCreationOptions.LongRunning); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void Stop() | ||||||
|  |     { | ||||||
|  |         if (_tcpListener != null) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 _isRunning = false; | ||||||
|  |                 _tcpListener.Stop(); | ||||||
|  |                 _tcpListener = null; | ||||||
|  |             } | ||||||
|  |             catch (Exception e) | ||||||
|  |             { | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								v2rayN/PacLib/PacLib.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								v2rayN/PacLib/PacLib.csproj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  | 
 | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <TargetFramework>net8.0</TargetFramework> | ||||||
|  |     <Nullable>enable</Nullable> | ||||||
|  |   </PropertyGroup> | ||||||
|  | 
 | ||||||
|  |   <ItemGroup> | ||||||
|  |     <Compile Update="Resources.Designer.cs"> | ||||||
|  |       <DesignTime>True</DesignTime> | ||||||
|  |       <AutoGen>True</AutoGen> | ||||||
|  |       <DependentUpon>Resources.resx</DependentUpon> | ||||||
|  |     </Compile>  | ||||||
|  |     <EmbeddedResource Update="Resources.resx"> | ||||||
|  |       <Generator>ResXFileCodeGenerator</Generator> | ||||||
|  |       <LastGenOutput>Resources.Designer.cs</LastGenOutput> | ||||||
|  |     </EmbeddedResource> | ||||||
|  |   </ItemGroup> | ||||||
|  | 
 | ||||||
|  | </Project> | ||||||
							
								
								
									
										95
									
								
								v2rayN/PacLib/Resources.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								v2rayN/PacLib/Resources.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | // <auto-generated> | ||||||
|  | //     此代码由工具生成。 | ||||||
|  | //     运行时版本:4.0.30319.42000 | ||||||
|  | // | ||||||
|  | //     对此文件的更改可能会导致不正确的行为,并且如果 | ||||||
|  | //     重新生成代码,这些更改将会丢失。 | ||||||
|  | // </auto-generated> | ||||||
|  | //------------------------------------------------------------------------------ | ||||||
|  | 
 | ||||||
|  | namespace PacLib { | ||||||
|  |     using System; | ||||||
|  |      | ||||||
|  |      | ||||||
|  |     /// <summary> | ||||||
|  |     ///   一个强类型的资源类,用于查找本地化的字符串等。 | ||||||
|  |     /// </summary> | ||||||
|  |     // 此类是由 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() { | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /// <summary> | ||||||
|  |         ///   返回此类使用的缓存的 ResourceManager 实例。 | ||||||
|  |         /// </summary> | ||||||
|  |         [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("PacLib.Resources", typeof(Resources).Assembly); | ||||||
|  |                     resourceMan = temp; | ||||||
|  |                 } | ||||||
|  |                 return resourceMan; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /// <summary> | ||||||
|  |         ///   重写当前线程的 CurrentUICulture 属性,对 | ||||||
|  |         ///   使用此强类型资源类的所有资源查找执行重写。 | ||||||
|  |         /// </summary> | ||||||
|  |         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | ||||||
|  |         internal static global::System.Globalization.CultureInfo Culture { | ||||||
|  |             get { | ||||||
|  |                 return resourceCulture; | ||||||
|  |             } | ||||||
|  |             set { | ||||||
|  |                 resourceCulture = value; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /// <summary> | ||||||
|  |         ///   查找类似 var proxy = '__PROXY__'; | ||||||
|  |         ///var rules = [ | ||||||
|  |         ///    [ | ||||||
|  |         ///        [], | ||||||
|  |         ///        [] | ||||||
|  |         ///    ], | ||||||
|  |         ///    [ | ||||||
|  |         ///        [ | ||||||
|  |         ///            "aftygh.gov.tw", | ||||||
|  |         ///            "aide.gov.tw", | ||||||
|  |         ///            "aliyun.com", | ||||||
|  |         ///            "arte.gov.tw", | ||||||
|  |         ///            "baidu.com", | ||||||
|  |         ///            "chinaso.com", | ||||||
|  |         ///            "chinaz.com", | ||||||
|  |         ///            "chukuang.gov.tw", | ||||||
|  |         ///            "cycab.gov.tw", | ||||||
|  |         ///            "dbnsa.gov.tw", | ||||||
|  |         ///            "df.gov.tw", | ||||||
|  |         ///            "eastcoast-nsa.gov.tw", | ||||||
|  |         ///            "erv-nsa.gov.tw", | ||||||
|  |         ///            "grb.gov.tw", | ||||||
|  |         ///            "haosou.com", | ||||||
|  |         ///     [字符串的其余部分被截断]"; 的本地化字符串。 | ||||||
|  |         /// </summary> | ||||||
|  |         internal static string pac { | ||||||
|  |             get { | ||||||
|  |                 return ResourceManager.GetString("pac", resourceCulture); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <root> | <root> | ||||||
|   <!--  |   <!--  | ||||||
|     Microsoft ResX Schema  |     Microsoft ResX Schema  | ||||||
|  | @ -117,40 +117,8 @@ | ||||||
|   <resheader name="writer"> |   <resheader name="writer"> | ||||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||||
|   </resheader> |   </resheader> | ||||||
|   <data name="Restartv2rayN" xml:space="preserve"> |   <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> | ||||||
|     <value>正在重启,请等待...</value> |   <data name="pac" type="System.Resources.ResXFileRef, System.Windows.Forms"> | ||||||
|   </data> |     <value>Resources\pac.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312</value> | ||||||
|   <data name="Guidelines" xml:space="preserve"> |  | ||||||
|     <value>请从主应用运行。</value> |  | ||||||
|   </data> |  | ||||||
|   <data name="UpgradeFileNotFound" xml:space="preserve"> |  | ||||||
|     <value>升级失败,文件不存在。</value> |  | ||||||
|   </data> |  | ||||||
|   <data name="InProgress" xml:space="preserve"> |  | ||||||
|     <value>正在进行中,请等待...</value> |  | ||||||
|   </data> |  | ||||||
|   <data name="TryTerminateProcess" xml:space="preserve"> |  | ||||||
|     <value>尝试结束 v2rayN 进程...</value> |  | ||||||
|   </data> |  | ||||||
|   <data name="FailedTerminateProcess" xml:space="preserve"> |  | ||||||
|     <value>请手动关闭正在运行的 v2rayN,否则可能升级失败。</value> |  | ||||||
|   </data> |  | ||||||
|   <data name="StartUnzipping" xml:space="preserve"> |  | ||||||
|     <value>开始解压缩更新包...</value> |  | ||||||
|   </data> |  | ||||||
|   <data name="SuccessUnzipping" xml:space="preserve"> |  | ||||||
|     <value>解压缩更新包成功。</value> |  | ||||||
|   </data> |  | ||||||
|   <data name="FailedUnzipping" xml:space="preserve"> |  | ||||||
|     <value>解压缩更新包失败。</value> |  | ||||||
|   </data> |  | ||||||
|   <data name="FailedUpgrade" xml:space="preserve"> |  | ||||||
|     <value>升级失败。</value> |  | ||||||
|   </data> |  | ||||||
|   <data name="SuccessUpgrade" xml:space="preserve"> |  | ||||||
|     <value>升级成功。</value> |  | ||||||
|   </data> |  | ||||||
|   <data name="Information" xml:space="preserve"> |  | ||||||
|     <value>提示</value> |  | ||||||
|   </data> |   </data> | ||||||
| </root> | </root> | ||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										20
									
								
								v2rayN/ProtosLib/ProtosLib.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								v2rayN/ProtosLib/ProtosLib.csproj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <TargetFramework>net8.0</TargetFramework> | ||||||
|  |     <Nullable>enable</Nullable>	   | ||||||
|  |   </PropertyGroup> | ||||||
|  | 	 | ||||||
|  | 	<ItemGroup> | ||||||
|  | 		<Protobuf Include="Statistics.proto" /> | ||||||
|  | 	</ItemGroup> | ||||||
|  | 
 | ||||||
|  | 	<ItemGroup> | ||||||
|  | 		<PackageReference Include="Google.Protobuf" Version="3.26.1" /> | ||||||
|  | 		<PackageReference Include="Grpc.Net.Client" Version="2.62.0" /> | ||||||
|  | 		<PackageReference Include="Grpc.Tools" Version="2.63.0"> | ||||||
|  | 			<PrivateAssets>all</PrivateAssets> | ||||||
|  | 			<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|  | 		</PackageReference> | ||||||
|  | 	</ItemGroup> | ||||||
|  | 	 | ||||||
|  | </Project> | ||||||
							
								
								
									
										53
									
								
								v2rayN/ProtosLib/Statistics.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								v2rayN/ProtosLib/Statistics.proto
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v2ray.core.app.stats.command; | ||||||
|  | option csharp_namespace = "ProtosLib.Statistics"; | ||||||
|  | 
 | ||||||
|  | message GetStatsRequest { | ||||||
|  |   // Name of the stat counter. | ||||||
|  |   string name = 1; | ||||||
|  |   // Whether or not to reset the counter to fetching its value. | ||||||
|  |   bool reset = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message Stat { | ||||||
|  |   string name = 1; | ||||||
|  |   int64 value = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetStatsResponse { | ||||||
|  |   Stat stat = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message QueryStatsRequest { | ||||||
|  |   string pattern = 1; | ||||||
|  |   bool reset = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message QueryStatsResponse { | ||||||
|  |   repeated Stat stat = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SysStatsRequest { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SysStatsResponse { | ||||||
|  |   uint32 NumGoroutine = 1; | ||||||
|  |   uint32 NumGC = 2; | ||||||
|  |   uint64 Alloc = 3; | ||||||
|  |   uint64 TotalAlloc = 4; | ||||||
|  |   uint64 Sys = 5; | ||||||
|  |   uint64 Mallocs = 6; | ||||||
|  |   uint64 Frees = 7; | ||||||
|  |   uint64 LiveObjects = 8; | ||||||
|  |   uint64 PauseTotalNs = 9; | ||||||
|  |   uint32 Uptime = 10; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | service StatsService { | ||||||
|  |   rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {} | ||||||
|  |   rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {} | ||||||
|  |   rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message Config {} | ||||||
							
								
								
									
										13
									
								
								v2rayN/ProtosLib/Tests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								v2rayN/ProtosLib/Tests.cs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | using ProtosLib.Statistics; | ||||||
|  | 
 | ||||||
|  | namespace ProtosLib | ||||||
|  | { | ||||||
|  |     public class Tests | ||||||
|  |     { | ||||||
|  |         private StatsService.StatsServiceClient client_; | ||||||
|  | 
 | ||||||
|  |         public Tests() | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,7 +0,0 @@ | ||||||
| namespace ServiceLib.Base; |  | ||||||
| 
 |  | ||||||
| public class MyReactiveObject : ReactiveObject |  | ||||||
| { |  | ||||||
|     protected static Config? _config; |  | ||||||
|     protected Func<EViewAction, object?, Task<bool>>? _updateView; |  | ||||||
| } |  | ||||||
|  | @ -1,58 +0,0 @@ | ||||||
| namespace ServiceLib.Common; |  | ||||||
| 
 |  | ||||||
| public static class EmbedUtils |  | ||||||
| { |  | ||||||
|     private static readonly string _tag = "EmbedUtils"; |  | ||||||
|     private static readonly ConcurrentDictionary<string, string> _dicEmbedCache = new(); |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Get embedded text resources |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="res"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Get local storage resources |  | ||||||
|     /// </summary> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static string? LoadResource(string? res) |  | ||||||
|     { |  | ||||||
|         try |  | ||||||
|         { |  | ||||||
|             if (File.Exists(res)) |  | ||||||
|             { |  | ||||||
|                 return File.ReadAllText(res); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             Logging.SaveLog(_tag, ex); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,97 +0,0 @@ | ||||||
| using System.Diagnostics.CodeAnalysis; |  | ||||||
| 
 |  | ||||||
| namespace ServiceLib.Common; |  | ||||||
| 
 |  | ||||||
| public static class Extension |  | ||||||
| { |  | ||||||
|     public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) |  | ||||||
|     { |  | ||||||
|         return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value) |  | ||||||
|     { |  | ||||||
|         return string.IsNullOrWhiteSpace(value); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static bool IsNotEmpty([NotNullWhen(false)] this string? value) |  | ||||||
|     { |  | ||||||
|         return !string.IsNullOrEmpty(value); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static bool BeginWithAny(this string s, IEnumerable<char> 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<string> 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<string> AppendEmpty(this IEnumerable<string> 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,249 +0,0 @@ | ||||||
| using System.Formats.Tar; |  | ||||||
| using System.IO.Compression; |  | ||||||
| 
 |  | ||||||
| namespace ServiceLib.Common; |  | ||||||
| 
 |  | ||||||
| public static class FileManager |  | ||||||
| { |  | ||||||
|     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<string>? 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 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Creates a Linux shell file with the specified contents. |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="fileName"></param> |  | ||||||
|     /// <param name="contents"></param> |  | ||||||
|     /// <param name="overwrite"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static async Task<string> 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,150 +0,0 @@ | ||||||
| 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 |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// DeepCopy |  | ||||||
|     /// </summary> |  | ||||||
|     /// <typeparam name="T"></typeparam> |  | ||||||
|     /// <param name="obj"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static T? DeepCopy<T>(T? obj) |  | ||||||
|     { |  | ||||||
|         if (obj is null) |  | ||||||
|         { |  | ||||||
|             return default; |  | ||||||
|         } |  | ||||||
|         return Deserialize<T>(Serialize(obj, false)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Deserialize to object |  | ||||||
|     /// </summary> |  | ||||||
|     /// <typeparam name="T"></typeparam> |  | ||||||
|     /// <param name="strJson"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static T? Deserialize<T>(string? strJson) |  | ||||||
|     { |  | ||||||
|         try |  | ||||||
|         { |  | ||||||
|             if (string.IsNullOrWhiteSpace(strJson)) |  | ||||||
|             { |  | ||||||
|                 return default; |  | ||||||
|             } |  | ||||||
|             return JsonSerializer.Deserialize<T>(strJson, _defaultDeserializeOptions); |  | ||||||
|         } |  | ||||||
|         catch |  | ||||||
|         { |  | ||||||
|             return default; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// parse |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="strJson"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     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; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Serialize Object to Json string |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="obj"></param> |  | ||||||
|     /// <param name="indented"></param> |  | ||||||
|     /// <param name="nullValue"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Serialize Object to Json string |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="obj"></param> |  | ||||||
|     /// <param name="options"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// SerializeToNode |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="obj"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static JsonNode? SerializeToNode(object? obj, JsonSerializerOptions? options = null) |  | ||||||
|     { |  | ||||||
|         return JsonSerializer.SerializeToNode(obj, options); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,55 +0,0 @@ | ||||||
| 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); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,68 +0,0 @@ | ||||||
| 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); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,125 +0,0 @@ | ||||||
| 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,186 +0,0 @@ | ||||||
| namespace ServiceLib.Common; |  | ||||||
| 
 |  | ||||||
| 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(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Use ToVersionString(string? prefix) instead if possible. |  | ||||||
|     /// </summary> |  | ||||||
|     /// <returns>major.minor.patch</returns> |  | ||||||
|     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 diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,71 +0,0 @@ | ||||||
| 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); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,79 +0,0 @@ | ||||||
| using YamlDotNet.Core; |  | ||||||
| using YamlDotNet.Serialization; |  | ||||||
| using YamlDotNet.Serialization.NamingConventions; |  | ||||||
| 
 |  | ||||||
| namespace ServiceLib.Common; |  | ||||||
| 
 |  | ||||||
| public class YamlUtils |  | ||||||
| { |  | ||||||
|     private static readonly string _tag = "YamlUtils"; |  | ||||||
| 
 |  | ||||||
|     #region YAML |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Deserialize |  | ||||||
|     /// </summary> |  | ||||||
|     /// <typeparam name="T"></typeparam> |  | ||||||
|     /// <param name="str"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     public static T FromYaml<T>(string str) |  | ||||||
|     { |  | ||||||
|         var deserializer = new DeserializerBuilder() |  | ||||||
|             .WithNamingConvention(PascalCaseNamingConvention.Instance) |  | ||||||
|             .Build(); |  | ||||||
|         try |  | ||||||
|         { |  | ||||||
|             var obj = deserializer.Deserialize<T>(str); |  | ||||||
|             return obj; |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             Logging.SaveLog(_tag, ex); |  | ||||||
|             return deserializer.Deserialize<T>(""); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Serialize |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="obj"></param> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     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 |  | ||||||
| } |  | ||||||
|  | @ -1,18 +0,0 @@ | ||||||
| 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, |  | ||||||
| } |  | ||||||
|  | @ -1,20 +0,0 @@ | ||||||
| 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 |  | ||||||
| } |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum EGirdOrientation |  | ||||||
| { |  | ||||||
|     Horizontal, |  | ||||||
|     Vertical, |  | ||||||
|     Tab, |  | ||||||
| } |  | ||||||
|  | @ -1,10 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum EGlobalHotkey |  | ||||||
| { |  | ||||||
|     ShowForm = 0, |  | ||||||
|     SystemProxyClear = 1, |  | ||||||
|     SystemProxySet = 2, |  | ||||||
|     SystemProxyUnchanged = 3, |  | ||||||
|     SystemProxyPac = 4, |  | ||||||
| } |  | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum EInboundProtocol |  | ||||||
| { |  | ||||||
|     socks = 0, |  | ||||||
|     socks2, |  | ||||||
|     socks3, |  | ||||||
|     pac, |  | ||||||
|     api, |  | ||||||
|     api2, |  | ||||||
|     mixed, |  | ||||||
|     speedtest = 21 |  | ||||||
| } |  | ||||||
|  | @ -1,10 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum EMove |  | ||||||
| { |  | ||||||
|     Top = 1, |  | ||||||
|     Up = 2, |  | ||||||
|     Down = 3, |  | ||||||
|     Bottom = 4, |  | ||||||
|     Position = 5 |  | ||||||
| } |  | ||||||
|  | @ -1,10 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum EMultipleLoad |  | ||||||
| { |  | ||||||
|     LeastPing, |  | ||||||
|     Fallback, |  | ||||||
|     Random, |  | ||||||
|     RoundRobin, |  | ||||||
|     LeastLoad |  | ||||||
| } |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum EPresetType |  | ||||||
| { |  | ||||||
|     Default = 0, |  | ||||||
|     Russia = 1, |  | ||||||
|     Iran = 2, |  | ||||||
| } |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum ERuleMode |  | ||||||
| { |  | ||||||
|     Rule = 0, |  | ||||||
|     Global = 1, |  | ||||||
|     Direct = 2, |  | ||||||
|     Unchanged = 3 |  | ||||||
| } |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum ERuleType |  | ||||||
| { |  | ||||||
|     ALL = 0, |  | ||||||
|     Routing = 1, |  | ||||||
|     DNS = 2, |  | ||||||
| } |  | ||||||
|  | @ -1,20 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum EServerColName |  | ||||||
| { |  | ||||||
|     Def = 0, |  | ||||||
|     ConfigType, |  | ||||||
|     Remarks, |  | ||||||
|     Address, |  | ||||||
|     Port, |  | ||||||
|     Network, |  | ||||||
|     StreamSecurity, |  | ||||||
|     SubRemarks, |  | ||||||
|     DelayVal, |  | ||||||
|     SpeedVal, |  | ||||||
| 
 |  | ||||||
|     TodayDown, |  | ||||||
|     TodayUp, |  | ||||||
|     TotalDown, |  | ||||||
|     TotalUp |  | ||||||
| } |  | ||||||
|  | @ -1,10 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum ESpeedActionType |  | ||||||
| { |  | ||||||
|     Tcping, |  | ||||||
|     Realping, |  | ||||||
|     Speedtest, |  | ||||||
|     Mixedtest, |  | ||||||
|     FastRealping |  | ||||||
| } |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum ESysProxyType |  | ||||||
| { |  | ||||||
|     ForcedClear = 0, |  | ||||||
|     ForcedChange = 1, |  | ||||||
|     Unchanged = 2, |  | ||||||
|     Pac = 3 |  | ||||||
| } |  | ||||||
|  | @ -1,12 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum ETheme |  | ||||||
| { |  | ||||||
|     FollowSystem, |  | ||||||
|     Dark, |  | ||||||
|     Light, |  | ||||||
|     Aquatic, |  | ||||||
|     Desert, |  | ||||||
|     Dusk, |  | ||||||
|     NightSky |  | ||||||
| } |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| namespace ServiceLib.Enums; |  | ||||||
| 
 |  | ||||||
| public enum ETransport |  | ||||||
| { |  | ||||||
|     tcp, |  | ||||||
|     kcp, |  | ||||||
|     ws, |  | ||||||
|     httpupgrade, |  | ||||||
|     xhttp, |  | ||||||
|     h2, |  | ||||||
|     http, |  | ||||||
|     quic, |  | ||||||
|     grpc |  | ||||||
| } |  | ||||||
|  | @ -1,36 +0,0 @@ | ||||||
| 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, |  | ||||||
| } |  | ||||||
|  | @ -1,30 +0,0 @@ | ||||||
| namespace ServiceLib.Events; |  | ||||||
| 
 |  | ||||||
| public static class AppEvents |  | ||||||
| { |  | ||||||
|     public static readonly EventChannel<Unit> ReloadRequested = new(); |  | ||||||
|     public static readonly EventChannel<bool?> ShowHideWindowRequested = new(); |  | ||||||
|     public static readonly EventChannel<Unit> AddServerViaScanRequested = new(); |  | ||||||
|     public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new(); |  | ||||||
|     public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new(); |  | ||||||
| 
 |  | ||||||
|     public static readonly EventChannel<Unit> ProfilesRefreshRequested = new(); |  | ||||||
|     public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new(); |  | ||||||
|     public static readonly EventChannel<Unit> ProxiesReloadRequested = new(); |  | ||||||
|     public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new(); |  | ||||||
| 
 |  | ||||||
|     public static readonly EventChannel<string> SendSnackMsgRequested = new(); |  | ||||||
|     public static readonly EventChannel<string> SendMsgViewRequested = new(); |  | ||||||
| 
 |  | ||||||
|     public static readonly EventChannel<Unit> AppExitRequested = new(); |  | ||||||
|     public static readonly EventChannel<bool> ShutdownRequested = new(); |  | ||||||
| 
 |  | ||||||
|     public static readonly EventChannel<Unit> AdjustMainLvColWidthRequested = new(); |  | ||||||
| 
 |  | ||||||
|     public static readonly EventChannel<string> SetDefaultServerRequested = new(); |  | ||||||
| 
 |  | ||||||
|     public static readonly EventChannel<Unit> RoutingsMenuRefreshRequested = new(); |  | ||||||
|     public static readonly EventChannel<Unit> TestServerRequested = new(); |  | ||||||
|     public static readonly EventChannel<Unit> InboundDisplayRequested = new(); |  | ||||||
|     public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new(); |  | ||||||
| } |  | ||||||
|  | @ -1,27 +0,0 @@ | ||||||
| using System.Reactive.Subjects; |  | ||||||
| 
 |  | ||||||
| namespace ServiceLib.Events; |  | ||||||
| 
 |  | ||||||
| public sealed class EventChannel<T> |  | ||||||
| { |  | ||||||
|     private readonly ISubject<T> _subject = Subject.Synchronize(new Subject<T>()); |  | ||||||
| 
 |  | ||||||
|     public IObservable<T> 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<Unit>."); |  | ||||||
|         } |  | ||||||
|         _subject.OnNext((T)(object)Unit.Default); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,3 +0,0 @@ | ||||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |  | ||||||
|   <ReactiveUI /> |  | ||||||
| </Weavers> |  | ||||||
|  | @ -1,630 +0,0 @@ | ||||||
| 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 CoreMultipleLoadConfigFileName = "configMultipleLoad.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 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 = "<COMMA>"; |  | ||||||
|     public const string GrpcGunMode = "gun"; |  | ||||||
|     public const string GrpcMultiMode = "multi"; |  | ||||||
|     public const int MaxPort = 65536; |  | ||||||
|     public const int MinFontSize = 8; |  | ||||||
|     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 static readonly List<string> 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<string> 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<string> SubConvertConfig = |  | ||||||
|         [@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> SubConvertTargets = |  | ||||||
|     [ |  | ||||||
|         "", |  | ||||||
|             "mixed", |  | ||||||
|             "v2ray", |  | ||||||
|             "clash", |  | ||||||
|             "ss" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> 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<string> 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<string> 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<string> 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<string> 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<string> 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<string, string> 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<EConfigType, string> 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<EConfigType, string> 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<string> VmessSecurities = |  | ||||||
|     [ |  | ||||||
|         "aes-128-gcm", |  | ||||||
|             "chacha20-poly1305", |  | ||||||
|             "auto", |  | ||||||
|             "none", |  | ||||||
|             "zero" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> SsSecurities = |  | ||||||
|     [ |  | ||||||
|         "aes-256-gcm", |  | ||||||
|             "aes-128-gcm", |  | ||||||
|             "chacha20-poly1305", |  | ||||||
|             "chacha20-ietf-poly1305", |  | ||||||
|             "none", |  | ||||||
|             "plain" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> 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<string> 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<string> Flows = |  | ||||||
|     [ |  | ||||||
|         "", |  | ||||||
|             "xtls-rprx-vision", |  | ||||||
|             "xtls-rprx-vision-udp443" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> Networks = |  | ||||||
|     [ |  | ||||||
|         "tcp", |  | ||||||
|             "kcp", |  | ||||||
|             "ws", |  | ||||||
|             "httpupgrade", |  | ||||||
|             "xhttp", |  | ||||||
|             "h2", |  | ||||||
|             "quic", |  | ||||||
|             "grpc" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> KcpHeaderTypes = |  | ||||||
|     [ |  | ||||||
|         "srtp", |  | ||||||
|             "utp", |  | ||||||
|             "wechat-video", |  | ||||||
|             "dtls", |  | ||||||
|             "wireguard", |  | ||||||
|             "dns" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> CoreTypes = |  | ||||||
|     [ |  | ||||||
|         "Xray", |  | ||||||
|             "sing_box" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly HashSet<EConfigType> XraySupportConfigType = |  | ||||||
|     [ |  | ||||||
|         EConfigType.VMess, |  | ||||||
|             EConfigType.VLESS, |  | ||||||
|             EConfigType.Shadowsocks, |  | ||||||
|             EConfigType.Trojan, |  | ||||||
|             EConfigType.WireGuard, |  | ||||||
|             EConfigType.SOCKS, |  | ||||||
|             EConfigType.HTTP, |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly HashSet<EConfigType> 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<EConfigType> SingboxOnlyConfigType = SingboxSupportConfigType.Except(XraySupportConfigType).ToHashSet(); |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> DomainStrategies = |  | ||||||
|     [ |  | ||||||
|         AsIs, |  | ||||||
|         IPIfNonMatch, |  | ||||||
|         IPOnDemand |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> DomainStrategies4Singbox = |  | ||||||
|     [ |  | ||||||
|         "ipv4_only", |  | ||||||
|             "ipv6_only", |  | ||||||
|             "prefer_ipv4", |  | ||||||
|             "prefer_ipv6", |  | ||||||
|             "" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> Fingerprints = |  | ||||||
|     [ |  | ||||||
|         "chrome", |  | ||||||
|             "firefox", |  | ||||||
|             "safari", |  | ||||||
|             "ios", |  | ||||||
|             "android", |  | ||||||
|             "edge", |  | ||||||
|             "360", |  | ||||||
|             "qq", |  | ||||||
|             "random", |  | ||||||
|             "randomized", |  | ||||||
|             "" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> UserAgent = |  | ||||||
|     [ |  | ||||||
|         "chrome", |  | ||||||
|             "firefox", |  | ||||||
|             "safari", |  | ||||||
|             "edge", |  | ||||||
|             "none" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> XhttpMode = |  | ||||||
|     [ |  | ||||||
|         "auto", |  | ||||||
|             "packet-up", |  | ||||||
|             "stream-up", |  | ||||||
|             "stream-one" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> AllowInsecure = |  | ||||||
|     [ |  | ||||||
|         "true", |  | ||||||
|             "false", |  | ||||||
|             "" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> DomainStrategy4Freedoms = |  | ||||||
|     [ |  | ||||||
|         "AsIs", |  | ||||||
|             "UseIP", |  | ||||||
|             "UseIPv4", |  | ||||||
|             "UseIPv6", |  | ||||||
|             "" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> SingboxDomainStrategy4Out = |  | ||||||
|     [ |  | ||||||
|         "", |  | ||||||
|             "ipv4_only", |  | ||||||
|             "prefer_ipv4", |  | ||||||
|             "prefer_ipv6", |  | ||||||
|             "ipv6_only" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> DomainDirectDNSAddress = |  | ||||||
|     [ |  | ||||||
|         "https://dns.alidns.com/dns-query", |  | ||||||
|             "https://doh.pub/dns-query", |  | ||||||
|             "223.5.5.5", |  | ||||||
|             "119.29.29.29", |  | ||||||
|             "localhost" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> DomainRemoteDNSAddress = |  | ||||||
|     [ |  | ||||||
|         "https://cloudflare-dns.com/dns-query", |  | ||||||
|             "https://dns.cloudflare.com/dns-query", |  | ||||||
|             "https://dns.google/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<string> DomainPureIPDNSAddress = |  | ||||||
|     [ |  | ||||||
|         "223.5.5.5", |  | ||||||
|             "119.29.29.29", |  | ||||||
|             "localhost" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> Languages = |  | ||||||
|     [ |  | ||||||
|         "zh-Hans", |  | ||||||
|             "zh-Hant", |  | ||||||
|             "en", |  | ||||||
|             "fa-Ir", |  | ||||||
|             "fr", |  | ||||||
|             "ru", |  | ||||||
|             "hu" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> Alpns = |  | ||||||
|     [ |  | ||||||
|         "h3", |  | ||||||
|             "h2", |  | ||||||
|             "http/1.1", |  | ||||||
|             "h3,h2", |  | ||||||
|             "h2,http/1.1", |  | ||||||
|             "h3,h2,http/1.1", |  | ||||||
|             "" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> LogLevels = |  | ||||||
|     [ |  | ||||||
|         "debug", |  | ||||||
|             "info", |  | ||||||
|             "warning", |  | ||||||
|             "error", |  | ||||||
|             "none" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly Dictionary<string, string> LogLevelColors = new() |  | ||||||
|     { |  | ||||||
|         { "debug",   "#6C757D" }, |  | ||||||
|         { "info",    "#2ECC71" }, |  | ||||||
|         { "warning", "#FFA500" }, |  | ||||||
|         { "error",   "#E74C3C" }, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> InboundTags = |  | ||||||
|     [ |  | ||||||
|         "socks", |  | ||||||
|             "socks2", |  | ||||||
|             "socks3" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> RuleProtocols = |  | ||||||
|     [ |  | ||||||
|         "http", |  | ||||||
|             "tls", |  | ||||||
|             "bittorrent" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> RuleNetworks = |  | ||||||
|     [ |  | ||||||
|         "", |  | ||||||
|             "tcp", |  | ||||||
|             "udp", |  | ||||||
|             "tcp,udp" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> destOverrideProtocols = |  | ||||||
|     [ |  | ||||||
|         "http", |  | ||||||
|             "tls", |  | ||||||
|             "quic", |  | ||||||
|             "fakedns", |  | ||||||
|             "fakedns+others" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<int> TunMtus = |  | ||||||
|     [ |  | ||||||
|         1280, |  | ||||||
|         1408, |  | ||||||
|         1500, |  | ||||||
|         4064, |  | ||||||
|         9000, |  | ||||||
|         65535 |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> TunStacks = |  | ||||||
|     [ |  | ||||||
|         "gvisor", |  | ||||||
|             "system", |  | ||||||
|             "mixed" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> PresetMsgFilters = |  | ||||||
|     [ |  | ||||||
|         "proxy", |  | ||||||
|             "direct", |  | ||||||
|             "block", |  | ||||||
|             "" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> SingboxMuxs = |  | ||||||
|     [ |  | ||||||
|         "h2mux", |  | ||||||
|             "smux", |  | ||||||
|             "yamux", |  | ||||||
|             "" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> TuicCongestionControls = |  | ||||||
|     [ |  | ||||||
|         "cubic", |  | ||||||
|             "new_reno", |  | ||||||
|             "bbr" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> allowSelectType = |  | ||||||
|     [ |  | ||||||
|         "selector", |  | ||||||
|             "urltest", |  | ||||||
|             "loadbalance", |  | ||||||
|             "fallback" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> notAllowTestType = |  | ||||||
|     [ |  | ||||||
|         "selector", |  | ||||||
|             "urltest", |  | ||||||
|             "direct", |  | ||||||
|             "reject", |  | ||||||
|             "compatible", |  | ||||||
|             "pass", |  | ||||||
|             "loadbalance", |  | ||||||
|             "fallback" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> proxyVehicleType = |  | ||||||
|     [ |  | ||||||
|         "file", |  | ||||||
|             "http" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly Dictionary<ECoreType, string> 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<string> 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<string> IPAPIUrls = |  | ||||||
|     [ |  | ||||||
|         @"https://speed.cloudflare.com/meta", |  | ||||||
|         @"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<string> OutboundTags = |  | ||||||
|     [ |  | ||||||
|         ProxyTag, |  | ||||||
|         DirectTag, |  | ||||||
|         BlockTag |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public static readonly Dictionary<string, List<string>> PredefinedHosts = new() |  | ||||||
|         { |  | ||||||
|             { "dns.google", new List<string> { "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844" } }, |  | ||||||
|             { "dns.alidns.com", new List<string> { "223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1" } }, |  | ||||||
|             { "one.one.one.one", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } }, |  | ||||||
|             { "1dot1dot1dot1.cloudflare-dns.com", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } }, |  | ||||||
|             { "cloudflare-dns.com", new List<string> { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } }, |  | ||||||
|             { "dns.cloudflare.com", new List<string> { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } }, |  | ||||||
|             { "dot.pub", new List<string> { "1.12.12.12", "120.53.53.53" } }, |  | ||||||
|             { "doh.pub", new List<string> { "1.12.12.12", "120.53.53.53" } }, |  | ||||||
|             { "dns.quad9.net", new List<string> { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } }, |  | ||||||
|             { "dns.yandex.net", new List<string> { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } }, |  | ||||||
|             { "dns.sb", new List<string> { "185.222.222.222", "2a09::" } }, |  | ||||||
|             { "dns.umbrella.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } }, |  | ||||||
|             { "dns.sse.cisco.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } }, |  | ||||||
|             { "engage.cloudflareclient.com", new List<string> { "162.159.192.1", "2606:4700:d0::a29f:c001" } } |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|     public static readonly List<string> ExpectedIPs = |  | ||||||
|     [ |  | ||||||
|         "geoip:cn", |  | ||||||
|             "geoip:ir", |  | ||||||
|             "geoip:ru", |  | ||||||
|             "" |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     #endregion const |  | ||||||
| } |  | ||||||
|  | @ -1,36 +0,0 @@ | ||||||
| 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.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; |  | ||||||
|  | @ -1,240 +0,0 @@ | ||||||
| using System.Security.Principal; |  | ||||||
| 
 |  | ||||||
| namespace ServiceLib.Handler; |  | ||||||
| 
 |  | ||||||
| public static class AutoStartupHandler |  | ||||||
| { |  | ||||||
|     private static readonly string _tag = "AutoStartupHandler"; |  | ||||||
| 
 |  | ||||||
|     public static async Task<bool> 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.IsOSX()) |  | ||||||
|         { |  | ||||||
|             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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Auto Start via TaskService |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="taskName"></param> |  | ||||||
|     /// <param name="fileName"></param> |  | ||||||
|     /// <param name="description"></param> |  | ||||||
|     /// <exception cref="ArgumentNullException"></exception> |  | ||||||
|     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 $@"<?xml version=""1.0"" encoding=""UTF-8""?>
 |  | ||||||
| <!DOCTYPE plist PUBLIC ""-//Apple//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd""> |  | ||||||
| <plist version=""1.0""> |  | ||||||
| <dict> |  | ||||||
|     <key>Label</key> |  | ||||||
|     <string>{Global.AppName}-LaunchAgent</string> |  | ||||||
|     <key>ProgramArguments</key> |  | ||||||
|     <array> |  | ||||||
|         <string>/bin/sh</string> |  | ||||||
|         <string>-c</string> |  | ||||||
|         <string>if ! pgrep -x ""{appName}"" > /dev/null; then ""{exePath}""; fi</string> |  | ||||||
|     </array> |  | ||||||
|     <key>RunAtLoad</key> |  | ||||||
|     <true/> |  | ||||||
|     <key>KeepAlive</key> |  | ||||||
|     <false/> |  | ||||||
| </dict> |  | ||||||
| </plist>";
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #endregion macOS |  | ||||||
| } |  | ||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,68 +0,0 @@ | ||||||
| namespace ServiceLib.Handler; |  | ||||||
| 
 |  | ||||||
| public static class ConnectionHandler |  | ||||||
| { |  | ||||||
|     private static readonly string _tag = "ConnectionHandler"; |  | ||||||
| 
 |  | ||||||
|     public static async Task<string> RunAvailabilityCheck() |  | ||||||
|     { |  | ||||||
|         var time = await GetRealPingTime(); |  | ||||||
|         var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None; |  | ||||||
| 
 |  | ||||||
|         return string.Format(ResUI.TestMeOutput, time, ip); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static async Task<string?> 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<IPAPIInfo>(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<int> GetRealPingTime() |  | ||||||
|     { |  | ||||||
|         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 HttpClientHelper.Instance.GetRealPingTime(url, webProxy, 10); |  | ||||||
|                 if (responseTime > 0) |  | ||||||
|                 { |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|                 await Task.Delay(500); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             Logging.SaveLog(_tag, ex); |  | ||||||
|             return -1; |  | ||||||
|         } |  | ||||||
|         return responseTime; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,135 +0,0 @@ | ||||||
| namespace ServiceLib.Handler; |  | ||||||
| 
 |  | ||||||
| /// <summary> |  | ||||||
| /// Core configuration file processing class |  | ||||||
| /// </summary> |  | ||||||
| public static class CoreConfigHandler |  | ||||||
| { |  | ||||||
|     private static readonly string _tag = "CoreConfigHandler"; |  | ||||||
| 
 |  | ||||||
|     public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName) |  | ||||||
|     { |  | ||||||
|         var config = AppManager.Instance.Config; |  | ||||||
|         var result = new RetResult(); |  | ||||||
| 
 |  | ||||||
|         if (node.ConfigType == EConfigType.Custom) |  | ||||||
|         { |  | ||||||
|             result = node.CoreType switch |  | ||||||
|             { |  | ||||||
|                 ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName), |  | ||||||
|                 ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName), |  | ||||||
|                 _ => await GenerateClientCustomConfig(node, fileName) |  | ||||||
|             }; |  | ||||||
|         } |  | ||||||
|         else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) |  | ||||||
|         { |  | ||||||
|             result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node); |  | ||||||
|         } |  | ||||||
|         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<RetResult> 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); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             string 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<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType) |  | ||||||
|     { |  | ||||||
|         var result = new RetResult(); |  | ||||||
|         if (coreType == ECoreType.sing_box) |  | ||||||
|         { |  | ||||||
|             result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds); |  | ||||||
|         } |  | ||||||
|         else if (coreType == ECoreType.Xray) |  | ||||||
|         { |  | ||||||
|             result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds); |  | ||||||
|         } |  | ||||||
|         if (result.Success != true) |  | ||||||
|         { |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
|         await File.WriteAllTextAsync(fileName, result.Data.ToString()); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, 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 (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) |  | ||||||
|         { |  | ||||||
|             result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port); |  | ||||||
|         } |  | ||||||
|         if (result.Success != true) |  | ||||||
|         { |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         await File.WriteAllTextAsync(fileName, result.Data.ToString()); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,48 +0,0 @@ | ||||||
| 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.Id = rawUserInfo; |  | ||||||
| 
 |  | ||||||
|         var query = Utils.ParseQueryString(parsedUrl.Query); |  | ||||||
|         _ = ResolveStdTransport(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.Id; |  | ||||||
|         var dicQuery = new Dictionary<string, string>(); |  | ||||||
|         _ = GetStdTransport(item, Global.None, ref dicQuery); |  | ||||||
| 
 |  | ||||||
|         return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,251 +0,0 @@ | ||||||
| using System.Collections.Specialized; |  | ||||||
| 
 |  | ||||||
| namespace ServiceLib.Handler.Fmt; |  | ||||||
| 
 |  | ||||||
| public class BaseFmt |  | ||||||
| { |  | ||||||
|     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 GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery) |  | ||||||
|     { |  | ||||||
|         if (item.Flow.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("flow", item.Flow); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (item.StreamSecurity.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("security", item.StreamSecurity); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             if (securityDef != null) |  | ||||||
|             { |  | ||||||
|                 dicQuery.Add("security", securityDef); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (item.Sni.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("sni", item.Sni); |  | ||||||
|         } |  | ||||||
|         if (item.Alpn.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); |  | ||||||
|         } |  | ||||||
|         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.AllowInsecure.Equals("true")) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("allowInsecure", "1"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         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()) |  | ||||||
|                 { |  | ||||||
|                     dicQuery.Add("extra", Utils.UrlEncode(item.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 ResolveStdTransport(NameValueCollection query, ref ProfileItem item) |  | ||||||
|     { |  | ||||||
|         item.Flow = GetQueryValue(query, "flow"); |  | ||||||
|         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.AllowInsecure = new[] { "allowInsecure", "allow_insecure", "insecure" }.Any(k => (query[k] ?? "") == "1") ? "true" : ""; |  | ||||||
| 
 |  | ||||||
|         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"); |  | ||||||
|                 item.Extra = GetQueryDecoded(query, "extra"); |  | ||||||
|                 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<string, string>? 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)); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,22 +0,0 @@ | ||||||
| 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,96 +0,0 @@ | ||||||
| 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 |  | ||||||
|         { |  | ||||||
|             string 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; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| namespace ServiceLib.Handler.Fmt; |  | ||||||
| 
 |  | ||||||
| public class HtmlPageFmt : BaseFmt |  | ||||||
| { |  | ||||||
|     public static bool IsHtmlPage(string strData) |  | ||||||
|     { |  | ||||||
|         return Contains(strData, "<html", "<!doctype html", "<head"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,83 +0,0 @@ | ||||||
| namespace ServiceLib.Handler.Fmt; |  | ||||||
| 
 |  | ||||||
| public class Hysteria2Fmt : BaseFmt |  | ||||||
| { |  | ||||||
|     public static ProfileItem? Resolve(string str, out string msg) |  | ||||||
|     { |  | ||||||
|         msg = ResUI.ConfigurationFormatIncorrect; |  | ||||||
|         ProfileItem item = new() |  | ||||||
|         { |  | ||||||
|             ConfigType = EConfigType.Hysteria2 |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         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.Id = Utils.UrlDecode(url.UserInfo); |  | ||||||
| 
 |  | ||||||
|         var query = Utils.ParseQueryString(url.Query); |  | ||||||
|         ResolveStdTransport(query, ref item); |  | ||||||
|         item.Path = GetQueryDecoded(query, "obfs-password"); |  | ||||||
|         item.AllowInsecure = GetQueryValue(query, "insecure") == "1" ? "true" : "false"; |  | ||||||
| 
 |  | ||||||
|         item.Ports = GetQueryDecoded(query, "mport"); |  | ||||||
| 
 |  | ||||||
|         return item; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static string? ToUri(ProfileItem? item) |  | ||||||
|     { |  | ||||||
|         if (item == null) |  | ||||||
|             return null; |  | ||||||
|         string url = string.Empty; |  | ||||||
| 
 |  | ||||||
|         string remark = string.Empty; |  | ||||||
|         if (item.Remarks.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             remark = "#" + Utils.UrlEncode(item.Remarks); |  | ||||||
|         } |  | ||||||
|         var dicQuery = new Dictionary<string, string>(); |  | ||||||
|         if (item.Sni.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("sni", item.Sni); |  | ||||||
|         } |  | ||||||
|         if (item.Alpn.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); |  | ||||||
|         } |  | ||||||
|         if (item.Path.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("obfs", "salamander"); |  | ||||||
|             dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path)); |  | ||||||
|         } |  | ||||||
|         dicQuery.Add("insecure", item.AllowInsecure.ToLower() == "true" ? "1" : "0"); |  | ||||||
|         if (item.Ports.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-'))); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,177 +0,0 @@ | ||||||
| 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.Security.Length == 0 || item.Id.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.Security}:{item.Id}", true); |  | ||||||
|         return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static readonly Regex UrlFinder = new(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled); |  | ||||||
|     private static readonly Regex DetailsParser = new(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\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.Security = details.Groups["method"].Value; |  | ||||||
|         item.Id = 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.Security = userInfoParts.First(); |  | ||||||
|             item.Id = 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.Security = userInfoParts.First(); |  | ||||||
|             item.Id = userInfoParts.Last(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var queryParameters = Utils.ParseQueryString(parsedUrl.Query); |  | ||||||
|         if (queryParameters["plugin"] != null) |  | ||||||
|         { |  | ||||||
|             //obfs-host exists |  | ||||||
|             var obfsHost = queryParameters["plugin"]?.Split(';').FirstOrDefault(t => t.Contains("obfs-host")); |  | ||||||
|             if (queryParameters["plugin"].Contains("obfs=http") && obfsHost.IsNotEmpty()) |  | ||||||
|             { |  | ||||||
|                 obfsHost = obfsHost?.Replace("obfs-host=", ""); |  | ||||||
|                 item.Network = Global.DefaultNetwork; |  | ||||||
|                 item.HeaderType = Global.TcpHeaderHttp; |  | ||||||
|                 item.RequestHost = obfsHost ?? ""; |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 return null; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return item; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static List<ProfileItem>? ResolveSip008(string result) |  | ||||||
|     { |  | ||||||
|         //SsSIP008 |  | ||||||
|         var lstSsServer = JsonUtils.Deserialize<List<SsServer>>(result); |  | ||||||
|         if (lstSsServer?.Count <= 0) |  | ||||||
|         { |  | ||||||
|             var ssSIP008 = JsonUtils.Deserialize<SsSIP008>(result); |  | ||||||
|             if (ssSIP008?.servers?.Count > 0) |  | ||||||
|             { |  | ||||||
|                 lstSsServer = ssSIP008.servers; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (lstSsServer?.Count > 0) |  | ||||||
|         { |  | ||||||
|             List<ProfileItem> lst = []; |  | ||||||
|             foreach (var it in lstSsServer) |  | ||||||
|             { |  | ||||||
|                 var ssItem = new ProfileItem() |  | ||||||
|                 { |  | ||||||
|                     Remarks = it.remarks, |  | ||||||
|                     Security = it.method, |  | ||||||
|                     Id = it.password, |  | ||||||
|                     Address = it.server, |  | ||||||
|                     Port = it.server_port.ToInt() |  | ||||||
|                 }; |  | ||||||
|                 lst.Add(ssItem); |  | ||||||
|             } |  | ||||||
|             return lst; |  | ||||||
|         } |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,47 +0,0 @@ | ||||||
| namespace ServiceLib.Handler.Fmt; |  | ||||||
| 
 |  | ||||||
| public class SingboxFmt : BaseFmt |  | ||||||
| { |  | ||||||
|     public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks) |  | ||||||
|     { |  | ||||||
|         var configObjects = JsonUtils.Deserialize<object[]>(strData); |  | ||||||
|         if (configObjects is not { Length: > 0 }) |  | ||||||
|         { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         List<ProfileItem> 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,114 +0,0 @@ | ||||||
| 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.Security}:{item.Id}", 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, result.Length - 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.Security = arr21.First(); |  | ||||||
|         item.Id = 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.Security = userInfoParts.First(); |  | ||||||
|             item.Id = userInfoParts[1]; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return item; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,47 +0,0 @@ | ||||||
| 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.Id = Utils.UrlDecode(url.UserInfo); |  | ||||||
| 
 |  | ||||||
|         var query = Utils.ParseQueryString(url.Query); |  | ||||||
|         _ = ResolveStdTransport(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<string, string>(); |  | ||||||
|         _ = GetStdTransport(item, null, ref dicQuery); |  | ||||||
| 
 |  | ||||||
|         return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Id, dicQuery, remark); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,63 +0,0 @@ | ||||||
| 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.Id = userInfoParts.First(); |  | ||||||
|             item.Security = userInfoParts.Last(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var query = Utils.ParseQueryString(url.Query); |  | ||||||
|         ResolveStdTransport(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<string, string>(); |  | ||||||
|         if (item.Sni.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("sni", item.Sni); |  | ||||||
|         } |  | ||||||
|         if (item.Alpn.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); |  | ||||||
|         } |  | ||||||
|         dicQuery.Add("congestion_control", item.HeaderType); |  | ||||||
| 
 |  | ||||||
|         return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,48 +0,0 @@ | ||||||
| namespace ServiceLib.Handler.Fmt; |  | ||||||
| 
 |  | ||||||
| public class V2rayFmt : BaseFmt |  | ||||||
| { |  | ||||||
|     public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks) |  | ||||||
|     { |  | ||||||
|         var configObjects = JsonUtils.Deserialize<object[]>(strData); |  | ||||||
|         if (configObjects is not { Length: > 0 }) |  | ||||||
|         { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         List<ProfileItem> 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,59 +0,0 @@ | ||||||
| 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, |  | ||||||
|             Security = Global.None |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         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.Id = Utils.UrlDecode(url.UserInfo); |  | ||||||
| 
 |  | ||||||
|         var query = Utils.ParseQueryString(url.Query); |  | ||||||
|         item.Security = GetQueryValue(query, "encryption", Global.None); |  | ||||||
|         item.StreamSecurity = GetQueryValue(query, "security"); |  | ||||||
|         _ = ResolveStdTransport(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<string, string>(); |  | ||||||
|         if (item.Security.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("encryption", item.Security); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("encryption", Global.None); |  | ||||||
|         } |  | ||||||
|         _ = GetStdTransport(item, Global.None, ref dicQuery); |  | ||||||
| 
 |  | ||||||
|         return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Id, dicQuery, remark); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,125 +0,0 @@ | ||||||
| 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 = item.ConfigVersion, |  | ||||||
|             ps = item.Remarks.TrimEx(), |  | ||||||
|             add = item.Address, |  | ||||||
|             port = item.Port, |  | ||||||
|             id = item.Id, |  | ||||||
|             aid = item.AlterId, |  | ||||||
|             scy = item.Security, |  | ||||||
|             net = item.Network, |  | ||||||
|             type = item.HeaderType, |  | ||||||
|             host = item.RequestHost, |  | ||||||
|             path = item.Path, |  | ||||||
|             tls = item.StreamSecurity, |  | ||||||
|             sni = item.Sni, |  | ||||||
|             alpn = item.Alpn, |  | ||||||
|             fp = item.Fingerprint |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         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<VmessQRCode>(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.Id = Utils.ToString(vmessQRCode.id); |  | ||||||
|         item.AlterId = vmessQRCode.aid; |  | ||||||
|         item.Security = Utils.ToString(vmessQRCode.scy); |  | ||||||
| 
 |  | ||||||
|         item.Security = vmessQRCode.scy.IsNotEmpty() ? vmessQRCode.scy : Global.DefaultSecurity; |  | ||||||
|         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); |  | ||||||
| 
 |  | ||||||
|         return item; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static ProfileItem? ResolveStdVmess(string str) |  | ||||||
|     { |  | ||||||
|         var item = new ProfileItem |  | ||||||
|         { |  | ||||||
|             ConfigType = EConfigType.VMess, |  | ||||||
|             Security = "auto" |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         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.Id = Utils.UrlDecode(url.UserInfo); |  | ||||||
| 
 |  | ||||||
|         var query = Utils.ParseQueryString(url.Query); |  | ||||||
|         ResolveStdTransport(query, ref item); |  | ||||||
| 
 |  | ||||||
|         return item; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,67 +0,0 @@ | ||||||
| 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.Id = Utils.UrlDecode(url.UserInfo); |  | ||||||
| 
 |  | ||||||
|         var query = Utils.ParseQueryString(url.Query); |  | ||||||
| 
 |  | ||||||
|         item.PublicKey = GetQueryDecoded(query, "publickey"); |  | ||||||
|         item.Path = GetQueryDecoded(query, "reserved"); |  | ||||||
|         item.RequestHost = GetQueryDecoded(query, "address"); |  | ||||||
|         item.ShortId = GetQueryDecoded(query, "mtu"); |  | ||||||
| 
 |  | ||||||
|         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<string, string>(); |  | ||||||
|         if (item.PublicKey.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("publickey", Utils.UrlEncode(item.PublicKey)); |  | ||||||
|         } |  | ||||||
|         if (item.Path.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("reserved", Utils.UrlEncode(item.Path)); |  | ||||||
|         } |  | ||||||
|         if (item.RequestHost.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("address", Utils.UrlEncode(item.RequestHost)); |  | ||||||
|         } |  | ||||||
|         if (item.ShortId.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             dicQuery.Add("mtu", Utils.UrlEncode(item.ShortId)); |  | ||||||
|         } |  | ||||||
|         return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Id, dicQuery, remark); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,221 +0,0 @@ | ||||||
| namespace ServiceLib.Handler; |  | ||||||
| 
 |  | ||||||
| public static class SubscriptionHandler |  | ||||||
| { |  | ||||||
|     public static async Task UpdateProcess(Config config, string subId, bool blProxy, Func<bool, string, Task> 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<bool, string, Task> updateFunc) |  | ||||||
|     { |  | ||||||
|         var downloadHandle = new DownloadService(); |  | ||||||
|         downloadHandle.Error += (sender2, args) => |  | ||||||
|         { |  | ||||||
|             updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}"); |  | ||||||
|         }; |  | ||||||
|         return downloadHandle; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static async Task<string> 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<string> 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<string> 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<string> 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<bool> ProcessDownloadResult(Config config, string id, string result, string hashCode, Func<bool, string, Task> 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,25 +0,0 @@ | ||||||
| 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<string> args = ["manual", host, port.ToString(), exceptions]; |  | ||||||
|         await ExecCmd(args); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static async Task UnsetProxy() |  | ||||||
|     { |  | ||||||
|         List<string> args = ["none"]; |  | ||||||
|         await ExecCmd(args); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static async Task ExecCmd(List<string> args) |  | ||||||
|     { |  | ||||||
|         var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false); |  | ||||||
| 
 |  | ||||||
|         await Utils.GetCliWrapOutput(fileName, args); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,30 +0,0 @@ | ||||||
| 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<string> args = ["set", host, port.ToString()]; |  | ||||||
|         if (exceptions.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             args.AddRange(exceptions.Split(',')); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         await ExecCmd(args); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static async Task UnsetProxy() |  | ||||||
|     { |  | ||||||
|         List<string> args = ["clear"]; |  | ||||||
|         await ExecCmd(args); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static async Task ExecCmd(List<string> args) |  | ||||||
|     { |  | ||||||
|         var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName), false); |  | ||||||
| 
 |  | ||||||
|         await Utils.GetCliWrapOutput(fileName, args); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,358 +0,0 @@ | ||||||
| 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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     // set to use no proxy |  | ||||||
|     /// </summary> |  | ||||||
|     /// <exception cref="ApplicationException">Error message with win32 error code</exception> |  | ||||||
|     public static bool UnsetProxy() |  | ||||||
|     { |  | ||||||
|         return SetProxy(null, null, 1); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Set system proxy settings |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="strProxy"> proxy address</param> |  | ||||||
|     /// <param name="exceptions">exception addresses that do not use proxy</param> |  | ||||||
|     /// <param name="type">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 |  | ||||||
|     /// </param> |  | ||||||
|     /// <exception cref="ApplicationException">Error message with win32 error code</exception> |  | ||||||
|     /// <returns>true: one of connection is successfully updated proxy settings</returns> |  | ||||||
|     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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// Retrieve list of connections including LAN and WAN to support PPPoE connection |  | ||||||
|     /// </summary> |  | ||||||
|     /// <returns>A list of RAS connection names. May be empty list if no dial up connection.</returns> |  | ||||||
|     /// <exception cref="ApplicationException">Error message with win32 error code</exception> |  | ||||||
|     private static IEnumerable<string> 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<string>(); |  | ||||||
|             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 |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,98 +0,0 @@ | ||||||
| namespace ServiceLib.Handler.SysProxy; |  | ||||||
| 
 |  | ||||||
| public static class SysProxyHandler |  | ||||||
| { |  | ||||||
|     private static readonly string _tag = "SysProxyHandler"; |  | ||||||
| 
 |  | ||||||
|     public static async Task<bool> 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.IsOSX(): |  | ||||||
|                     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.IsOSX(): |  | ||||||
|                     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 = $"<local>;{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(Utils.GetConfigPath(), port, portPac); |  | ||||||
|         var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}"; |  | ||||||
|         ProxySettingWindows.SetProxy(strProxy, "", 4); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,179 +0,0 @@ | ||||||
| using Downloader; |  | ||||||
| 
 |  | ||||||
| namespace ServiceLib.Helper; |  | ||||||
| 
 |  | ||||||
| public class DownloaderHelper |  | ||||||
| { |  | ||||||
|     private static readonly Lazy<DownloaderHelper> _instance = new(() => new()); |  | ||||||
|     public static DownloaderHelper Instance => _instance.Value; |  | ||||||
| 
 |  | ||||||
|     public async Task<string?> 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() |  | ||||||
|         { |  | ||||||
|             Timeout = timeout * 1000, |  | ||||||
|             MaxTryAgainOnFailure = 2, |  | ||||||
|             RequestConfiguration = |  | ||||||
|                 { |  | ||||||
|                     Headers = headers, |  | ||||||
|                     UserAgent = userAgent, |  | ||||||
|                     Timeout = 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<string> progress, int timeout) |  | ||||||
|     { |  | ||||||
|         if (url.IsNullOrEmpty()) |  | ||||||
|         { |  | ||||||
|             throw new ArgumentNullException(nameof(url)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var downloadOpt = new DownloadConfiguration() |  | ||||||
|         { |  | ||||||
|             Timeout = timeout * 1000, |  | ||||||
|             MaxTryAgainOnFailure = 2, |  | ||||||
|             RequestConfiguration = |  | ||||||
|                 { |  | ||||||
|                     Timeout= timeout * 1000, |  | ||||||
|                     Proxy = webProxy |  | ||||||
|                 } |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         var totalDatetime = DateTime.Now; |  | ||||||
|         var totalSecond = 0; |  | ||||||
|         var hasValue = false; |  | ||||||
|         double maxSpeed = 0; |  | ||||||
|         await using var downloader = new Downloader.DownloadService(downloadOpt); |  | ||||||
|         //downloader.DownloadStarted += (sender, value) => |  | ||||||
|         //{ |  | ||||||
|         //    if (progress != null) |  | ||||||
|         //    { |  | ||||||
|         //        progress.Report("Start download data..."); |  | ||||||
|         //    } |  | ||||||
|         //}; |  | ||||||
|         downloader.DownloadProgressChanged += (sender, value) => |  | ||||||
|         { |  | ||||||
|             var ts = DateTime.Now - totalDatetime; |  | ||||||
|             if (progress != null && ts.Seconds > totalSecond) |  | ||||||
|             { |  | ||||||
|                 hasValue = true; |  | ||||||
|                 totalSecond = ts.Seconds; |  | ||||||
|                 if (value.BytesPerSecondSpeed > maxSpeed) |  | ||||||
|                 { |  | ||||||
|                     maxSpeed = value.BytesPerSecondSpeed; |  | ||||||
|                     var speed = (maxSpeed / 1000 / 1000).ToString("#0.0"); |  | ||||||
|                     progress.Report(speed); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|         downloader.DownloadFileCompleted += (sender, value) => |  | ||||||
|         { |  | ||||||
|             if (progress != null) |  | ||||||
|             { |  | ||||||
|                 if (!hasValue && value.Error != null) |  | ||||||
|                 { |  | ||||||
|                     progress.Report(value.Error?.Message); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|         //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<double> 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() |  | ||||||
|         { |  | ||||||
|             Timeout = timeout * 1000, |  | ||||||
|             MaxTryAgainOnFailure = 2, |  | ||||||
|             RequestConfiguration = |  | ||||||
|                 { |  | ||||||
|                     Timeout= 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,235 +0,0 @@ | ||||||
| using System.Net.Http.Headers; |  | ||||||
| using System.Net.Mime; |  | ||||||
| 
 |  | ||||||
| namespace ServiceLib.Helper; |  | ||||||
| 
 |  | ||||||
| /// <summary> |  | ||||||
| /// </summary> |  | ||||||
| public class HttpClientHelper |  | ||||||
| { |  | ||||||
|     private static readonly Lazy<HttpClientHelper> _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<string?> 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<string?> GetAsync(string url) |  | ||||||
|     { |  | ||||||
|         if (url.IsNullOrEmpty()) |  | ||||||
|         { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         return await httpClient.GetStringAsync(url); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<string?> GetAsync(HttpClient client, string url, CancellationToken token = default) |  | ||||||
|     { |  | ||||||
|         if (url.IsNullOrEmpty()) |  | ||||||
|         { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         return await client.GetStringAsync(url, token); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task PutAsync(string url, Dictionary<string, string> 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<string, string> 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); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static async Task DownloadFileAsync(HttpClient client, string url, string fileName, IProgress<double>? progress, CancellationToken token = default) |  | ||||||
|     { |  | ||||||
|         ArgumentNullException.ThrowIfNull(url); |  | ||||||
|         ArgumentNullException.ThrowIfNull(fileName); |  | ||||||
|         if (File.Exists(fileName)) |  | ||||||
|         { |  | ||||||
|             File.Delete(fileName); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); |  | ||||||
| 
 |  | ||||||
|         if (!response.IsSuccessStatusCode) |  | ||||||
|         { |  | ||||||
|             throw new Exception(response.StatusCode.ToString()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var total = response.Content.Headers.ContentLength ?? -1L; |  | ||||||
|         var canReportProgress = total != -1 && progress != null; |  | ||||||
| 
 |  | ||||||
|         await using var stream = await response.Content.ReadAsStreamAsync(token); |  | ||||||
|         await using var file = File.Create(fileName); |  | ||||||
|         var totalRead = 0L; |  | ||||||
|         var buffer = new byte[1024 * 1024]; |  | ||||||
|         var progressPercentage = 0; |  | ||||||
| 
 |  | ||||||
|         while (true) |  | ||||||
|         { |  | ||||||
|             token.ThrowIfCancellationRequested(); |  | ||||||
| 
 |  | ||||||
|             var read = await stream.ReadAsync(buffer, token); |  | ||||||
|             totalRead += read; |  | ||||||
| 
 |  | ||||||
|             if (read == 0) |  | ||||||
|             { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             await file.WriteAsync(buffer.AsMemory(0, read), token); |  | ||||||
| 
 |  | ||||||
|             if (canReportProgress) |  | ||||||
|             { |  | ||||||
|                 var percent = (int)(100.0 * totalRead / total); |  | ||||||
|                 //if (progressPercentage != percent && percent % 10 == 0) |  | ||||||
|                 { |  | ||||||
|                     progressPercentage = percent; |  | ||||||
|                     progress?.Report(percent); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (canReportProgress) |  | ||||||
|         { |  | ||||||
|             progress?.Report(101); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task DownloadDataAsync4Speed(HttpClient client, string url, IProgress<string> progress, CancellationToken token = default) |  | ||||||
|     { |  | ||||||
|         if (url.IsNullOrEmpty()) |  | ||||||
|         { |  | ||||||
|             throw new ArgumentNullException(nameof(url)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); |  | ||||||
| 
 |  | ||||||
|         if (!response.IsSuccessStatusCode) |  | ||||||
|         { |  | ||||||
|             throw new Exception(response.StatusCode.ToString()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         //var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L; |  | ||||||
|         //var canReportProgress = total != -1 && progress != null; |  | ||||||
| 
 |  | ||||||
|         await using var stream = await response.Content.ReadAsStreamAsync(token); |  | ||||||
|         var totalRead = 0L; |  | ||||||
|         var buffer = new byte[1024 * 64]; |  | ||||||
|         var isMoreToRead = true; |  | ||||||
|         var progressSpeed = string.Empty; |  | ||||||
|         var totalDatetime = DateTime.Now; |  | ||||||
|         var totalSecond = 0; |  | ||||||
| 
 |  | ||||||
|         do |  | ||||||
|         { |  | ||||||
|             if (token.IsCancellationRequested) |  | ||||||
|             { |  | ||||||
|                 if (totalRead > 0) |  | ||||||
|                 { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     token.ThrowIfCancellationRequested(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             var read = await stream.ReadAsync(buffer, token); |  | ||||||
| 
 |  | ||||||
|             if (read == 0) |  | ||||||
|             { |  | ||||||
|                 isMoreToRead = false; |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 var data = new byte[read]; |  | ||||||
|                 buffer.ToList().CopyTo(0, data, 0, read); |  | ||||||
| 
 |  | ||||||
|                 totalRead += read; |  | ||||||
| 
 |  | ||||||
|                 var ts = DateTime.Now - totalDatetime; |  | ||||||
|                 if (progress != null && ts.Seconds > totalSecond) |  | ||||||
|                 { |  | ||||||
|                     totalSecond = ts.Seconds; |  | ||||||
|                     var speed = (totalRead * 1d / ts.TotalMilliseconds / 1000).ToString("#0.0"); |  | ||||||
|                     if (progressSpeed != speed) |  | ||||||
|                     { |  | ||||||
|                         progressSpeed = speed; |  | ||||||
|                         progress.Report(speed); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } while (isMoreToRead); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<int> 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<int> 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 //(Exception ex) |  | ||||||
|         { |  | ||||||
|             //Utile.SaveLog(ex.Message, ex); |  | ||||||
|         } |  | ||||||
|         return responseTime; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,89 +0,0 @@ | ||||||
| using System.Collections; |  | ||||||
| 
 |  | ||||||
| namespace ServiceLib.Helper; |  | ||||||
| 
 |  | ||||||
| public sealed class SQLiteHelper |  | ||||||
| { |  | ||||||
|     private static readonly Lazy<SQLiteHelper> _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<T>() |  | ||||||
|     { |  | ||||||
|         return _db.CreateTable<T>(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<int> InsertAllAsync(IEnumerable models) |  | ||||||
|     { |  | ||||||
|         return await _dbAsync.InsertAllAsync(models, runInTransaction: true).ConfigureAwait(false); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<int> InsertAsync(object model) |  | ||||||
|     { |  | ||||||
|         return await _dbAsync.InsertAsync(model); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<int> ReplaceAsync(object model) |  | ||||||
|     { |  | ||||||
|         return await _dbAsync.InsertOrReplaceAsync(model); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<int> UpdateAsync(object model) |  | ||||||
|     { |  | ||||||
|         return await _dbAsync.UpdateAsync(model); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<int> UpdateAllAsync(IEnumerable models) |  | ||||||
|     { |  | ||||||
|         return await _dbAsync.UpdateAllAsync(models, runInTransaction: true).ConfigureAwait(false); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<int> DeleteAsync(object model) |  | ||||||
|     { |  | ||||||
|         return await _dbAsync.DeleteAsync(model); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<int> DeleteAllAsync<T>() |  | ||||||
|     { |  | ||||||
|         return await _dbAsync.DeleteAllAsync<T>(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<int> ExecuteAsync(string sql) |  | ||||||
|     { |  | ||||||
|         return await _dbAsync.ExecuteAsync(sql); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<List<T>> QueryAsync<T>(string sql) where T : new() |  | ||||||
|     { |  | ||||||
|         return await _dbAsync.QueryAsync<T>(sql); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public AsyncTableQuery<T> TableAsync<T>() where T : new() |  | ||||||
|     { |  | ||||||
|         return _dbAsync.Table<T>(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task DisposeDbConnectionAsync() |  | ||||||
|     { |  | ||||||
|         await Task.Factory.StartNew(() => |  | ||||||
|         { |  | ||||||
|             _db?.Close(); |  | ||||||
|             _db?.Dispose(); |  | ||||||
|             _db = null; |  | ||||||
| 
 |  | ||||||
|             _dbAsync?.GetConnection()?.Close(); |  | ||||||
|             _dbAsync?.GetConnection()?.Dispose(); |  | ||||||
|             _dbAsync = null; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,282 +0,0 @@ | ||||||
| namespace ServiceLib.Manager; |  | ||||||
| 
 |  | ||||||
| /// <summary> |  | ||||||
| /// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.). |  | ||||||
| /// </summary> |  | ||||||
| public class ActionPrecheckManager(Config config) |  | ||||||
| { |  | ||||||
|     private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config)); |  | ||||||
|     public static ActionPrecheckManager Instance => _instance.Value; |  | ||||||
| 
 |  | ||||||
|     private readonly Config _config = config; |  | ||||||
| 
 |  | ||||||
|     public async Task<List<string>> Check(string? indexId) |  | ||||||
|     { |  | ||||||
|         if (indexId.IsNullOrEmpty()) |  | ||||||
|         { |  | ||||||
|             return [ResUI.PleaseSelectServer]; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var item = await AppManager.Instance.GetProfileItem(indexId); |  | ||||||
|         if (item is null) |  | ||||||
|         { |  | ||||||
|             return [ResUI.PleaseSelectServer]; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return await Check(item); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<List<string>> Check(ProfileItem? item) |  | ||||||
|     { |  | ||||||
|         if (item is null) |  | ||||||
|         { |  | ||||||
|             return [ResUI.PleaseSelectServer]; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var errors = new List<string>(); |  | ||||||
| 
 |  | ||||||
|         errors.AddRange(await ValidateCurrentNodeAndCoreSupport(item)); |  | ||||||
|         errors.AddRange(await ValidateRelatedNodesExistAndValid(item)); |  | ||||||
| 
 |  | ||||||
|         return errors; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private async Task<List<string>> ValidateCurrentNodeAndCoreSupport(ProfileItem item) |  | ||||||
|     { |  | ||||||
|         if (item.ConfigType == EConfigType.Custom) |  | ||||||
|         { |  | ||||||
|             return []; |  | ||||||
|         } |  | ||||||
|         var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType); |  | ||||||
|         return await ValidateNodeAndCoreSupport(item, coreType); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private async Task<List<string>> ValidateNodeAndCoreSupport(ProfileItem item, ECoreType? coreType = null) |  | ||||||
|     { |  | ||||||
|         var errors = new List<string>(); |  | ||||||
| 
 |  | ||||||
|         coreType ??= AppManager.Instance.GetCoreType(item, item.ConfigType); |  | ||||||
| 
 |  | ||||||
|         if (item.ConfigType is EConfigType.Custom) |  | ||||||
|         { |  | ||||||
|             errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString())); |  | ||||||
|             return errors; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (!item.IsComplex()) |  | ||||||
|         { |  | ||||||
|             if (item.Address.IsNullOrEmpty()) |  | ||||||
|             { |  | ||||||
|                 errors.Add(string.Format(ResUI.InvalidProperty, "Address")); |  | ||||||
|                 return errors; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (item.Port is <= 0 or >= 65536) |  | ||||||
|             { |  | ||||||
|                 errors.Add(string.Format(ResUI.InvalidProperty, "Port")); |  | ||||||
|                 return errors; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             switch (item.ConfigType) |  | ||||||
|             { |  | ||||||
|                 case EConfigType.VMess: |  | ||||||
|                     if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) |  | ||||||
|                         errors.Add(string.Format(ResUI.InvalidProperty, "Id")); |  | ||||||
|                     break; |  | ||||||
| 
 |  | ||||||
|                 case EConfigType.VLESS: |  | ||||||
|                     if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id) && item.Id.Length > 30) |  | ||||||
|                         errors.Add(string.Format(ResUI.InvalidProperty, "Id")); |  | ||||||
|                     if (!Global.Flows.Contains(item.Flow)) |  | ||||||
|                         errors.Add(string.Format(ResUI.InvalidProperty, "Flow")); |  | ||||||
|                     break; |  | ||||||
| 
 |  | ||||||
|                 case EConfigType.Shadowsocks: |  | ||||||
|                     if (item.Id.IsNullOrEmpty()) |  | ||||||
|                         errors.Add(string.Format(ResUI.InvalidProperty, "Id")); |  | ||||||
|                     if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security)) |  | ||||||
|                         errors.Add(string.Format(ResUI.InvalidProperty, "Security")); |  | ||||||
|                     break; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan |  | ||||||
|                 && item.StreamSecurity == Global.StreamSecurityReality |  | ||||||
|                 && item.PublicKey.IsNullOrEmpty()) |  | ||||||
|             { |  | ||||||
|                 errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey")); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (errors.Count > 0) |  | ||||||
|             { |  | ||||||
|                 return errors; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (item.ConfigType.IsGroupType()) |  | ||||||
|         { |  | ||||||
|             ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group); |  | ||||||
|             if (group is null || group.ChildItems.IsNullOrEmpty()) |  | ||||||
|             { |  | ||||||
|                 errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks)); |  | ||||||
|                 return errors; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId); |  | ||||||
|             if (hasCycle) |  | ||||||
|             { |  | ||||||
|                 errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks)); |  | ||||||
|                 return errors; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             foreach (var child in Utils.String2List(group.ChildItems)) |  | ||||||
|             { |  | ||||||
|                 var childErrors = new List<string>(); |  | ||||||
|                 if (child.IsNullOrEmpty()) |  | ||||||
|                 { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 var childItem = await AppManager.Instance.GetProfileItem(child); |  | ||||||
|                 if (childItem is null) |  | ||||||
|                 { |  | ||||||
|                     childErrors.Add(string.Format(ResUI.NodeTagNotExist, child)); |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain) |  | ||||||
|                 { |  | ||||||
|                     childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks)); |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType)); |  | ||||||
|                 errors.AddRange(childErrors); |  | ||||||
|             } |  | ||||||
|             return errors; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var net = item.GetNetwork() ?? item.Network; |  | ||||||
| 
 |  | ||||||
|         if (coreType == ECoreType.sing_box) |  | ||||||
|         { |  | ||||||
|             // sing-box does not support xhttp / kcp |  | ||||||
|             // sing-box does not support transports like ws/http/httpupgrade/etc. when the node is not vmess/trojan/vless |  | ||||||
|             if (net is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) |  | ||||||
|             { |  | ||||||
|                 errors.Add(string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net)); |  | ||||||
|                 return errors; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (item.ConfigType is not (EConfigType.VMess or EConfigType.VLESS or EConfigType.Trojan)) |  | ||||||
|             { |  | ||||||
|                 if (net is nameof(ETransport.ws) or nameof(ETransport.http) or nameof(ETransport.h2) or nameof(ETransport.quic) or nameof(ETransport.httpupgrade)) |  | ||||||
|                 { |  | ||||||
|                     errors.Add(string.Format(ResUI.CoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), item.ConfigType.ToString(), net)); |  | ||||||
|                     return errors; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else if (coreType is ECoreType.Xray) |  | ||||||
|         { |  | ||||||
|             // Xray core does not support these protocols |  | ||||||
|             if (!Global.XraySupportConfigType.Contains(item.ConfigType) |  | ||||||
|                 && !item.IsComplex()) |  | ||||||
|             { |  | ||||||
|                 errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString())); |  | ||||||
|                 return errors; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return errors; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private async Task<List<string>> ValidateRelatedNodesExistAndValid(ProfileItem? item) |  | ||||||
|     { |  | ||||||
|         var errors = new List<string>(); |  | ||||||
|         errors.AddRange(await ValidateProxyChainedNodeExistAndValid(item)); |  | ||||||
|         errors.AddRange(await ValidateRoutingNodeExistAndValid(item)); |  | ||||||
|         return errors; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private async Task<List<string>> ValidateProxyChainedNodeExistAndValid(ProfileItem? item) |  | ||||||
|     { |  | ||||||
|         var errors = new List<string>(); |  | ||||||
|         if (item is null) |  | ||||||
|         { |  | ||||||
|             return errors; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // prev node and next node |  | ||||||
|         var subItem = await AppManager.Instance.GetSubItem(item.Subid); |  | ||||||
|         if (subItem is null) |  | ||||||
|         { |  | ||||||
|             return errors; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); |  | ||||||
|         var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); |  | ||||||
|         var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType); |  | ||||||
| 
 |  | ||||||
|         await CollectProxyChainedNodeValidation(prevNode, subItem.PrevProfile, coreType, errors); |  | ||||||
|         await CollectProxyChainedNodeValidation(nextNode, subItem.NextProfile, coreType, errors); |  | ||||||
| 
 |  | ||||||
|         return errors; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private async Task CollectProxyChainedNodeValidation(ProfileItem? node, string tag, ECoreType coreType, List<string> errors) |  | ||||||
|     { |  | ||||||
|         if (node is not null) |  | ||||||
|         { |  | ||||||
|             var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType); |  | ||||||
|             errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s)); |  | ||||||
|         } |  | ||||||
|         else if (tag.IsNotEmpty()) |  | ||||||
|         { |  | ||||||
|             errors.Add(ResUI.ProxyChainedPrefix + string.Format(ResUI.NodeTagNotExist, tag)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private async Task<List<string>> ValidateRoutingNodeExistAndValid(ProfileItem? item) |  | ||||||
|     { |  | ||||||
|         var errors = new List<string>(); |  | ||||||
| 
 |  | ||||||
|         if (item is null) |  | ||||||
|         { |  | ||||||
|             return errors; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType); |  | ||||||
|         var routing = await ConfigHandler.GetDefaultRouting(_config); |  | ||||||
|         if (routing == null) |  | ||||||
|         { |  | ||||||
|             return errors; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet); |  | ||||||
|         foreach (var ruleItem in rules ?? []) |  | ||||||
|         { |  | ||||||
|             if (!ruleItem.Enabled) |  | ||||||
|             { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             var outboundTag = ruleItem.OutboundTag; |  | ||||||
|             if (outboundTag.IsNullOrEmpty() || Global.OutboundTags.Contains(outboundTag)) |  | ||||||
|             { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             var tagItem = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); |  | ||||||
|             if (tagItem is null) |  | ||||||
|             { |  | ||||||
|                 errors.Add(ResUI.RoutingRuleOutboundPrefix + string.Format(ResUI.NodeTagNotExist, outboundTag)); |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType); |  | ||||||
|             errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return errors; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,283 +0,0 @@ | ||||||
| namespace ServiceLib.Manager; |  | ||||||
| 
 |  | ||||||
| public sealed class AppManager |  | ||||||
| { |  | ||||||
|     #region Property |  | ||||||
| 
 |  | ||||||
|     private static readonly Lazy<AppManager> _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; } |  | ||||||
| 
 |  | ||||||
|     #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<SubItem>(); |  | ||||||
|         SQLiteHelper.Instance.CreateTable<ProfileItem>(); |  | ||||||
|         SQLiteHelper.Instance.CreateTable<ServerStatItem>(); |  | ||||||
|         SQLiteHelper.Instance.CreateTable<RoutingItem>(); |  | ||||||
|         SQLiteHelper.Instance.CreateTable<ProfileExItem>(); |  | ||||||
|         SQLiteHelper.Instance.CreateTable<DNSItem>(); |  | ||||||
|         SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>(); |  | ||||||
|         SQLiteHelper.Instance.CreateTable<ProfileGroupItem>(); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public bool InitComponents() |  | ||||||
|     { |  | ||||||
|         Logging.SaveLog($"v2rayN start up | {Utils.GetRuntimeInfo()}"); |  | ||||||
|         Logging.LoggingEnabled(_config.GuiItem.EnableLog); |  | ||||||
| 
 |  | ||||||
|         //First determine the port value |  | ||||||
|         _ = StatePort; |  | ||||||
|         _ = StatePort2; |  | ||||||
| 
 |  | ||||||
|         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<List<SubItem>?> SubItems() |  | ||||||
|     { |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<SubItem>().OrderBy(t => t.Sort).ToListAsync(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<SubItem?> GetSubItem(string? subid) |  | ||||||
|     { |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<SubItem>().FirstOrDefaultAsync(t => t.Id == subid); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<List<ProfileItem>?> ProfileItems(string subid) |  | ||||||
|     { |  | ||||||
|         if (subid.IsNullOrEmpty()) |  | ||||||
|         { |  | ||||||
|             return await SQLiteHelper.Instance.TableAsync<ProfileItem>().ToListAsync(); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             return await SQLiteHelper.Instance.TableAsync<ProfileItem>().Where(t => t.Subid == subid).ToListAsync(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<List<string>?> ProfileItemIndexes(string subid) |  | ||||||
|     { |  | ||||||
|         return (await ProfileItems(subid))?.Select(t => t.IndexId)?.ToList(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<List<ProfileItemModel>?> ProfileItems(string subid, string filter) |  | ||||||
|     { |  | ||||||
|         var sql = @$"select a.*
 |  | ||||||
|                            ,b.remarks 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<ProfileItemModel>(sql); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<ProfileItem?> GetProfileItem(string indexId) |  | ||||||
|     { |  | ||||||
|         if (indexId.IsNullOrEmpty()) |  | ||||||
|         { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.IndexId == indexId); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks) |  | ||||||
|     { |  | ||||||
|         if (remarks.IsNullOrEmpty()) |  | ||||||
|         { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<ProfileGroupItem?> GetProfileGroupItem(string indexId) |  | ||||||
|     { |  | ||||||
|         if (indexId.IsNullOrEmpty()) |  | ||||||
|         { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().FirstOrDefaultAsync(it => it.IndexId == indexId); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<List<RoutingItem>?> RoutingItems() |  | ||||||
|     { |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<RoutingItem?> GetRoutingItem(string id) |  | ||||||
|     { |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<RoutingItem>().FirstOrDefaultAsync(it => it.Id == id); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<List<DNSItem>?> DNSItems() |  | ||||||
|     { |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<DNSItem>().ToListAsync(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<DNSItem?> GetDNSItem(ECoreType eCoreType) |  | ||||||
|     { |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<DNSItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<List<FullConfigTemplateItem>?> FullConfigTemplateItem() |  | ||||||
|     { |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().ToListAsync(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<FullConfigTemplateItem?> GetFullConfigTemplateItem(ECoreType eCoreType) |  | ||||||
|     { |  | ||||||
|         return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #endregion SqliteHelper |  | ||||||
| 
 |  | ||||||
|     #region Core Type |  | ||||||
| 
 |  | ||||||
|     public List<string> 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 |  | ||||||
| } |  | ||||||
|  | @ -1,187 +0,0 @@ | ||||||
| using static ServiceLib.Models.ClashProxies; |  | ||||||
| 
 |  | ||||||
| namespace ServiceLib.Manager; |  | ||||||
| 
 |  | ||||||
| public sealed class ClashApiManager |  | ||||||
| { |  | ||||||
|     private static readonly Lazy<ClashApiManager> instance = new(() => new()); |  | ||||||
|     public static ClashApiManager Instance => instance.Value; |  | ||||||
| 
 |  | ||||||
|     private static readonly string _tag = "ClashApiHandler"; |  | ||||||
|     private Dictionary<string, ProxiesItem>? _proxies; |  | ||||||
|     public Dictionary<string, object> ProfileContent { get; set; } |  | ||||||
| 
 |  | ||||||
|     public async Task<Tuple<ClashProxies, ClashProviders>?> GetClashProxiesAsync() |  | ||||||
|     { |  | ||||||
|         for (var i = 0; i < 3; i++) |  | ||||||
|         { |  | ||||||
|             var url = $"{GetApiUrl()}/proxies"; |  | ||||||
|             var result = await HttpClientHelper.Instance.TryGetAsync(url); |  | ||||||
|             var clashProxies = JsonUtils.Deserialize<ClashProxies>(result); |  | ||||||
| 
 |  | ||||||
|             var url2 = $"{GetApiUrl()}/providers/proxies"; |  | ||||||
|             var result2 = await HttpClientHelper.Instance.TryGetAsync(url2); |  | ||||||
|             var clashProviders = JsonUtils.Deserialize<ClashProviders>(result2); |  | ||||||
| 
 |  | ||||||
|             if (clashProxies != null || clashProviders != null) |  | ||||||
|             { |  | ||||||
|                 _proxies = clashProxies?.proxies; |  | ||||||
|                 return new Tuple<ClashProxies, ClashProviders>(clashProxies, clashProviders); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             await Task.Delay(2000); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Func<ClashProxyModel?, string, Task> updateFunc) |  | ||||||
|     { |  | ||||||
|         Task.Run(async () => |  | ||||||
|         { |  | ||||||
|             if (blAll) |  | ||||||
|             { |  | ||||||
|                 if (_proxies == null) |  | ||||||
|                 { |  | ||||||
|                     await GetClashProxiesAsync(); |  | ||||||
|                 } |  | ||||||
|                 lstProxy = new List<ClashProxyModel>(); |  | ||||||
|                 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<Task>(); |  | ||||||
|             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<ProxiesItem>? GetClashProxyGroups() |  | ||||||
|     { |  | ||||||
|         try |  | ||||||
|         { |  | ||||||
|             var fileContent = ProfileContent; |  | ||||||
|             if (fileContent is null || fileContent?.ContainsKey("proxy-groups") == false) |  | ||||||
|             { |  | ||||||
|                 return null; |  | ||||||
|             } |  | ||||||
|             return JsonUtils.Deserialize<List<ProxiesItem>>(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<string, string>(); |  | ||||||
|             headers.Add("name", nameNode); |  | ||||||
|             await HttpClientHelper.Instance.PutAsync(url, headers); |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             Logging.SaveLog(_tag, ex); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task ClashConfigUpdate(Dictionary<string, string> 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<string, string>(); |  | ||||||
|             headers.Add("path", filePath); |  | ||||||
|             await HttpClientHelper.Instance.PutAsync(url, headers); |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             Logging.SaveLog(_tag, ex); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async Task<ClashConnections?> GetClashConnectionsAsync() |  | ||||||
|     { |  | ||||||
|         try |  | ||||||
|         { |  | ||||||
|             var url = $"{GetApiUrl()}/connections"; |  | ||||||
|             var result = await HttpClientHelper.Instance.TryGetAsync(url); |  | ||||||
|             var clashConnections = JsonUtils.Deserialize<ClashConnections>(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}"; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
		Reference in a new issue