first commit

This commit is contained in:
sp0937 2024-07-20 16:54:25 +08:00
parent ad1a1f8015
commit 50356b1ee2
136 changed files with 32772 additions and 0 deletions

63
v2rayMiniConsole/.gitattributes vendored Normal file
View file

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

363
v2rayMiniConsole/.gitignore vendored Normal file
View 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

View 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)
{
}
}
}
}

View 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>

View 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 = &apos;__PROXY__&apos;;
///var rules = [
/// [
/// [],
/// []
/// ],
/// [
/// [
/// &quot;aftygh.gov.tw&quot;,
/// &quot;aide.gov.tw&quot;,
/// &quot;aliyun.com&quot;,
/// &quot;arte.gov.tw&quot;,
/// &quot;baidu.com&quot;,
/// &quot;chinaso.com&quot;,
/// &quot;chinaz.com&quot;,
/// &quot;chukuang.gov.tw&quot;,
/// &quot;cycab.gov.tw&quot;,
/// &quot;dbnsa.gov.tw&quot;,
/// &quot;df.gov.tw&quot;,
/// &quot;eastcoast-nsa.gov.tw&quot;,
/// &quot;erv-nsa.gov.tw&quot;,
/// &quot;grb.gov.tw&quot;,
/// &quot;haosou.com&quot;,
/// [字符串的其余部分被截断]&quot;; 的本地化字符串。
/// </summary>
internal static string pac {
get {
return ResourceManager.GetString("pac", resourceCulture);
}
}
}
}

View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="pac" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\pac.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312</value>
</data>
</root>

File diff suppressed because it is too large Load diff

View 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.27.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.63.0" />
<PackageReference Include="Grpc.Tools" Version="2.64.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View 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 {}

View file

@ -0,0 +1,13 @@
using ProtosLib.Statistics;
namespace ProtosLib
{
public class Tests
{
private StatsService.StatsServiceClient client_;
public Tests()
{
}
}
}

View file

@ -0,0 +1,39 @@
param (
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]
$OutputPath = '.\bin\v2rayMiniConsole'
)
Write-Host 'Building'
dotnet publish `
.\v2rayMiniConsole\v2rayMiniConsole.csproj `
-c Release `
--self-contained false `
-p:PublishReadyToRun=true `
-p:PublishSingleFile=true `
-o $OutputPath
dotnet publish `
.\v2rayUpgrade\v2rayUpgrade.csproj `
-c Release `
--self-contained false `
-p:PublishReadyToRun=true `
-p:PublishSingleFile=true `
-o $OutputPath
if ( -Not $? ) {
exit $lastExitCode
}
if ( Test-Path -Path .\bin\v2rayMiniConsole ) {
rm -Force "$OutputPath\*.pdb"
rm -Force "$OutputPath\*.xml"
}
Write-Host 'Build done'
ls $OutputPath
7z.exe a v2rayMiniConsole.zip $OutputPath
exit 0

View file

@ -0,0 +1,42 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34511.84
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "v2rayMiniConsole", "v2rayMiniConsole\v2rayMiniConsole.csproj", "{C8323215-8D45-4358-84EA-C4EA3257A72E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PacLib", "PacLib\PacLib.csproj", "{42DAD4B6-7B75-4924-85DA-4BF143F11278}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProtosLib", "ProtosLib\ProtosLib.csproj", "{B8338E4D-728C-4BC8-9E4C-5707903B6DDD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{A915ED0D-4CA4-405C-A342-D7AD4DF567D3}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C8323215-8D45-4358-84EA-C4EA3257A72E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8323215-8D45-4358-84EA-C4EA3257A72E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8323215-8D45-4358-84EA-C4EA3257A72E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8323215-8D45-4358-84EA-C4EA3257A72E}.Release|Any CPU.Build.0 = Release|Any CPU
{42DAD4B6-7B75-4924-85DA-4BF143F11278}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42DAD4B6-7B75-4924-85DA-4BF143F11278}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42DAD4B6-7B75-4924-85DA-4BF143F11278}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42DAD4B6-7B75-4924-85DA-4BF143F11278}.Release|Any CPU.Build.0 = Release|Any CPU
{B8338E4D-728C-4BC8-9E4C-5707903B6DDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8338E4D-728C-4BC8-9E4C-5707903B6DDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8338E4D-728C-4BC8-9E4C-5707903B6DDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8338E4D-728C-4BC8-9E4C-5707903B6DDD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {19B3D25E-4C3A-4FAC-A76F-BFBD2EF88ECA}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,186 @@
using Downloader;
using System.IO;
using System.Net;
namespace v2rayN
{
internal 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 (Utils.IsNullOrEmpty(url))
{
return null;
}
Uri uri = new(url);
//Authorization Header
var headers = new WebHeaderCollection();
if (!Utils.IsNullOrEmpty(uri.UserInfo))
{
headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo));
}
var downloadOpt = new DownloadConfiguration()
{
Timeout = timeout * 1000,
MaxTryAgainOnFailover = 2,
RequestConfiguration =
{
Headers = headers,
UserAgent = userAgent,
Timeout = timeout * 1000,
Proxy = webProxy
}
};
using var downloader = new DownloadService(downloadOpt);
downloader.DownloadFileCompleted += (sender, value) =>
{
if (value.Error != null)
{
throw value.Error;
}
};
using var cts = new CancellationTokenSource();
using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
using StreamReader reader = new(stream);
downloadOpt = null;
return reader.ReadToEnd();
}
public async Task DownloadDataAsync4Speed(IWebProxy webProxy, string url, IProgress<string> progress, int timeout)
{
if (Utils.IsNullOrEmpty(url))
{
throw new ArgumentNullException(nameof(url));
}
var downloadOpt = new DownloadConfiguration()
{
Timeout = timeout * 1000,
MaxTryAgainOnFailover = 2,
RequestConfiguration =
{
Timeout= timeout * 1000,
Proxy = webProxy
}
};
DateTime totalDatetime = DateTime.Now;
int totalSecond = 0;
var hasValue = false;
double maxSpeed = 0;
using var downloader = new DownloadService(downloadOpt);
//downloader.DownloadStarted += (sender, value) =>
//{
// if (progress != null)
// {
// progress.Report("Start download data...");
// }
//};
downloader.DownloadProgressChanged += (sender, value) =>
{
TimeSpan 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);
// downloader库的这个调用可能有内存泄露是导致测速过程中内存持续增加的原因
using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
downloadOpt = null;
await downloader.Clear();
downloader?.Dispose();
stream?.Close();
stream?.Dispose();
GC.Collect();
}
public async Task DownloadFileAsync(IWebProxy? webProxy, string url, string fileName, IProgress<double> progress, int timeout)
{
if (Utils.IsNullOrEmpty(url))
{
throw new ArgumentNullException(nameof(url));
}
if (Utils.IsNullOrEmpty(fileName))
{
throw new ArgumentNullException(nameof(fileName));
}
if (File.Exists(fileName))
{
File.Delete(fileName);
}
var downloadOpt = new DownloadConfiguration()
{
Timeout = timeout * 1000,
MaxTryAgainOnFailover = 2,
RequestConfiguration =
{
Timeout= timeout * 1000,
Proxy = webProxy
}
};
var progressPercentage = 0;
var hasValue = false;
using var downloader = new 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);
}
}
};
using var cts = new CancellationTokenSource();
await downloader.DownloadFileTaskAsync(url, fileName, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
downloadOpt = null;
}
}
}

View file

@ -0,0 +1,90 @@
using System.IO;
using System.IO.Compression;
using System.Text;
namespace v2rayN
{
public static class FileManager
{
public static bool ByteArrayToFile(string fileName, byte[] content)
{
try
{
File.WriteAllBytes(fileName, content);
return true;
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
return false;
}
public static void UncompressedFile(string fileName, byte[] content)
{
try
{
using FileStream fs = File.Create(fileName);
using GZipStream input = new(new MemoryStream(content), CompressionMode.Decompress, false);
input.CopyTo(fs);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
public static string NonExclusiveReadAllText(string path)
{
return NonExclusiveReadAllText(path, Encoding.Default);
}
public 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(ex.Message, ex);
throw;
}
}
public static bool ZipExtractToFile(string fileName, string toPath, string ignoredName)
{
try
{
using ZipArchive archive = ZipFile.OpenRead(fileName);
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.Length == 0)
{
continue;
}
try
{
if (!Utils.IsNullOrEmpty(ignoredName) && entry.Name.Contains(ignoredName))
{
continue;
}
entry.ExtractToFile(Path.Combine(toPath, entry.Name), true);
}
catch (IOException ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
return false;
}
return true;
}
}
}

View file

@ -0,0 +1,188 @@
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
namespace v2rayN
{
/// <summary>
/// </summary>
public class HttpClientHelper
{
private static readonly Lazy<HttpClientHelper> _instance = new(() =>
{
HttpClientHandler 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 (string.IsNullOrEmpty(url))
return null;
try
{
HttpResponseMessage response = await httpClient.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
catch
{
return null;
}
}
public async Task<string?> GetAsync(string url)
{
if (Utils.IsNullOrEmpty(url)) return null;
return await httpClient.GetStringAsync(url);
}
public async Task<string?> GetAsync(HttpClient client, string url, CancellationToken token = default)
{
if (Utils.IsNullOrEmpty(url)) 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);
var result = await httpClient.PutAsync(url, content);
}
public async Task PatchAsync(string url, Dictionary<string, string> headers)
{
var myContent = JsonUtils.Serialize(headers);
var buffer = System.Text.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;
using var stream = await response.Content.ReadAsStreamAsync(token);
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;
file.Write(buffer, 0, read);
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 (Utils.IsNullOrEmpty(url))
{
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;
using var stream = await response.Content.ReadAsStreamAsync(token);
var totalRead = 0L;
var buffer = new byte[1024 * 64];
var isMoreToRead = true;
string progressSpeed = string.Empty;
DateTime totalDatetime = DateTime.Now;
int 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;
TimeSpan 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);
}
}
}

View file

@ -0,0 +1,176 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace v2rayN
{
/*
* See:
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
*/
public class Job : IDisposable
{
private IntPtr handle = IntPtr.Zero;
public Job()
{
handle = CreateJobObject(IntPtr.Zero, null);
IntPtr extendedInfoPtr = IntPtr.Zero;
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new()
{
LimitFlags = 0x2000
};
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new()
{
BasicLimitInformation = info
};
try
{
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
(uint)length))
throw new Exception(string.Format("Unable to set information. Error: {0}",
Marshal.GetLastWin32Error()));
}
finally
{
if (extendedInfoPtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(extendedInfoPtr);
}
}
}
public bool AddProcess(IntPtr processHandle)
{
bool succ = AssignProcessToJobObject(handle, processHandle);
if (!succ)
{
Logging.SaveLog("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error());
}
return succ;
}
public bool AddProcess(int processId)
{
return AddProcess(Process.GetProcessById(processId).Handle);
}
#region IDisposable
private bool disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
disposed = true;
if (disposing)
{
// no managed objects to free
}
if (handle != IntPtr.Zero)
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
}
~Job()
{
Dispose(false);
}
#endregion IDisposable
#region Interop
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr CreateJobObject(IntPtr a, string? lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
#endregion Interop
}
#region Helper classes
[StructLayout(LayoutKind.Sequential)]
internal struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public Int64 PerProcessUserTimeLimit;
public Int64 PerJobUserTimeLimit;
public UInt32 LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public UInt32 ActiveProcessLimit;
public UIntPtr Affinity;
public UInt32 PriorityClass;
public UInt32 SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public UInt32 nLength;
public IntPtr lpSecurityDescriptor;
public Int32 bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
#endregion Helper classes
}

View file

@ -0,0 +1,134 @@
using System.IO;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace v2rayN
{
internal class JsonUtils
{
/// <summary>
/// DeepCopy
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static T DeepCopy<T>(T obj)
{
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);
}
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);
}
catch
{
//SaveLog(ex.Message, ex);
return null;
}
}
/// <summary>
/// Serialize Object to Json string
/// </summary>
/// <param name="obj"></param>
/// <param name="indented"></param>
/// <returns></returns>
public static string Serialize(object? obj, bool indented = true)
{
string result = string.Empty;
try
{
if (obj == null)
{
return result;
}
var options = new JsonSerializerOptions
{
WriteIndented = indented ? true : false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
result = JsonSerializer.Serialize(obj, options);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
return result;
}
/// <summary>
/// SerializeToNode
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static JsonNode? SerializeToNode(object? obj) => JsonSerializer.SerializeToNode(obj);
/// <summary>
/// Save as json file
/// </summary>
/// <param name="obj"></param>
/// <param name="filePath"></param>
/// <param name="nullValue"></param>
/// <returns></returns>
public static int ToFile(object? obj, string? filePath, bool nullValue = true)
{
if (filePath is null)
{
return -1;
}
try
{
using FileStream file = File.Create(filePath);
var options = new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull
};
JsonSerializer.Serialize(file, obj, options);
return 0;
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
return -1;
}
}
}
}

View file

@ -0,0 +1,78 @@
using NLog;
using NLog.Config;
using NLog.Targets;
using System.IO;
namespace v2rayN
{
public class Logging
{
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 ClearLogs()
{
Task.Run(() =>
{
try
{
var now = DateTime.Now.AddMonths(-1);
var dir = Utils.GetLogPath();
var files = Directory.GetFiles(dir, "*.txt");
foreach (var filePath in files)
{
var file = new FileInfo(filePath);
if (file.CreationTime < now)
{
try
{
file.Delete();
}
catch { }
}
}
}
catch { }
});
}
public static void SaveLog(string strContent)
{
if (LogManager.IsLoggingEnabled())
{
var logger = LogManager.GetLogger("Log1");
logger.Info(strContent);
}
}
public static void SaveLog(string strTitle, Exception ex)
{
if (LogManager.IsLoggingEnabled())
{
var logger = LogManager.GetLogger("Log2");
logger.Debug($"{strTitle},{ex.Message}");
logger.Debug(ex.StackTrace);
if (ex?.InnerException != null)
{
logger.Error(ex.InnerException);
}
}
}
}
}

View file

@ -0,0 +1,50 @@
using System.Linq.Expressions;
using System.Reflection;
namespace v2rayN
{
public static class QueryableExtension
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName)
{
return _OrderBy<T>(query, propertyName, false);
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName)
{
return _OrderBy<T>(query, propertyName, true);
}
private static IOrderedQueryable<T> _OrderBy<T>(IQueryable<T> query, string propertyName, bool isDesc)
{
string methodname = (isDesc) ? "OrderByDescendingInternal" : "OrderByInternal";
var memberProp = typeof(T).GetProperty(propertyName);
var method = typeof(QueryableExtension).GetMethod(methodname)
.MakeGenericMethod(typeof(T), memberProp.PropertyType);
return (IOrderedQueryable<T>)method.Invoke(null, new object[] { query, memberProp });
}
public static IOrderedQueryable<T> OrderByInternal<T, TProp>(IQueryable<T> query, PropertyInfo memberProperty)
{//public
return query.OrderBy(_GetLambda<T, TProp>(memberProperty));
}
public static IOrderedQueryable<T> OrderByDescendingInternal<T, TProp>(IQueryable<T> query, PropertyInfo memberProperty)
{//public
return query.OrderByDescending(_GetLambda<T, TProp>(memberProperty));
}
private static Expression<Func<T, TProp>> _GetLambda<T, TProp>(PropertyInfo memberProperty)
{
if (memberProperty.PropertyType != typeof(TProp)) throw new Exception();
var thisArg = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda<Func<T, TProp>>(Expression.Property(thisArg, memberProperty), thisArg);
return lambda;
}
}
}

View file

@ -0,0 +1,180 @@
namespace v2rayN
{
public class SemanticVersion
{
private int major;
private int minor;
private int patch;
private string version;
public SemanticVersion(int major, int minor, int patch)
{
this.major = major;
this.minor = minor;
this.patch = patch;
this.version = $"{major}.{minor}.{patch}";
}
public SemanticVersion(string version)
{
this.version = version.RemovePrefix('v');
try
{
string[] parts = this.version.Split('.');
if (parts.Length == 2)
{
this.major = int.Parse(parts[0]);
this.minor = int.Parse(parts[1]);
this.patch = 0;
}
else if (parts.Length == 3)
{
this.major = int.Parse(parts[0]);
this.minor = int.Parse(parts[1]);
this.patch = int.Parse(parts[2]);
}
else
{
throw new ArgumentException("Invalid version string");
}
}
catch
{
this.major = 0;
this.minor = 0;
this.patch = 0;
//this.version = "0.0.0";
}
}
public override bool Equals(object? obj)
{
if (obj is SemanticVersion other)
{
return this.major == other.major && this.minor == other.minor && this.patch == other.patch;
}
else
{
return false;
}
}
public override int GetHashCode()
{
return this.major.GetHashCode() ^ this.minor.GetHashCode() ^ this.patch.GetHashCode();
}
/// <summary>
/// Use ToVersionString(string? prefix) instead if possible.
/// </summary>
/// <returns>major.minor.patch</returns>
public override string ToString()
{
return this.version;
}
public string ToVersionString(string? prefix = null)
{
if (prefix == null)
{
return this.version;
}
else
{
return $"{prefix}{this.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 (this.major < other.major)
{
return false;
}
else if (this.major > other.major)
{
return true;
}
else
{
if (this.minor < other.minor)
{
return false;
}
else if (this.minor > other.minor)
{
return true;
}
else
{
if (this.patch < other.patch)
{
return false;
}
else if (this.patch > other.patch)
{
return true;
}
else
{
return true;
}
}
}
}
private bool LessEquals(SemanticVersion other)
{
if (this.major < other.major)
{
return true;
}
else if (this.major > other.major)
{
return false;
}
else
{
if (this.minor < other.minor)
{
return true;
}
else if (this.minor > other.minor)
{
return false;
}
else
{
if (this.patch < other.patch)
{
return true;
}
else if (this.patch > other.patch)
{
return false;
}
else
{
return true;
}
}
}
}
#endregion Private
}
}

View file

@ -0,0 +1,123 @@
using SQLite;
using System.Collections;
namespace v2rayN
{
public sealed class SQLiteHelper
{
private static readonly Lazy<SQLiteHelper> _instance = new(() => new());
public static SQLiteHelper Instance => _instance.Value;
private string _connstr;
private SQLiteConnection _db;
private SQLiteAsyncConnection _dbAsync;
private static readonly object objLock = new();
public 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 int Insert(object model)
{
return _db.Insert(model);
}
public int InsertAll(IEnumerable models)
{
lock (objLock)
{
return _db.InsertAll(models);
}
}
public async Task<int> InsertAsync(object model)
{
return await _dbAsync.InsertAsync(model);
}
public int Replace(object model)
{
lock (objLock)
{
return _db.InsertOrReplace(model);
}
}
public async Task<int> ReplaceAsync(object model)
{
return await _dbAsync.InsertOrReplaceAsync(model);
}
public int Update(object model)
{
lock (objLock)
{
return _db.Update(model);
}
}
public async Task<int> UpdateAsync(object model)
{
return await _dbAsync.UpdateAsync(model);
}
public int UpdateAll(IEnumerable models)
{
lock (objLock)
{
return _db.UpdateAll(models);
}
}
public int Delete(object model)
{
lock (objLock)
{
return _db.Delete(model);
}
}
public async Task<int> DeleteAsync(object model)
{
return await _dbAsync.DeleteAsync(model);
}
public List<T> Query<T>(string sql) where T : new()
{
return _db.Query<T>(sql);
}
public async Task<List<T>> QueryAsync<T>(string sql) where T : new()
{
return await _dbAsync.QueryAsync<T>(sql);
}
public int Execute(string sql)
{
return _db.Execute(sql);
}
public async Task<int> ExecuteAsync(string sql)
{
return await _dbAsync.ExecuteAsync(sql);
}
public TableQuery<T> Table<T>() where T : new()
{
return _db.Table<T>();
}
public AsyncTableQuery<T> TableAsync<T>() where T : new()
{
return _dbAsync.Table<T>();
}
}
}

View file

@ -0,0 +1,94 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
namespace v2rayN
{
internal static class StringEx
{
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
{
return string.IsNullOrEmpty(value);
}
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value)
{
return string.IsNullOrWhiteSpace(value);
}
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
{
if (s.IsNullOrEmpty()) return false;
return chars.Contains(s[0]);
}
public static bool IsWhiteSpace(this string value)
{
foreach (char c in value)
{
if (char.IsWhiteSpace(c)) continue;
return false;
}
return true;
}
public static IEnumerable<string> NonWhiteSpaceLines(this TextReader reader)
{
string? line;
while ((line = reader.ReadLine()) != null)
{
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)
{
if (value.StartsWith(prefix))
{
return value.Substring(1);
}
else
{
return value;
}
}
public static string RemovePrefix(this string value, string prefix)
{
if (value.StartsWith(prefix))
{
return value.Substring(prefix.Length);
}
else
{
return value;
}
}
public static string UpperFirstChar(this string value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
return char.ToUpper(value[0]) + value.Substring(1);
}
public static string AppendQuotes(this string value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
return $"\"{value}\"";
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace v2rayN.Common
{
internal class YamlUtils
{
#region YAML
/// <summary>
/// 反序列化成对象
/// </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
{
T obj = deserializer.Deserialize<T>(str);
return obj;
}
catch (Exception ex)
{
Logging.SaveLog("FromYaml", ex);
return deserializer.Deserialize<T>("");
}
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static string ToYaml(Object obj)
{
var serializer = new SerializerBuilder()
.WithNamingConvention(HyphenatedNamingConvention.Instance)
.Build();
string result = string.Empty;
try
{
result = serializer.Serialize(obj);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
return result;
}
#endregion YAML
}
}

View file

@ -0,0 +1,16 @@
namespace v2rayN.Enums
{
public enum EConfigType
{
VMess = 1,
Custom = 2,
Shadowsocks = 3,
Socks = 4,
VLESS = 5,
Trojan = 6,
Hysteria2 = 7,
Tuic = 8,
Wireguard = 9,
Http = 10
}
}

View file

@ -0,0 +1,20 @@
namespace v2rayN.Enums
{
public enum ECoreType
{
v2fly = 1,
Xray = 2,
SagerNet = 3,
v2fly_v5 = 4,
clash = 11,
clash_meta = 12,
mihomo = 13,
hysteria = 21,
naiveproxy = 22,
tuic = 23,
sing_box = 24,
juicity = 25,
hysteria2 = 26,
v2rayN = 99
}
}

View file

@ -0,0 +1,11 @@
namespace v2rayN.Enums
{
public enum EGlobalHotkey
{
ShowForm = 0,
SystemProxyClear = 1,
SystemProxySet = 2,
SystemProxyUnchanged = 3,
SystemProxyPac = 4,
}
}

View file

@ -0,0 +1,14 @@
namespace v2rayN.Enums
{
public enum EInboundProtocol
{
socks = 0,
http,
socks2,
http2,
pac,
api,
api2,
speedtest = 21
}
}

View file

@ -0,0 +1,11 @@
namespace v2rayN.Enums
{
public enum EMove
{
Top = 1,
Up = 2,
Down = 3,
Bottom = 4,
Position = 5
}
}

View file

@ -0,0 +1,10 @@
namespace v2rayN.Enums
{
public enum ERuleMode
{
Rule = 0,
Global = 1,
Direct = 2,
Unchanged = 3
}
}

View file

@ -0,0 +1,21 @@
namespace v2rayN.Enums
{
public enum EServerColName
{
def = 0,
configType,
remarks,
address,
port,
network,
streamSecurity,
subRemarks,
delayVal,
speedVal,
todayDown,
todayUp,
totalDown,
totalUp
}
}

View file

@ -0,0 +1,10 @@
namespace v2rayN.Enums
{
public enum ESpeedActionType
{
Tcping,
Realping,
Speedtest,
Mixedtest
}
}

View file

@ -0,0 +1,10 @@
namespace v2rayN.Enums
{
public enum ESysProxyType
{
ForcedClear = 0, // "清除系统代理"
ForcedChange = 1, // "自动配置系统代理"
Unchanged = 2, // "不改变系统代理"
Pac = 3 // "pac"
}
}

View file

@ -0,0 +1,15 @@
namespace v2rayN.Enums
{
public enum ETransport
{
tcp,
kcp,
ws,
httpupgrade,
splithttp,
h2,
http,
quic,
grpc
}
}

View file

@ -0,0 +1,8 @@
namespace v2rayN.Enums
{
public enum EViewAction
{
AdjustMainLvColWidth,
ProfilesFocus
}
}

View file

@ -0,0 +1,199 @@
using v2rayN.Enums;
namespace v2rayN
{
internal class Global
{
#region const
public const string GithubUrl = "https://github.com";
public const string GithubApiUrl = "https://api.github.com/repos";
public const string V2flyCoreUrl = "https://github.com/v2fly/v2ray-core/releases";
public const string XrayCoreUrl = "https://github.com/XTLS/Xray-core/releases";
public const string SagerNetCoreUrl = "https://github.com/SagerNet/v2ray-core/releases";
public const string NUrl = @"https://github.com/2dust/v2rayN/releases";
public const string ClashCoreUrl = "https://github.com/Dreamacro/clash/releases";
public const string ClashMetaCoreUrl = "https://github.com/MetaCubeX/Clash.Meta/releases";
public const string MihomoCoreUrl = "https://github.com/MetaCubeX/mihomo/releases";
public const string HysteriaCoreUrl = "https://github.com/apernet/hysteria/releases";
public const string NaiveproxyCoreUrl = "https://github.com/klzgrad/naiveproxy/releases";
public const string TuicCoreUrl = "https://github.com/EAimTY/tuic/releases";
public const string SingboxCoreUrl = "https://github.com/SagerNet/sing-box/releases";
public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat";
public const string SpeedPingTestUrl = @"https://www.google.com/generate_204";
public const string JuicityCoreUrl = "https://github.com/juicity/juicity/releases";
public const string CustomRoutingListUrl = @"https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/";
public const string SingboxRulesetUrlGeosite = @"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/{0}.srs";
public const string SingboxRulesetUrlGeoip = @"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/srs/{0}.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 = "configSpeedtest.json";
public const string ClashMixinConfigFileName = "Mixin.yaml";
public const string V2raySampleClient = "v2rayMiniConsole.Sample.SampleClientConfig";
public const string SingboxSampleClient = "v2rayMiniConsole.Sample.SingboxSampleClientConfig";
public const string V2raySampleHttpRequestFileName = "v2rayMiniConsole.Sample.SampleHttpRequest";
public const string V2raySampleHttpResponseFileName = "v2rayMiniConsole.Sample.SampleHttpResponse";
public const string V2raySampleInbound = "v2rayMiniConsole.Sample.SampleInbound";
public const string V2raySampleOutbound = "v2rayMiniConsole.Sample.SampleOutbound";
public const string SingboxSampleOutbound = "v2rayMiniConsole.Sample.SingboxSampleOutbound";
public const string CustomRoutingFileName = "v2rayMiniConsole.Sample.custom_routing_";
public const string TunSingboxDNSFileName = "v2rayMiniConsole.Sample.tun_singbox_dns";
public const string TunSingboxInboundFileName = "v2rayMiniConsole.Sample.tun_singbox_inbound";
public const string TunSingboxRulesFileName = "v2rayMiniConsole.Sample.tun_singbox_rules";
public const string DNSV2rayNormalFileName = "v2rayMiniConsole.Sample.dns_v2ray_normal";
public const string DNSSingboxNormalFileName = "v2rayMiniConsole.Sample.dns_singbox_normal";
public const string ClashMixinYaml = "v2rayMiniConsole.Sample.clash_mixin_yaml";
public const string ClashTunYaml = "v2rayMiniConsole.Sample.clash_tun_yaml";
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 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 UserEMail = "t@t.tt";
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
public const string AutoRunName = "v2rayNAutoRun";
public const string CustomIconName = "v2rayN.ico";
public const string IEProxyExceptions = "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 RoutingRuleComma = "<COMMA>";
public const string GrpcGunMode = "gun";
public const string GrpcMultiMode = "multi";
public const int MaxPort = 65536;
public const string CommandClearMsg = "CommandClearMsg";
public const string CommandSendMsgView = "CommandSendMsgView";
public const string CommandStopSpeedTest = "CommandStopSpeedTest";
public const string DelayUnit = "";
public const string SpeedUnit = "";
public const int MinFontSize = 10;
public const string RebootAs = "rebootas";
public static readonly List<string> IEProxyProtocols = new() {
"{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 = new List<string> {
@"https://sub.xeton.dev/sub?url={0}",
@"https://api.dler.io/sub?url={0}",
@"http://127.0.0.1:25500/sub?url={0}",
""
};
public const string StatusTemplate = "[current server:{0}] / [current proxy mode:{1}] / [current routing:{2}]";
public const string DefaultStatusStr = "[current server:-] / [current proxy mode:-] / [current routing:-]";
public static readonly List<string> SubConvertConfig = new List<string> {
@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"
};
public static readonly List<string> SubConvertTargets = new List<string> {
"",
"mixed",
"v2ray",
"clash",
"ss",
};
public static readonly List<string> SpeedTestUrls = new() {
@"https://speed.cloudflare.com/__down?bytes=100000000",
@"https://speed.cloudflare.com/__down?bytes=10000000",
@"http://cachefly.cachefly.net/50mb.test",
@"http://cachefly.cachefly.net/10mb.test"
};
public static readonly List<string> SpeedPingTestUrls = new() {
@"https://www.google.com/generate_204",
@"https://www.youtube.com",
@"https://www.apple.com/library/test/success.html",
@"http://www.msftconnecttest.com/connecttest.txt",
};
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://"}
};
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"}
};
public static readonly List<string> VmessSecurities = new() { "aes-128-gcm", "chacha20-poly1305", "auto", "none", "zero" };
public static readonly List<string> SsSecurities = new() { "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "none", "plain" };
public static readonly List<string> SsSecuritiesInSagerNet = new() { "none", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "rc4", "rc4-md5", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-cfb8", "aes-192-cfb8", "aes-256-cfb8", "aes-128-ofb", "aes-192-ofb", "aes-256-ofb", "bf-cfb", "cast5-cfb", "des-cfb", "idea-cfb", "rc2-cfb", "seed-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "camellia-128-cfb8", "camellia-192-cfb8", "camellia-256-cfb8", "salsa20", "chacha20", "chacha20-ietf", "xchacha20" };
public static readonly List<string> SsSecuritiesInXray = new() { "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 = new() { "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 = new() { "", "xtls-rprx-vision", "xtls-rprx-vision-udp443" };
public static readonly List<string> Networks = new() { "tcp", "kcp", "ws", "httpupgrade", "splithttp", "h2", "quic", "grpc" };
public static readonly List<string> KcpHeaderTypes = new() { "srtp", "utp", "wechat-video", "dtls", "wireguard" };
public static readonly List<string> CoreTypes = new() { "v2fly", "SagerNet", "Xray", "sing_box" };
public static readonly List<string> CoreTypes4VLESS = new() { "Xray", "sing_box" };
public static readonly List<string> DomainStrategies = new() { "AsIs", "IPIfNonMatch", "IPOnDemand" };
public static readonly List<string> DomainStrategies4Singbox = new() { "ipv4_only", "ipv6_only", "prefer_ipv4", "prefer_ipv6", "" };
public static readonly List<string> DomainMatchers = new() { "linear", "mph", "" };
public static readonly List<string> Fingerprints = new() { "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random", "randomized", "" };
public static readonly List<string> UserAgent = new() { "chrome", "firefox", "safari", "edge", "none" };
public static readonly List<string> AllowInsecure = new() { "true", "false", "" };
public static readonly List<string> DomainStrategy4Freedoms = new() { "AsIs", "UseIP", "UseIPv4", "UseIPv6", "" };
public static readonly List<string> Languages = new() { "zh-Hans", "zh-Hant", "en", "fa-Ir", "ru" };
public static readonly List<string> Alpns = new() { "h3", "h2", "http/1.1", "h3,h2,http/1.1", "h3,h2", "h2,http/1.1", "" };
public static readonly List<string> LogLevels = new() { "debug", "info", "warning", "error", "none" };
public static readonly List<string> InboundTags = new() { "socks", "http", "socks2", "http2" };
public static readonly List<string> RuleProtocols = new() { "http", "tls", "bittorrent" };
public static readonly List<string> RuleNetworks = new() { "", "tcp", "udp", "tcp,udp" };
public static readonly List<string> destOverrideProtocols = ["http", "tls", "quic", "fakedns", "fakedns+others"];
public static readonly List<string> TunMtus = new() { "1280", "1408", "1500", "9000" };
public static readonly List<string> TunStacks = new() { "gvisor", "system" };
public static readonly List<string> PresetMsgFilters = new() { "proxy", "direct", "block", "" };
public static readonly List<string> SingboxMuxs = new() { "h2mux", "smux", "yamux", "" };
public static readonly List<string> TuicCongestionControls = new() { "cubic", "new_reno", "bbr" };
public static readonly List<string> allowSelectType = new List<string> { "selector", "urltest", "loadbalance", "fallback" };
public static readonly List<string> notAllowTestType = new List<string> { "selector", "urltest", "direct", "reject", "compatible", "pass", "loadbalance", "fallback" };
public static readonly List<string> proxyVehicleType = new List<string> { "file", "http" };
#endregion const
}
}

View file

@ -0,0 +1,218 @@
using v2rayN.Models;
using static v2rayN.Models.ClashProxies;
namespace v2rayN.Handler
{
public sealed class ClashApiHandler
{
private static readonly Lazy<ClashApiHandler> instance = new(() => new());
public static ClashApiHandler Instance => instance.Value;
private Dictionary<String, ProxiesItem> _proxies;
public Dictionary<string, object> ProfileContent { get; set; }
public void SetProxies(Dictionary<String, ProxiesItem> proxies)
{
_proxies = proxies;
}
public Dictionary<String, ProxiesItem> GetProxies()
{
return _proxies;
}
public void GetClashProxies(Config config, Action<ClashProxies, ClashProviders> update)
{
Task.Run(() => GetClashProxiesAsync(config, update));
}
private async Task GetClashProxiesAsync(Config config, Action<ClashProxies, ClashProviders> update)
{
for (var i = 0; i < 5; 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)
{
update(clashProxies, clashProviders);
return;
}
Thread.Sleep(5000);
}
update(null, null);
}
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Action<ClashProxyModel?, string> update)
{
Task.Run(() =>
{
if (blAll)
{
for (int i = 0; i < 5; i++)
{
if (GetProxies() != null)
{
break;
}
Thread.Sleep(5000);
}
var proxies = GetProxies();
if (proxies == null)
{
return;
}
lstProxy = new List<ClashProxyModel>();
foreach (KeyValuePair<string, ProxiesItem> 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 == null)
{
return;
}
var urlBase = $"{GetApiUrl()}/proxies";
urlBase += @"/{0}/delay?timeout=10000&url=" + LazyConfig.Instance.GetConfig().speedTestItem.speedPingTestUrl;
List<Task> 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);
update(it, result);
}));
}
Task.WaitAll(tasks.ToArray());
Thread.Sleep(1000);
update(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("GetClashProxyGroups", ex);
return null;
}
}
public async void ClashSetActiveProxy(string name, string nameNode)
{
try
{
var url = $"{GetApiUrl()}/proxies/{name}";
Dictionary<string, string> headers = new Dictionary<string, string>();
headers.Add("name", nameNode);
await HttpClientHelper.Instance.PutAsync(url, headers);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
public void ClashConfigUpdate(Dictionary<string, string> headers)
{
Task.Run(async () =>
{
var proxies = GetProxies();
if (proxies == null)
{
return;
}
var urlBase = $"{GetApiUrl()}/configs";
await HttpClientHelper.Instance.PatchAsync(urlBase, headers);
});
}
public async void ClashConfigReload(string filePath)
{
ClashConnectionClose("");
try
{
var url = $"{GetApiUrl()}/configs?force=true";
Dictionary<string, string> headers = new Dictionary<string, string>();
headers.Add("path", filePath);
await HttpClientHelper.Instance.PutAsync(url, headers);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
public void GetClashConnections(Config config, Action<ClashConnections> update)
{
Task.Run(() => GetClashConnectionsAsync(config, update));
}
private async Task GetClashConnectionsAsync(Config config, Action<ClashConnections> update)
{
try
{
var url = $"{GetApiUrl()}/connections";
var result = await HttpClientHelper.Instance.TryGetAsync(url);
var clashConnections = JsonUtils.Deserialize<ClashConnections>(result);
update(clashConnections);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
public async void ClashConnectionClose(string id)
{
try
{
var url = $"{GetApiUrl()}/connections/{id}";
await HttpClientHelper.Instance.DeleteAsync(url);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
private string GetApiUrl()
{
return $"{Global.HttpProtocol}{Global.Loopback}:{LazyConfig.Instance.StatePort2}";
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,271 @@
using System.IO;
using v2rayN.Common;
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.CoreConfig
{
/// <summary>
/// Core configuration file processing class
/// </summary>
internal class CoreConfigClash
{
private Config _config;
public CoreConfigClash(Config config)
{
_config = config;
}
/// <summary>
/// 生成配置文件
/// </summary>
/// <param name="node"></param>
/// <param name="fileName"></param>
/// <param name="msg"></param>
/// <returns></returns>
public int GenerateClientConfig(ProfileItem node, string? fileName, out string msg)
{
if (node == null || fileName is null)
{
msg = ResUI.CheckServerSettings;
return -1;
}
msg = ResUI.InitialConfiguration;
try
{
if (node == null)
{
msg = ResUI.CheckServerSettings;
return -1;
}
if (File.Exists(fileName))
{
File.Delete(fileName);
}
string addressFileName = node.address;
if (string.IsNullOrEmpty(addressFileName))
{
msg = ResUI.FailedGetDefaultConfiguration;
return -1;
}
if (!File.Exists(addressFileName))
{
addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName);
}
if (!File.Exists(addressFileName))
{
msg = ResUI.FailedReadConfiguration + "1";
return -1;
}
string tagYamlStr1 = "!<str>";
string tagYamlStr2 = "__strn__";
string tagYamlStr3 = "!!str";
var txtFile = File.ReadAllText(addressFileName);
txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2);
var fileContent = YamlUtils.FromYaml<Dictionary<string, object>>(txtFile);
if (fileContent == null)
{
msg = ResUI.FailedConversionConfiguration;
return -1;
}
//port
fileContent["port"] = LazyConfig.Instance.GetLocalPort(EInboundProtocol.http);
//socks-port
fileContent["socks-port"] = LazyConfig.Instance.GetLocalPort(EInboundProtocol.socks);
//log-level
fileContent["log-level"] = GetLogLevel(_config.coreBasicItem.loglevel);
//external-controller
fileContent["external-controller"] = $"{Global.Loopback}:{LazyConfig.Instance.StatePort2}";
//allow-lan
if (_config.inbound[0].allowLANConn)
{
fileContent["allow-lan"] = "true";
fileContent["bind-address"] = "*";
}
else
{
fileContent["allow-lan"] = "false";
}
//ipv6
fileContent["ipv6"] = _config.clashUIItem.enableIPv6;
//mode
if (!fileContent.ContainsKey("mode"))
{
fileContent["mode"] = ERuleMode.Rule.ToString().ToLower();
}
else
{
if (_config.clashUIItem.ruleMode != ERuleMode.Unchanged)
{
fileContent["mode"] = _config.clashUIItem.ruleMode.ToString().ToLower();
}
}
//enable tun mode
if (_config.tunModeItem.enableTun)
{
string tun = Utils.GetEmbedText(Global.ClashTunYaml);
if (!string.IsNullOrEmpty(tun))
{
var tunContent = YamlUtils.FromYaml<Dictionary<string, object>>(tun);
if (tunContent != null)
fileContent["tun"] = tunContent["tun"];
}
}
//Mixin
try
{
MixinContent(fileContent, node);
}
catch (Exception ex)
{
Logging.SaveLog("GenerateClientConfigClash-Mixin", ex);
}
var txtFileNew = YamlUtils.ToYaml(fileContent).Replace(tagYamlStr2, tagYamlStr3);
File.WriteAllText(fileName, txtFileNew);
//check again
if (!File.Exists(fileName))
{
msg = ResUI.FailedReadConfiguration + "2";
return -1;
}
ClashApiHandler.Instance.ProfileContent = fileContent;
msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}");
}
catch (Exception ex)
{
Logging.SaveLog("GenerateClientConfigClash", ex);
msg = ResUI.FailedGenDefaultConfiguration;
return -1;
}
return 0;
}
private void MixinContent(Dictionary<string, object> fileContent, ProfileItem node)
{
//if (!_config.clashUIItem.enableMixinContent)
//{
// return;
//}
var path = Utils.GetConfigPath(Global.ClashMixinConfigFileName);
if (!File.Exists(path))
{
return;
}
var txtFile = File.ReadAllText(Utils.GetConfigPath(Global.ClashMixinConfigFileName));
var mixinContent = YamlUtils.FromYaml<Dictionary<string, object>>(txtFile);
if (mixinContent == null)
{
return;
}
foreach (var item in mixinContent)
{
if (!_config.tunModeItem.enableTun && item.Key == "tun")
{
continue;
}
if (item.Key.StartsWith("prepend-")
|| item.Key.StartsWith("append-")
|| item.Key.StartsWith("removed-"))
{
ModifyContentMerge(fileContent, item.Key, item.Value);
}
else
{
fileContent[item.Key] = item.Value;
}
}
return;
}
private void ModifyContentMerge(Dictionary<string, object> fileContent, string key, object value)
{
bool blPrepend = false;
bool blRemoved = false;
if (key.StartsWith("prepend-"))
{
blPrepend = true;
key = key.Replace("prepend-", "");
}
else if (key.StartsWith("append-"))
{
blPrepend = false;
key = key.Replace("append-", "");
}
else if (key.StartsWith("removed-"))
{
blRemoved = true;
key = key.Replace("removed-", "");
}
else
{
return;
}
if (!blRemoved && !fileContent.ContainsKey(key))
{
fileContent.Add(key, value);
return;
}
var lstOri = (List<object>)fileContent[key];
var lstValue = (List<object>)value;
if (blRemoved)
{
foreach (var item in lstValue)
{
lstOri.RemoveAll(t => t.ToString().StartsWith(item.ToString()));
}
return;
}
if (blPrepend)
{
lstValue.Reverse();
foreach (var item in lstValue)
{
lstOri.Insert(0, item);
}
}
else
{
foreach (var item in lstValue)
{
lstOri.Add(item);
}
}
}
private string GetLogLevel(string level)
{
if (level == "none")
{
return "silent";
}
else
{
return level;
}
}
}
}

View file

@ -0,0 +1,183 @@
using System.IO;
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.CoreConfig
{
/// <summary>
/// Core configuration file processing class
/// </summary>
internal class CoreConfigHandler
{
public static int GenerateClientConfig(ProfileItem node, string? fileName, out string msg, out string content)
{
content = string.Empty;
try
{
if (node == null)
{
msg = ResUI.CheckServerSettings;
return -1;
}
var config = LazyConfig.Instance.GetConfig();
msg = ResUI.InitialConfiguration;
if (node.configType == EConfigType.Custom)
{
if (node.coreType is ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo)
{
var configGenClash = new CoreConfigClash(config);
return configGenClash.GenerateClientConfig(node, fileName, out msg);
}
else
{
return GenerateClientCustomConfig(node, fileName, out msg);
}
}
else if (LazyConfig.Instance.GetCoreType(node, node.configType) == ECoreType.sing_box)
{
var configGenSingbox = new CoreConfigSingbox(config);
if (configGenSingbox.GenerateClientConfigContent(node, out SingboxConfig? singboxConfig, out msg) != 0)
{
return -1;
}
if (Utils.IsNullOrEmpty(fileName))
{
content = JsonUtils.Serialize(singboxConfig);
}
else
{
JsonUtils.ToFile(singboxConfig, fileName, false);
}
}
else
{
var coreConfigV2ray = new CoreConfigV2ray(config);
if (coreConfigV2ray.GenerateClientConfigContent(node, out V2rayConfig? v2rayConfig, out msg) != 0)
{
return -1;
}
if (Utils.IsNullOrEmpty(fileName))
{
content = JsonUtils.Serialize(v2rayConfig);
}
else
{
JsonUtils.ToFile(v2rayConfig, fileName, false);
}
}
}
catch (Exception ex)
{
Logging.SaveLog("GenerateClientConfig", ex);
msg = ResUI.FailedGenDefaultConfiguration;
return -1;
}
return 0;
}
private static int GenerateClientCustomConfig(ProfileItem node, string? fileName, out string msg)
{
try
{
if (node == null || fileName is null)
{
msg = ResUI.CheckServerSettings;
return -1;
}
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))
{
msg = ResUI.FailedGenDefaultConfiguration;
return -1;
}
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))
{
msg = ResUI.FailedGenDefaultConfiguration;
return -1;
}
//overwrite port
if (node.preSocksPort <= 0)
{
var fileContent = File.ReadAllLines(fileName).ToList();
var coreType = LazyConfig.Instance.GetCoreType(node, node.configType);
switch (coreType)
{
case ECoreType.v2fly:
case ECoreType.SagerNet:
case ECoreType.Xray:
case ECoreType.v2fly_v5:
break;
case ECoreType.clash:
case ECoreType.clash_meta:
case ECoreType.mihomo:
//remove the original
var indexPort = fileContent.FindIndex(t => t.Contains("port:"));
if (indexPort >= 0)
{
fileContent.RemoveAt(indexPort);
}
indexPort = fileContent.FindIndex(t => t.Contains("socks-port:"));
if (indexPort >= 0)
{
fileContent.RemoveAt(indexPort);
}
fileContent.Add($"port: {LazyConfig.Instance.GetLocalPort(EInboundProtocol.http)}");
fileContent.Add($"socks-port: {LazyConfig.Instance.GetLocalPort(EInboundProtocol.socks)}");
break;
}
File.WriteAllLines(fileName, fileContent);
}
msg = string.Format(ResUI.SuccessfulConfiguration, "");
}
catch (Exception ex)
{
Logging.SaveLog("GenerateClientCustomConfig", ex);
msg = ResUI.FailedGenDefaultConfiguration;
return -1;
}
return 0;
}
public static int GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType, out string msg)
{
if (coreType == ECoreType.sing_box)
{
if (new CoreConfigSingbox(config).GenerateClientSpeedtestConfig(selecteds, out SingboxConfig? singboxConfig, out msg) != 0)
{
return -1;
}
JsonUtils.ToFile(singboxConfig, fileName, false);
}
else
{
if (new CoreConfigV2ray(config).GenerateClientSpeedtestConfig(selecteds, out V2rayConfig? v2rayConfig, out msg) != 0)
{
return -1;
}
JsonUtils.ToFile(v2rayConfig, fileName, false);
}
return 0;
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,390 @@
using System.Diagnostics;
using System.IO;
using System.Text;
using v2rayN.Enums;
using v2rayN.Handler.CoreConfig;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler
{
/// <summary>
/// Core process processing class
/// </summary>
internal class CoreHandler
{
private Config _config;
private Process? _process;
private Process? _processPre;
private Action<bool, string> _updateFunc;
public CoreHandler(Config config, Action<bool, string> update)
{
_config = config;
_updateFunc = update;
Environment.SetEnvironmentVariable("v2ray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("xray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
}
public void LoadCore()
{
var node = ConfigHandler.GetDefaultServer(_config);
if (node == null)
{
ShowMsg(false, ResUI.CheckServerSettings);
return;
}
string fileName = Utils.GetConfigPath(Global.CoreConfigFileName);
if (CoreConfigHandler.GenerateClientConfig(node, fileName, out string msg, out string content) != 0)
{
ShowMsg(false, msg);
return;
}
else
{
ShowMsg(false, msg);
ShowMsg(true, $"{node.GetSummary()}");
CoreStop();
if (_config.tunModeItem.enableTun)
{
Thread.Sleep(1000);
Utils.RemoveTunDevice();
}
CoreStart(node);
//In tun mode, do a delay check and restart the core
//if (_config.tunModeItem.enableTun)
//{
// Observable.Range(1, 1)
// .Delay(TimeSpan.FromSeconds(15))
// .Subscribe(x =>
// {
// {
// if (_process == null || _process.HasExited)
// {
// CoreStart(node);
// ShowMsg(false, "Tun mode restart the core once");
// Logging.SaveLog("Tun mode restart the core once");
// }
// }
// });
//}
}
}
public int LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
{
int pid = -1;
var coreType = selecteds.Exists(t => t.configType == EConfigType.Hysteria2 || t.configType == EConfigType.Tuic || t.configType == EConfigType.Wireguard) ? ECoreType.sing_box : ECoreType.Xray;
string configPath = Utils.GetConfigPath(Global.CoreSpeedtestConfigFileName);
if (CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType, out string msg) != 0)
{
ShowMsg(false, msg);
}
else
{
ShowMsg(false, msg);
pid = CoreStartSpeedtest(configPath, coreType);
}
return pid;
}
public void CoreStop()
{
try
{
bool hasProc = false;
if (_process != null)
{
KillProcess(_process);
_process.Dispose();
_process = null;
hasProc = true;
}
if (_processPre != null)
{
KillProcess(_processPre);
_processPre.Dispose();
_processPre = null;
hasProc = true;
}
if (!hasProc)
{
var coreInfo = LazyConfig.Instance.GetCoreInfo();
foreach (var it in coreInfo)
{
if (it.coreType == ECoreType.v2rayN)
{
continue;
}
foreach (string vName in it.coreExes)
{
var existing = Process.GetProcessesByName(vName);
foreach (Process p in existing)
{
string? path = p.MainModule?.FileName;
if (path == $"{Utils.GetBinPath(vName, it.coreType.ToString())}.exe")
{
KillProcess(p);
}
}
}
}
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
public void CoreStopPid(int pid)
{
try
{
var _p = Process.GetProcessById(pid);
KillProcess(_p);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
#region Private
private string CoreFindExe(CoreInfo coreInfo)
{
string fileName = string.Empty;
foreach (string name in coreInfo.coreExes)
{
string vName = $"{name}.exe";
vName = Utils.GetBinPath(vName, coreInfo.coreType.ToString());
if (File.Exists(vName))
{
fileName = vName;
break;
}
}
if (Utils.IsNullOrEmpty(fileName))
{
string msg = string.Format(ResUI.NotFoundCore, Utils.GetBinPath("", coreInfo.coreType.ToString()), string.Join(", ", coreInfo.coreExes.ToArray()), coreInfo.coreUrl);
Logging.SaveLog(msg);
ShowMsg(false, msg);
}
return fileName;
}
private void CoreStart(ProfileItem node)
{
ShowMsg(false, $"{Environment.OSVersion} - {(Environment.Is64BitOperatingSystem ? 64 : 32)}");
ShowMsg(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
//ECoreType coreType;
//if (node.configType != EConfigType.Custom && _config.tunModeItem.enableTun)
//{
// coreType = ECoreType.sing_box;
//}
//else
//{
// coreType = LazyConfig.Instance.GetCoreType(node, node.configType);
//}
var coreType = LazyConfig.Instance.GetCoreType(node, node.configType);
_config.runningCoreType = coreType;
var coreInfo = LazyConfig.Instance.GetCoreInfo(coreType);
var displayLog = node.configType != EConfigType.Custom || node.displayLog;
var proc = RunProcess(node, coreInfo, "", displayLog);
if (proc is null)
{
return;
}
_process = proc;
//start a pre service
if (_process != null && !_process.HasExited)
{
ProfileItem? itemSocks = null;
var preCoreType = ECoreType.sing_box;
if (node.configType != EConfigType.Custom && coreType != ECoreType.sing_box && _config.tunModeItem.enableTun)
{
itemSocks = new ProfileItem()
{
coreType = preCoreType,
configType = EConfigType.Socks,
address = Global.Loopback,
sni = node.address, //Tun2SocksAddress
port = LazyConfig.Instance.GetLocalPort(EInboundProtocol.socks)
};
}
else if ((node.configType == EConfigType.Custom && node.preSocksPort > 0))
{
preCoreType = _config.tunModeItem.enableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem()
{
coreType = preCoreType,
configType = EConfigType.Socks,
address = Global.Loopback,
port = node.preSocksPort
};
_config.runningCoreType = preCoreType;
}
if (itemSocks != null)
{
string fileName2 = Utils.GetConfigPath(Global.CorePreConfigFileName);
if (CoreConfigHandler.GenerateClientConfig(itemSocks, fileName2, out string msg2, out string configStr) == 0)
{
var coreInfo2 = LazyConfig.Instance.GetCoreInfo(preCoreType);
var proc2 = RunProcess(node, coreInfo2, $" -c {Global.CorePreConfigFileName}", true);
if (proc2 is not null)
{
_processPre = proc2;
}
}
}
}
}
private int CoreStartSpeedtest(string configPath, ECoreType coreType)
{
ShowMsg(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
ShowMsg(false, configPath);
try
{
var coreInfo = LazyConfig.Instance.GetCoreInfo(coreType);
var proc = RunProcess(new(), coreInfo, $" -c {Global.CoreSpeedtestConfigFileName}", true);
if (proc is null)
{
return -1;
}
return proc.Id;
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
string msg = ex.Message;
ShowMsg(false, msg);
return -1;
}
}
private void ShowMsg(bool notify, string msg)
{
_updateFunc(notify, msg);
}
#endregion Private
#region Process
private Process? RunProcess(ProfileItem node, CoreInfo coreInfo, string configPath, bool displayLog)
{
try
{
string fileName = CoreFindExe(coreInfo);
if (Utils.IsNullOrEmpty(fileName))
{
return null;
}
Process proc = new()
{
StartInfo = new()
{
FileName = fileName,
Arguments = string.Format(coreInfo.arguments, configPath),
WorkingDirectory = Utils.GetConfigPath(),
UseShellExecute = false,
RedirectStandardOutput = displayLog,
RedirectStandardError = displayLog,
CreateNoWindow = true,
StandardOutputEncoding = displayLog ? Encoding.UTF8 : null,
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
}
};
var startUpErrorMessage = new StringBuilder();
var startUpSuccessful = false;
if (displayLog)
{
proc.OutputDataReceived += (sender, e) =>
{
if (!Utils.IsNullOrEmpty(e.Data))
{
string msg = e.Data + Environment.NewLine;
ShowMsg(false, msg);
}
};
proc.ErrorDataReceived += (sender, e) =>
{
if (!Utils.IsNullOrEmpty(e.Data))
{
string msg = e.Data + Environment.NewLine;
ShowMsg(false, msg);
if (!startUpSuccessful)
{
startUpErrorMessage.Append(msg);
}
}
};
}
proc.Start();
if (displayLog)
{
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
}
if (proc.WaitForExit(1000))
{
proc.CancelErrorRead();
throw new Exception(displayLog ? startUpErrorMessage.ToString() : "启动进程失败并退出 (Failed to start the process and exited)");
}
else
{
startUpSuccessful = true;
}
LazyConfig.Instance.AddProcess(proc.Handle);
return proc;
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
string msg = ex.Message;
ShowMsg(true, msg);
return null;
}
}
private void KillProcess(Process? proc)
{
if (proc is null)
{
return;
}
try
{
proc.Kill();
proc.WaitForExit(100);
if (!proc.HasExited)
{
proc.Kill();
proc.WaitForExit(100);
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
#endregion Process
}
}

View file

@ -0,0 +1,341 @@
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Sockets;
using v2rayN.Enums;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler
{
/// <summary>
///Download
/// </summary>
internal class DownloadHandle
{
public event EventHandler<ResultEventArgs>? UpdateCompleted;
public event ErrorEventHandler? Error;
public class ResultEventArgs : EventArgs
{
public bool Success;
public string Msg;
public ResultEventArgs(bool success, string msg)
{
Success = success;
Msg = msg;
}
}
public async Task<int> DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Action<bool, string> update)
{
try
{
Utils.SetSecurityProtocol(LazyConfig.Instance.GetConfig().guiItem.enableSecurityProtocolTls13);
var progress = new Progress<string>();
progress.ProgressChanged += (sender, value) =>
{
if (update != null)
{
string msg = $"{value}";
update(false, msg);
}
};
await DownloaderHelper.Instance.DownloadDataAsync4Speed(webProxy,
url,
progress,
downloadTimeout);
}
catch (Exception ex)
{
update(false, ex.Message);
if (ex.InnerException != null)
{
update(false, ex.InnerException.Message);
}
}
return 0;
}
public async Task DownloadFileAsync(string url, bool blProxy, int downloadTimeout)
{
try
{
Utils.SetSecurityProtocol(LazyConfig.Instance.GetConfig().guiItem.enableSecurityProtocolTls13);
UpdateCompleted?.Invoke(this, new ResultEventArgs(false, $"{ResUI.Downloading} {url}"));
var progress = new Progress<double>();
progress.ProgressChanged += (sender, value) =>
{
UpdateCompleted?.Invoke(this, new ResultEventArgs(value > 100, $"...{value}%"));
};
var webProxy = GetWebProxy(blProxy);
await DownloaderHelper.Instance.DownloadFileAsync(webProxy,
url,
Utils.GetTempPath(Utils.GetDownloadFileName(url)),
progress,
downloadTimeout);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
Error?.Invoke(this, new ErrorEventArgs(ex));
if (ex.InnerException != null)
{
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
}
}
}
public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
{
Utils.SetSecurityProtocol(LazyConfig.Instance.GetConfig().guiItem.enableSecurityProtocolTls13);
var webRequestHandler = new SocketsHttpHandler
{
AllowAutoRedirect = false,
Proxy = GetWebProxy(blProxy)
};
HttpClient client = new(webRequestHandler);
HttpResponseMessage response = await client.GetAsync(url);
if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null)
{
return response.Headers.Location.ToString();
}
else
{
Logging.SaveLog("StatusCode error: " + url);
return null;
}
}
public async Task<string?> TryDownloadString(string url, bool blProxy, string userAgent)
{
try
{
var result1 = await DownloadStringAsync(url, blProxy, userAgent);
if (!Utils.IsNullOrEmpty(result1))
{
return result1;
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
Error?.Invoke(this, new ErrorEventArgs(ex));
if (ex.InnerException != null)
{
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
}
}
try
{
var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent);
if (!Utils.IsNullOrEmpty(result2))
{
return result2;
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
Error?.Invoke(this, new ErrorEventArgs(ex));
if (ex.InnerException != null)
{
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
}
}
try
{
using var wc = new WebClient();
wc.Proxy = GetWebProxy(blProxy);
var result3 = await wc.DownloadStringTaskAsync(url);
if (!Utils.IsNullOrEmpty(result3))
{
return result3;
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
Error?.Invoke(this, new ErrorEventArgs(ex));
if (ex.InnerException != null)
{
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
}
}
return null;
}
/// <summary>
/// DownloadString
/// </summary>
/// <param name="url"></param>
public async Task<string?> DownloadStringAsync(string url, bool blProxy, string userAgent)
{
try
{
Utils.SetSecurityProtocol(LazyConfig.Instance.GetConfig().guiItem.enableSecurityProtocolTls13);
var webProxy = GetWebProxy(blProxy);
var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
UseProxy = webProxy != null
});
if (Utils.IsNullOrEmpty(userAgent))
{
userAgent = Utils.GetVersion(false);
}
client.DefaultRequestHeaders.UserAgent.TryParseAdd(userAgent);
Uri uri = new(url);
//Authorization Header
if (!Utils.IsNullOrEmpty(uri.UserInfo))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Utils.Base64Encode(uri.UserInfo));
}
using var cts = new CancellationTokenSource();
var result = await HttpClientHelper.Instance.GetAsync(client, url, cts.Token).WaitAsync(TimeSpan.FromSeconds(30), cts.Token);
return result;
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
Error?.Invoke(this, new ErrorEventArgs(ex));
if (ex.InnerException != null)
{
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
}
}
return null;
}
/// <summary>
/// DownloadString
/// </summary>
/// <param name="url"></param>
public async Task<string?> DownloadStringViaDownloader(string url, bool blProxy, string userAgent)
{
try
{
Utils.SetSecurityProtocol(LazyConfig.Instance.GetConfig().guiItem.enableSecurityProtocolTls13);
var webProxy = GetWebProxy(blProxy);
if (Utils.IsNullOrEmpty(userAgent))
{
userAgent = Utils.GetVersion(false);
}
var result = await DownloaderHelper.Instance.DownloadStringAsync(webProxy, url, userAgent, 30);
return result;
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
Error?.Invoke(this, new ErrorEventArgs(ex));
if (ex.InnerException != null)
{
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
}
}
return null;
}
public async Task<int> RunAvailabilityCheck(IWebProxy? webProxy)
{
try
{
if (webProxy == null)
{
webProxy = GetWebProxy(true);
}
try
{
var config = LazyConfig.Instance.GetConfig();
int responseTime = await GetRealPingTime(config.speedTestItem.speedPingTestUrl, webProxy, 10);
return responseTime;
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
return -1;
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
return -1;
}
}
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
{
int responseTime = -1;
try
{
Stopwatch timer = Stopwatch.StartNew();
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
using var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
UseProxy = webProxy != null
});
// cts超时后会抛出一个异常
await client.GetAsync(url, cts.Token);
responseTime = timer.Elapsed.Milliseconds;
client.Dispose();
}
catch (Exception ex)
{
(new NoticeHandler()).SendMessage($"{ex.Message}--test failure with url--{url}", true);
}
return responseTime;
}
private WebProxy? GetWebProxy(bool blProxy)
{
if (!blProxy)
{
return null;
}
var httpPort = LazyConfig.Instance.GetLocalPort(EInboundProtocol.http);
if (!SocketCheck(Global.Loopback, httpPort))
{
return null;
}
return new WebProxy(Global.Loopback, httpPort);
}
private bool SocketCheck(string ip, int port)
{
try
{
IPEndPoint point = new(IPAddress.Parse(ip), port);
using Socket? sock = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sock.Connect(point);
return true;
}
catch (Exception)
{
return false;
}
}
}
}

View file

@ -0,0 +1,203 @@
using System.Collections.Specialized;
using System.IO;
using v2rayN.Enums;
using v2rayN.Models;
namespace v2rayN.Handler.Fmt
{
internal class BaseFmt
{
protected static string GetIpv6(string address)
{
if (Utils.IsIpv6(address))
{
// 检查地址是否已经被方括号包围,如果没有,则添加方括号
return address.StartsWith('[') && address.EndsWith(']') ? address : $"[{address}]";
}
return address; // 如果不是IPv6地址直接返回原地址
}
protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
{
if (!Utils.IsNullOrEmpty(item.flow))
{
dicQuery.Add("flow", item.flow);
}
if (!Utils.IsNullOrEmpty(item.streamSecurity))
{
dicQuery.Add("security", item.streamSecurity);
}
else
{
if (securityDef != null)
{
dicQuery.Add("security", securityDef);
}
}
if (!Utils.IsNullOrEmpty(item.sni))
{
dicQuery.Add("sni", item.sni);
}
if (!Utils.IsNullOrEmpty(item.alpn))
{
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
}
if (!Utils.IsNullOrEmpty(item.fingerprint))
{
dicQuery.Add("fp", Utils.UrlEncode(item.fingerprint));
}
if (!Utils.IsNullOrEmpty(item.publicKey))
{
dicQuery.Add("pbk", Utils.UrlEncode(item.publicKey));
}
if (!Utils.IsNullOrEmpty(item.shortId))
{
dicQuery.Add("sid", Utils.UrlEncode(item.shortId));
}
if (!Utils.IsNullOrEmpty(item.spiderX))
{
dicQuery.Add("spx", Utils.UrlEncode(item.spiderX));
}
dicQuery.Add("type", !Utils.IsNullOrEmpty(item.network) ? item.network : nameof(ETransport.tcp));
switch (item.network)
{
case nameof(ETransport.tcp):
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
if (!Utils.IsNullOrEmpty(item.requestHost))
{
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
}
break;
case nameof(ETransport.kcp):
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
if (!Utils.IsNullOrEmpty(item.path))
{
dicQuery.Add("seed", Utils.UrlEncode(item.path));
}
break;
case nameof(ETransport.ws):
case nameof(ETransport.httpupgrade):
case nameof(ETransport.splithttp):
if (!Utils.IsNullOrEmpty(item.requestHost))
{
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
}
if (!Utils.IsNullOrEmpty(item.path))
{
dicQuery.Add("path", Utils.UrlEncode(item.path));
}
break;
case nameof(ETransport.http):
case nameof(ETransport.h2):
dicQuery["type"] = nameof(ETransport.http);
if (!Utils.IsNullOrEmpty(item.requestHost))
{
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
}
if (!Utils.IsNullOrEmpty(item.path))
{
dicQuery.Add("path", Utils.UrlEncode(item.path));
}
break;
case nameof(ETransport.quic):
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
dicQuery.Add("quicSecurity", Utils.UrlEncode(item.requestHost));
dicQuery.Add("key", Utils.UrlEncode(item.path));
break;
case nameof(ETransport.grpc):
if (!Utils.IsNullOrEmpty(item.path))
{
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 = query["flow"] ?? "";
item.streamSecurity = query["security"] ?? "";
item.sni = query["sni"] ?? "";
item.alpn = Utils.UrlDecode(query["alpn"] ?? "");
item.fingerprint = Utils.UrlDecode(query["fp"] ?? "");
item.publicKey = Utils.UrlDecode(query["pbk"] ?? "");
item.shortId = Utils.UrlDecode(query["sid"] ?? "");
item.spiderX = Utils.UrlDecode(query["spx"] ?? "");
item.network = query["type"] ?? nameof(ETransport.tcp);
switch (item.network)
{
case nameof(ETransport.tcp):
item.headerType = query["headerType"] ?? Global.None;
item.requestHost = Utils.UrlDecode(query["host"] ?? "");
break;
case nameof(ETransport.kcp):
item.headerType = query["headerType"] ?? Global.None;
item.path = Utils.UrlDecode(query["seed"] ?? "");
break;
case nameof(ETransport.ws):
case nameof(ETransport.httpupgrade):
case nameof(ETransport.splithttp):
item.requestHost = Utils.UrlDecode(query["host"] ?? "");
item.path = Utils.UrlDecode(query["path"] ?? "/");
break;
case nameof(ETransport.http):
case nameof(ETransport.h2):
item.network = nameof(ETransport.h2);
item.requestHost = Utils.UrlDecode(query["host"] ?? "");
item.path = Utils.UrlDecode(query["path"] ?? "/");
break;
case nameof(ETransport.quic):
item.headerType = query["headerType"] ?? Global.None;
item.requestHost = query["quicSecurity"] ?? Global.None;
item.path = Utils.UrlDecode(query["key"] ?? "");
break;
case nameof(ETransport.grpc):
item.requestHost = Utils.UrlDecode(query["authority"] ?? "");
item.path = Utils.UrlDecode(query["serviceName"] ?? "");
item.headerType = Utils.UrlDecode(query["mode"] ?? Global.GrpcGunMode);
break;
default:
break;
}
return 0;
}
protected static bool Contains(string str, params string[] s)
{
foreach (var item in s)
{
if (str.Contains(item, StringComparison.OrdinalIgnoreCase)) return true;
}
return false;
}
protected static string WriteAllText(string strData, string ext = "json")
{
var fileName = Utils.GetTempPath($"{Utils.GetGUID(false)}.{ext}");
File.WriteAllText(fileName, strData);
return fileName;
}
}
}

View file

@ -0,0 +1,26 @@
using v2rayN.Enums;
using v2rayN.Models;
namespace v2rayN.Handler.Fmt
{
internal class ClashFmt : BaseFmt
{
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
if (Contains(strData, "port", "socks-port", "proxies"))
{
var fileName = WriteAllText(strData, "yaml");
var profileItem = new ProfileItem
{
coreType = ECoreType.mihomo,
address = fileName,
remarks = subRemarks ?? "clash_custom"
};
return profileItem;
}
return null;
}
}
}

View file

@ -0,0 +1,94 @@
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.Fmt
{
internal class 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),
_ => null,
};
return url;
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
return "";
}
}
public static ProfileItem? ResolveConfig(string config, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
try
{
string str = config.TrimEx();
if (Utils.IsNullOrEmpty(str))
{
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
{
msg = ResUI.NonvmessOrssProtocol;
return null;
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
msg = ResUI.Incorrectconfiguration;
return null;
}
}
}
}

View file

@ -0,0 +1,104 @@
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.Fmt
{
internal class Hysteria2Fmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
ProfileItem item = new()
{
configType = EConfigType.Hysteria2
};
Uri url = new(str);
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 = Utils.UrlDecode(query["obfs-password"] ?? "");
item.allowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false";
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null) return null;
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
var dicQuery = new Dictionary<string, string>();
if (!Utils.IsNullOrEmpty(item.sni))
{
dicQuery.Add("sni", item.sni);
}
if (!Utils.IsNullOrEmpty(item.alpn))
{
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
}
if (!Utils.IsNullOrEmpty(item.path))
{
dicQuery.Add("obfs", "salamander");
dicQuery.Add("obfs-password", Utils.UrlEncode(item.path));
}
dicQuery.Add("insecure", item.allowInsecure.ToLower() == "true" ? "1" : "0");
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
url = string.Format("{0}@{1}:{2}",
item.id,
GetIpv6(item.address),
item.port);
url = $"{Global.ProtocolShares[EConfigType.Hysteria2]}{url}/{query}{remark}";
return url;
}
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
if (Contains(strData, "server", "up", "down", "listen", "<html>", "<body>"))
{
var fileName = WriteAllText(strData);
var profileItem = new ProfileItem
{
coreType = ECoreType.hysteria,
address = fileName,
remarks = subRemarks ?? "hysteria_custom"
};
return profileItem;
}
return null;
}
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;
}
}
}

View file

@ -0,0 +1,26 @@
using v2rayN.Enums;
using v2rayN.Models;
namespace v2rayN.Handler.Fmt
{
internal class NaiveproxyFmt : BaseFmt
{
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
if (Contains(strData, "listen", "proxy", "<html>", "<body>"))
{
var fileName = WriteAllText(strData);
var profileItem = new ProfileItem
{
coreType = ECoreType.naiveproxy,
address = fileName,
remarks = subRemarks ?? "naiveproxy_custom"
};
return profileItem;
}
return null;
}
}
}

View file

@ -0,0 +1,183 @@
using System.Text.RegularExpressions;
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.Fmt
{
internal 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;
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
{
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}");
url = $"{pw}@{GetIpv6(item.address)}:{item.port}";
url = $"{Global.ProtocolShares[EConfigType.Shadowsocks]}{url}{remark}";
return url;
}
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 (!Utils.IsNullOrEmpty(tag))
{
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 = Utils.ToInt(details.Groups["port"].Value);
return item;
}
private static ProfileItem? ResolveSip002(string result)
{
Uri parsedUrl;
try
{
parsedUrl = new Uri(result);
}
catch (UriFormatException)
{
return null;
}
ProfileItem item = new()
{
remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
address = parsedUrl.IdnHost,
port = parsedUrl.Port,
};
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.UriEscaped);
//2022-blake3
if (rawUserInfo.Contains(':'))
{
string[] userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
if (userInfoParts.Length != 2)
{
return null;
}
item.security = userInfoParts[0];
item.id = Utils.UrlDecode(userInfoParts[1]);
}
else
{
// parse base64 UserInfo
string userInfo = Utils.Base64Decode(rawUserInfo);
string[] userInfoParts = userInfo.Split(new[] { ':' }, 2);
if (userInfoParts.Length != 2)
{
return null;
}
item.security = userInfoParts[0];
item.id = userInfoParts[1];
}
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") && !Utils.IsNullOrEmpty(obfsHost))
{
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 = Utils.ToInt(it.server_port)
};
lst.Add(ssItem);
}
return lst;
}
return null;
}
}
}

View file

@ -0,0 +1,58 @@
using v2rayN.Enums;
using v2rayN.Models;
namespace v2rayN.Handler.Fmt
{
internal class SingboxFmt : BaseFmt
{
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
{
var configObjects = JsonUtils.Deserialize<Object[]>(strData);
if (configObjects != null && configObjects.Length > 0)
{
List<ProfileItem> lstResult = [];
foreach (var configObject in configObjects)
{
var objectString = JsonUtils.Serialize(configObject);
var singboxCon = JsonUtils.Deserialize<SingboxConfig>(objectString);
if (singboxCon?.inbounds?.Count > 0
&& singboxCon.outbounds?.Count > 0
&& singboxCon.route != null)
{
var fileName = WriteAllText(objectString);
var profileIt = new ProfileItem
{
coreType = ECoreType.sing_box,
address = fileName,
remarks = subRemarks ?? "singbox_custom",
};
lstResult.Add(profileIt);
}
}
return lstResult;
}
return null;
}
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(strData);
if (singboxConfig?.inbounds?.Count > 0
&& singboxConfig.outbounds?.Count > 0
&& singboxConfig.route != null)
{
var fileName = WriteAllText(strData);
var profileItem = new ProfileItem
{
coreType = ECoreType.sing_box,
address = fileName,
remarks = subRemarks ?? "singbox_custom"
};
return profileItem;
}
return null;
}
}
}

View file

@ -0,0 +1,131 @@
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.Fmt
{
internal class SocksFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
ProfileItem? item;
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;
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
{
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
var pw = Utils.Base64Encode($"{item.security}:{item.id}");
url = $"{pw}@{GetIpv6(item.address)}:{item.port}";
url = $"{Global.ProtocolShares[EConfigType.Socks]}{url}{remark}";
return url;
}
private static ProfileItem? ResolveSocks(string result)
{
ProfileItem item = new()
{
configType = EConfigType.Socks
};
result = result[Global.ProtocolShares[EConfigType.Socks].Length..];
//remark
int 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
int indexS = result.IndexOf("@");
if (indexS > 0)
{
}
else
{
result = Utils.Base64Decode(result);
}
string[] arr1 = result.Split('@');
if (arr1.Length != 2)
{
return null;
}
string[] arr21 = arr1[0].Split(':');
//string[] arr22 = arr1[1].Split(':');
int indexPort = arr1[1].LastIndexOf(":");
if (arr21.Length != 2 || indexPort < 0)
{
return null;
}
item.address = arr1[1][..indexPort];
item.port = Utils.ToInt(arr1[1][(indexPort + 1)..]);
item.security = arr21[0];
item.id = arr21[1];
return item;
}
private static ProfileItem? ResolveSocksNew(string result)
{
Uri parsedUrl;
try
{
parsedUrl = new Uri(result);
}
catch (UriFormatException)
{
return null;
}
ProfileItem item = new()
{
remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
address = parsedUrl.IdnHost,
port = parsedUrl.Port,
};
// parse base64 UserInfo
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped);
string userInfo = Utils.Base64Decode(rawUserInfo);
string[] userInfoParts = userInfo.Split(new[] { ':' }, 2);
if (userInfoParts.Length == 2)
{
item.security = userInfoParts[0];
item.id = userInfoParts[1];
}
return item;
}
}
}

View file

@ -0,0 +1,53 @@
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.Fmt
{
internal class TrojanFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
ProfileItem item = new()
{
configType = EConfigType.Trojan
};
Uri url = new(str);
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;
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
var dicQuery = new Dictionary<string, string>();
GetStdTransport(item, null, ref dicQuery);
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
url = string.Format("{0}@{1}:{2}",
item.id,
GetIpv6(item.address),
item.port);
url = $"{Global.ProtocolShares[EConfigType.Trojan]}{url}{query}{remark}";
return url;
}
}
}

View file

@ -0,0 +1,68 @@
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.Fmt
{
internal class TuicFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
ProfileItem item = new()
{
configType = EConfigType.Tuic
};
Uri url = new(str);
item.address = url.IdnHost;
item.port = url.Port;
item.remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
var userInfoParts = url.UserInfo.Split(new[] { ':' }, 2);
if (userInfoParts.Length == 2)
{
item.id = userInfoParts[0];
item.security = userInfoParts[1];
}
var query = Utils.ParseQueryString(url.Query);
ResolveStdTransport(query, ref item);
item.headerType = query["congestion_control"] ?? "";
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null) return null;
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
var dicQuery = new Dictionary<string, string>();
if (!Utils.IsNullOrEmpty(item.sni))
{
dicQuery.Add("sni", item.sni);
}
if (!Utils.IsNullOrEmpty(item.alpn))
{
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
}
dicQuery.Add("congestion_control", item.headerType);
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
url = string.Format("{0}@{1}:{2}",
$"{item.id}:{item.security}",
GetIpv6(item.address),
item.port);
url = $"{Global.ProtocolShares[EConfigType.Tuic]}{url}{query}{remark}";
return url;
}
}
}

View file

@ -0,0 +1,59 @@
using v2rayN.Enums;
using v2rayN.Models;
namespace v2rayN.Handler.Fmt
{
internal class V2rayFmt : BaseFmt
{
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
{
var configObjects = JsonUtils.Deserialize<Object[]>(strData);
if (configObjects != null && configObjects.Length > 0)
{
List<ProfileItem> lstResult = [];
foreach (var configObject in configObjects)
{
var objectString = JsonUtils.Serialize(configObject);
var v2rayCon = JsonUtils.Deserialize<V2rayConfig>(objectString);
if (v2rayCon?.inbounds?.Count > 0
&& v2rayCon.outbounds?.Count > 0
&& v2rayCon.routing != null)
{
var fileName = WriteAllText(objectString);
var profileIt = new ProfileItem
{
coreType = ECoreType.Xray,
address = fileName,
remarks = v2rayCon.remarks ?? subRemarks ?? "v2ray_custom",
};
lstResult.Add(profileIt);
}
}
return lstResult;
}
return null;
}
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(strData);
if (v2rayConfig?.inbounds?.Count > 0
&& v2rayConfig.outbounds?.Count > 0
&& v2rayConfig.routing != null)
{
var fileName = WriteAllText(strData);
var profileItem = new ProfileItem
{
coreType = ECoreType.Xray,
address = fileName,
remarks = v2rayConfig.remarks ?? subRemarks ?? "v2ray_custom"
};
return profileItem;
}
return null;
}
}
}

View file

@ -0,0 +1,64 @@
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.Fmt
{
internal class VLESSFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
ProfileItem item = new()
{
configType = EConfigType.VLESS,
security = Global.None
};
Uri url = new(str);
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 = query["encryption"] ?? Global.None;
item.streamSecurity = query["security"] ?? "";
ResolveStdTransport(query, ref item);
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null) return null;
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
var dicQuery = new Dictionary<string, string>();
if (!Utils.IsNullOrEmpty(item.security))
{
dicQuery.Add("encryption", item.security);
}
else
{
dicQuery.Add("encryption", Global.None);
}
GetStdTransport(item, Global.None, ref dicQuery);
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
url = string.Format("{0}@{1}:{2}",
item.id,
GetIpv6(item.address),
item.port);
url = $"{Global.ProtocolShares[EConfigType.VLESS]}{url}{query}{remark}";
return url;
}
}
}

View file

@ -0,0 +1,229 @@
using System.Text.RegularExpressions;
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.Fmt
{
internal class VmessFmt : BaseFmt
{
private static readonly Regex StdVmessUserInfo = new(
@"^(?<network>[a-z]+)(\+(?<streamSecurity>[a-z]+))?:(?<id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$", RegexOptions.Compiled);
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
ProfileItem? item;
int indexSplit = str.IndexOf("?");
if (indexSplit > 0)
{
item = ResolveStdVmess(str) ?? ResolveVmess4Kitsunebi(str);
}
else
{
item = ResolveVmess(str, out msg);
}
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null) return null;
string url = string.Empty;
VmessQRCode vmessQRCode = new()
{
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
};
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);
//转成Json
VmessQRCode? vmessQRCode = JsonUtils.Deserialize<VmessQRCode>(result);
if (vmessQRCode == null)
{
msg = ResUI.FailedConversionConfiguration;
return null;
}
item.network = Global.DefaultNetwork;
item.headerType = Global.None;
item.configVersion = Utils.ToInt(vmessQRCode.v);
item.remarks = Utils.ToString(vmessQRCode.ps);
item.address = Utils.ToString(vmessQRCode.add);
item.port = Utils.ToInt(vmessQRCode.port);
item.id = Utils.ToString(vmessQRCode.id);
item.alterId = Utils.ToInt(vmessQRCode.aid);
item.security = Utils.ToString(vmessQRCode.scy);
item.security = !Utils.IsNullOrEmpty(vmessQRCode.scy) ? vmessQRCode.scy : Global.DefaultSecurity;
if (!Utils.IsNullOrEmpty(vmessQRCode.net))
{
item.network = vmessQRCode.net;
}
if (!Utils.IsNullOrEmpty(vmessQRCode.type))
{
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;
}
private static ProfileItem? ResolveStdVmess(string result)
{
ProfileItem item = new()
{
configType = EConfigType.VMess,
security = "auto"
};
Uri u = new(result);
item.address = u.IdnHost;
item.port = u.Port;
item.remarks = u.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
var query = Utils.ParseQueryString(u.Query);
var m = StdVmessUserInfo.Match(u.UserInfo);
if (!m.Success) return null;
item.id = m.Groups["id"].Value;
if (m.Groups["streamSecurity"].Success)
{
item.streamSecurity = m.Groups["streamSecurity"].Value;
}
switch (item.streamSecurity)
{
case Global.StreamSecurity:
break;
default:
if (!Utils.IsNullOrEmpty(item.streamSecurity))
return null;
break;
}
item.network = m.Groups["network"].Value;
switch (item.network)
{
case nameof(ETransport.tcp):
string t1 = query["type"] ?? Global.None;
item.headerType = t1;
break;
case nameof(ETransport.kcp):
item.headerType = query["type"] ?? Global.None;
break;
case nameof(ETransport.ws):
case nameof(ETransport.httpupgrade):
case nameof(ETransport.splithttp):
string p1 = query["path"] ?? "/";
string h1 = query["host"] ?? "";
item.requestHost = Utils.UrlDecode(h1);
item.path = p1;
break;
case nameof(ETransport.http):
case nameof(ETransport.h2):
item.network = nameof(ETransport.h2);
string p2 = query["path"] ?? "/";
string h2 = query["host"] ?? "";
item.requestHost = Utils.UrlDecode(h2);
item.path = p2;
break;
case nameof(ETransport.quic):
string s = query["security"] ?? Global.None;
string k = query["key"] ?? "";
string t3 = query["type"] ?? Global.None;
item.headerType = t3;
item.requestHost = Utils.UrlDecode(s);
item.path = k;
break;
default:
return null;
}
return item;
}
private static ProfileItem? ResolveVmess4Kitsunebi(string result)
{
ProfileItem item = new()
{
configType = EConfigType.VMess
};
result = result[Global.ProtocolShares[EConfigType.VMess].Length..];
int indexSplit = result.IndexOf("?");
if (indexSplit > 0)
{
result = result[..indexSplit];
}
result = Utils.Base64Decode(result);
string[] arr1 = result.Split('@');
if (arr1.Length != 2)
{
return null;
}
string[] arr21 = arr1[0].Split(':');
string[] arr22 = arr1[1].Split(':');
if (arr21.Length != 2 || arr22.Length != 2)
{
return null;
}
item.address = arr22[0];
item.port = Utils.ToInt(arr22[1]);
item.security = arr21[0];
item.id = arr21[1];
item.network = Global.DefaultNetwork;
item.headerType = Global.None;
item.remarks = "Alien";
return item;
}
}
}

View file

@ -0,0 +1,73 @@
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
namespace v2rayN.Handler.Fmt
{
internal class WireguardFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
ProfileItem item = new()
{
configType = EConfigType.Wireguard
};
Uri url = new(str);
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 = Utils.UrlDecode(query["publickey"] ?? "");
item.path = Utils.UrlDecode(query["reserved"] ?? "");
item.requestHost = Utils.UrlDecode(query["address"] ?? "");
item.shortId = Utils.UrlDecode(query["mtu"] ?? "");
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null) return null;
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
var dicQuery = new Dictionary<string, string>();
if (!Utils.IsNullOrEmpty(item.publicKey))
{
dicQuery.Add("publickey", Utils.UrlEncode(item.publicKey));
}
if (!Utils.IsNullOrEmpty(item.path))
{
dicQuery.Add("reserved", Utils.UrlEncode(item.path));
}
if (!Utils.IsNullOrEmpty(item.requestHost))
{
dicQuery.Add("address", Utils.UrlEncode(item.requestHost));
}
if (!Utils.IsNullOrEmpty(item.shortId))
{
dicQuery.Add("mtu", Utils.UrlEncode(item.shortId));
}
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
url = string.Format("{0}@{1}:{2}",
Utils.UrlEncode(item.id),
GetIpv6(item.address),
item.port);
url = $"{Global.ProtocolShares[EConfigType.Wireguard]}{url}/{query}{remark}";
return url;
}
}
}

View file

@ -0,0 +1,417 @@
using System.Runtime.Intrinsics.X86;
using v2rayN.Enums;
using v2rayN.Models;
namespace v2rayN.Handler
{
public sealed class LazyConfig
{
private static readonly Lazy<LazyConfig> _instance = new(() => new());
private Config _config;
private List<CoreInfo> coreInfo;
public static LazyConfig Instance => _instance.Value;
private int? _statePort;
private int? _statePort2;
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;
}
}
private Job _processJob = new();
public LazyConfig()
{
SQLiteHelper.Instance.CreateTable<SubItem>();
SQLiteHelper.Instance.CreateTable<ProfileItem>();
SQLiteHelper.Instance.CreateTable<ServerStatItem>();
SQLiteHelper.Instance.CreateTable<RoutingItem>();
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
SQLiteHelper.Instance.CreateTable<DNSItem>();
}
#region Config
public void SetConfig(Config config)
{
_config = config;
}
public Config GetConfig()
{
return _config;
}
public int GetLocalPort(EInboundProtocol protocol)
{
var localPort = _config.inbound.FirstOrDefault(t => t.protocol == nameof(EInboundProtocol.socks))?.localPort ?? 10808;
return localPort + (int)protocol;
}
public void AddProcess(IntPtr processHandle)
{
_processJob.AddProcess(processHandle);
}
#endregion Config
#region SqliteHelper
public List<SubItem> SubItems()
{
return SQLiteHelper.Instance.Table<SubItem>().ToList();
}
public SubItem GetSubItem(string subid)
{
return SQLiteHelper.Instance.Table<SubItem>().FirstOrDefault(t => t.id == subid);
}
public List<ProfileItem> ProfileItems(string subid = null)
{
if (Utils.IsNullOrEmpty(subid))
{
return SQLiteHelper.Instance.Table<ProfileItem>().ToList();
}
else
{
return SQLiteHelper.Instance.Table<ProfileItem>().Where(t => t.subid == subid).ToList();
}
}
public List<string> ProfileItemIndexes(string subid)
{
if (Utils.IsNullOrEmpty(subid))
{
return SQLiteHelper.Instance.Table<ProfileItem>().Select(t => t.indexId).ToList();
}
else
{
return SQLiteHelper.Instance.Table<ProfileItem>().Where(t => t.subid == subid).Select(t => t.indexId).ToList();
}
}
public 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 (!Utils.IsNullOrEmpty(subid))
{
sql += $" and a.subid = '{subid}'";
}
if (!Utils.IsNullOrEmpty(filter))
{
if (filter.Contains('\''))
{
filter = filter.Replace("'", "");
}
sql += String.Format(" and (a.remarks like '%{0}%' or a.address like '%{0}%') ", filter);
}
return SQLiteHelper.Instance.Query<ProfileItemModel>(sql).ToList();
}
public ProfileItem? GetProfileItem(string indexId)
{
if (Utils.IsNullOrEmpty(indexId))
{
return null;
}
return SQLiteHelper.Instance.Table<ProfileItem>().FirstOrDefault(it => it.indexId == indexId);
}
public ProfileItem? GetProfileItemViaRemarks(string remarks)
{
if (Utils.IsNullOrEmpty(remarks))
{
return null;
}
return SQLiteHelper.Instance.Table<ProfileItem>().FirstOrDefault(it => it.remarks == remarks);
}
public List<RoutingItem> RoutingItems()
{
return SQLiteHelper.Instance.Table<RoutingItem>().Where(it => it.locked == false).OrderBy(t => t.sort).ToList();
}
public RoutingItem GetRoutingItem(string id)
{
return SQLiteHelper.Instance.Table<RoutingItem>().FirstOrDefault(it => it.locked == false && it.id == id);
}
public List<DNSItem> DNSItems()
{
return SQLiteHelper.Instance.Table<DNSItem>().ToList();
}
public DNSItem GetDNSItem(ECoreType eCoreType)
{
return SQLiteHelper.Instance.Table<DNSItem>().FirstOrDefault(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.SsSecuritiesInSagerNet;
}
public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType)
{
if (profileItem?.coreType != null)
{
return (ECoreType)profileItem.coreType;
}
if (_config.coreTypeItem == null)
{
return ECoreType.Xray;
}
var item = _config.coreTypeItem.FirstOrDefault(it => it.configType == eConfigType);
if (item == null)
{
return ECoreType.Xray;
}
return item.coreType;
}
public CoreInfo? GetCoreInfo(ECoreType coreType)
{
if (coreInfo == null)
{
InitCoreInfo();
}
return coreInfo?.FirstOrDefault(t => t.coreType == coreType);
}
public List<CoreInfo> GetCoreInfo()
{
if (coreInfo == null)
{
InitCoreInfo();
}
return coreInfo!;
}
private void InitCoreInfo()
{
coreInfo = new(16);
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.v2rayN,
coreUrl = Global.NUrl,
coreReleaseApiUrl = Global.NUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.NUrl + "/download/{0}/v2rayN-32.zip",
coreDownloadUrl64 = Global.NUrl + "/download/{0}/v2rayN.zip",
coreDownloadUrlArm64 = Global.NUrl + "/download/{0}/v2rayN-arm64.zip"
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.v2fly,
coreExes = new List<string> { "wv2ray", "v2ray" },
arguments = "",
coreUrl = Global.V2flyCoreUrl,
coreReleaseApiUrl = Global.V2flyCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.V2flyCoreUrl + "/download/{0}/v2ray-windows-{1}.zip",
coreDownloadUrl64 = Global.V2flyCoreUrl + "/download/{0}/v2ray-windows-{1}.zip",
coreDownloadUrlArm64 = Global.V2flyCoreUrl + "/download/{0}/v2ray-windows-{1}.zip",
match = "V2Ray",
versionArg = "-version",
redirectInfo = true,
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.SagerNet,
coreExes = new List<string> { "SagerNet", "v2ray" },
arguments = "run",
coreUrl = Global.SagerNetCoreUrl,
coreReleaseApiUrl = Global.SagerNetCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.SagerNetCoreUrl + "/download/{0}/v2ray-windows-{1}.zip",
coreDownloadUrl64 = Global.SagerNetCoreUrl + "/download/{0}/v2ray-windows-{1}.zip",
coreDownloadUrlArm64 = Global.SagerNetCoreUrl + "/download/{0}/v2ray-windows-{1}.zip",
match = "V2Ray",
versionArg = "version",
redirectInfo = true,
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.v2fly_v5,
coreExes = new List<string> { "v2ray" },
arguments = "run -c config.json -format jsonv5",
coreUrl = Global.V2flyCoreUrl,
coreReleaseApiUrl = Global.V2flyCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.V2flyCoreUrl + "/download/{0}/v2ray-windows-{1}.zip",
coreDownloadUrl64 = Global.V2flyCoreUrl + "/download/{0}/v2ray-windows-{1}.zip",
coreDownloadUrlArm64 = Global.V2flyCoreUrl + "/download/{0}/v2ray-windows-{1}.zip",
match = "V2Ray",
versionArg = "version",
redirectInfo = true,
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.Xray,
coreExes = new List<string> { "xray", "wxray" },
arguments = "run {0}",
coreUrl = Global.XrayCoreUrl,
coreReleaseApiUrl = Global.XrayCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-{1}.zip",
coreDownloadUrl64 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-{1}.zip",
coreDownloadUrlArm64 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-{1}.zip",
match = "Xray",
versionArg = "-version",
redirectInfo = true,
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.clash,
coreExes = new List<string> { "clash-windows-amd64-v3", "clash-windows-amd64", "clash-windows-386", "clash" },
arguments = "-f config.json",
coreUrl = Global.ClashCoreUrl,
coreReleaseApiUrl = Global.ClashCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.ClashCoreUrl + "/download/{0}/clash-windows-386-{0}.zip",
coreDownloadUrl64 = Global.ClashCoreUrl + "/download/{0}/clash-windows-amd64-{0}.zip",
coreDownloadUrlArm64 = Global.ClashCoreUrl + "/download/{0}/clash-windows-arm64-{0}.zip",
match = "v",
versionArg = "-v",
redirectInfo = true,
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.clash_meta,
coreExes = new List<string> { "Clash.Meta-windows-amd64-compatible", "Clash.Meta-windows-amd64", "Clash.Meta-windows-386", "Clash.Meta", "clash" },
arguments = "-f config.json",
coreUrl = Global.ClashMetaCoreUrl,
coreReleaseApiUrl = Global.ClashMetaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.ClashMetaCoreUrl + "/download/{0}/Clash.Meta-windows-386-{0}.zip",
coreDownloadUrl64 = Global.ClashMetaCoreUrl + "/download/{0}/Clash.Meta-windows-amd64-compatible-{0}.zip",
coreDownloadUrlArm64 = Global.ClashMetaCoreUrl + "/download/{0}/Clash.Meta-windows-arm64-{0}.zip",
match = "v",
versionArg = "-v",
redirectInfo = true,
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.mihomo,
coreExes = new List<string> { $"mihomo-windows-amd64{(Avx2.X64.IsSupported ? "" : "-compatible")}", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-windows-386", "mihomo", "clash" },
arguments = "-f config.json",
coreUrl = Global.MihomoCoreUrl,
coreReleaseApiUrl = Global.MihomoCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-windows-386-{0}.zip",
coreDownloadUrl64 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-windows-amd64-compatible-{0}.zip",
coreDownloadUrlArm64 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-windows-arm64-{0}.zip",
match = "Mihomo",
versionArg = "-v",
redirectInfo = true,
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.hysteria,
coreExes = new List<string> { "hysteria-windows-amd64", "hysteria-windows-386", "hysteria" },
arguments = "",
coreUrl = Global.HysteriaCoreUrl,
coreReleaseApiUrl = Global.HysteriaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.HysteriaCoreUrl + "/download/{0}/hysteria-windows-386.exe",
coreDownloadUrl64 = Global.HysteriaCoreUrl + "/download/{0}/hysteria-windows-amd64.exe",
coreDownloadUrlArm64 = Global.HysteriaCoreUrl + "/download/{0}/hysteria-windows-arm64.exe",
redirectInfo = true,
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.naiveproxy,
coreExes = new List<string> { "naiveproxy", "naive" },
arguments = "config.json",
coreUrl = Global.NaiveproxyCoreUrl,
redirectInfo = false,
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.tuic,
coreExes = new List<string> { "tuic-client", "tuic" },
arguments = "-c config.json",
coreUrl = Global.TuicCoreUrl,
redirectInfo = true,
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.sing_box,
coreExes = new List<string> { "sing-box-client", "sing-box" },
arguments = "run {0} --disable-color",
coreUrl = Global.SingboxCoreUrl,
redirectInfo = true,
coreReleaseApiUrl = Global.SingboxCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-386.zip",
coreDownloadUrl64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-amd64.zip",
coreDownloadUrlArm64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-arm64.zip",
match = "sing-box",
versionArg = "version",
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.juicity,
coreExes = new List<string> { "juicity-client", "juicity" },
arguments = "run -c config.json",
coreUrl = Global.JuicityCoreUrl
});
coreInfo.Add(new CoreInfo
{
coreType = ECoreType.hysteria2,
coreExes = new List<string> { "hysteria-windows-amd64", "hysteria-windows-386", "hysteria" },
arguments = "",
coreUrl = Global.HysteriaCoreUrl,
coreReleaseApiUrl = Global.HysteriaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
coreDownloadUrl32 = Global.HysteriaCoreUrl + "/download/{0}/hysteria-windows-386.exe",
coreDownloadUrl64 = Global.HysteriaCoreUrl + "/download/{0}/hysteria-windows-amd64.exe",
coreDownloadUrlArm64 = Global.HysteriaCoreUrl + "/download/{0}/hysteria-windows-arm64.exe",
redirectInfo = true,
});
}
#endregion Core Type
}
}

View file

@ -0,0 +1,112 @@
using v2rayN.Models;
using v2rayMiniConsole;
using System.Collections.Concurrent;
namespace v2rayN.Handler
{
public sealed class MainFormHandler
{
private static readonly Lazy<MainFormHandler> instance = new(() => new());
public static MainFormHandler Instance => instance.Value;
// 使用ConcurrentBag来存储任务因为它提供了线程安全的集合
public readonly ConcurrentBag<Task> MainFormTasks = new ConcurrentBag<Task>();
public void UpdateTask(Config config, Action<bool, string> update)
{
MainFormTasks.Add(Task.Run(() => TimedTask()));
MainFormTasks.Add(Task.Run(() => UpdateTaskRunSubscription(config, update)));
MainFormTasks.Add(Task.Run(() => UpdateTaskRunGeo(config, update)));
}
private async Task TimedTask()
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(MainTask.Instance.GetCancellationToken()))
{
while (!cts.Token.IsCancellationRequested)
{
await Task.Delay(30000, cts.Token); // 传入CancellationToken以便在取消时中断Delay
MainTask.Instance.TestServerAvailability();
}
}
}
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> update)
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(MainTask.Instance.GetCancellationToken()))
{
Logging.SaveLog("UpdateTaskRunSubscription");
var updateHandle = new UpdateHandle();
while (!cts.IsCancellationRequested)
{
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
var lstSubs = LazyConfig.Instance.SubItems();
if (!lstSubs.Any())
{
RunningObjects.Instance.SetStatus("Subscription list is empty, use \"add_subscription\" to add at least one.");
}
else
{
await Task.Delay(5000, cts.Token);
}
lstSubs = lstSubs.Where(t => t.autoUpdateInterval > 0)
.Where(t => updateTime - t.updateTime >= t.autoUpdateInterval * 60)
.ToList();
if (lstSubs.Any())
{
RunningObjects.Instance.ProfileItems?.Clear();
}
foreach (var item in lstSubs)
{
updateHandle.UpdateSubscriptionProcess(config, item.id, true, (bool success, string msg) =>
{
update(success, msg);
if (success)
{
Logging.SaveLog("subscription" + msg);
}
});
item.updateTime = updateTime;
ConfigHandler.AddSubItem(config, item);
await Task.Delay(5000, cts.Token);
}
await Task.Delay(60000, cts.Token);
}
}
}
private async Task UpdateTaskRunGeo(Config config, Action<bool, string> update)
{
var autoUpdateGeoTime = DateTime.Now;
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(MainTask.Instance.GetCancellationToken()))
{
await Task.Delay(1000 * 120, cts.Token);
Logging.SaveLog("UpdateTaskRunGeo");
var updateHandle = new UpdateHandle();
while (!cts.IsCancellationRequested)
{
var dtNow = DateTime.Now;
if (config.guiItem.autoUpdateInterval > 0)
{
if ((dtNow - autoUpdateGeoTime).Hours % config.guiItem.autoUpdateInterval == 0)
{
updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
{
update(false, msg);
});
autoUpdateGeoTime = dtNow;
}
}
await Task.Delay(1000 * 3600, cts.Token);
}
}
}
}
}

View file

@ -0,0 +1,24 @@
using v2rayMiniConsole;
namespace v2rayN.Handler
{
public class NoticeHandler
{
public void SendMessage(string msg)
{
if (RunningObjects.Instance.IsMessageOn())
{
Console.WriteLine($"message: {msg}");
}
}
public void SendMessage(string msg, bool time)
{
if (RunningObjects.Instance.IsMessageOn())
{
msg = $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} {msg}";
SendMessage(msg);
}
}
}
}

View file

@ -0,0 +1,153 @@
using System.Collections.Concurrent;
using System.Linq;
using v2rayN.Models;
namespace v2rayN.Handler
{
internal class ProfileExHandler
{
private static readonly Lazy<ProfileExHandler> _instance = new(() => new());
private ConcurrentBag<ProfileExItem> _lstProfileEx = [];
private Queue<string> _queIndexIds = new();
public ConcurrentBag<ProfileExItem> ProfileExs => _lstProfileEx;
public static ProfileExHandler Instance => _instance.Value;
private void IndexIdEnqueue(string indexId)
{
if (!Utils.IsNullOrEmpty(indexId) && !_queIndexIds.Contains(indexId))
{
_queIndexIds.Enqueue(indexId);
}
}
private void SaveQueueIndexIds()
{
var cnt = _queIndexIds.Count;
if (cnt > 0)
{
var lstExists = SQLiteHelper.Instance.Table<ProfileExItem>();
List<ProfileExItem> lstInserts = [];
List<ProfileExItem> lstUpdates = [];
for (int i = 0; i < cnt; i++)
{
var id = _queIndexIds.Dequeue();
var item = lstExists.FirstOrDefault(t => t.indexId == id);
var itemNew = _lstProfileEx?.FirstOrDefault(t => t.indexId == id);
if (itemNew is null)
{
continue;
}
if (item is not null)
{
lstUpdates.Add(itemNew);
}
else
{
lstInserts.Add(itemNew);
}
}
try
{
if (lstInserts.Count() > 0)
SQLiteHelper.Instance.InsertAll(lstInserts);
if (lstUpdates.Count() > 0)
SQLiteHelper.Instance.UpdateAll(lstUpdates);
}
catch (Exception ex)
{
Logging.SaveLog("ProfileExHandler", ex);
}
}
}
private void AddProfileEx(string indexId, ref ProfileExItem? profileEx)
{
profileEx = new()
{
indexId = indexId,
delay = 0,
speed = 0,
sort = 0
};
_lstProfileEx.Add(profileEx);
IndexIdEnqueue(indexId);
}
public void ClearAll()
{
SQLiteHelper.Instance.Execute($"delete from ProfileExItem ");
_lstProfileEx = new();
}
public void SaveTo()
{
try
{
SaveQueueIndexIds();
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
public void SetTestDelay(string indexId, string delayVal)
{
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
if (profileEx == null)
{
AddProfileEx(indexId, ref profileEx);
}
int.TryParse(delayVal, out int delay);
profileEx.delay = delay;
IndexIdEnqueue(indexId);
}
public void SetTestSpeed(string indexId, string speedVal)
{
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
if (profileEx == null)
{
AddProfileEx(indexId, ref profileEx);
}
decimal.TryParse(speedVal, out decimal speed);
profileEx.speed = speed;
IndexIdEnqueue(indexId);
}
public void SetSort(string indexId, int sort)
{
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
if (profileEx == null)
{
AddProfileEx(indexId, ref profileEx);
}
profileEx.sort = sort;
IndexIdEnqueue(indexId);
}
public int GetSort(string indexId)
{
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
if (profileEx == null)
{
return 0;
}
return profileEx.sort;
}
public int GetMaxSort()
{
if (_lstProfileEx.Count <= 0)
{
return 0;
}
return _lstProfileEx.Max(t => t == null ? 0 : t.sort);
}
}
}

View file

@ -0,0 +1,323 @@
using System.Runtime.InteropServices;
using static v2rayN.Handler.ProxySetting.InternetConnectionOption;
namespace v2rayN.Handler
{
internal class ProxySetting
{
/// <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)
{
// set proxy for LAN
bool 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;
}
private static bool SetConnectionProxy(string? connectionName, string? strProxy, string? exceptions, int type)
{
InternetPerConnOptionList list = new();
int optionCount = 1;
if (type == 1) // No proxy
{
optionCount = 1;
}
else if (type is 2 or 4) // named proxy or autoproxy script URL
{
optionCount = Utils.IsNullOrEmpty(exceptions) ? 2 : 3;
}
int m_Int = (int)PerConnFlags.PROXY_TYPE_DIRECT;
PerConnOption 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;
}
//int optionCount = Utile.IsNullOrEmpty(strProxy) ? 1 : (Utile.IsNullOrEmpty(exceptions) ? 2 : 3);
InternetConnectionOption[] options = new InternetConnectionOption[optionCount];
// USE a proxy server ...
options[0].m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS;
//options[0].m_Value.m_Int = (int)((optionCount < 2) ? PerConnFlags.PROXY_TYPE_DIRECT : (PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_PROXY));
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 = IntPtr.Zero;
}
list.dwOptionCount = options.Length;
list.dwOptionError = 0;
int optSize = Marshal.SizeOf(typeof(InternetConnectionOption));
// make a pointer out of all that ...
IntPtr optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length); // !! remember to deallocate memory 4
// copy the array over into that spot in memory ...
for (int i = 0; i < options.Length; ++i)
{
if (Environment.Is64BitOperatingSystem)
{
IntPtr opt = new(optionsPtr.ToInt64() + (i * optSize));
Marshal.StructureToPtr(options[i], opt, false);
}
else
{
IntPtr opt = new(optionsPtr.ToInt32() + (i * optSize));
Marshal.StructureToPtr(options[i], opt, false);
}
}
list.options = optionsPtr;
// and then make a pointer out of the whole list
IntPtr ipcoListPtr = Marshal.AllocCoTaskMem(list.dwSize); // !! remember to deallocate memory 5
Marshal.StructureToPtr(list, ipcoListPtr, false);
// and finally, call the API method!
bool isSuccess = NativeMethods.InternetSetOption(IntPtr.Zero,
InternetOption.INTERNET_OPTION_PER_CONNECTION_OPTION,
ipcoListPtr, list.dwSize);
int 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(IntPtr.Zero, InternetOption.INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
NativeMethods.InternetSetOption(IntPtr.Zero, InternetOption.INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
}
// FREE the data ASAP
if (list.szConnection != IntPtr.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()
{
int entries = 0;
// attempt to query with 1 entry buffer
RASENTRYNAME[] rasEntryNames = new RASENTRYNAME[1];
int bufferSize = Marshal.SizeOf(typeof(RASENTRYNAME));
rasEntryNames[0].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME));
uint 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 (int 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 (int 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 IntPtr 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 IntPtr 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 IntPtr 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(IntPtr hInternet, InternetOption dwOption, IntPtr 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
);
}
}
}

View file

@ -0,0 +1,418 @@
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
using v2rayMiniConsole;
using NLog.LayoutRenderers;
namespace v2rayN.Handler
{
internal class SpeedtestHandler
{
private Config? _config;
private CoreHandler _coreHandler;
private List<ServerTestItem> _selecteds;
private ESpeedActionType _actionType;
private Action<string, string, string> _updateFunc;
public SpeedtestHandler(Config config)
{
_config = config;
}
public SpeedtestHandler(Config config, CoreHandler coreHandler, List<ProfileItem> selecteds, ESpeedActionType actionType, Action<string, string, string> update)
{
_config = config;
_coreHandler = coreHandler;
_actionType = actionType;
_updateFunc = update;
_selecteds = new List<ServerTestItem>();
foreach (var it in selecteds)
{
if (it.configType == EConfigType.Custom)
{
continue;
}
if (it.port <= 0)
{
continue;
}
_selecteds.Add(new ServerTestItem()
{
indexId = it.indexId,
address = it.address,
port = it.port,
configType = it.configType,
allowTest = true
});
}
//clear test result
foreach (var it in _selecteds)
{
switch (actionType)
{
case ESpeedActionType.Tcping:
case ESpeedActionType.Realping:
UpdateFunc(it.indexId, ResUI.Speedtesting, "");
ProfileExHandler.Instance.SetTestDelay(it.indexId, "0");
break;
case ESpeedActionType.Speedtest:
UpdateFunc(it.indexId, "", ResUI.SpeedtestingWait);
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "0");
break;
case ESpeedActionType.Mixedtest:
UpdateFunc(it.indexId, ResUI.Speedtesting, ResUI.SpeedtestingWait);
ProfileExHandler.Instance.SetTestDelay(it.indexId, "0");
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "0");
break;
}
}
switch (actionType)
{
case ESpeedActionType.Tcping:
Task.Run(RunTcping);
break;
case ESpeedActionType.Realping:
Task.Run(RunRealPing);
break;
case ESpeedActionType.Speedtest:
Task.Run(RunSpeedTestAsync);
break;
case ESpeedActionType.Mixedtest:
Task.Run(RunMixedtestAsync);
break;
}
}
private Task RunTcping()
{
try
{
List<Task> tasks = [];
foreach (var it in _selecteds)
{
if (it.configType == EConfigType.Custom)
{
continue;
}
tasks.Add(Task.Run(() =>
{
try
{
int time = GetTcpingTime(it.address, it.port);
var output = FormatOut(time, Global.DelayUnit);
ProfileExHandler.Instance.SetTestDelay(it.indexId, output);
UpdateFunc(it.indexId, output);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}));
}
Task.WaitAll([.. tasks]);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
finally
{
ProfileExHandler.Instance.SaveTo();
}
return Task.CompletedTask;
}
private async Task RunRealPing()
{
int pid = -1;
try
{
string msg = string.Empty;
pid = _coreHandler.LoadCoreConfigSpeedtest(_selecteds);
if (pid < 0)
{
UpdateFunc("", ResUI.FailedToRunCore);
return;
}
DownloadHandle downloadHandle = new DownloadHandle();
_selecteds = _selecteds.Where(t => t.allowTest && t.configType != EConfigType.Custom).ToList();
var cts = CancellationTokenSource.CreateLinkedTokenSource(MainTask.Instance.GetCancellationToken());
int totalTasks = _selecteds.Count;
int completedTasks = 0;
List<Task> tasks = new();
foreach (var it in _selecteds)
{
if (cts.IsCancellationRequested)
{
//Console.WriteLine("Cancellation received, giving up testing...");
break;
}
if (!it.allowTest || it.configType == EConfigType.Custom)
{
continue;
}
tasks.Add(Task.Run(async () =>
{
try
{
WebProxy webProxy = new(Global.Loopback, it.port);
string output = await GetRealPingTime(downloadHandle, webProxy);
ProfileExHandler.Instance.SetTestDelay(it.indexId, output);
UpdateFunc(it.indexId, output);
int.TryParse(output, out int delay);
it.delay = delay;
lock (tasks)
{
completedTasks++;
RunningObjects.Instance.SetStatus("real ping test", totalTasks, completedTasks);
//int percentage = (completedTasks * 100) / totalTasks;
//string percentageStr = $"{completedTasks}/{totalTasks}";
//// 更新进度条和百分比在同一行
//string progressBar = new string('#', (int)(percentage * 0.2)) + new string('-', 20 - (int)(percentage * 0.2)); // 假设进度条长度为20个字符
// //Console.Write("\r[{0}] {1}%", progressBar, percentage);
//if (!RunningObjects.Instance.IsMessageOn())
//{
// Console.Write("\r[{0}] {1}", progressBar, percentageStr);
//}
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}));
}
await Task.WhenAll(tasks);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
finally
{
if (pid > 0)
{
_coreHandler.CoreStopPid(pid);
}
ProfileExHandler.Instance.SaveTo();
}
}
private async Task RunSpeedTestAsync()
{
int pid = -1;
//if (_actionType == ESpeedActionType.Mixedtest)
//{
// _selecteds = _selecteds.OrderBy(t => t.delay).ToList();
//}
pid = _coreHandler.LoadCoreConfigSpeedtest(_selecteds);
if (pid < 0)
{
UpdateFunc("", ResUI.FailedToRunCore);
return;
}
string url = _config.speedTestItem.speedTestUrl;
var timeout = _config.speedTestItem.speedTestTimeout;
DownloadHandle downloadHandle = new();
foreach (var it in _selecteds)
{
if (!it.allowTest)
{
continue;
}
if (it.configType == EConfigType.Custom)
{
continue;
}
//if (it.delay < 0)
//{
// UpdateFunc(it.indexId, "", ResUI.SpeedtestingSkip);
// continue;
//}
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "-1");
UpdateFunc(it.indexId, "", ResUI.Speedtesting);
var item = LazyConfig.Instance.GetProfileItem(it.indexId);
if (item is null) continue;
WebProxy webProxy = new(Global.Loopback, it.port);
await downloadHandle.DownloadDataAsync(url, webProxy, timeout, (bool success, string msg) =>
{
decimal.TryParse(msg, out decimal dec);
if (dec > 0)
{
ProfileExHandler.Instance.SetTestSpeed(it.indexId, msg);
}
UpdateFunc(it.indexId, "", msg);
});
}
if (pid > 0)
{
_coreHandler.CoreStopPid(pid);
}
UpdateFunc("", ResUI.SpeedtestingCompleted);
ProfileExHandler.Instance.SaveTo();
}
private async Task RunSpeedTestMulti()
{
int pid = -1;
pid = _coreHandler.LoadCoreConfigSpeedtest(_selecteds);
if (pid < 0)
{
UpdateFunc("", ResUI.FailedToRunCore);
return;
}
string url = _config.speedTestItem.speedTestUrl;
var timeout = _config.speedTestItem.speedTestTimeout;
DownloadHandle downloadHandle = new();
_selecteds = _selecteds.Where(t => t.delay > 0).ToList();
int totalTasks = _selecteds.Count;
if (totalTasks == 0)
{
UpdateFunc("", ResUI.SpeedtestingCompleted);
return;
}
int completedTasks = 0;
var cts = CancellationTokenSource.CreateLinkedTokenSource(MainTask.Instance.GetCancellationToken());
//Console.WriteLine("start speed testing");
foreach (var it in _selecteds)
{
if (cts.IsCancellationRequested)
{
//Console.WriteLine("Cancellation received, giving up testing...");
break;
}
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "-1");
UpdateFunc(it.indexId, "", ResUI.Speedtesting);
var item = RunningObjects.Instance.ProfileItems.Where(t => t.indexId == it.indexId).FirstOrDefault();
if (item is null) continue;
WebProxy webProxy = new(Global.Loopback, it.port);
_ = downloadHandle.DownloadDataAsync(url, webProxy, timeout, (bool success, string msg) =>
{
decimal.TryParse(msg, out decimal dec);
if (dec > 0)
{
ProfileExHandler.Instance.SetTestSpeed(it.indexId, msg);
}
UpdateFunc(it.indexId, "", msg);
});
{
completedTasks++;
RunningObjects.Instance.SetStatus("speed test", totalTasks, completedTasks);
//int percentage = (completedTasks * 100) / totalTasks;
//string percentageStr = $"{completedTasks}/{totalTasks}";
//// 更新进度条和百分比在同一行
//string progressBar = new string('#', (int)(percentage * 0.2)) + new string('-', 20 - (int)(percentage * 0.2)); // 假设进度条长度为20个字符
////Console.Write("\r[{0}] {1}%", progressBar, percentage);
//if (!RunningObjects.Instance.IsMessageOn())
//{
// Console.Write("\r[{0}] {1}", progressBar, percentageStr);
//}
}
await Task.Delay(2000);
}
await Task.Delay((timeout + 2) * 1000, cts.Token);
if (pid > 0)
{
_coreHandler.CoreStopPid(pid);
}
UpdateFunc("", ResUI.SpeedtestingCompleted);
//ProfileExHandler.Instance.SaveTo();
}
private async Task RunMixedtestAsync()
{
await RunRealPing();
await Task.Delay(1000);
await RunSpeedTestMulti();
}
private async Task<string> GetRealPingTime(DownloadHandle downloadHandle, IWebProxy webProxy)
{
int responseTime = await downloadHandle.GetRealPingTime(_config.speedTestItem.speedPingTestUrl, webProxy, 10);
//string output = Utile.IsNullOrEmpty(status) ? FormatOut(responseTime, "ms") : status;
return FormatOut(responseTime, Global.DelayUnit);
}
private int GetTcpingTime(string url, int port)
{
int responseTime = -1;
try
{
if (!IPAddress.TryParse(url, out IPAddress? ipAddress))
{
IPHostEntry ipHostInfo = System.Net.Dns.GetHostEntry(url);
ipAddress = ipHostInfo.AddressList[0];
}
Stopwatch timer = new();
timer.Start();
IPEndPoint endPoint = new(ipAddress, port);
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
IAsyncResult result = clientSocket.BeginConnect(endPoint, null, null);
if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5)))
throw new TimeoutException("connect timeout (5s): " + url);
clientSocket.EndConnect(result);
timer.Stop();
clientSocket.Close();
responseTime = timer.Elapsed.Milliseconds;
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
return responseTime;
}
private string FormatOut(object time, string unit)
{
//if (time.ToString().Equals("-1"))
//{
// return "Timeout";
//}
return $"{time}";
}
private void UpdateFunc(string indexId, string delay, string speed = "")
{
_updateFunc(indexId, delay, speed);
}
}
}

View file

@ -0,0 +1,135 @@
using v2rayN.Models;
namespace v2rayN.Handler
{
internal class StatisticsHandler
{
private Config _config;
private ServerStatItem? _serverStatItem;
private List<ServerStatItem> _lstServerStat;
private Action<ServerSpeedItem> _updateFunc;
private StatisticsV2ray? _statisticsV2Ray;
private StatisticsSingbox? _statisticsSingbox;
public List<ServerStatItem> ServerStat => _lstServerStat;
public bool Enable { get; set; }
public StatisticsHandler(Config config, Action<ServerSpeedItem> update)
{
_config = config;
Enable = config.guiItem.enableStatistics;
if (!Enable)
{
return;
}
_updateFunc = update;
Init();
_statisticsV2Ray = new StatisticsV2ray(config, UpdateServerStat);
_statisticsSingbox = new StatisticsSingbox(config, UpdateServerStat);
}
public void Close()
{
try
{
_statisticsV2Ray?.Close();
_statisticsSingbox?.Close();
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
public void ClearAllServerStatistics()
{
SQLiteHelper.Instance.Execute($"delete from ServerStatItem ");
_serverStatItem = null;
_lstServerStat = new();
}
public void SaveTo()
{
try
{
SQLiteHelper.Instance.UpdateAll(_lstServerStat);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
private void Init()
{
SQLiteHelper.Instance.Execute($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )");
long ticks = DateTime.Now.Date.Ticks;
SQLiteHelper.Instance.Execute($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}");
_lstServerStat = SQLiteHelper.Instance.Table<ServerStatItem>().ToList();
}
private void UpdateServerStat(ServerSpeedItem server)
{
GetServerStatItem(_config.indexId);
if (_serverStatItem is null)
{
return;
}
if (server.proxyUp != 0 || server.proxyDown != 0)
{
_serverStatItem.todayUp += server.proxyUp;
_serverStatItem.todayDown += server.proxyDown;
_serverStatItem.totalUp += server.proxyUp;
_serverStatItem.totalDown += server.proxyDown;
}
server.indexId = _config.indexId;
server.todayUp = _serverStatItem.todayUp;
server.todayDown = _serverStatItem.todayDown;
server.totalUp = _serverStatItem.totalUp;
server.totalDown = _serverStatItem.totalDown;
_updateFunc(server);
}
private void GetServerStatItem(string indexId)
{
long ticks = DateTime.Now.Date.Ticks;
if (_serverStatItem != null && _serverStatItem.indexId != indexId)
{
_serverStatItem = null;
}
if (_serverStatItem == null)
{
_serverStatItem = _lstServerStat.FirstOrDefault(t => t.indexId == indexId);
if (_serverStatItem == null)
{
_serverStatItem = new ServerStatItem
{
indexId = indexId,
totalUp = 0,
totalDown = 0,
todayUp = 0,
todayDown = 0,
dateNow = ticks
};
SQLiteHelper.Instance.Replace(_serverStatItem);
_lstServerStat.Add(_serverStatItem);
}
}
if (_serverStatItem.dateNow != ticks)
{
_serverStatItem.todayUp = 0;
_serverStatItem.todayDown = 0;
_serverStatItem.dateNow = ticks;
}
}
}
}

View file

@ -0,0 +1,130 @@
using System.Net.WebSockets;
using System.Text;
using v2rayN.Enums;
using v2rayN.Models;
namespace v2rayN.Handler
{
internal class StatisticsSingbox
{
private Config _config;
private bool _exitFlag;
private ClientWebSocket? webSocket;
private string url = string.Empty;
private Action<ServerSpeedItem> _updateFunc;
public StatisticsSingbox(Config config, Action<ServerSpeedItem> update)
{
_config = config;
_updateFunc = update;
_exitFlag = false;
Task.Run(() => Run());
}
private async void Init()
{
await Task.Delay(5000);
try
{
url = $"ws://{Global.Loopback}:{LazyConfig.Instance.StatePort2}/traffic";
if (webSocket == null)
{
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(url), CancellationToken.None);
}
}
catch { }
}
public void Close()
{
try
{
_exitFlag = true;
if (webSocket != null)
{
webSocket.Abort();
webSocket = null;
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
private async void Run()
{
Init();
while (!_exitFlag)
{
await Task.Delay(1000);
try
{
if (!(_config.runningCoreType is ECoreType.sing_box or ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo))
{
continue;
}
if (webSocket != null)
{
if (webSocket.State == WebSocketState.Aborted
|| webSocket.State == WebSocketState.Closed)
{
webSocket.Abort();
webSocket = null;
Init();
continue;
}
if (webSocket.State != WebSocketState.Open)
{
continue;
}
var buffer = new byte[1024];
var res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!res.CloseStatus.HasValue)
{
var result = Encoding.UTF8.GetString(buffer, 0, res.Count);
if (!Utils.IsNullOrEmpty(result))
{
ParseOutput(result, out ulong up, out ulong down);
_updateFunc(new ServerSpeedItem()
{
proxyUp = (long)(up / 1000),
proxyDown = (long)(down / 1000)
});
}
res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
}
}
catch
{
}
}
}
private void ParseOutput(string source, out ulong up, out ulong down)
{
up = 0; down = 0;
try
{
var trafficItem = JsonUtils.Deserialize<TrafficItem>(source);
if (trafficItem != null)
{
up = trafficItem.up;
down = trafficItem.down;
}
}
catch
{
}
}
}
}

View file

@ -0,0 +1,137 @@
using Grpc.Core;
using Grpc.Net.Client;
using ProtosLib.Statistics;
using v2rayN.Enums;
using v2rayN.Models;
namespace v2rayN.Handler
{
internal class StatisticsV2ray
{
private Models.Config _config;
private GrpcChannel? _channel;
private StatsService.StatsServiceClient? _client;
private bool _exitFlag;
private Action<ServerSpeedItem> _updateFunc;
public StatisticsV2ray(Models.Config config, Action<ServerSpeedItem> update)
{
_config = config;
_updateFunc = update;
_exitFlag = false;
GrpcInit();
Task.Run(Run);
}
private void GrpcInit()
{
if (_channel is null)
{
try
{
_channel = GrpcChannel.ForAddress($"{Global.HttpProtocol}{Global.Loopback}:{LazyConfig.Instance.StatePort}");
_client = new StatsService.StatsServiceClient(_channel);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
}
}
public void Close()
{
_exitFlag = true;
}
private async void Run()
{
while (!_exitFlag)
{
await Task.Delay(1000);
try
{
if (!(_config.runningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5 or ECoreType.SagerNet))
{
continue;
}
if (_channel?.State == ConnectivityState.Ready)
{
QueryStatsResponse? res = null;
try
{
if (_client != null)
res = await _client.QueryStatsAsync(new QueryStatsRequest() { Pattern = "", Reset = true });
}
catch
{
}
if (res != null)
{
ParseOutput(res.Stat, out ServerSpeedItem server);
_updateFunc(server);
}
}
if (_channel != null)
await _channel.ConnectAsync();
}
catch
{
}
}
}
private void ParseOutput(Google.Protobuf.Collections.RepeatedField<Stat> source, out ServerSpeedItem server)
{
server = new();
long aggregateProxyUp = 0;
long aggregateProxyDown = 0;
try
{
foreach (Stat stat in source)
{
string name = stat.Name;
long value = stat.Value / 1024; //KByte
string[] nStr = name.Split(">>>".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
string type = "";
name = name.Trim();
name = nStr[1];
type = nStr[3];
if (name.StartsWith(Global.ProxyTag))
{
if (type == "uplink")
{
aggregateProxyUp += value;
}
else if (type == "downlink")
{
aggregateProxyDown += value;
}
}
else if (name == Global.DirectTag)
{
if (type == "uplink")
{
server.directUp = value;
}
else if (type == "downlink")
{
server.directDown = value;
}
}
}
server.proxyUp = aggregateProxyUp;
server.proxyDown = aggregateProxyDown;
}
catch
{
}
}
}
}

View file

@ -0,0 +1,104 @@
using PacLib;
using v2rayN.Enums;
using v2rayN.Models;
namespace v2rayN.Handler
{
public static class SysProxyHandle
{
//private const string _userWininetConfigFile = "user-wininet.json";
//private static string _queryStr;
// In general, this won't change
// format:
// <flags><CR-LF>
// <proxy-server><CR-LF>
// <bypass-list><CR-LF>
// <pac-url>
private enum RET_ERRORS : int
{
RET_NO_ERROR = 0,
INVALID_FORMAT = 1,
NO_PERMISSION = 2,
SYSCALL_FAILED = 3,
NO_MEMORY = 4,
INVAILD_OPTION_COUNT = 5,
};
public static bool UpdateSysProxy(Config config, bool forceDisable)
{
var type = config.sysProxyType;
if (forceDisable && type != ESysProxyType.Unchanged)
{
type = ESysProxyType.ForcedClear;
}
try
{
int port = LazyConfig.Instance.GetLocalPort(EInboundProtocol.http);
int portSocks = LazyConfig.Instance.GetLocalPort(EInboundProtocol.socks);
int portPac = LazyConfig.Instance.GetLocalPort(EInboundProtocol.pac);
if (port <= 0)
{
return false;
}
if (type == ESysProxyType.ForcedChange)
{
var strExceptions = $"<local>;{config.constItem.defIEProxyExceptions};{config.systemProxyExceptions}";
var strProxy = string.Empty;
if (Utils.IsNullOrEmpty(config.systemProxyAdvancedProtocol))
{
strProxy = $"{Global.Loopback}:{port}";
}
else
{
strProxy = config.systemProxyAdvancedProtocol
.Replace("{ip}", Global.Loopback)
.Replace("{http_port}", port.ToString())
.Replace("{socks_port}", portSocks.ToString());
}
ProxySetting.SetProxy(strProxy, strExceptions, 2); // set a named proxy
}
else if (type == ESysProxyType.ForcedClear)
{
ProxySetting.UnsetProxy(); // set to no proxy
}
else if (type == ESysProxyType.Unchanged)
{
}
else if (type == ESysProxyType.Pac)
{
PacHandler.Start(Utils.GetConfigPath(), port, portPac);
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
ProxySetting.SetProxy(strProxy, "", 4); // use pac script url for auto-config proxy
}
if (type != ESysProxyType.Pac)
{
PacHandler.Stop();
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
}
return true;
}
public static void ResetIEProxy4WindowsShutDown()
{
try
{
//TODO To be verified
Utils.RegWriteValue(@"Software\Microsoft\Windows\CurrentVersion\Internet Settings", "ProxyEnable", 0);
}
catch
{
}
}
}
}

View file

@ -0,0 +1,597 @@
using Splat;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using v2rayN.Enums;
using v2rayN.Models;
using v2rayMiniConsole.Resx;
using v2rayMiniConsole;
namespace v2rayN.Handler
{
internal class UpdateHandle
{
private Action<bool, string> _updateFunc;
private Config _config;
public event EventHandler<ResultEventArgs> AbsoluteCompleted;
public class ResultEventArgs : EventArgs
{
public bool Success;
public string Msg;
public string Url;
public ResultEventArgs(bool success, string msg, string url = "")
{
Success = success;
Msg = msg;
Url = url;
}
}
public void CheckUpdateGuiN(Config config, Action<bool, string> update, bool preRelease)
{
_config = config;
_updateFunc = update;
var url = string.Empty;
DownloadHandle downloadHandle = new();
downloadHandle.UpdateCompleted += (sender2, args) =>
{
if (args.Success)
{
_updateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
try
{
string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(url));
fileName = Utils.UrlEncode(fileName);
Process process = new()
{
StartInfo = new ProcessStartInfo
{
FileName = "v2rayUpgrade.exe",
Arguments = fileName.AppendQuotes(),
WorkingDirectory = Utils.StartupPath()
}
};
process.Start();
if (process.Id > 0)
{
_updateFunc(true, "");
}
}
catch (Exception ex)
{
_updateFunc(false, ex.Message);
}
}
else
{
_updateFunc(false, args.Msg);
}
};
downloadHandle.Error += (sender2, args) =>
{
_updateFunc(false, args.GetException().Message);
};
AbsoluteCompleted += (sender2, args) =>
{
if (args.Success)
{
_updateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, "v2rayN"));
_updateFunc(false, args.Msg);
url = args.Url;
AskToDownload(downloadHandle, url, true).ContinueWith(task =>
{
_updateFunc(false, url);
});
}
else
{
_updateFunc(false, args.Msg);
}
};
_updateFunc(false, string.Format(ResUI.MsgStartUpdating, "v2rayN"));
CheckUpdateAsync(ECoreType.v2rayN, preRelease);
}
public void CheckUpdateCore(ECoreType type, Config config, Action<bool, string> update, bool preRelease)
{
_config = config;
_updateFunc = update;
var url = string.Empty;
DownloadHandle downloadHandle = new();
downloadHandle.UpdateCompleted += (sender2, args) =>
{
if (args.Success)
{
_updateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
_updateFunc(false, ResUI.MsgUnpacking);
try
{
_updateFunc(true, url);
}
catch (Exception ex)
{
_updateFunc(false, ex.Message);
}
}
else
{
_updateFunc(false, args.Msg);
}
};
downloadHandle.Error += (sender2, args) =>
{
_updateFunc(true, args.GetException().Message);
};
AbsoluteCompleted += (sender2, args) =>
{
if (args.Success)
{
_updateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, "Core"));
_updateFunc(false, args.Msg);
url = args.Url;
AskToDownload(downloadHandle, url, true).ContinueWith(task =>
{
_updateFunc(false, url);
});
}
else
{
_updateFunc(false, args.Msg);
}
};
_updateFunc(false, string.Format(ResUI.MsgStartUpdating, "Core"));
CheckUpdateAsync(type, preRelease);
}
public void UpdateSubscriptionProcess(Config config, string subId, bool blProxy, Action<bool, string> update)
{
_config = config;
_updateFunc = update;
_updateFunc(false, ResUI.MsgUpdateSubscriptionStart);
var subItem = LazyConfig.Instance.SubItems().OrderBy(t => t.sort).ToList();
if (subItem == null || subItem.Count <= 0)
{
_updateFunc(false, ResUI.MsgNoValidSubscription);
return;
}
Task.Run(async () =>
{
foreach (var item in subItem)
{
string id = item.id.TrimEx();
string url = item.url.TrimEx();
string userAgent = item.userAgent.TrimEx();
string hashCode = $"{item.remarks}->";
if (Utils.IsNullOrEmpty(id) || Utils.IsNullOrEmpty(url) || (!Utils.IsNullOrEmpty(subId) && item.id != subId))
{
_updateFunc(false, $"{hashCode}{ResUI.MsgNoValidSubscription}");
continue;
}
if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol))
{
continue;
}
if (item.enabled == false)
{
_updateFunc(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
continue;
}
var downloadHandle = new DownloadHandle();
downloadHandle.Error += (sender2, args) =>
{
_updateFunc(false, $"{hashCode}{args.GetException().Message}");
};
_updateFunc(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
//one url
url = Utils.GetPunycode(url);
//convert
if (!Utils.IsNullOrEmpty(item.convertTarget))
{
var subConvertUrl = Utils.IsNullOrEmpty(config.constItem.subConvertUrl) ? 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());
}
}
var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent);
if (blProxy && Utils.IsNullOrEmpty(result))
{
result = await downloadHandle.TryDownloadString(url, false, userAgent);
}
//more url
if (Utils.IsNullOrEmpty(item.convertTarget) && !Utils.IsNullOrEmpty(item.moreUrl.TrimEx()))
{
if (!Utils.IsNullOrEmpty(result) && Utils.IsBase64String(result!))
{
result = Utils.Base64Decode(result);
}
var lstUrl = new List<string>(item.moreUrl.TrimEx().Split(","));
foreach (var it in lstUrl)
{
var url2 = Utils.GetPunycode(it);
if (Utils.IsNullOrEmpty(url2))
{
continue;
}
var result2 = await downloadHandle.TryDownloadString(url2, blProxy, userAgent);
if (blProxy && Utils.IsNullOrEmpty(result2))
{
result2 = await downloadHandle.TryDownloadString(url2, false, userAgent);
}
if (!Utils.IsNullOrEmpty(result2))
{
if (Utils.IsBase64String(result2!))
{
result += Utils.Base64Decode(result2);
}
else
{
result += result2;
}
}
}
}
if (Utils.IsNullOrEmpty(result))
{
_updateFunc(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}");
}
else
{
_updateFunc(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}");
if (result?.Length < 99)
{
_updateFunc(false, $"{hashCode}{result}");
}
int ret = ConfigHandler.AddBatchServers(config, result, id, true);
if (ret <= 0)
{
Logging.SaveLog("FailedImportSubscription");
Logging.SaveLog(result);
}
_updateFunc(false,
ret > 0
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
}
_updateFunc(false, "-------------------------------------------------------");
}
_updateFunc(true, $"{ResUI.MsgUpdateSubscriptionEnd}");
MainTask.Instance.ServerSpeedtest();
});
}
public void UpdateGeoFileAll(Config config, Action<bool, string> update)
{
Task.Run(async () =>
{
await UpdateGeoFile("geosite", _config, update);
await UpdateGeoFile("geoip", _config, update);
});
}
public void RunAvailabilityCheck(Action<bool, string> update)
{
Task.Run(async () =>
{
var time = await (new DownloadHandle()).RunAvailabilityCheck(null);
if (time == -1)
{
RunningObjects.Instance.SetStatus();
MainTask.Instance.UpdateProfileItemFailure(true, MainTask.Instance.CurrentServerStats.indexId);
MainTask.Instance.SwitchServer();
return;
}
MainTask.Instance.UpdateProfileItemFailure(false, MainTask.Instance.CurrentServerStats.indexId);
MainTask.Instance.CurrentServerStats.delay = time;
RunningObjects.Instance.SetStatus();
update(false, string.Format(ResUI.TestMeOutput, time));
});
}
#region private
private async void CheckUpdateAsync(ECoreType type, bool preRelease)
{
try
{
var coreInfo = LazyConfig.Instance.GetCoreInfo(type);
string url = coreInfo.coreReleaseApiUrl;
var result = await (new DownloadHandle()).DownloadStringAsync(url, true, "");
if (!Utils.IsNullOrEmpty(result))
{
ResponseHandler(type, result, preRelease);
}
else
{
Logging.SaveLog("StatusCode error: " + url);
return;
}
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
_updateFunc(false, ex.Message);
}
}
/// <summary>
/// 获取V2RayCore版本
/// </summary>
private SemanticVersion GetCoreVersion(ECoreType type)
{
try
{
var coreInfo = LazyConfig.Instance.GetCoreInfo(type);
string filePath = string.Empty;
foreach (string name in coreInfo.coreExes)
{
string vName = $"{name}.exe";
vName = Utils.GetBinPath(vName, coreInfo.coreType.ToString());
if (File.Exists(vName))
{
filePath = vName;
break;
}
}
if (!File.Exists(filePath))
{
string msg = string.Format(ResUI.NotFoundCore, @"", "", "");
//ShowMsg(true, msg);
return new SemanticVersion("");
}
using Process p = new();
p.StartInfo.FileName = filePath.AppendQuotes();
p.StartInfo.Arguments = coreInfo.versionArg;
p.StartInfo.WorkingDirectory = Utils.StartupPath();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
p.Start();
p.WaitForExit(5000);
string echo = p.StandardOutput.ReadToEnd();
string version = string.Empty;
switch (type)
{
case ECoreType.v2fly:
case ECoreType.SagerNet:
case ECoreType.Xray:
case ECoreType.v2fly_v5:
version = Regex.Match(echo, $"{coreInfo.match} ([0-9.]+) \\(").Groups[1].Value;
break;
case ECoreType.clash:
case ECoreType.clash_meta:
case ECoreType.mihomo:
version = Regex.Match(echo, $"v[0-9.]+").Groups[0].Value;
break;
case ECoreType.sing_box:
version = Regex.Match(echo, $"([0-9.]+)").Groups[1].Value;
break;
}
return new SemanticVersion(version);
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
_updateFunc(false, ex.Message);
return new SemanticVersion("");
}
}
private void ResponseHandler(ECoreType type, string gitHubReleaseApi, bool preRelease)
{
try
{
var gitHubReleases = JsonUtils.Deserialize<List<GitHubRelease>>(gitHubReleaseApi);
var gitHubRelease = preRelease ? gitHubReleases?.First() : gitHubReleases?.First(r => r.Prerelease == false);
var version = new SemanticVersion(gitHubRelease?.TagName!);
var body = gitHubRelease?.Body;
var coreInfo = LazyConfig.Instance.GetCoreInfo(type);
SemanticVersion curVersion;
string message;
string url;
switch (type)
{
case ECoreType.v2fly:
case ECoreType.SagerNet:
case ECoreType.Xray:
case ECoreType.v2fly_v5:
{
curVersion = GetCoreVersion(type);
message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v"));
string osBit = "64";
switch (RuntimeInformation.ProcessArchitecture)
{
case Architecture.Arm64:
osBit = "arm64-v8a";
break;
case Architecture.X86:
osBit = "32";
break;
default:
osBit = "64";
break;
}
url = string.Format(coreInfo.coreDownloadUrl64, version.ToVersionString("v"), osBit);
break;
}
case ECoreType.clash:
case ECoreType.clash_meta:
case ECoreType.mihomo:
{
curVersion = GetCoreVersion(type);
message = string.Format(ResUI.IsLatestCore, type, curVersion);
switch (RuntimeInformation.ProcessArchitecture)
{
case Architecture.Arm64:
url = coreInfo.coreDownloadUrlArm64;
break;
case Architecture.X86:
url = coreInfo.coreDownloadUrl32;
break;
default:
url = coreInfo.coreDownloadUrl64;
break;
}
url = string.Format(url, version.ToVersionString("v"));
break;
}
case ECoreType.sing_box:
{
curVersion = GetCoreVersion(type);
message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v"));
switch (RuntimeInformation.ProcessArchitecture)
{
case Architecture.Arm64:
url = coreInfo.coreDownloadUrlArm64;
break;
case Architecture.X86:
url = coreInfo.coreDownloadUrl32;
break;
default:
url = coreInfo.coreDownloadUrl64;
break;
}
url = string.Format(url, version.ToVersionString("v"), version);
break;
}
case ECoreType.v2rayN:
{
curVersion = new SemanticVersion(FileVersionInfo.GetVersionInfo(Utils.GetExePath()).FileVersion.ToString());
message = string.Format(ResUI.IsLatestN, type, curVersion);
switch (RuntimeInformation.ProcessArchitecture)
{
case Architecture.Arm64:
url = string.Format(coreInfo.coreDownloadUrlArm64, version);
break;
case Architecture.X86:
url = string.Format(coreInfo.coreDownloadUrl32, version);
break;
default:
url = string.Format(coreInfo.coreDownloadUrl64, version);
break;
}
break;
}
default:
throw new ArgumentException("Type");
}
if (curVersion >= version && version != new SemanticVersion(0, 0, 0))
{
AbsoluteCompleted?.Invoke(this, new ResultEventArgs(false, message));
return;
}
AbsoluteCompleted?.Invoke(this, new ResultEventArgs(true, body, url));
}
catch (Exception ex)
{
Logging.SaveLog(ex.Message, ex);
_updateFunc(false, ex.Message);
}
}
private async Task AskToDownload(DownloadHandle downloadHandle, string url, bool blAsk)
{
await downloadHandle.DownloadFileAsync(url, true, 600);
}
private async Task UpdateGeoFile(string geoName, Config config, Action<bool, string> update)
{
_config = config;
_updateFunc = update;
var url = string.Format(Global.GeoUrl, geoName);
DownloadHandle downloadHandle = new();
downloadHandle.UpdateCompleted += (sender2, args) =>
{
if (args.Success)
{
_updateFunc(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, geoName));
try
{
string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(url));
if (File.Exists(fileName))
{
//Global.coreTypes.ForEach(it =>
//{
// string targetPath = Utile.GetBinPath($"{geoName}.dat", (ECoreType)Enum.Parse(typeof(ECoreType), it));
// File.Copy(fileName, targetPath, true);
//});
string targetPath = Utils.GetBinPath($"{geoName}.dat");
File.Copy(fileName, targetPath, true);
File.Delete(fileName);
//_updateFunc(true, "");
}
}
catch (Exception ex)
{
_updateFunc(false, ex.Message);
}
}
else
{
_updateFunc(false, args.Msg);
}
};
downloadHandle.Error += (sender2, args) =>
{
_updateFunc(false, args.GetException().Message);
};
await AskToDownload(downloadHandle, url, false);
}
#endregion private
}
}

View file

@ -0,0 +1,619 @@
using v2rayMiniConsole.Resx;
using v2rayN;
using v2rayN.Enums;
using v2rayN.Handler;
using v2rayN.Models;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Tab;
namespace v2rayMiniConsole
{
internal class MainTask
{
private static readonly Lazy<MainTask> _instance = new(() => new());
public static MainTask Instance => _instance.Value;
// 用来通知后台线程退出
private CancellationTokenSource _cts { get; } = new CancellationTokenSource();
#region private prop
private CoreHandler? _coreHandler;
private NoticeHandler _noticeHandler = new NoticeHandler();
private string _subId = string.Empty;
private string _serverFilter = string.Empty;
private static Config? _config;
#endregion private prop
#region public system proxy
public bool BlSystemProxyClear { get; set; }
public bool BlSystemProxySet { get; set; }
public bool BlSystemProxyNothing { get; set; }
public bool BlSystemProxyPac { get; set; }
public int SystemProxySelected { get; set; }
#endregion public system proxy
public string? ServerFilter { get; set; }
public bool BlServers { get; set; }
public ProfileExItem? CurrentServerStats;
public bool BlReloadEnabled { get; set; }
public bool ShowCalshUI { get; set; }
private MainTask() => Init();
private void Init()
{
Logging.Setup();
if (ConfigHandler.LoadConfig(ref _config) != 0)
{
Console.Write($"Loading GUI configuration file is abnormal,please restart the application{Environment.NewLine}加载GUI配置文件异常,请重启应用");
Environment.Exit(0);
return;
}
Logging.LoggingEnabled(_config.guiItem.enableLog);
Logging.SaveLog($"v2rayMiniConsole start up | {Utils.GetVersion()} | {Utils.GetExePath()}");
Logging.ClearLogs();
Thread.CurrentThread.CurrentUICulture = new(_config.uiItem.currentLanguage);
ConfigHandler.InitBuiltinRouting(_config);
ConfigHandler.InitBuiltinDNS(_config);
_coreHandler = new CoreHandler(_config, UpdateHandler);
//Under Win10
if (Environment.OSVersion.Version.Major < 10)
{
Environment.SetEnvironmentVariable("DOTNET_EnableWriteXorExecute", "0", EnvironmentVariableTarget.User);
}
var item = ConfigHandler.GetDefaultServer(_config);
if (item == null || item.configType == EConfigType.Custom)
{
MainFormHandler.Instance.UpdateTask(_config, UpdateTaskHandler);
return;
}
if (ConfigHandler.SetDefaultServerIndex(_config, item.indexId) == 0)
{
Reload();
}
CurrentServerStats = ProfileExHandler.Instance.ProfileExs.Where(t => t.indexId == item.indexId).FirstOrDefault();
if (CurrentServerStats == null)
{
CurrentServerStats = new ProfileExItem();
CurrentServerStats.indexId = item.indexId;
}
MainFormHandler.Instance.UpdateTask(_config, UpdateTaskHandler);
}
private void UpdateHandler(bool notify, string msg)
{
_noticeHandler?.SendMessage(msg);
}
private void UpdateTaskHandler(bool success, string msg)
{
_noticeHandler.SendMessage(msg, true);
}
#region proxy_management
public void SetListenerType(ESysProxyType type)
{
if (_config.sysProxyType == type)
{
return;
}
_config.sysProxyType = type;
ChangeSystemProxyStatus(type, true);
SystemProxySelected = (int)_config.sysProxyType;
ConfigHandler.SaveConfig(_config, false);
}
public void ChangeSystemProxyMode()
{
List<string> proxyModes = new List<string>(){
ResUI.menuSystemProxyClear,
ResUI.menuSystemProxySet,
ResUI.menuSystemProxyNothing,
ResUI.menuSystemProxyPac
};
List<ESysProxyType> eSysProxyTypes = new List<ESysProxyType>() {
ESysProxyType.ForcedClear,
ESysProxyType.ForcedChange,
ESysProxyType.Unchanged,
ESysProxyType.Pac
};
UserPromptUI(proxyModes, choice =>
{
SetListenerType(eSysProxyTypes[choice - 1]);
});
}
public void ChangeSystemProxyStatus(ESysProxyType type, bool blChange)
{
SysProxyHandle.UpdateSysProxy(_config, _config.tunModeItem.enableTun ? true : false);
_noticeHandler.SendMessage($"{ResUI.TipChangeSystemProxy} - {_config.sysProxyType.ToString()}", true);
RunningObjects.Instance.SetStatus();
BlSystemProxyClear = (type == ESysProxyType.ForcedClear);
BlSystemProxySet = (type == ESysProxyType.ForcedChange);
BlSystemProxyNothing = (type == ESysProxyType.Unchanged);
BlSystemProxyPac = (type == ESysProxyType.Pac);
}
#endregion proxy_management
#region routing_management
public void Show_Current_Routing()
{
Console.WriteLine();
var currentRoutingItem = LazyConfig.Instance.GetRoutingItem(_config.routingBasicItem.routingIndexId);
Console.WriteLine(currentRoutingItem.remarks);
}
public void Change_Routing()
{
var routings = LazyConfig.Instance.RoutingItems();
var routingStr = routings.Select(t => t.remarks).ToList();
UserPromptUI(routingStr, choice =>
{
RoutingItemChanged(routings[choice - 1]);
});
}
private void RoutingItemChanged(RoutingItem item)
{
if (_config.routingBasicItem.routingIndexId == item.id)
{
return;
}
if (ConfigHandler.SetDefaultRouting(_config, item) == 0)
{
_noticeHandler?.SendMessage(ResUI.TipChangeRouting, true);
RunningObjects.Instance.SetStatus();
Reload();
}
}
#endregion routing_management
public void ServerSpeedtest()
{
if (RunningObjects.Instance.ProfileItems.Count == 0)
{
return;
}
RunningObjects.Instance.ProfileItems = new System.Collections.Concurrent.ConcurrentBag<ProfileItem>(
RemoveDuplicateServer(RunningObjects.Instance.ProfileItems.ToList()));
new SpeedtestHandler(_config, _coreHandler, RunningObjects.Instance.ProfileItems.ToList(), ESpeedActionType.Mixedtest, UpdateSpeedtestHandler);
}
private void UpdateSpeedtestHandler(string indexId, string delay, string speed)
{
Task.Run((Action)(() =>
{
SetTestResult(indexId, delay, speed);
}));
}
private void SetTestResult(string indexId, string delay, string speed)
{
if (Utils.IsNullOrEmpty(indexId))
{
_noticeHandler.SendMessage(delay, true);
if (delay == ResUI.SpeedtestingCompleted)
{
// 测速结束,将可用服务器持久化。若有更快的服务器,换过去
SaveAvailableProfileItemsAndUpdateCurrentServer();
// 拿一键测速后的结果更新数据库现有服务器失败计数器
UpdateServerFailureAfterMultiSpeedTest();
}
return;
}
var item = ProfileExHandler.Instance.ProfileExs.Where(it => it.indexId == indexId).FirstOrDefault();
if (item == null)
{
_noticeHandler.SendMessage($"profileEx didn't find {indexId}", true);
return;
}
if (!Utils.IsNullOrEmpty(delay))
{
int.TryParse(delay, out int temp);
item.delay = temp;
_noticeHandler.SendMessage($"delay:{indexId}-{delay} {Global.DelayUnit}", true);
}
if (!Utils.IsNullOrEmpty(speed))
{
_noticeHandler.SendMessage($"speed:{indexId}-{speed} {Global.SpeedUnit}", true);
}
}
public void UpdateProfileItemFailure(bool serverFailure, string indexId)
{
var profileItem = LazyConfig.Instance.GetProfileItem(indexId);
if (profileItem == null) // 当订阅更新时,有新服务器就会触发这个,不用打印了
{
//_noticeHandler?.SendMessage("profileItem is null while profileExItem not in SetTestResult", true);
return;
}
if (serverFailure) // 若测速或测延时失败当failures >= 10时删除此服务器记录否则failures自增
{
if (++profileItem.failures >= 10)
{
SQLiteHelper.Instance.Delete(profileItem);
_noticeHandler?.SendMessage($"{indexId} failed for more than 10 times, dropping record", true);
}
else
{
SQLiteHelper.Instance.Update(profileItem);
_noticeHandler?.SendMessage($"{indexId} failed for {profileItem.failures} times", true);
}
}
else // 若测速或测延时成功当failures == 0时不做操作否则failures自减
{
if (profileItem.failures > 0)
{
--profileItem.failures;
SQLiteHelper.Instance.Update(profileItem);
_noticeHandler?.SendMessage($"{indexId} succeeded, failures = {profileItem.failures}", true);
}
}
}
public void SwitchServer()
{
var profileItems = LazyConfig.Instance.ProfileItems();
if (profileItems.Count <= 1)
{
RunningObjects.Instance.SetStatus("no more available server to use.");
return;
}
var currentIndex = profileItems.FindIndex(t => t.indexId == MainTask.Instance.CurrentServerStats.indexId);
var profile = profileItems[(++currentIndex) % profileItems.Count];
MainTask.Instance.SetDefaultServer(profile.indexId);
}
private void SaveAvailableProfileItemsAndUpdateCurrentServer()
{
var availableProfileExItems = ProfileExHandler.Instance.ProfileExs.Where(item=> item.speed > 0).ToList();
if (availableProfileExItems.Count == 0)
{
_noticeHandler.SendMessage("no available server found", true);
return;
}
// 新的可用服务器入库
var availableProfileItems = RunningObjects.Instance.ProfileItems.Where(item => availableProfileExItems.Select(exItem => exItem.indexId).Contains(item.indexId)).ToList();
//availableProfileItems = RemoveDuplicateServer(availableProfileItems);git
SQLiteHelper.Instance.InsertAll(availableProfileItems);
_noticeHandler.SendMessage("saved availableProfileItems", true);
// 切换最快服务器
var FastestExItem = ProfileExHandler.Instance.ProfileExs.OrderByDescending(t => t.speed).FirstOrDefault();
if (CurrentServerStats == null && FastestExItem == null
|| CurrentServerStats != null && FastestExItem != null && FastestExItem.speed <= CurrentServerStats.speed)
{
return;
}
SetDefaultServer(FastestExItem?.indexId);
CurrentServerStats = FastestExItem;
var currentProfileItem = LazyConfig.Instance.GetProfileItem(FastestExItem.indexId);
_noticeHandler.SendMessage($"server changed to { currentProfileItem?.address }-{ currentProfileItem?.remarks }", true);
}
private void UpdateServerFailureAfterMultiSpeedTest()
{
var profileItems = LazyConfig.Instance.ProfileItems();
foreach (var exItem in ProfileExHandler.Instance.ProfileExs)
{
if (profileItems.Exists(t => t.indexId == exItem.indexId))
{
if (exItem.speed > 0)
{
UpdateProfileItemFailure(false, exItem.indexId);
}
else
{
UpdateProfileItemFailure(true, exItem.indexId);
}
}
}
}
private List<ProfileItem> RemoveDuplicateServer(List<ProfileItem> profiles)
{
var ret = ConfigHandler.DedupServerList(profiles);
Reload();
return ret;
}
public void TestServerAvailability()
{
var item = ConfigHandler.GetDefaultServer(_config);
if (item == null || item.configType == EConfigType.Custom)
{
return;
}
(new UpdateHandle()).RunAvailabilityCheck((bool success, string msg) =>
{
_noticeHandler?.SendMessage(msg, true);
});
}
public void SetDefaultServer(string indexId)
{
if (Utils.IsNullOrEmpty(indexId))
{
return;
}
if (indexId == _config.indexId)
{
return;
}
var item = LazyConfig.Instance.GetProfileItem(indexId);
if (item is null)
{
_noticeHandler.SendMessage(ResUI.PleaseSelectServer, true);
return;
}
if (ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0)
{
Reload();
}
CurrentServerStats = ProfileExHandler.Instance.ProfileExs.Where(t => t.indexId == indexId).FirstOrDefault();
if (CurrentServerStats == null)
{
CurrentServerStats = new ProfileExItem();
CurrentServerStats.indexId = indexId;
}
}
#region subscription management
public void ShowCurrentSubscriptions()
{
int i = 0;
foreach(var subItem in LazyConfig.Instance.SubItems())
{
Console.WriteLine($"{i++}->{subItem.remarks}: {subItem.url}, update interval = " +
$"{subItem.autoUpdateInterval}, isEnabled = {subItem.enabled}");
}
}
public void AddSubscription()
{
SubItem subItem = new SubItem();
subItem.remarks = GetUserInput("remarks:", input => !string.IsNullOrEmpty(input.Trim()));
subItem.url = GetUserInput("url:", input => Utils.IsValidUrl(input.Trim().ToLower()), "g");
int autoUpdateInterval;
try
{
autoUpdateInterval = GetUserIntInput("update interval:", input => int.TryParse(input.Trim(), out autoUpdateInterval) && autoUpdateInterval > 0, "g");
subItem.autoUpdateInterval = autoUpdateInterval;
}
catch (OperationCanceledException)
{
Console.WriteLine("Gave up on adding subscription, returning to main task.");
return;
}
subItem.id = Utils.GetGUID(false);
try
{
SQLiteHelper.Instance.Insert(subItem);
Console.WriteLine();
Console.WriteLine("Subscription added.");
RunningObjects.Instance.SetStatus();
}
catch (Exception ex)
{
Console.WriteLine("An error occurred while adding the subscription: " + ex.Message);
}
}
private string GetUserInput(string prompt, Func<string, bool> validator = null, string quitCommand = null)
{
string input;
do
{
Console.Write(prompt);
input = Console.ReadLine()?.Trim();
if (quitCommand != null && input?.ToLower() == quitCommand)
{
throw new OperationCanceledException("User canceled operation.");
}
if (validator != null && !validator(input))
{
Console.WriteLine("Invalid input. Please try again.");
}
} while (validator != null && !validator(input));
return input;
}
private int GetUserIntInput(string prompt, Func<string, bool> validator, string quitCommand)
{
int value;
string input;
do
{
input = GetUserInput(prompt, validator, quitCommand);
if (!int.TryParse(input, out value))
{
Console.WriteLine("Invalid input. Please enter a valid integer.");
}
} while (!int.TryParse(input, out value));
return value;
}
public void RemoveSubscription()
{
var subItems = LazyConfig.Instance.SubItems();
var subItemsStr = LazyConfig.Instance.SubItems().Select(t => t.remarks).ToList();
UserPromptUI(subItemsStr, choice =>
{
SQLiteHelper.Instance.Delete(subItems[choice - 1]);
});
}
#endregion subscription management
public void Reload()
{
BlReloadEnabled = false;
LoadCore().ContinueWith(task =>
{
TestServerAvailability();
BlReloadEnabled = true;
ShowCalshUI = (_config.runningCoreType is ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo);
if (ShowCalshUI)
{
_noticeHandler?.SendMessage("ShowClashUI triggered, how to deal with that?", true);
}
});
}
#region core_job
private async Task LoadCore()
{
await Task.Run(() =>
{
_coreHandler.LoadCore();
//ConfigHandler.SaveConfig(_config, false);
ChangeSystemProxyStatus(_config.sysProxyType, false);
});
}
private void CloseCore()
{
ConfigHandler.SaveConfig(_config, false);
ChangeSystemProxyStatus(ESysProxyType.ForcedClear, false);
_coreHandler.CoreStop();
}
#endregion core job
public bool IsCancellationRequested()
{
return _cts.IsCancellationRequested;
}
public CancellationToken GetCancellationToken()
{
return _cts.Token;
}
public void BroadcastExit(bool blWindowsShutDown)
{
_cts.Cancel();
// 等待所有任务优雅地结束,但注意这里可能会阻塞,特别是如果有任务永远不会完成
// 或者使用Task.WaitAll(_mainFormTasks.ToArray()); 但不推荐因为它会抛出AggregateException
try
{
// 等待所有任务完成,或等待指定的超时时间
Task.WhenAll(MainFormHandler.Instance.MainFormTasks).Wait();
}
catch (AggregateException ae)
{
// 处理取消操作等导致的异常
ae.Handle(e => e is TaskCanceledException);
}
OnExit(blWindowsShutDown);
Environment.Exit(0);
}
public void OnExit(bool blWindowsShutDown)
{
Logging.SaveLog("MyAppExit Begin");
ConfigHandler.SaveConfig(_config);
if (blWindowsShutDown)
{
SysProxyHandle.ResetIEProxy4WindowsShutDown(); //受到用户注销或关机做的操作
}
else
{
SysProxyHandle.UpdateSysProxy(_config, true);
}
_coreHandler.CoreStop();
Logging.SaveLog("MyAppExit End");
}
public void SetLanguage()
{
UserPromptUI(Global.Languages, choice =>
{
var selectedLanguage = Global.Languages[choice - 1];
if (selectedLanguage == _config.uiItem.currentLanguage)
{
return;
}
_config.uiItem.currentLanguage = selectedLanguage;
ConfigHandler.SaveConfig(_config, false);
Thread.CurrentThread.CurrentUICulture = new(_config.uiItem.currentLanguage);
});
}
private void UserPromptUI(List<string> options, Action<int> action)
{
if (options.Count == 0)
{
Console.WriteLine("no options to choose");
return;
}
int i = 1;
for (; i <= options.Count; ++i)
{
Console.WriteLine($"{i}.{options[i - 1]}");
}
Console.WriteLine($"{i}.Quit");
Console.WriteLine();
Console.Write("Please select:");
while (true)
{
string input = Console.ReadLine();
if (int.TryParse(input, out int choice) && choice >= 1 && choice <= options.Count + 1)
{
if (choice == options.Count + 1)
{
return;
}
action(choice);
break;
}
else
{
Console.WriteLine("error input, try again");
}
}
}
}
}

View file

@ -0,0 +1,17 @@
namespace v2rayN.Models
{
public class ClashConnectionModel
{
public string id { get; set; }
public string network { get; set; }
public string type { get; set; }
public string host { get; set; }
public ulong upload { get; set; }
public ulong download { get; set; }
public string uploadTraffic { get; set; }
public string downloadTraffic { get; set; }
public double time { get; set; }
public string elapsed { get; set; }
public string chain { get; set; }
}
}

View file

@ -0,0 +1,37 @@
namespace v2rayN.Models
{
public class ClashConnections
{
public ulong downloadTotal { get; set; }
public ulong uploadTotal { get; set; }
public List<ConnectionItem>? connections { get; set; }
}
public class ConnectionItem
{
public string id { get; set; } = string.Empty;
public MetadataItem metadata { get; set; }
public ulong upload { get; set; }
public ulong download { get; set; }
public DateTime start { get; set; }
public List<string>? chains { get; set; }
public string rule { get; set; }
public string rulePayload { get; set; }
}
public class MetadataItem
{
public string network { get; set; }
public string type { get; set; }
public string sourceIP { get; set; }
public string destinationIP { get; set; }
public string sourcePort { get; set; }
public string destinationPort { get; set; }
public string host { get; set; }
public string nsMode { get; set; }
public object uid { get; set; }
public string process { get; set; }
public string processPath { get; set; }
public string remoteDestination { get; set; }
}
}

View file

@ -0,0 +1,19 @@

using static v2rayN.Models.ClashProxies;
namespace v2rayN.Models
{
public class ClashProviders
{
public Dictionary<String, ProvidersItem> providers { get; set; }
public class ProvidersItem
{
public string name { get; set; }
public ProxiesItem[] proxies { get; set; }
public string type { get; set; }
public string vehicleType { get; set; }
}
}
}

View file

@ -0,0 +1,24 @@
namespace v2rayN.Models
{
public class ClashProxies
{
public Dictionary<String, ProxiesItem> proxies { get; set; }
public class ProxiesItem
{
public string[] all { get; set; }
public List<HistoryItem> history { get; set; }
public string name { get; set; }
public string type { get; set; }
public bool udp { get; set; }
public string now { get; set; }
public int delay { get; set; }
}
public class HistoryItem
{
public string time { get; set; }
public int delay { get; set; }
}
}
}

View file

@ -0,0 +1,24 @@
namespace v2rayN.Models
{
[Serializable]
public class ClashProxyModel
{
public string name { get; set; }
public string type { get; set; }
public string now { get; set; }
public int delay { get; set; }
public string delayName { get; set; }
public bool isActive { get; set; }
}
}

View file

@ -0,0 +1,15 @@
namespace v2rayN.Models
{
public class ComboItem
{
public string ID
{
get; set;
}
public string Text
{
get; set;
}
}
}

View file

@ -0,0 +1,43 @@
using v2rayN.Enums;
namespace v2rayN.Models
{
/// <summary>
/// 本软件配置文件实体类
/// </summary>
[Serializable]
public class Config
{
#region property
public string indexId { get; set; }
public string subIndexId { get; set; }
public ESysProxyType sysProxyType { get; set; }
public string systemProxyExceptions { get; set; }
public string systemProxyAdvancedProtocol { get; set; }
public ECoreType runningCoreType { get; set; }
#endregion property
#region other entities
public CoreBasicItem coreBasicItem { get; set; }
public TunModeItem tunModeItem { get; set; }
public KcpItem kcpItem { get; set; }
public GrpcItem grpcItem { get; set; }
public RoutingBasicItem routingBasicItem { get; set; }
public GUIItem guiItem { get; set; }
public UIItem uiItem { get; set; }
public ConstItem constItem { get; set; }
public SpeedTestItem speedTestItem { get; set; }
public Mux4SboxItem mux4SboxItem { get; set; }
public HysteriaItem hysteriaItem { get; set; }
public ClashUIItem clashUIItem { get; set; }
public List<InItem> inbound { get; set; }
public List<KeyEventItem> globalHotkeys { get; set; }
public List<CoreTypeItem> coreTypeItem { get; set; }
#endregion other entities
}
}

View file

@ -0,0 +1,229 @@
using System.Windows.Forms;
using v2rayN.Enums;
namespace v2rayN.Models
{
[Serializable]
public class CoreBasicItem
{
/// <summary>
/// 允许日志
/// </summary>
public bool logEnabled { get; set; }
/// <summary>
/// 日志等级
/// </summary>
public string loglevel { get; set; }
/// <summary>
/// 允许Mux多路复用
/// </summary>
public bool muxEnabled { get; set; }
/// <summary>
/// 是否允许不安全连接
/// </summary>
public bool defAllowInsecure { get; set; }
public string defFingerprint { get; set; }
/// <summary>
/// 默认用户代理
/// </summary>
public string defUserAgent { get; set; }
public bool enableFragment { get; set; }
public bool enableCacheFile4Sbox { get; set; } = true;
}
[Serializable]
public class InItem
{
public int localPort { get; set; }
public string protocol { get; set; }
public bool udpEnabled { get; set; }
public bool sniffingEnabled { get; set; } = true;
public List<string>? destOverride { get; set; } = ["http", "tls"];
public bool routeOnly { get; set; }
public bool allowLANConn { get; set; }
public bool newPort4LAN { get; set; }
public string user { get; set; }
public string pass { get; set; }
}
[Serializable]
public class KcpItem
{
public int mtu { get; set; }
public int tti { get; set; }
public int uplinkCapacity { get; set; }
public int downlinkCapacity { get; set; }
public bool congestion { get; set; }
public int readBufferSize { get; set; }
public int writeBufferSize { get; set; }
}
[Serializable]
public class GrpcItem
{
public int idle_timeout { get; set; }
public int health_check_timeout { get; set; }
public bool permit_without_stream { get; set; }
public int initial_windows_size { get; set; }
}
[Serializable]
public class GUIItem
{
public bool autoRun { get; set; }
public bool enableStatistics { get; set; }
public bool keepOlderDedupl { get; set; }
public bool ignoreGeoUpdateCore { get; set; } = true;
public int autoUpdateInterval { get; set; } = 10;
public bool checkPreReleaseUpdate { get; set; } = false;
public bool enableSecurityProtocolTls13 { get; set; }
public int trayMenuServersLimit { get; set; } = 20;
public bool enableHWA { get; set; } = false;
public bool enableLog { get; set; } = true;
}
[Serializable]
public class UIItem
{
public bool enableAutoAdjustMainLvColWidth { get; set; }
public bool enableUpdateSubOnlyRemarksExist { get; set; }
public double mainWidth { get; set; }
public double mainHeight { get; set; }
public double mainGirdHeight1 { get; set; }
public double mainGirdHeight2 { get; set; }
public bool colorModeDark { get; set; }
public bool followSystemTheme { get; set; }
public string? colorPrimaryName { get; set; }
public string currentLanguage { get; set; }
public string currentFontFamily { get; set; }
public int currentFontSize { get; set; }
public bool enableDragDropSort { get; set; }
public bool doubleClick2Activate { get; set; }
public bool autoHideStartup { get; set; }
public string mainMsgFilter { get; set; }
public List<ColumnItem> mainColumnItem { get; set; }
}
[Serializable]
public class ConstItem
{
public string defIEProxyExceptions { get; set; }
public string subConvertUrl { get; set; } = string.Empty;
}
[Serializable]
public class KeyEventItem
{
public EGlobalHotkey eGlobalHotkey { get; set; }
public bool Alt { get; set; }
public bool Control { get; set; }
public bool Shift { get; set; }
public System.Windows.Forms.Keys? KeyCode { get; set; }
}
[Serializable]
public class CoreTypeItem
{
public EConfigType configType { get; set; }
public ECoreType coreType { get; set; }
}
[Serializable]
public class TunModeItem
{
public bool enableTun { get; set; }
public bool strictRoute { get; set; } = true;
public string stack { get; set; }
public int mtu { get; set; }
public bool enableExInbound { get; set; }
public bool enableIPv6Address { get; set; } = true;
}
[Serializable]
public class SpeedTestItem
{
public int speedTestTimeout { get; set; }
public string speedTestUrl { get; set; }
public string speedPingTestUrl { get; set; }
}
[Serializable]
public class RoutingBasicItem
{
public string domainStrategy { get; set; }
public string domainStrategy4Singbox { get; set; }
public string domainMatcher { get; set; }
public string routingIndexId { get; set; }
public bool enableRoutingAdvanced { get; set; }
}
[Serializable]
public class ColumnItem
{
public string Name { get; set; }
public int Width { get; set; }
public int Index { get; set; }
}
[Serializable]
public class Mux4SboxItem
{
public string protocol { get; set; }
public int max_connections { get; set; }
}
[Serializable]
public class HysteriaItem
{
public int up_mbps { get; set; }
public int down_mbps { get; set; }
}
[Serializable]
public class ClashUIItem
{
public ERuleMode ruleMode { get; set; }
public bool showInTaskbar { get; set; }
public bool enableIPv6 { get; set; }
public bool enableMixinContent { get; set; }
public int proxiesSorting { get; set; }
public bool proxiesAutoRefresh { get; set; }
public int proxiesAutoDelayTestInterval { get; set; } = 10;
public int connectionsSorting { get; set; }
public bool connectionsAutoRefresh { get; set; }
public int connectionsRefreshInterval { get; set; } = 2;
}
}

View file

@ -0,0 +1,29 @@
using v2rayN.Enums;
namespace v2rayN.Models
{
[Serializable]
public class CoreInfo
{
public ECoreType coreType { get; set; }
public List<string> coreExes { get; set; }
public string arguments { get; set; }
public string coreUrl { get; set; }
public string coreReleaseApiUrl { get; set; }
public string coreDownloadUrl32 { get; set; }
public string coreDownloadUrl64 { get; set; }
public string coreDownloadUrlArm64 { get; set; }
public string match { get; set; }
public string versionArg { get; set; }
public bool redirectInfo { get; set; }
}
}

View file

@ -0,0 +1,20 @@
using SQLite;
using v2rayN.Enums;
namespace v2rayN.Models
{
[Serializable]
public class DNSItem
{
[PrimaryKey]
public string id { get; set; }
public string remarks { get; set; }
public bool enabled { get; set; } = true;
public ECoreType coreType { get; set; }
public bool useSystemHosts { get; set; }
public string? normalDNS { get; set; }
public string? tunDNS { get; set; }
public string? domainStrategy4Freedom { get; set; }
}
}

View file

@ -0,0 +1,68 @@
using System.Text.Json.Serialization;
namespace v2rayN.Models
{
public class GitHubReleaseAsset
{
[JsonPropertyName("url")] public string Url { get; set; }
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("node_id")] public string NodeId { get; set; }
[JsonPropertyName("name")] public string Name { get; set; }
[JsonPropertyName("label")] public object Label { get; set; }
[JsonPropertyName("content_type")] public string ContentType { get; set; }
[JsonPropertyName("state")] public string State { get; set; }
[JsonPropertyName("size")] public int Size { get; set; }
[JsonPropertyName("download_count")] public int DownloadCount { get; set; }
[JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; }
[JsonPropertyName("updated_at")] public DateTime UpdatedAt { get; set; }
[JsonPropertyName("browser_download_url")] public string BrowserDownloadUrl { get; set; }
}
public class GitHubRelease
{
[JsonPropertyName("url")] public string Url { get; set; }
[JsonPropertyName("assets_url")] public string AssetsUrl { get; set; }
[JsonPropertyName("upload_url")] public string UploadUrl { get; set; }
[JsonPropertyName("html_url")] public string HtmlUrl { get; set; }
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("node_id")] public string NodeId { get; set; }
[JsonPropertyName("tag_name")] public string TagName { get; set; }
[JsonPropertyName("target_commitish")] public string TargetCommitish { get; set; }
[JsonPropertyName("name")] public string Name { get; set; }
[JsonPropertyName("draft")] public bool Draft { get; set; }
[JsonPropertyName("prerelease")] public bool Prerelease { get; set; }
[JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; }
[JsonPropertyName("published_at")] public DateTime PublishedAt { get; set; }
[JsonPropertyName("assets")] public List<GitHubReleaseAsset> Assets { get; set; }
[JsonPropertyName("tarball_url")] public string TarballUrl { get; set; }
[JsonPropertyName("zipball_url")] public string ZipballUrl { get; set; }
[JsonPropertyName("body")] public string Body { get; set; }
}
}

View file

@ -0,0 +1,15 @@
using SQLite;
namespace v2rayN.Models
{
[Serializable]
public class ProfileExItem
{
[PrimaryKey]
public string indexId { get; set; }
public int delay { get; set; }
public decimal speed { get; set; }
public int sort { get; set; }
}
}

View file

@ -0,0 +1,193 @@
using SQLite;
using v2rayN.Enums;
namespace v2rayN.Models
{
[Serializable]
public class ProfileItem
{
public ProfileItem()
{
indexId = string.Empty;
configType = EConfigType.VMess;
configVersion = 2;
address = string.Empty;
port = 0;
id = string.Empty;
alterId = 0;
security = string.Empty;
network = string.Empty;
remarks = string.Empty;
headerType = string.Empty;
requestHost = string.Empty;
path = string.Empty;
streamSecurity = string.Empty;
allowInsecure = string.Empty;
subid = string.Empty;
flow = string.Empty;
failures = 0;
}
#region function
public string GetSummary()
{
string summary = string.Format("[{0}] ", (configType).ToString());
string[] arrAddr = address.Split('.');
string addr;
if (arrAddr.Length > 2)
{
addr = $"{arrAddr[0]}***{arrAddr[arrAddr.Length - 1]}";
}
else if (arrAddr.Length > 1)
{
addr = $"***{arrAddr[arrAddr.Length - 1]}";
}
else
{
addr = address;
}
switch (configType)
{
case EConfigType.Custom:
summary += string.Format("{0}", remarks);
break;
default:
summary += string.Format("{0}({1}:{2})", remarks, addr, port);
break;
}
return summary;
}
public List<string> GetAlpn()
{
if (Utils.IsNullOrEmpty(alpn))
{
return null;
}
else
{
return Utils.String2List(alpn);
}
}
public string GetNetwork()
{
if (Utils.IsNullOrEmpty(network) || !Global.Networks.Contains(network))
{
return Global.DefaultNetwork;
}
return network.TrimEx();
}
#endregion function
[PrimaryKey]
public string indexId { get; set; }
/// <summary>
/// config type(1=normal,2=custom)
/// </summary>
public EConfigType configType { get; set; }
/// <summary>
/// 版本(现在=2)
/// </summary>
public int configVersion { get; set; }
/// <summary>
/// 远程服务器地址
/// </summary>
public string address { get; set; }
/// <summary>
/// 远程服务器端口
/// </summary>
public int port { get; set; }
/// <summary>
/// 远程服务器ID
/// </summary>
public string id { get; set; }
/// <summary>
/// 远程服务器额外ID
/// </summary>
public int alterId { get; set; }
/// <summary>
/// 本地安全策略
/// </summary>
public string security { get; set; }
/// <summary>
/// tcp,kcp,ws,h2,quic
/// </summary>
public string network { get; set; }
/// <summary>
///
/// </summary>
public string remarks { get; set; }
/// <summary>
/// 伪装类型
/// </summary>
public string headerType { get; set; }
/// <summary>
/// 伪装的域名
/// </summary>
public string requestHost { get; set; }
/// <summary>
/// ws h2 path
/// </summary>
public string path { get; set; }
/// <summary>
/// 传输层安全
/// </summary>
public string streamSecurity { get; set; }
/// <summary>
/// 是否允许不安全连接(用于客户端)
/// </summary>
public string allowInsecure { get; set; }
/// <summary>
/// SubItem id
/// </summary>
public string subid { get; set; }
public bool isSub { get; set; } = true;
/// <summary>
/// VLESS flow
/// </summary>
public string flow { get; set; }
/// <summary>
/// tls sni
/// </summary>
public string sni { get; set; }
/// <summary>
/// tls alpn
/// </summary>
public string alpn { get; set; } = string.Empty;
public ECoreType? coreType { get; set; }
public int preSocksPort { get; set; }
public string fingerprint { get; set; }
public bool displayLog { get; set; } = true;
public string publicKey { get; set; }
public string shortId { get; set; }
public string spiderX { get; set; }
public int failures { get; set; }
}
}

View file

@ -0,0 +1,18 @@
namespace v2rayN.Models
{
[Serializable]
public class ProfileItemModel : ProfileItem
{
public bool isActive { get; set; }
public string subRemarks { get; set; }
public int delay { get; set; }
public decimal speed { get; set; }
public int sort { get; set; }
public string delayVal { get; set; }
public string speedVal { get; set; }
public string todayUp { get; set; }
public string todayDown { get; set; }
public string totalUp { get; set; }
public string totalDown { get; set; }
}
}

View file

@ -0,0 +1,23 @@
using SQLite;
namespace v2rayN.Models
{
[Serializable]
public class RoutingItem
{
[PrimaryKey]
public string id { get; set; }
public string remarks { get; set; }
public string url { get; set; }
public string ruleSet { get; set; }
public int ruleNum { get; set; }
public bool enabled { get; set; } = true;
public bool locked { get; set; }
public string customIcon { get; set; }
public string customRulesetPath4Singbox { get; set; }
public string domainStrategy { get; set; }
public string domainStrategy4Singbox { get; set; }
public int sort { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace v2rayN.Models
{
[Serializable]
public class RoutingItemModel : RoutingItem
{
public bool isActive { get; set; }
}
}

View file

@ -0,0 +1,26 @@
namespace v2rayN.Models
{
[Serializable]
public class RulesItem
{
public string id { get; set; }
public string? type { get; set; }
public string? port { get; set; }
public string? network { get; set; }
public List<string>? inboundTag { get; set; }
public string? outboundTag { get; set; }
public List<string>? ip { get; set; }
public List<string>? domain { get; set; }
public List<string>? protocol { get; set; }
public List<string>? process { get; set; }
public bool enabled { get; set; } = true;
}
}

View file

@ -0,0 +1,14 @@
namespace v2rayN.Models
{
[Serializable]
public class RulesItemModel : RulesItem
{
public string inboundTags { get; set; }
public string ips { get; set; }
public string domains { get; set; }
public string protocols { get; set; }
}
}

View file

@ -0,0 +1,40 @@
namespace v2rayN.Models
{
[Serializable]
internal class ServerSpeedItem : ServerStatItem
{
public long proxyUp
{
get; set;
}
public long proxyDown
{
get; set;
}
public long directUp
{
get; set;
}
public long directDown
{
get; set;
}
}
[Serializable]
public class TrafficItem
{
public ulong up
{
get; set;
}
public ulong down
{
get; set;
}
}
}

View file

@ -0,0 +1,39 @@
using SQLite;
namespace v2rayN.Models
{
[Serializable]
public class ServerStatItem
{
[PrimaryKey]
public string indexId
{
get; set;
}
public long totalUp
{
get; set;
}
public long totalDown
{
get; set;
}
public long todayUp
{
get; set;
}
public long todayDown
{
get; set;
}
public long dateNow
{
get; set;
}
}
}

View file

@ -0,0 +1,15 @@
using v2rayN.Enums;
namespace v2rayN.Models
{
[Serializable]
internal class ServerTestItem
{
public string indexId { get; set; }
public string address { get; set; }
public int port { get; set; }
public EConfigType configType { get; set; }
public bool allowTest { get; set; }
public int delay { get; set; }
}
}

View file

@ -0,0 +1,252 @@
namespace v2rayN.Models
{
public class SingboxConfig
{
public Log4Sbox log { get; set; }
public Dns4Sbox? dns { get; set; }
public List<Inbound4Sbox> inbounds { get; set; }
public List<Outbound4Sbox> outbounds { get; set; }
public Route4Sbox route { get; set; }
public Experimental4Sbox? experimental { get; set; }
}
public class Log4Sbox
{
public bool? disabled { get; set; }
public string level { get; set; }
public string output { get; set; }
public bool? timestamp { get; set; }
}
public class Dns4Sbox
{
public List<Server4Sbox> servers { get; set; }
public List<Rule4Sbox> rules { get; set; }
public string? final { get; set; }
public string? strategy { get; set; }
public bool? disable_cache { get; set; }
public bool? disable_expire { get; set; }
public bool? independent_cache { get; set; }
public bool? reverse_mapping { get; set; }
public string? client_subnet { get; set; }
public Fakeip4Sbox? fakeip { get; set; }
}
public class Route4Sbox
{
public bool? auto_detect_interface { get; set; }
public List<Rule4Sbox> rules { get; set; }
public List<Ruleset4Sbox>? rule_set { get; set; }
}
[Serializable]
public class Rule4Sbox
{
public string? outbound { get; set; }
public string? server { get; set; }
public bool? disable_cache { get; set; }
public string? type { get; set; }
public string? mode { get; set; }
public bool? ip_is_private { get; set; }
public string? client_subnet { get; set; }
public bool? invert { get; set; }
public List<string>? inbound { get; set; }
public List<string>? protocol { get; set; }
public List<string>? network { get; set; }
public List<int>? port { get; set; }
public List<string>? port_range { get; set; }
public List<string>? geosite { get; set; }
public List<string>? domain { get; set; }
public List<string>? domain_suffix { get; set; }
public List<string>? domain_keyword { get; set; }
public List<string>? domain_regex { get; set; }
public List<string>? geoip { get; set; }
public List<string>? ip_cidr { get; set; }
public List<string>? source_ip_cidr { get; set; }
public List<string>? process_name { get; set; }
public List<string>? rule_set { get; set; }
public List<Rule4Sbox>? rules { get; set; }
}
[Serializable]
public class Inbound4Sbox
{
public string type { get; set; }
public string tag { get; set; }
public string listen { get; set; }
public int? listen_port { get; set; }
public string? domain_strategy { get; set; }
public string interface_name { get; set; }
public string inet4_address { get; set; }
public string? inet6_address { get; set; }
public int? mtu { get; set; }
public bool? auto_route { get; set; }
public bool? strict_route { get; set; }
public bool? endpoint_independent_nat { get; set; }
public string? stack { get; set; }
public bool? sniff { get; set; }
public bool? sniff_override_destination { get; set; }
public List<User4Sbox> users { get; set; }
}
public class User4Sbox
{
public string username { get; set; }
public string password { get; set; }
}
public class Outbound4Sbox
{
public string type { get; set; }
public string tag { get; set; }
public string? server { get; set; }
public int? server_port { get; set; }
public string uuid { get; set; }
public string security { get; set; }
public int? alter_id { get; set; }
public string flow { get; set; }
public int? up_mbps { get; set; }
public int? down_mbps { get; set; }
public string auth_str { get; set; }
public int? recv_window_conn { get; set; }
public int? recv_window { get; set; }
public bool? disable_mtu_discovery { get; set; }
public string? detour { get; set; }
public string method { get; set; }
public string username { get; set; }
public string password { get; set; }
public string congestion_control { get; set; }
public string? version { get; set; }
public string? network { get; set; }
public string? packet_encoding { get; set; }
public string[]? local_address { get; set; }
public string? private_key { get; set; }
public string? peer_public_key { get; set; }
public int[]? reserved { get; set; }
public int? mtu { get; set; }
public string? plugin { get; set; }
public string? plugin_opts { get; set; }
public Tls4Sbox? tls { get; set; }
public Multiplex4Sbox? multiplex { get; set; }
public Transport4Sbox? transport { get; set; }
public HyObfs4Sbox? obfs { get; set; }
}
public class Tls4Sbox
{
public bool enabled { get; set; }
public string server_name { get; set; }
public bool? insecure { get; set; }
public List<string> alpn { get; set; }
public Utls4Sbox utls { get; set; }
public Reality4Sbox reality { get; set; }
}
public class Multiplex4Sbox
{
public bool enabled { get; set; }
public string protocol { get; set; }
public int max_connections { get; set; }
}
public class Utls4Sbox
{
public bool enabled { get; set; }
public string fingerprint { get; set; }
}
public class Reality4Sbox
{
public bool enabled { get; set; }
public string public_key { get; set; }
public string short_id { get; set; }
}
public class Transport4Sbox
{
public string? type { get; set; }
public object? host { get; set; }
public string? path { get; set; }
public Headers4Sbox? headers { get; set; }
public string? service_name { get; set; }
public string? idle_timeout { get; set; }
public string? ping_timeout { get; set; }
public bool? permit_without_stream { get; set; }
}
public class Headers4Sbox
{
public string? Host { get; set; }
}
public class HyObfs4Sbox
{
public string? type { get; set; }
public string? password { get; set; }
}
public class Server4Sbox
{
public string? tag { get; set; }
public string? address { get; set; }
public string? address_resolver { get; set; }
public string? address_strategy { get; set; }
public string? strategy { get; set; }
public string? detour { get; set; }
public string? client_subnet { get; set; }
}
public class Experimental4Sbox
{
public CacheFile4Sbox? cache_file { get; set; }
public V2ray_Api4Sbox? v2ray_api { get; set; }
public Clash_Api4Sbox? clash_api { get; set; }
}
public class V2ray_Api4Sbox
{
public string listen { get; set; }
public Stats4Sbox stats { get; set; }
}
public class Clash_Api4Sbox
{
public string? external_controller { get; set; }
public bool? store_selected { get; set; }
}
public class Stats4Sbox
{
public bool enabled { get; set; }
public List<string>? inbounds { get; set; }
public List<string>? outbounds { get; set; }
public List<string>? users { get; set; }
}
public class Fakeip4Sbox
{
public bool enabled { get; set; }
public string inet4_range { get; set; }
public string inet6_range { get; set; }
}
public class CacheFile4Sbox
{
public bool enabled { get; set; }
public string? path { get; set; }
public string? cache_id { get; set; }
public bool? store_fakeip { get; set; }
}
public class Ruleset4Sbox
{
public string? tag { get; set; }
public string? type { get; set; }
public string? format { get; set; }
public string? path { get; set; }
public string? url { get; set; }
public string? download_detour { get; set; }
public string? update_interval { get; set; }
}
}

View file

@ -0,0 +1,18 @@
namespace v2rayN.Models
{
public class SsSIP008
{
public List<SsServer> servers { get; set; }
}
[Serializable]
public class SsServer
{
public string remarks { get; set; }
public string server { get; set; }
public string server_port { get; set; }
public string method { get; set; }
public string password { get; set; }
public string plugin { get; set; }
}
}

View file

@ -0,0 +1,35 @@
using SQLite;
namespace v2rayN.Models
{
[Serializable]
public class SubItem
{
[PrimaryKey]
public string id { get; set; }
public string remarks { get; set; }
public string url { get; set; }
public string moreUrl { get; set; }
public bool enabled { get; set; } = true;
public string userAgent { get; set; } = string.Empty;
public int sort { get; set; }
public string? filter { get; set; }
public int autoUpdateInterval { get; set; }
public long updateTime { get; set; }
public string? convertTarget { get; set; }
public string? prevProfile { get; set; }
public string? nextProfile { get; set; }
}
}

View file

@ -0,0 +1,20 @@
namespace v2rayN.Models
{
internal class SysProxyConfig
{
public bool UserSettingsRecorded;
public string Flags;
public string ProxyServer;
public string BypassList;
public string PacUrl;
public SysProxyConfig()
{
UserSettingsRecorded = false;
Flags = "1";
ProxyServer = "";
BypassList = "";
PacUrl = "";
}
}
}

View file

@ -0,0 +1,700 @@
using System.Text.Json.Serialization;
namespace v2rayN.Models
{
/// <summary>
/// v2ray配置文件实体类 例子SampleConfig.txt
/// </summary>
public class V2rayConfig
{
/// <summary>
/// Properties that do not belong to Ray
/// </summary>
public string? remarks { get; set; }
/// <summary>
/// 日志配置
/// </summary>
public Log4Ray log { get; set; }
/// <summary>
/// 传入连接配置
/// </summary>
public List<Inbounds4Ray> inbounds { get; set; }
/// <summary>
/// 传出连接配置
/// </summary>
public List<Outbounds4Ray> outbounds { get; set; }
/// <summary>
/// 统计需要, 空对象
/// </summary>
public Stats4Ray stats { get; set; }
/// </summary>
public API4Ray api { get; set; }
/// </summary>
public Policy4Ray policy { get; set; }
/// <summary>
/// DNS 配置
/// </summary>
public object dns { get; set; }
/// <summary>
/// 路由配置
/// </summary>
public Routing4Ray routing { get; set; }
}
public class Stats4Ray
{ };
public class API4Ray
{
public string tag { get; set; }
public List<string> services { get; set; }
}
public class Policy4Ray
{
public SystemPolicy4Ray system { get; set; }
}
public class SystemPolicy4Ray
{
public bool statsOutboundUplink { get; set; }
public bool statsOutboundDownlink { get; set; }
}
public class Log4Ray
{
/// <summary>
///
/// </summary>
public string access { get; set; }
/// <summary>
///
/// </summary>
public string error { get; set; }
/// <summary>
///
/// </summary>
public string loglevel { get; set; }
}
public class Inbounds4Ray
{
public string tag { get; set; }
/// <summary>
///
/// </summary>
public int port { get; set; }
/// <summary>
///
/// </summary>
public string listen { get; set; }
/// <summary>
///
/// </summary>
public string protocol { get; set; }
/// <summary>
///
/// </summary>
public Sniffing4Ray sniffing { get; set; }
/// <summary>
///
/// </summary>
public Inboundsettings4Ray settings { get; set; }
/// <summary>
///
/// </summary>
public StreamSettings4Ray streamSettings { get; set; }
}
public class Inboundsettings4Ray
{
/// <summary>
///
/// </summary>
public string auth { get; set; }
/// <summary>
///
/// </summary>
public bool udp { get; set; }
/// <summary>
///
/// </summary>
public string ip { get; set; }
/// <summary>
/// api 使用
/// </summary>
public string address { get; set; }
/// <summary>
///
/// </summary>
public List<UsersItem4Ray> clients { get; set; }
/// <summary>
/// VLESS
/// </summary>
public string decryption { get; set; }
public bool allowTransparent { get; set; }
public List<AccountsItem4Ray> accounts { get; set; }
}
public class UsersItem4Ray
{
/// <summary>
///
/// </summary>
public string id { get; set; }
/// <summary>
///
/// </summary>
public int alterId { get; set; }
/// <summary>
///
/// </summary>
public string email { get; set; }
/// <summary>
///
/// </summary>
public string security { get; set; }
/// <summary>
/// VLESS
/// </summary>
public string encryption { get; set; }
/// <summary>
/// VLESS
/// </summary>
public string? flow { get; set; }
}
public class Sniffing4Ray
{
public bool enabled { get; set; }
public List<string>? destOverride { get; set; }
public bool routeOnly { get; set; }
}
public class Outbounds4Ray
{
/// <summary>
/// 默认值agentout
/// </summary>
public string tag { get; set; }
/// <summary>
///
/// </summary>
public string protocol { get; set; }
/// <summary>
///
/// </summary>
public Outboundsettings4Ray settings { get; set; }
/// <summary>
///
/// </summary>
public StreamSettings4Ray streamSettings { get; set; }
/// <summary>
///
/// </summary>
public Mux4Ray mux { get; set; }
}
public class Outboundsettings4Ray
{
/// <summary>
///
/// </summary>
public List<VnextItem4Ray>? vnext { get; set; }
/// <summary>
///
/// </summary>
public List<ServersItem4Ray> servers { get; set; }
/// <summary>
///
/// </summary>
public Response4Ray response { get; set; }
/// <summary>
///
/// </summary>
public string domainStrategy { get; set; }
/// <summary>
///
/// </summary>
public int? userLevel { get; set; }
public FragmentItem4Ray? fragment { get; set; }
}
public class VnextItem4Ray
{
/// <summary>
///
/// </summary>
public string address { get; set; }
/// <summary>
///
/// </summary>
public int port { get; set; }
/// <summary>
///
/// </summary>
public List<UsersItem4Ray> users { get; set; }
}
public class ServersItem4Ray
{
/// <summary>
///
/// </summary>
public string email { get; set; }
/// <summary>
///
/// </summary>
public string address { get; set; }
/// <summary>
///
/// </summary>
public string? method { get; set; }
/// <summary>
///
/// </summary>
public bool? ota { get; set; }
/// <summary>
///
/// </summary>
public string? password { get; set; }
/// <summary>
///
/// </summary>
public int port { get; set; }
/// <summary>
///
/// </summary>
public int? level { get; set; }
/// <summary>
/// trojan
/// </summary>
public string flow { get; set; }
/// <summary>
///
/// </summary>
public List<SocksUsersItem4Ray> users { get; set; }
}
public class SocksUsersItem4Ray
{
/// <summary>
///
/// </summary>
public string user { get; set; }
/// <summary>
///
/// </summary>
public string pass { get; set; }
/// <summary>
///
/// </summary>
public int? level { get; set; }
}
public class Mux4Ray
{
/// <summary>
///
/// </summary>
public bool enabled { get; set; }
/// <summary>
///
/// </summary>
public int concurrency { get; set; }
}
public class Response4Ray
{
/// <summary>
///
/// </summary>
public string type { get; set; }
}
public class Dns4Ray
{
/// <summary>
///
/// </summary>
public List<string> servers { get; set; }
}
public class DnsServer4Ray
{
public string? address { get; set; }
public List<string>? domains { get; set; }
}
public class Routing4Ray
{
/// <summary>
///
/// </summary>
public string domainStrategy { get; set; }
/// <summary>
///
/// </summary>
public string? domainMatcher { get; set; }
/// <summary>
///
/// </summary>
public List<RulesItem4Ray> rules { get; set; }
}
[Serializable]
public class RulesItem4Ray
{
public string? type { get; set; }
public string? port { get; set; }
public string? network { get; set; }
public List<string>? inboundTag { get; set; }
public string? outboundTag { get; set; }
public List<string>? ip { get; set; }
public List<string>? domain { get; set; }
public List<string>? protocol { get; set; }
}
public class StreamSettings4Ray
{
/// <summary>
///
/// </summary>
public string network { get; set; }
/// <summary>
///
/// </summary>
public string security { get; set; }
/// <summary>
///
/// </summary>
public TlsSettings4Ray? tlsSettings { get; set; }
/// <summary>
/// Tcp传输额外设置
/// </summary>
public TcpSettings4Ray? tcpSettings { get; set; }
/// <summary>
/// Kcp传输额外设置
/// </summary>
public KcpSettings4Ray? kcpSettings { get; set; }
/// <summary>
/// ws传输额外设置
/// </summary>
public WsSettings4Ray? wsSettings { get; set; }
/// <summary>
///
/// </summary>
public HttpupgradeSettings4Ray? httpupgradeSettings { get; set; }
/// <summary>
///
/// </summary>
public SplithttpSettings4Ray? splithttpSettings { get; set; }
/// <summary>
/// h2传输额外设置
/// </summary>
public HttpSettings4Ray? httpSettings { get; set; }
/// <summary>
/// QUIC
/// </summary>
public QuicSettings4Ray? quicSettings { get; set; }
/// <summary>
/// VLESS only
/// </summary>
public TlsSettings4Ray? realitySettings { get; set; }
/// <summary>
/// grpc
/// </summary>
public GrpcSettings4Ray? grpcSettings { get; set; }
/// <summary>
/// sockopt
/// </summary>
public Sockopt4Ray? sockopt { get; set; }
}
public class TlsSettings4Ray
{
/// <summary>
/// 是否允许不安全连接(用于客户端)
/// </summary>
public bool? allowInsecure { get; set; }
/// <summary>
///
/// </summary>
public string? serverName { get; set; }
/// <summary>
///
/// </summary>
public List<string>? alpn { get; set; }
public string? fingerprint { get; set; }
public bool? show { get; set; }
public string? publicKey { get; set; }
public string? shortId { get; set; }
public string? spiderX { get; set; }
}
public class TcpSettings4Ray
{
/// <summary>
/// 数据包头部伪装设置
/// </summary>
public Header4Ray header { get; set; }
}
public class Header4Ray
{
/// <summary>
/// 伪装
/// </summary>
public string type { get; set; }
/// <summary>
/// 结构复杂,直接存起来
/// </summary>
public object request { get; set; }
/// <summary>
/// 结构复杂,直接存起来
/// </summary>
public object response { get; set; }
}
public class KcpSettings4Ray
{
/// <summary>
///
/// </summary>
public int mtu { get; set; }
/// <summary>
///
/// </summary>
public int tti { get; set; }
/// <summary>
///
/// </summary>
public int uplinkCapacity { get; set; }
/// <summary>
///
/// </summary>
public int downlinkCapacity { get; set; }
/// <summary>
///
/// </summary>
public bool congestion { get; set; }
/// <summary>
///
/// </summary>
public int readBufferSize { get; set; }
/// <summary>
///
/// </summary>
public int writeBufferSize { get; set; }
/// <summary>
///
/// </summary>
public Header4Ray header { get; set; }
/// <summary>
///
/// </summary>
public string seed { get; set; }
}
public class WsSettings4Ray
{
/// <summary>
///
/// </summary>
public string path { get; set; }
/// <summary>
///
/// </summary>
public Headers4Ray headers { get; set; }
}
public class Headers4Ray
{
/// <summary>
///
/// </summary>
public string Host { get; set; }
/// <summary>
/// 用户代理
/// </summary>
[JsonPropertyName("User-Agent")]
public string UserAgent { get; set; }
}
public class HttpupgradeSettings4Ray
{
/// <summary>
///
/// </summary>
public string? path { get; set; }
/// <summary>
///
/// </summary>
public string? host { get; set; }
}
public class SplithttpSettings4Ray
{
public string? path { get; set; }
public string? host { get; set; }
public int? maxUploadSize { get; set; }
public int? maxConcurrentUploads { get; set; }
}
public class HttpSettings4Ray
{
/// <summary>
///
/// </summary>
public string path { get; set; }
/// <summary>
///
/// </summary>
public List<string> host { get; set; }
}
public class QuicSettings4Ray
{
/// <summary>
///
/// </summary>
public string security { get; set; }
/// <summary>
///
/// </summary>
public string key { get; set; }
/// <summary>
///
/// </summary>
public Header4Ray header { get; set; }
}
public class GrpcSettings4Ray
{
public string? authority { get; set; }
public string? serviceName { get; set; }
public bool multiMode { get; set; }
public int idle_timeout { get; set; }
public int health_check_timeout { get; set; }
public bool permit_without_stream { get; set; }
public int initial_windows_size { get; set; }
}
public class AccountsItem4Ray
{
/// <summary>
///
/// </summary>
public string user { get; set; }
/// <summary>
///
/// </summary>
public string pass { get; set; }
}
public class Sockopt4Ray
{
public string? dialerProxy { get; set; }
}
public class FragmentItem4Ray
{
public string? packets { get; set; }
public string? length { get; set; }
public string? interval { get; set; }
}
}

View file

@ -0,0 +1,21 @@
namespace v2rayN.Models
{
/// <summary>
/// Tcp伪装http的Request只要Host
/// </summary>
public class V2rayTcpRequest
{
/// <summary>
///
/// </summary>
public RequestHeaders headers { get; set; }
}
public class RequestHeaders
{
/// <summary>
///
/// </summary>
public List<string> Host { get; set; }
}
}

View file

@ -0,0 +1,44 @@
using System.Text.Json.Serialization;
namespace v2rayN.Models
{
/// <summary>
/// https://github.com/2dust/v2rayN/wiki/
/// </summary>
[Serializable]
internal class VmessQRCode
{
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public int v { get; set; } = 2;
public string ps { get; set; } = string.Empty;
public string add { get; set; } = string.Empty;
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public int port { get; set; } = 0;
public string id { get; set; } = string.Empty;
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public int aid { get; set; } = 0;
public string scy { get; set; } = string.Empty;
public string net { get; set; } = string.Empty;
public string type { get; set; } = string.Empty;
public string host { get; set; } = string.Empty;
public string path { get; set; } = string.Empty;
public string tls { get; set; } = string.Empty;
public string sni { get; set; } = string.Empty;
public string alpn { get; set; } = string.Empty;
public string fp { get; set; } = string.Empty;
}
}

View file

@ -0,0 +1,163 @@
// See https://aka.ms/new-console-template for more information
using System.Runtime.InteropServices;
using v2rayMiniConsole;
using v2rayMiniConsole.Resx;
using v2rayN;
using v2rayN.Enums;
using v2rayN.Handler;
partial class Program
{
private static bool _exitProgram = false;
// 导入 SetConsoleCtrlHandler 函数的定义
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);
// 委托类型,与 HandlerRoutine 函数签名匹配
private delegate bool HandlerRoutine(CtrlTypes ctrlType);
// 枚举类型,用于表示控制事件类型
private enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
// 自定义的控制台事件处理程序
private static bool ConsoleEventHandler(CtrlTypes ctrlType)
{
switch (ctrlType)
{
case CtrlTypes.CTRL_C_EVENT:
case CtrlTypes.CTRL_BREAK_EVENT:
case CtrlTypes.CTRL_CLOSE_EVENT:
// 在这里执行关闭前的动作
Console.WriteLine("Console is closing. Executing cleanup actions...");
_exitProgram = true;
MainTask.Instance.BroadcastExit(false);
return true;
case CtrlTypes.CTRL_LOGOFF_EVENT:
case CtrlTypes.CTRL_SHUTDOWN_EVENT:
_exitProgram = true;
MainTask.Instance.BroadcastExit(true);
return true;
// 其他控制事件处理...
default:
return false;
}
}
static void Main(string[] args)
{
// 注册控制台事件处理程序
if (!SetConsoleCtrlHandler(ConsoleEventHandler, true))
{
Console.WriteLine("Error registering Console Ctrl Handler");
}
MainTask.Instance.SetListenerType(LazyConfig.Instance.GetConfig().sysProxyType);
StartupInfo();
string inputBuffer = ""; // 用于存储非回车键的输入
Console.Write(">");
while (!_exitProgram)
{
ConsoleKeyInfo keyInfo = Console.ReadKey(true); // 读取第一个按键,不显示在屏幕上
if (!RunningObjects.Instance.IsMessageOn() && keyInfo.Key == ConsoleKey.Backspace && inputBuffer.Length > 0)
{
// 如果按下退格键且输入缓冲区不为空,则删除最后一个字符
inputBuffer = inputBuffer.Substring(0, inputBuffer.Length - 1);
// 清除退格键之后的字符(如果有的话)
Console.Write("\b \b"); // \b 是退格符,后面跟一个空格覆盖原字符,再退格一次
}
else if (keyInfo.Modifiers == ConsoleModifiers.Control && keyInfo.Key == ConsoleKey.T)
{
// Ctrl + T 被按下
HandleCtrlT();
}
else if (!RunningObjects.Instance.IsMessageOn() && keyInfo.Key == ConsoleKey.Enter)
{
// 如果是回车键,则处理之前的输入(如果有的话)
string input = inputBuffer.Trim().ToLower();
inputBuffer = ""; // 重置输入缓冲区
Console.WriteLine();
Console.WriteLine();
// 使用switch语句来处理不同的输入
switch (input)
{
case "h":
Console.Write(ResUI.HelpInfo.Replace("\\t", "\t").Replace("\\n", "\n"));
break;
case "q":
_exitProgram = true;
Console.WriteLine("bye bye");
MainTask.Instance.BroadcastExit(false);
return; // 退出循环
case "set_language":
MainTask.Instance.SetLanguage();
break;
case "switch_server":
MainTask.Instance.SwitchServer();
break;
case "show_current_subs":
MainTask.Instance.ShowCurrentSubscriptions();
break;
case "add_subscription":
MainTask.Instance.AddSubscription();
break;
case "remove_subscription":
MainTask.Instance.RemoveSubscription();
break;
case "change_proxy_mode":
MainTask.Instance.ChangeSystemProxyMode();
break;
case "change_routing":
MainTask.Instance.Change_Routing();
break;
default:
if (!string.IsNullOrEmpty(input))
{
Console.WriteLine($"你输入了:{input}");
}
break;
}
if (!_exitProgram)
{
Console.WriteLine();
Console.Write(">");
}
}
else if (!RunningObjects.Instance.IsMessageOn() && keyInfo.KeyChar != '\0' && !char.IsControl(keyInfo.KeyChar)) // 排除控制字符
{
// 如果不是Enter键或Ctrl + T则将字符添加到输入缓冲区
inputBuffer += keyInfo.KeyChar;
// 可以选择性地回显非特殊字符(如果需要的话)
Console.Write(keyInfo.KeyChar);
}
}
}
static void StartupInfo()
{
Console.WriteLine($" {Utils.GetVersion(false)} - {ResUI.StartupInfo}");
Console.WriteLine();
}
static void HandleCtrlT()
{
// 在这里处理 Ctrl+M 被按下的逻辑
RunningObjects.Instance.ToggleMessage();
}
}

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more