diff --git a/v2rayMiniConsole/.gitattributes b/v2rayMiniConsole/.gitattributes new file mode 100644 index 00000000..1ff0c423 --- /dev/null +++ b/v2rayMiniConsole/.gitattributes @@ -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 diff --git a/v2rayMiniConsole/.gitignore b/v2rayMiniConsole/.gitignore new file mode 100644 index 00000000..9491a2fd --- /dev/null +++ b/v2rayMiniConsole/.gitignore @@ -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 \ No newline at end of file diff --git a/v2rayMiniConsole/PacLib/PacHandler.cs b/v2rayMiniConsole/PacLib/PacHandler.cs new file mode 100644 index 00000000..2e32f1f7 --- /dev/null +++ b/v2rayMiniConsole/PacLib/PacHandler.cs @@ -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) + { + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/PacLib/PacLib.csproj b/v2rayMiniConsole/PacLib/PacLib.csproj new file mode 100644 index 00000000..feb6e55d --- /dev/null +++ b/v2rayMiniConsole/PacLib/PacLib.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + + + + + True + True + Resources.resx + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/v2rayMiniConsole/PacLib/Resources.Designer.cs b/v2rayMiniConsole/PacLib/Resources.Designer.cs new file mode 100644 index 00000000..e7bfd02c --- /dev/null +++ b/v2rayMiniConsole/PacLib/Resources.Designer.cs @@ -0,0 +1,95 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace PacLib { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PacLib.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 var proxy = '__PROXY__'; + ///var rules = [ + /// [ + /// [], + /// [] + /// ], + /// [ + /// [ + /// "aftygh.gov.tw", + /// "aide.gov.tw", + /// "aliyun.com", + /// "arte.gov.tw", + /// "baidu.com", + /// "chinaso.com", + /// "chinaz.com", + /// "chukuang.gov.tw", + /// "cycab.gov.tw", + /// "dbnsa.gov.tw", + /// "df.gov.tw", + /// "eastcoast-nsa.gov.tw", + /// "erv-nsa.gov.tw", + /// "grb.gov.tw", + /// "haosou.com", + /// [字符串的其余部分被截断]"; 的本地化字符串。 + /// + internal static string pac { + get { + return ResourceManager.GetString("pac", resourceCulture); + } + } + } +} diff --git a/v2rayMiniConsole/PacLib/Resources.resx b/v2rayMiniConsole/PacLib/Resources.resx new file mode 100644 index 00000000..1fffd5af --- /dev/null +++ b/v2rayMiniConsole/PacLib/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Resources\pac.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312 + + \ No newline at end of file diff --git a/v2rayMiniConsole/PacLib/Resources/pac.txt b/v2rayMiniConsole/PacLib/Resources/pac.txt new file mode 100644 index 00000000..02576524 --- /dev/null +++ b/v2rayMiniConsole/PacLib/Resources/pac.txt @@ -0,0 +1,6024 @@ +var proxy = '__PROXY__'; +var rules = [ + [ + [], + [] + ], + [ + [ + "aftygh.gov.tw", + "aide.gov.tw", + "aliyun.com", + "arte.gov.tw", + "baidu.com", + "chinaso.com", + "chinaz.com", + "chukuang.gov.tw", + "cycab.gov.tw", + "dbnsa.gov.tw", + "df.gov.tw", + "eastcoast-nsa.gov.tw", + "erv-nsa.gov.tw", + "grb.gov.tw", + "haosou.com", + "haygo.com", + "hchcc.gov.tw", + "hsinchu-cc.gov.tw", + "iner.gov.tw", + "ip.cn", + "jike.com", + "jpush.cn", + "klsio.gov.tw", + "kmseh.gov.tw", + "locql.com", + "lungtanhr.gov.tw", + "maolin-nsa.gov.tw", + "matsu-news.gov.tw", + "matsu-nsa.gov.tw", + "matsucc.gov.tw", + "moe.gov.tw", + "nankan.gov.tw", + "ncree.gov.tw", + "necoast-nsa.gov.tw", + "ner.gov.tw", + "nmmba.gov.tw", + "nmp.gov.tw", + "nmvttc.gov.tw", + "northguan-nsa.gov.tw", + "npm.gov.tw", + "nstm.gov.tw", + "ntdmh.gov.tw", + "ntl.gov.tw", + "ntsec.gov.tw", + "ntuh.gov.tw", + "nvri.gov.tw", + "nyc.gov.tw", + "penghu-nsa.gov.tw", + "post.gov.tw", + "qq.com", + "simplecd.me", + "sina.cn", + "sina.com.cn", + "siraya-nsa.gov.tw", + "sl-reverse.com", + "so.com", + "sogou.com", + "soso.com", + "stdtime.gov.tw", + "sunmoonlake.gov.tw", + "syniumsoftware.com", + "taitung-house.gov.tw", + "taoyuan.gov.tw", + "tphcc.gov.tw", + "trimt-nsa.gov.tw", + "uluai.com.cn", + "vghks.gov.tw", + "vghtc.gov.tw", + "vghtpe.gov.tw", + "wallproxy.com.cn", + "wanfang.gov.tw", + "weibo.com", + "yahoo.cn", + "yatsen.gov.tw", + "yda.gov.tw", + "youdao.com", + "zhongsou.com" + ], + [ + "000webhost.com", + "030buy.com", + "0rz.tw", + "1-apple.com.tw", + "10.tt", + "1000giri.net", + "100ke.org", + "10beasts.net", + "10conditionsoflove.com", + "10musume.com", + "123rf.com", + "12bet.com", + "12vpn.com", + "12vpn.net", + "1337x.to", + "138.com", + "141hongkong.com", + "141jj.com", + "141tube.com", + "1688.com.au", + "173ng.com", + "177pic.info", + "17t17p.com", + "18board.com", + "18board.info", + "18onlygirls.com", + "18p2p.com", + "18virginsex.com", + "1949er.org", + "1984.city", + "1984bbs.com", + "1984bbs.org", + "1991way.com", + "1998cdp.org", + "1bao.org", + "1dumb.com", + "1e100.net", + "1eew.com", + "1mobile.com", + "1mobile.tw", + "1pondo.tv", + "2-hand.info", + "2000fun.com", + "2008xianzhang.info", + "2017.hk", + "2021hkcharter.com", + "2047.name", + "21andy.com", + "21join.com", + "21pron.com", + "21sextury.com", + "228.net.tw", + "233abc.com", + "24hrs.ca", + "24smile.org", + "25u.com", + "2lipstube.com", + "2shared.com", + "2waky.com", + "3-a.net", + "30boxes.com", + "315lz.com", + "32red.com", + "36rain.com", + "3a5a.com", + "3arabtv.com", + "3boys2girls.com", + "3d-game.com", + "3proxy.ru", + "3ren.ca", + "3tui.net", + "404museum.com", + "43110.cf", + "466453.com", + "4bluestones.biz", + "4chan.com", + "4dq.com", + "4everproxy.com", + "4irc.com", + "4mydomain.com", + "4pu.com", + "4rbtv.com", + "4shared.com", + "4sqi.net", + "50webs.com", + "51.ca", + "51jav.org", + "51luoben.com", + "5278.cc", + "5299.tv", + "5aimiku.com", + "5i01.com", + "5isotoi5.org", + "5maodang.com", + "63i.com", + "64museum.org", + "64tianwang.com", + "64wiki.com", + "66.ca", + "666kb.com", + "6do.news", + "6park.com", + "6parkbbs.com", + "6parker.com", + "6parknews.com", + "7-zip.org", + "7capture.com", + "7cow.com", + "8-d.com", + "85cc.net", + "85cc.us", + "85st.com", + "881903.com", + "888.com", + "888poker.com", + "89-64.org", + "8964museum.com", + "8news.com.tw", + "8z1.net", + "9001700.com", + "908taiwan.org", + "91porn.com", + "91vps.club", + "92ccav.com", + "991.com", + "99btgc01.com", + "99cn.info", + "9bis.com", + "9bis.net", + "9cache.com", + "9gag.com", + "9news.com.au", + "a-normal-day.com", + "aamacau.com", + "abc.com", + "abc.net.au", + "abc.xyz", + "abchinese.com", + "abclite.net", + "abebooks.com", + "ablwang.com", + "aboluowang.com", + "about.google", + "aboutgfw.com", + "abs.edu", + "acast.com", + "accim.org", + "accountkit.com", + "aceros-de-hispania.com", + "acevpn.com", + "acg18.me", + "acgbox.org", + "acgkj.com", + "acgnx.se", + "acmedia365.com", + "acmetoy.com", + "acnw.com.au", + "actfortibet.org", + "actimes.com.au", + "activpn.com", + "aculo.us", + "adcex.com", + "addictedtocoffee.de", + "addyoutube.com", + "adelaidebbs.com", + "admob.com", + "adpl.org.hk", + "ads-twitter.com", + "adsense.com", + "adult-sex-games.com", + "adultfriendfinder.com", + "adultkeep.net", + "advanscene.com", + "advertfan.com", + "advertisercommunity.com", + "ae.org", + "aenhancers.com", + "aex.com", + "af.mil", + "afantibbs.com", + "afr.com", + "afreecatv.com", + "agnesb.fr", + "agoogleaday.com", + "agro.hk", + "ai-kan.net", + "ai-wen.net", + "ai.google", + "aiph.net", + "airasia.com", + "airconsole.com", + "aircrack-ng.org", + "airvpn.org", + "aisex.com", + "ait.org.tw", + "aiweiwei.com", + "aiweiweiblog.com", + "ajsands.com", + "akademiye.org", + "akamai.net", + "akamaihd.net", + "akamaistream.net", + "akamaized.net", + "akiba-online.com", + "akiba-web.com", + "akow.org", + "al-islam.com", + "al-qimmah.net", + "alabout.com", + "alanhou.com", + "alarab.qa", + "alasbarricadas.org", + "alexlur.org", + "alforattv.net", + "alhayat.com", + "alicejapan.co.jp", + "aliengu.com", + "alive.bar", + "alkasir.com", + "all4mom.org", + "allcoin.com", + "allconnected.co", + "alldrawnsex.com", + "allervpn.com", + "allfinegirls.com", + "allgirlmassage.com", + "allgirlsallowed.org", + "allgravure.com", + "alliance.org.hk", + "allinfa.com", + "alljackpotscasino.com", + "allmovie.com", + "allowed.org", + "almasdarnews.com", + "almostmy.com", + "alphaporno.com", + "alternate-tools.com", + "alternativeto.net", + "altrec.com", + "alvinalexander.com", + "alwaysdata.com", + "alwaysdata.net", + "alwaysvpn.com", + "am730.com.hk", + "amazon.co.jp", + "amazon.com", + "amazonaws.com", + "ameblo.jp", + "america.gov", + "american.edu", + "americangreencard.com", + "americanunfinished.com", + "americorps.gov", + "amiblockedornot.com", + "amigobbs.net", + "amitabhafoundation.us", + "amnesty.org", + "amnesty.org.hk", + "amnesty.tw", + "amnestyusa.org", + "amnyemachen.org", + "amoiist.com", + "ampproject.org", + "amtb-taipei.org", + "anchor.fm", + "anchorfree.com", + "ancsconf.org", + "andfaraway.net", + "android-x86.org", + "android.com", + "androidify.com", + "androidplus.co", + "androidtv.com", + "andygod.com", + "angela-merkel.de", + "angelfire.com", + "angola.org", + "angularjs.org", + "animecrazy.net", + "aniscartujo.com", + "annatam.com", + "anobii.com", + "anontext.com", + "anonymitynetwork.com", + "anonymizer.com", + "anonymouse.org", + "anpopo.com", + "answering-islam.org", + "antd.org", + "anthonycalzadilla.com", + "anti1984.com", + "antichristendom.com", + "antiwave.net", + "anws.gov.tw", + "anyporn.com", + "anysex.com", + "ao3.org", + "aobo.com.au", + "aofriend.com", + "aofriend.com.au", + "aojiao.org", + "aol.ca", + "aol.co.uk", + "aol.com", + "aolnews.com", + "aomiwang.com", + "ap.org", + "apartmentratings.com", + "apartments.com", + "apat1989.org", + "apetube.com", + "api.ai", + "apiary.io", + "apigee.com", + "apk-dl.com", + "apk.support", + "apkcombo.com", + "apkmirror.com", + "apkmonk.com", + "apkplz.com", + "apkpure.com", + "aplusvpn.com", + "appbrain.com", + "appdownloader.net", + "appledaily.com", + "appledaily.com.hk", + "appledaily.com.tw", + "appshopper.com", + "appsocks.net", + "appspot.com", + "appsto.re", + "aptoide.com", + "archive.fo", + "archive.is", + "archive.li", + "archive.org", + "archive.ph", + "archive.today", + "archiveofourown.com", + "archiveofourown.org", + "archives.gov", + "archives.gov.tw", + "arctosia.com", + "areca-backup.org", + "arena.taipei", + "arethusa.su", + "arlingtoncemetery.mil", + "army.mil", + "art4tibet1998.org", + "arte.tv", + "artofpeacefoundation.org", + "artstation.com", + "artsy.net", + "asacp.org", + "asdfg.jp", + "asg.to", + "asia-gaming.com", + "asiaharvest.org", + "asianage.com", + "asianews.it", + "asianfreeforum.com", + "asiansexdiary.com", + "asianspiss.com", + "asianwomensfilm.de", + "asiaone.com", + "asiatgp.com", + "asiatoday.us", + "askstudent.com", + "askynz.net", + "aspi.org.au", + "aspistrategist.org.au", + "assembla.com", + "assimp.org", + "astrill.com", + "atc.org.au", + "atchinese.com", + "atdmt.com", + "atgfw.org", + "athenaeizou.com", + "atlanta168.com", + "atlaspost.com", + "atnext.com", + "audionow.com", + "authorizeddns.net", + "authorizeddns.org", + "authorizeddns.us", + "autodraw.com", + "av-e-body.com", + "av.com", + "av.movie", + "avaaz.org", + "avbody.tv", + "avcity.tv", + "avcool.com", + "avdb.in", + "avdb.tv", + "avfantasy.com", + "avg.com", + "avgle.com", + "avidemux.org", + "avmo.pw", + "avmoo.com", + "avmoo.net", + "avmoo.pw", + "avoision.com", + "avyahoo.com", + "axios.com", + "axureformac.com", + "azerbaycan.tv", + "azerimix.com", + "azubu.tv", + "azurewebsites.net", + "b-ok.cc", + "b0ne.com", + "baby-kingdom.com", + "babylonbee.com", + "babynet.com.hk", + "backchina.com", + "backpackers.com.tw", + "backtotiananmen.com", + "badiucao.com", + "badjojo.com", + "badoo.com", + "baidu.jp", + "baijie.org", + "bailandaily.com", + "baixing.me", + "bakgeekhome.tk", + "banana-vpn.com", + "band.us", + "bandcamp.com", + "bandwagonhost.com", + "bangbrosnetwork.com", + "bangchen.net", + "bangdream.space", + "bangkokpost.com", + "bangyoulater.com", + "bankmobilevibe.com", + "bannedbook.org", + "bannednews.org", + "banorte.com", + "baramangaonline.com", + "barenakedislam.com", + "barnabu.co.uk", + "barton.de", + "bastillepost.com", + "bayvoice.net", + "baywords.com", + "bb-chat.tv", + "bbc.co.uk", + "bbc.com", + "bbc.in", + "bbcchinese.com", + "bbchat.tv", + "bbci.co.uk", + "bbg.gov", + "bbkz.com", + "bbnradio.org", + "bbs-tw.com", + "bbsdigest.com", + "bbsfeed.com", + "bbsland.com", + "bbsmo.com", + "bbsone.com", + "bbtoystore.com", + "bcast.co.nz", + "bcc.com.tw", + "bcchinese.net", + "bcex.ca", + "bcmorning.com", + "bdsmvideos.net", + "beaconevents.com", + "bebo.com", + "beeg.com", + "beevpn.com", + "behance.net", + "behindkink.com", + "beijing1989.com", + "beijing2022.art", + "beijingspring.com", + "beijingzx.org", + "belamionline.com", + "bell.wiki", + "bemywife.cc", + "beric.me", + "berlinerbericht.de", + "berlintwitterwall.com", + "berm.co.nz", + "bestforchina.org", + "bestgore.com", + "bestpornstardb.com", + "bestvpn.com", + "bestvpnanalysis.com", + "bestvpnserver.com", + "bestvpnservice.com", + "bestvpnusa.com", + "bet365.com", + "betfair.com", + "betternet.co", + "bettervpn.com", + "bettween.com", + "betvictor.com", + "bewww.net", + "beyondfirewall.com", + "bfnn.org", + "bfsh.hk", + "bgvpn.com", + "bianlei.com", + "biantailajiao.com", + "biantailajiao.in", + "biblesforamerica.org", + "bibox.com", + "bic2011.org", + "biedian.me", + "big.one", + "bigfools.com", + "bigjapanesesex.com", + "bigmoney.biz", + "bignews.org", + "bigone.com", + "bigsound.org", + "bild.de", + "biliworld.com", + "billypan.com", + "binance.com", + "bing.com", + "binux.me", + "binwang.me", + "bird.so", + "bit-z.com", + "bit.do", + "bit.ly", + "bitbay.net", + "bitchute.com", + "bitcointalk.org", + "bitcoinworld.com", + "bitfinex.com", + "bithumb.com", + "bitinka.com.ar", + "bitmex.com", + "bitshare.com", + "bitsnoop.com", + "bitterwinter.org", + "bitvise.com", + "bitz.ai", + "bizhat.com", + "bjnewlife.org", + "bjs.org", + "bjzc.org", + "bl-doujinsouko.com", + "blacklogic.com", + "blackvpn.com", + "blewpass.com", + "blingblingsquad.net", + "blinkx.com", + "blinw.com", + "blip.tv", + "blockcast.it", + "blockcn.com", + "blockedbyhk.com", + "blockless.com", + "blog.de", + "blog.google", + "blog.jp", + "blogblog.com", + "blogcatalog.com", + "blogcity.me", + "blogdns.org", + "blogger.com", + "blogimg.jp", + "bloglines.com", + "bloglovin.com", + "blogs.com", + "blogspot.com", + "blogspot.hk", + "blogspot.jp", + "blogspot.tw", + "blogtd.net", + "blogtd.org", + "bloodshed.net", + "bloomberg.cn", + "bloomberg.com", + "bloomberg.de", + "bloombergview.com", + "bloomfortune.com", + "blubrry.com", + "blueangellive.com", + "bmfinn.com", + "bnews.co", + "bnext.com.tw", + "bnn.co", + "bnrmetal.com", + "boardreader.com", + "bod.asia", + "bodog88.com", + "bolehvpn.net", + "bonbonme.com", + "bonbonsex.com", + "bonfoundation.org", + "bongacams.com", + "boobstagram.com", + "book.com.tw", + "bookdepository.com", + "bookepub.com", + "books.com.tw", + "booktopia.com.au", + "boomssr.com", + "borgenmagazine.com", + "bot.nu", + "botanwang.com", + "bowenpress.com", + "box.com", + "box.net", + "boxpn.com", + "boxun.com", + "boxun.tv", + "boxunblog.com", + "boxunclub.com", + "boyangu.com", + "boyfriendtv.com", + "boysfood.com", + "boysmaster.com", + "br.st", + "brainyquote.com", + "brandonhutchinson.com", + "braumeister.org", + "brave.com", + "bravotube.net", + "brazzers.com", + "breached.to", + "break.com", + "breakgfw.com", + "breaking911.com", + "breakingtweets.com", + "breakwall.net", + "briefdream.com", + "briian.com", + "brill.com", + "brizzly.com", + "brkmd.com", + "broadbook.com", + "broadpressinc.com", + "brockbbs.com", + "brookings.edu", + "brucewang.net", + "brutaltgp.com", + "bt2mag.com", + "bt95.com", + "btaia.com", + "btbtav.com", + "btc98.com", + "btcbank.bank", + "btctrade.im", + "btdigg.org", + "btku.me", + "btku.org", + "btspread.com", + "btsynckeys.com", + "budaedu.org", + "buddhanet.com.tw", + "buddhistchannel.tv", + "buffered.com", + "bullguard.com", + "bullog.org", + "bullogger.com", + "bunbunhk.com", + "busayari.com", + "business-humanrights.org", + "business.page", + "businessinsider.com", + "businessinsider.com.au", + "businesstoday.com.tw", + "businessweek.com", + "busu.org", + "busytrade.com", + "buugaa.com", + "buzzhand.com", + "buzzhand.net", + "buzzorange.com", + "bvpn.com", + "bwbx.io", + "bwgyhw.com", + "bwh1.net", + "bwsj.hk", + "bx.in.th", + "bx.tl", + "bybit.com", + "bynet.co.il", + "bypasscensorship.org", + "byrut.org", + "c-est-simple.com", + "c-span.org", + "c-spanvideo.org", + "c100tibet.org", + "c2cx.com", + "cablegatesearch.net", + "cachinese.com", + "cacnw.com", + "cactusvpn.com", + "cafepress.com", + "cahr.org.tw", + "caijinglengyan.com", + "calameo.com", + "calebelston.com", + "calgarychinese.ca", + "calgarychinese.com", + "calgarychinese.net", + "calibre-ebook.com", + "caltech.edu", + "cam4.com", + "cam4.jp", + "cam4.sg", + "camfrog.com", + "campaignforuyghurs.org", + "cams.com", + "cams.org.sg", + "canadameet.com", + "canalporno.com", + "cantonese.asia", + "canyu.org", + "cao.im", + "caobian.info", + "caochangqing.com", + "cap.org.hk", + "carabinasypistolas.com", + "cardinalkungfoundation.org", + "careerengine.us", + "carfax.com", + "cari.com.my", + "caribbeancom.com", + "carmotorshow.com", + "carrd.co", + "carryzhou.com", + "cartoonmovement.com", + "casadeltibetbcn.org", + "casatibet.org.mx", + "casinobellini.com", + "casinoking.com", + "casinoriva.com", + "castbox.fm", + "catch22.net", + "catchgod.com", + "catfightpayperview.xxx", + "catholic.org.hk", + "catholic.org.tw", + "cathvoice.org.tw", + "cato.org", + "cattt.com", + "cbc.ca", + "cbsnews.com", + "cbtc.org.hk", + "cc.com", + "cccat.cc", + "cccat.co", + "ccdtr.org", + "cchere.com", + "ccim.org", + "cclife.ca", + "cclife.org", + "cclifefl.org", + "ccthere.com", + "ccthere.net", + "cctmweb.net", + "cctongbao.com", + "ccue.ca", + "ccue.com", + "ccvoice.ca", + "ccw.org.tw", + "cdbook.org", + "cdcparty.com", + "cdef.org", + "cdig.info", + "cdjp.org", + "cdnews.com.tw", + "cdninstagram.com", + "cdp1989.org", + "cdp1998.org", + "cdp2006.org", + "cdpeu.org", + "cdpusa.org", + "cdpweb.org", + "cdpwu.org", + "cdw.com", + "cecc.gov", + "cellulo.info", + "cenews.eu", + "centauro.com.br", + "centerforhumanreprod.com", + "centralnation.com", + "centurys.net", + "certificate-transparency.org", + "cfhks.org.hk", + "cfos.de", + "cfr.org", + "cftfc.com", + "cgdepot.org", + "cgst.edu", + "change.org", + "changeip.name", + "changeip.net", + "changeip.org", + "changp.com", + "changsa.net", + "channelnewsasia.com", + "chaoex.com", + "chapm25.com", + "chatnook.com", + "chaturbate.com", + "checkgfw.com", + "chengmingmag.com", + "chenguangcheng.com", + "chenpokong.com", + "chenpokong.net", + "chenpokongvip.com", + "cherrysave.com", + "chhongbi.org", + "chicagoncmtv.com", + "china-mmm.net", + "china-review.com.ua", + "china-week.com", + "china101.com", + "china18.org", + "china21.com", + "china21.org", + "china5000.us", + "chinaaffairs.org", + "chinaaid.me", + "chinaaid.net", + "chinaaid.org", + "chinaaid.us", + "chinachange.org", + "chinachannel.hk", + "chinacitynews.be", + "chinacomments.org", + "chinadialogue.net", + "chinadigitaltimes.net", + "chinaelections.org", + "chinaeweekly.com", + "chinafreepress.org", + "chinagate.com", + "chinageeks.org", + "chinagfw.org", + "chinagonet.com", + "chinagreenparty.org", + "chinahorizon.org", + "chinahush.com", + "chinainperspective.com", + "chinainterimgov.org", + "chinalaborwatch.org", + "chinalawandpolicy.com", + "chinalawtranslate.com", + "chinamule.com", + "chinamz.org", + "chinanewscenter.com", + "chinapost.com.tw", + "chinapress.com.my", + "chinarightsia.org", + "chinasmile.net", + "chinasocialdemocraticparty.com", + "chinasoul.org", + "chinasucks.net", + "chinatimes.com", + "chinatopsex.com", + "chinatown.com.au", + "chinatweeps.com", + "chinaway.org", + "chinaworker.info", + "chinaxchina.com", + "chinayouth.org.hk", + "chinayuanmin.org", + "chinese-hermit.net", + "chinese-leaders.org", + "chinese-memorial.org", + "chinesedaily.com", + "chinesedailynews.com", + "chinesedemocracy.com", + "chinesegay.org", + "chinesen.de", + "chinesenews.net.au", + "chinesepen.org", + "chineseradioseattle.com", + "chinesetalks.net", + "chineseupress.com", + "chingcheong.com", + "chinman.net", + "chithu.org", + "chobit.cc", + "chosun.com", + "chrdnet.com", + "christianfreedom.org", + "christianstudy.com", + "christiantimes.org.hk", + "christusrex.org", + "chrlawyers.hk", + "chrome.com", + "chromecast.com", + "chromeenterprise.google", + "chromeexperiments.com", + "chromercise.com", + "chromestatus.com", + "chromium.org", + "chuang-yen.org", + "chubold.com", + "chubun.com", + "churchinhongkong.org", + "chushigangdrug.ch", + "cienen.com", + "cineastentreff.de", + "cipfg.org", + "circlethebayfortibet.org", + "cirosantilli.com", + "citizencn.com", + "citizenlab.ca", + "citizenlab.org", + "citizenscommission.hk", + "citizensradio.org", + "city365.ca", + "city9x.com", + "citypopulation.de", + "citytalk.tw", + "civicparty.hk", + "civildisobediencemovement.org", + "civilhrfront.org", + "civiliangunner.com", + "civilmedia.tw", + "civisec.org", + "cjb.net", + "ck101.com", + "clarionproject.org", + "classicalguitarblog.net", + "clb.org.hk", + "cleansite.biz", + "cleansite.info", + "cleansite.us", + "clearharmony.net", + "clearsurance.com", + "clearwisdom.net", + "clementine-player.org", + "clinica-tibet.ru", + "clipfish.de", + "cloakpoint.com", + "cloudcone.com", + "cloudflare-ipfs.com", + "cloudfront.net", + "club1069.com", + "clubhouseapi.com", + "clyp.it", + "cmcn.org", + "cmi.org.tw", + "cmoinc.org", + "cms.gov", + "cmu.edu", + "cmule.com", + "cmule.org", + "cmx.im", + "cn-proxy.com", + "cn.com", + "cn6.eu", + "cna.com.tw", + "cnabc.com", + "cnd.org", + "cnet.com", + "cnex.org.cn", + "cnineu.com", + "cnitter.com", + "cnn.com", + "cnpolitics.org", + "cnproxy.com", + "cnyes.com", + "co.tv", + "coat.co.jp", + "cobinhood.com", + "cochina.co", + "cochina.org", + "code1984.com", + "codeplex.com", + "codeshare.io", + "codeskulptor.org", + "coin2co.in", + "coinbene.com", + "coinegg.com", + "coinex.com", + "coingecko.com", + "coingi.com", + "coinmarketcap.com", + "coinrail.co.kr", + "cointiger.com", + "cointobe.com", + "coinut.com", + "collateralmurder.com", + "collateralmurder.org", + "com.google", + "com.ru", + "com.uk", + "comedycentral.com", + "comefromchina.com", + "comic-mega.me", + "comico.tw", + "commandarms.com", + "commentshk.com", + "communistcrimes.org", + "communitychoicecu.com", + "comparitech.com", + "compileheart.com", + "compress.to", + "compython.net", + "conoha.jp", + "constitutionalism.solutions", + "contactmagazine.net", + "convio.net", + "coobay.com", + "cool18.com", + "coolaler.com", + "coolder.com", + "coolloud.org.tw", + "coolncute.com", + "coolstuffinc.com", + "corumcollege.com", + "cos-moe.com", + "cosplayjav.pl", + "costco.com", + "cotweet.com", + "counter.social", + "coursehero.com", + "cpj.org", + "cq99.us", + "crackle.com", + "crazys.cc", + "crazyshit.com", + "crbug.com", + "crchina.org", + "crd-net.org", + "creaders.net", + "creadersnet.com", + "creativelab5.com", + "crisisresponse.google", + "cristyli.com", + "crocotube.com", + "crossfire.co.kr", + "crossthewall.net", + "crossvpn.net", + "croxyproxy.com", + "crrev.com", + "crucial.com", + "crunchyroll.com", + "cryptographyengineering.com", + "csdparty.com", + "csis.org", + "csmonitor.com", + "csuchen.de", + "csw.org.uk", + "ct.org.tw", + "ctao.org", + "ctfriend.net", + "ctitv.com.tw", + "ctowc.org", + "cts.com.tw", + "ctwant.com", + "cuhk.edu.hk", + "cuhkacs.org", + "cuihua.org", + "cuiweiping.net", + "culture.tw", + "cumlouder.com", + "curvefish.com", + "cusp.hk", + "cusu.hk", + "cutscenes.net", + "cw.com.tw", + "cwb.gov.tw", + "cyberctm.com", + "cyberghostvpn.com", + "cynscribe.com", + "cytode.us", + "cz.cc", + "d-fukyu.com", + "d0z.net", + "d100.net", + "d2bay.com", + "d2pass.com", + "dabr.co.uk", + "dabr.eu", + "dabr.me", + "dabr.mobi", + "dadazim.com", + "dadi360.com", + "dafabet.com", + "dafagood.com", + "dafahao.com", + "dafoh.org", + "daftporn.com", + "dagelijksestandaard.nl", + "daidostup.ru", + "dailidaili.com", + "dailymail.co.uk", + "dailymotion.com", + "dailysabah.com", + "dailyview.tw", + "daiphapinfo.net", + "dajiyuan.com", + "dajiyuan.de", + "dajiyuan.eu", + "dalailama-archives.org", + "dalailama.com", + "dalailama.mn", + "dalailama.ru", + "dalailama80.org", + "dalailamacenter.org", + "dalailamafellows.org", + "dalailamafilm.com", + "dalailamafoundation.org", + "dalailamahindi.com", + "dalailamainaustralia.org", + "dalailamajapanese.com", + "dalailamaprotesters.info", + "dalailamaquotes.org", + "dalailamatrust.org", + "dalailamavisit.org.nz", + "dalailamaworld.com", + "dalianmeng.org", + "daliulian.org", + "danke4china.net", + "daolan.net", + "darktech.org", + "darktoy.net", + "darpa.mil", + "darrenliuwei.com", + "dastrassi.org", + "data-vocabulary.org", + "data.gov.tw", + "daum.net", + "david-kilgour.com", + "dawangidc.com", + "daxa.cn", + "dayabook.com", + "daylife.com", + "db.tt", + "dbc.hk", + "dbgjd.com", + "dcard.tw", + "dcmilitary.com", + "ddc.com.tw", + "ddhw.info", + "ddns.info", + "ddns.me.uk", + "ddns.mobi", + "ddns.ms", + "ddns.name", + "ddns.net", + "ddns.us", + "de-sci.org", + "deadline.com", + "deaftone.com", + "debug.com", + "deck.ly", + "decodet.co", + "deepmind.com", + "deezer.com", + "definebabe.com", + "deja.com", + "delcamp.net", + "delicious.com", + "democrats.org", + "demosisto.hk", + "depositphotos.com", + "desc.se", + "design.google", + "desipro.de", + "dessci.com", + "destroy-china.jp", + "deutsche-welle.de", + "deviantart.com", + "deviantart.net", + "devio.us", + "devpn.com", + "dfas.mil", + "dfn.org", + "dharamsalanet.com", + "dharmakara.net", + "dhcp.biz", + "diaoyuislands.org", + "difangwenge.org", + "digiland.tw", + "digisfera.com", + "digitalnomadsproject.org", + "diigo.com", + "dilber.se", + "dingchin.com.tw", + "dipity.com", + "directcreative.com", + "discoins.com", + "disconnect.me", + "discord.com", + "discord.gg", + "discordapp.com", + "discordapp.net", + "discuss.com.hk", + "discuss4u.com", + "dish.com", + "disp.cc", + "disqus.com", + "dit-inc.us", + "dizhidizhi.com", + "dizhuzhishang.com", + "djangosnippets.org", + "djorz.com", + "dl-laby.jp", + "dlive.tv", + "dlsite.com", + "dlsite.jp", + "dlyoutube.com", + "dm530.net", + "dmc.nico", + "dmcdn.net", + "dmhy.org", + "dmm.co.jp", + "dmm.com", + "dns-dns.com", + "dns-stuff.com", + "dns.google", + "dns04.com", + "dns05.com", + "dns1.us", + "dns2.us", + "dns2go.com", + "dnscrypt.org", + "dnset.com", + "dnsrd.com", + "dnssec.net", + "dnvod.tv", + "doctorvoice.org", + "documentingreality.com", + "dogfartnetwork.com", + "dojin.com", + "dok-forum.net", + "dolc.de", + "dolf.org.hk", + "dollf.com", + "domain.club.tw", + "domains.google", + "domaintoday.com.au", + "donga.com", + "dongtaiwang.com", + "dongtaiwang.net", + "dongyangjing.com", + "donmai.us", + "dontfilter.us", + "dontmovetochina.com", + "dorjeshugden.com", + "dotplane.com", + "dotsub.com", + "dotvpn.com", + "doub.io", + "doubibackup.com", + "doubmirror.cf", + "dougscripts.com", + "douhokanko.net", + "doujincafe.com", + "dowei.org", + "dowjones.com", + "dphk.org", + "dpp.org.tw", + "dpr.info", + "dragonex.io", + "dragonsprings.org", + "dreamamateurs.com", + "drepung.org", + "drgan.net", + "drmingxia.org", + "dropbooks.tv", + "dropbox.com", + "dropboxapi.com", + "dropboxusercontent.com", + "drsunacademy.com", + "drtuber.com", + "dscn.info", + "dsmtp.com", + "dstk.dk", + "dtdns.net", + "dtiblog.com", + "dtic.mil", + "dtwang.org", + "duanzhihu.com", + "dubox.com", + "duck.com", + "duckdns.org", + "duckduckgo.com", + "duckload.com", + "duckmylife.com", + "duga.jp", + "duihua.org", + "duihuahrjournal.org", + "dumb1.com", + "dunyabulteni.net", + "duoweitimes.com", + "duping.net", + "duplicati.com", + "dupola.com", + "dupola.net", + "dushi.ca", + "duyaoss.com", + "dvdpac.com", + "dvorak.org", + "dw-world.com", + "dw-world.de", + "dw.com", + "dw.de", + "dwheeler.com", + "dwnews.com", + "dwnews.net", + "dxiong.com", + "dynamic-dns.net", + "dynamicdns.biz", + "dynamicdns.co.uk", + "dynamicdns.me.uk", + "dynamicdns.org.uk", + "dynawebinc.com", + "dyndns-ip.com", + "dyndns-pics.com", + "dyndns.org", + "dyndns.pro", + "dynssl.com", + "dynu.com", + "dynu.net", + "dysfz.cc", + "dzze.com", + "e-classical.com.tw", + "e-gold.com", + "e-hentai.org", + "e-hentaidb.com", + "e-info.org.tw", + "e-traderland.net", + "e-zone.com.hk", + "e123.hk", + "earlytibet.com", + "earthcam.com", + "earthvpn.com", + "eastern-ark.com", + "easternlightning.org", + "eastturkestan.com", + "eastturkistan-gov.org", + "eastturkistan.net", + "eastturkistancc.org", + "eastturkistangovernmentinexile.us", + "easyca.ca", + "easypic.com", + "ebc.net.tw", + "ebony-beauty.com", + "ebookbrowse.com", + "ebookee.com", + "ebtcbank.com", + "ecfa.org.tw", + "echainhost.com", + "echofon.com", + "ecimg.tw", + "ecministry.net", + "economist.com", + "ecstart.com", + "edgecastcdn.net", + "edgesuite.net", + "edicypages.com", + "edmontonchina.cn", + "edmontonservice.com", + "edns.biz", + "edoors.com", + "edubridge.com", + "edupro.org", + "eesti.ee", + "eevpn.com", + "efcc.org.hk", + "effers.com", + "efksoft.com", + "efukt.com", + "eic-av.com", + "eireinikotaerukai.com", + "eisbb.com", + "eksisozluk.com", + "electionsmeter.com", + "elgoog.im", + "ellawine.org", + "elpais.com", + "eltondisney.com", + "emaga.com", + "emanna.com", + "emilylau.org.hk", + "emory.edu", + "empfil.com", + "emule-ed2k.com", + "emulefans.com", + "emuparadise.me", + "enanyang.my", + "encrypt.me", + "encyclopedia.com", + "enewstree.com", + "enfal.de", + "engadget.com", + "engagedaily.org", + "englishforeveryone.org", + "englishfromengland.co.uk", + "englishpen.org", + "enlighten.org.tw", + "entermap.com", + "environment.google", + "epa.gov.tw", + "epac.to", + "episcopalchurch.org", + "epochhk.com", + "epochtimes-bg.com", + "epochtimes-romania.com", + "epochtimes.co.il", + "epochtimes.co.kr", + "epochtimes.com", + "epochtimes.cz", + "epochtimes.de", + "epochtimes.fr", + "epochtimes.ie", + "epochtimes.it", + "epochtimes.jp", + "epochtimes.ru", + "epochtimes.se", + "epochtimestr.com", + "epochweek.com", + "epochweekly.com", + "eporner.com", + "equinenow.com", + "erabaru.net", + "eracom.com.tw", + "eraysoft.com.tr", + "erepublik.com", + "erights.net", + "eriversoft.com", + "erktv.com", + "ernestmandel.org", + "erodaizensyu.com", + "erodoujinlog.com", + "erodoujinworld.com", + "eromanga-kingdom.com", + "eromangadouzin.com", + "eromon.net", + "eroprofile.com", + "eroticsaloon.net", + "eslite.com", + "esmtp.biz", + "esu.dog", + "esu.im", + "esurance.com", + "etaa.org.au", + "etadult.com", + "etaiwannews.com", + "etherdelta.com", + "etherscan.io", + "etizer.org", + "etokki.com", + "etowns.net", + "etowns.org", + "etsy.com", + "ettoday.net", + "etvonline.hk", + "eu.org", + "eucasino.com", + "eulam.com", + "eurekavpt.com", + "euronews.com", + "europa.eu", + "evozi.com", + "evschool.net", + "exblog.co.jp", + "exblog.jp", + "exchristian.hk", + "excite.co.jp", + "exhentai.org", + "exmo.com", + "exmormon.org", + "expatshield.com", + "expecthim.com", + "expekt.com", + "experts-univers.com", + "exploader.net", + "expofutures.com", + "expressvpn.com", + "exrates.me", + "extmatrix.com", + "extremetube.com", + "exx.com", + "eyevio.jp", + "eyny.com", + "ezpc.tk", + "ezpeer.com", + "ezua.com", + "f8.com", + "fa.gov.tw", + "facebook.br", + "facebook.com", + "facebook.design", + "facebook.hu", + "facebook.in", + "facebook.net", + "facebook.nl", + "facebook.se", + "facebookmail.com", + "facebookquotes4u.com", + "faceless.me", + "facesofnyfw.com", + "facesoftibetanselfimmolators.info", + "factpedia.org", + "fail.hk", + "faith100.org", + "faithfuleye.com", + "faiththedog.info", + "fakku.net", + "fallenark.com", + "falsefire.com", + "falun-co.org", + "falun-ny.net", + "falunart.org", + "falunasia.info", + "falunau.org", + "falunaz.net", + "falundafa-dc.org", + "falundafa-florida.org", + "falundafa-nc.org", + "falundafa-pa.net", + "falundafa-sacramento.org", + "falundafa.org", + "falundafaindia.org", + "falundafamuseum.org", + "falungong.club", + "falungong.de", + "falungong.org.uk", + "falunhr.org", + "faluninfo.de", + "faluninfo.net", + "falunpilipinas.net", + "falunworld.net", + "familyfed.org", + "famunion.com", + "fan-qiang.com", + "fandom.com", + "fangbinxing.com", + "fangeming.com", + "fangeqiang.com", + "fanglizhi.info", + "fangmincn.org", + "fangong.org", + "fangongheike.com", + "fanhaodang.com", + "fanhaolou.com", + "fanqiang.network", + "fanqiang.tk", + "fanqiangdang.com", + "fanqianghou.com", + "fanqiangyakexi.net", + "fanqiangzhe.com", + "fanswong.com", + "fantv.hk", + "fanyue.info", + "fapdu.com", + "faproxy.com", + "faqserv.com", + "fartit.com", + "farwestchina.com", + "fastestvpn.com", + "fastly.net", + "fastpic.ru", + "fastssh.com", + "faststone.org", + "fatbtc.com", + "favotter.net", + "favstar.fm", + "fawanghuihui.org", + "faydao.com", + "faz.net", + "fb.com", + "fb.me", + "fb.watch", + "fbaddins.com", + "fbcdn.net", + "fbsbx.com", + "fbworkmail.com", + "fc2.com", + "fc2blog.net", + "fc2china.com", + "fc2cn.com", + "fc2web.com", + "fda.gov.tw", + "fdbox.com", + "fdc64.de", + "fdc64.org", + "fdc89.jp", + "feedburner.com", + "feeder.co", + "feedly.com", + "feedx.net", + "feelssh.com", + "feer.com", + "feifeiss.com", + "feitian-california.org", + "feitianacademy.org", + "feixiaohao.com", + "feministteacher.com", + "fengzhenghu.com", + "fengzhenghu.net", + "fevernet.com", + "ff.im", + "fffff.at", + "fflick.com", + "ffvpn.com", + "fgmtv.net", + "fgmtv.org", + "fhreports.net", + "figprayer.com", + "fileflyer.com", + "fileforum.com", + "files2me.com", + "fileserve.com", + "filesor.com", + "fillthesquare.org", + "filmingfortibet.org", + "filthdump.com", + "financetwitter.com", + "finchvpn.com", + "findmespot.com", + "findyoutube.com", + "findyoutube.net", + "fingerdaily.com", + "finler.net", + "firearmsworld.net", + "firebaseio.com", + "firefox.com", + "fireofliberty.org", + "firetweet.io", + "firstfivefollowers.com", + "firstpost.com", + "firstrade.com", + "fizzik.com", + "flagsonline.it", + "flecheinthepeche.fr", + "fleshbot.com", + "fleursdeslettres.com", + "flgg.us", + "flgjustice.org", + "flickr.com", + "flickrhivemind.net", + "flickriver.com", + "fling.com", + "flipboard.com", + "flipkart.com", + "flitto.com", + "flnet.org", + "flog.tw", + "flurry.com", + "flyvpn.com", + "flyzy2005.com", + "fmnnow.com", + "fnac.be", + "fnac.com", + "fochk.org", + "focustaiwan.tw", + "focusvpn.com", + "fofg-europe.net", + "fofg.org", + "fofldfradio.org", + "foolsmountain.com", + "fooooo.com", + "foreignaffairs.com", + "foreignpolicy.com", + "forum4hk.com", + "forums-free.com", + "fotile.me", + "fourthinternational.org", + "foxbusiness.com", + "foxdie.us", + "foxgay.com", + "foxsub.com", + "foxtang.com", + "fpmt-osel.org", + "fpmt.org", + "fpmt.tw", + "fpmtmexico.org", + "fqok.org", + "fqrouter.com", + "franklc.com", + "freakshare.com", + "free-gate.org", + "free-hada-now.org", + "free-proxy.cz", + "free-ss.site", + "free-ssh.com", + "free.fr", + "free4u.com.ar", + "freealim.com", + "freebeacon.com", + "freebearblog.org", + "freebrowser.org", + "freechal.com", + "freechina.net", + "freechina.news", + "freechinaforum.org", + "freechinaweibo.com", + "freeddns.com", + "freeddns.org", + "freedomchina.info", + "freedomcollection.org", + "freedomhouse.org", + "freedomsherald.org", + "freeforums.org", + "freefq.com", + "freefuckvids.com", + "freegao.com", + "freehongkong.org", + "freeilhamtohti.org", + "freekazakhs.org", + "freekwonpyong.org", + "freelotto.com", + "freeman2.com", + "freemoren.com", + "freemorenews.com", + "freemuse.org", + "freenet-china.org", + "freenetproject.org", + "freenewscn.com", + "freeones.com", + "freeopenvpn.com", + "freeoz.org", + "freerk.com", + "freessh.us", + "freetcp.com", + "freetibet.net", + "freetibet.org", + "freetibetanheroes.org", + "freetribe.me", + "freeviewmovies.com", + "freevpn.me", + "freevpn.nl", + "freewallpaper4.me", + "freewebs.com", + "freewechat.com", + "freeweibo.com", + "freewww.biz", + "freewww.info", + "freexinwen.com", + "freeyellow.com", + "freeyoutubeproxy.net", + "frienddy.com", + "friendfeed-media.com", + "friendfeed.com", + "friendfinder.com", + "friends-of-tibet.org", + "friendsoftibet.org", + "fring.com", + "fringenetwork.com", + "from-pr.com", + "from-sd.com", + "fromchinatousa.net", + "frommel.net", + "frontlinedefenders.org", + "frootvpn.com", + "fscked.org", + "fsurf.com", + "ftchinese.com", + "ftp1.biz", + "ftpserver.biz", + "ftv.com.tw", + "ftvnews.com.tw", + "ftx.com", + "fucd.com", + "fuckcnnic.net", + "fuckgfw.org", + "fuckgfw233.org", + "fulione.com", + "fullerconsideration.com", + "fulue.com", + "funf.tw", + "funkyimg.com", + "funp.com", + "fuq.com", + "furbo.org", + "furhhdl.org", + "furinkan.com", + "furl.net", + "futurechinaforum.org", + "futuremessage.org", + "fux.com", + "fuyin.net", + "fuyindiantai.org", + "fuyu.org.tw", + "fw.cm", + "fxcm-chinese.com", + "fxnetworks.com", + "fzh999.com", + "fzh999.net", + "fzlm.com", + "g-area.org", + "g-queen.com", + "g.co", + "g0v.social", + "g6hentai.com", + "gab.com", + "gabocorp.com", + "gaeproxy.com", + "gaforum.org", + "gagaoolala.com", + "galaxymacau.com", + "galenwu.com", + "galstars.net", + "game735.com", + "gamebase.com.tw", + "gamejolt.com", + "gamer.com.tw", + "gamerp.jp", + "gamez.com.tw", + "gamousa.com", + "ganges.com", + "ganjingworld.com", + "gaoming.net", + "gaopi.net", + "gaozhisheng.net", + "gaozhisheng.org", + "gardennetworks.com", + "gardennetworks.org", + "gartlive.com", + "gate-project.com", + "gate.io", + "gatecoin.com", + "gather.com", + "gatherproxy.com", + "gati.org.tw", + "gaybubble.com", + "gaycn.net", + "gayhub.com", + "gaymap.cc", + "gaymenring.com", + "gaytube.com", + "gaywatch.com", + "gazotube.com", + "gcc.org.hk", + "gclooney.com", + "gclubs.com", + "gcmasia.com", + "gcpnews.com", + "gcr.io", + "gdbt.net", + "gdzf.org", + "geek-art.net", + "geekerhome.com", + "geekheart.info", + "gekikame.com", + "gelbooru.com", + "genius.com", + "geocities.co.jp", + "geocities.com", + "geocities.jp", + "geph.io", + "gerefoundation.org", + "get.app", + "get.dev", + "get.how", + "get.page", + "getastrill.com", + "getchu.com", + "getcloak.com", + "getfoxyproxy.org", + "getfreedur.com", + "getgom.com", + "geti2p.net", + "getiton.com", + "getjetso.com", + "getlantern.org", + "getmalus.com", + "getmdl.io", + "getoutline.org", + "getsocialscope.com", + "getsync.com", + "gettr.com", + "gettrials.com", + "gettyimages.com", + "getuploader.com", + "gfbv.de", + "gfgold.com.hk", + "gfsale.com", + "gfw.org.ua", + "gfw.press", + "gfw.report", + "ggpht.com", + "ggssl.com", + "ghidra-sre.org", + "ghostpath.com", + "ghut.org", + "giantessnight.com", + "gifree.com", + "giga-web.jp", + "gigacircle.com", + "giganews.com", + "gigporno.ru", + "girlbanker.com", + "git.io", + "gitbooks.io", + "githack.com", + "github.blog", + "github.com", + "github.io", + "githubassets.com", + "githubusercontent.com", + "gizlen.net", + "gjczz.com", + "glass8.eu", + "globaljihad.net", + "globalmediaoutreach.com", + "globalmuseumoncommunism.org", + "globalrescue.net", + "globaltm.org", + "globalvoices.org", + "globalvoicesonline.org", + "globalvpn.net", + "glock.com", + "gloryhole.com", + "glorystar.me", + "gluckman.com", + "glype.com", + "gmail.com", + "gmgard.com", + "gmhz.org", + "gmiddle.com", + "gmiddle.net", + "gmll.org", + "gmodules.com", + "gmx.net", + "gnci.org.hk", + "gnews.org", + "go-pki.com", + "go141.com", + "goagent.biz", + "goagentplus.com", + "gobet.cc", + "godfootsteps.org", + "godns.work", + "godoc.org", + "godsdirectcontact.co.uk", + "godsdirectcontact.org", + "godsdirectcontact.org.tw", + "godsimmediatecontact.com", + "gofundme.com", + "gogotunnel.com", + "gohappy.com.tw", + "gokbayrak.com", + "golang.org", + "goldbet.com", + "goldbetsports.com", + "golden-ages.org", + "goldeneyevault.com", + "goldenfrog.com", + "goldjizz.com", + "goldstep.net", + "goldwave.com", + "gongm.in", + "gongmeng.info", + "gongminliliang.com", + "gongwt.com", + "goo.gl", + "goo.gle", + "goo.ne.jp", + "gooday.xyz", + "gooddns.info", + "goodhope.school", + "goodreaders.com", + "goodreads.com", + "goodtv.com.tw", + "goodtv.tv", + "goofind.com", + "google.ac", + "google.ad", + "google.ae", + "google.af", + "google.ai", + "google.al", + "google.am", + "google.as", + "google.at", + "google.az", + "google.ba", + "google.be", + "google.bf", + "google.bg", + "google.bi", + "google.bj", + "google.bs", + "google.bt", + "google.by", + "google.ca", + "google.cat", + "google.cd", + "google.cf", + "google.cg", + "google.ch", + "google.ci", + "google.cl", + "google.cm", + "google.cn", + "google.co.ao", + "google.co.bw", + "google.co.ck", + "google.co.cr", + "google.co.id", + "google.co.il", + "google.co.in", + "google.co.jp", + "google.co.ke", + "google.co.kr", + "google.co.ls", + "google.co.ma", + "google.co.mz", + "google.co.nz", + "google.co.th", + "google.co.tz", + "google.co.ug", + "google.co.uk", + "google.co.uz", + "google.co.ve", + "google.co.vi", + "google.co.za", + "google.co.zm", + "google.co.zw", + "google.com", + "google.com.af", + "google.com.ag", + "google.com.ai", + "google.com.ar", + "google.com.au", + "google.com.bd", + "google.com.bh", + "google.com.bn", + "google.com.bo", + "google.com.br", + "google.com.bz", + "google.com.co", + "google.com.cu", + "google.com.cy", + "google.com.do", + "google.com.ec", + "google.com.eg", + "google.com.et", + "google.com.fj", + "google.com.gh", + "google.com.gi", + "google.com.gt", + "google.com.hk", + "google.com.jm", + "google.com.kh", + "google.com.kw", + "google.com.lb", + "google.com.ly", + "google.com.mm", + "google.com.mt", + "google.com.mx", + "google.com.my", + "google.com.na", + "google.com.nf", + "google.com.ng", + "google.com.ni", + "google.com.np", + "google.com.om", + "google.com.pa", + "google.com.pe", + "google.com.pg", + "google.com.ph", + "google.com.pk", + "google.com.pr", + "google.com.py", + "google.com.qa", + "google.com.sa", + "google.com.sb", + "google.com.sg", + "google.com.sl", + "google.com.sv", + "google.com.tj", + "google.com.tr", + "google.com.tw", + "google.com.ua", + "google.com.uy", + "google.com.vc", + "google.com.vn", + "google.cv", + "google.cz", + "google.de", + "google.dev", + "google.dj", + "google.dk", + "google.dm", + "google.dz", + "google.ee", + "google.es", + "google.eu", + "google.fi", + "google.fm", + "google.fr", + "google.ga", + "google.ge", + "google.gg", + "google.gl", + "google.gm", + "google.gp", + "google.gr", + "google.gy", + "google.hk", + "google.hn", + "google.hr", + "google.ht", + "google.hu", + "google.ie", + "google.im", + "google.iq", + "google.is", + "google.it", + "google.it.ao", + "google.je", + "google.jo", + "google.kg", + "google.ki", + "google.kz", + "google.la", + "google.li", + "google.lk", + "google.lt", + "google.lu", + "google.lv", + "google.md", + "google.me", + "google.mg", + "google.mk", + "google.ml", + "google.mn", + "google.ms", + "google.mu", + "google.mv", + "google.mw", + "google.mx", + "google.ne", + "google.nl", + "google.no", + "google.nr", + "google.nu", + "google.org", + "google.pl", + "google.pn", + "google.ps", + "google.pt", + "google.ro", + "google.rs", + "google.ru", + "google.rw", + "google.sc", + "google.se", + "google.sh", + "google.si", + "google.sk", + "google.sm", + "google.sn", + "google.so", + "google.sr", + "google.st", + "google.td", + "google.tg", + "google.tk", + "google.tl", + "google.tm", + "google.tn", + "google.to", + "google.tt", + "google.us", + "google.vg", + "google.vn", + "google.vu", + "google.ws", + "googleapis.cn", + "googleapis.com", + "googleapps.com", + "googlearth.com", + "googleartproject.com", + "googleblog.com", + "googlebot.com", + "googlechinawebmaster.com", + "googlecode.com", + "googlecommerce.com", + "googledomains.com", + "googledrive.com", + "googleearth.com", + "googlefiber.net", + "googlegroups.com", + "googlehosted.com", + "googleideas.com", + "googleinsidesearch.com", + "googlelabs.com", + "googlemail.com", + "googlemashups.com", + "googlepagecreator.com", + "googleplay.com", + "googleplus.com", + "googlesile.com", + "googlesource.com", + "googleusercontent.com", + "googlevideo.com", + "googleweblight.com", + "googlezip.net", + "gopetition.com", + "goproxing.net", + "goreforum.com", + "goregrish.com", + "gospelherald.com", + "got-game.org", + "gotdns.ch", + "gotgeeks.com", + "gotrusted.com", + "gotw.ca", + "gov.taipei", + "gr8domain.biz", + "gr8name.biz", + "gradconnection.com", + "grammaly.com", + "grandtrial.org", + "grangorz.org", + "graphis.ne.jp", + "graphql.org", + "gravatar.com", + "greasespot.net", + "great-firewall.com", + "great-roc.org", + "greatfire.org", + "greatfirewall.biz", + "greatfirewallofchina.net", + "greatfirewallofchina.org", + "greatroc.org", + "greatroc.tw", + "greatzhonghua.org", + "greenfieldbookstore.com.hk", + "greenparty.org.tw", + "greenpeace.com.tw", + "greenpeace.org", + "greenreadings.com", + "greenvpn.net", + "greenvpn.org", + "grotty-monday.com", + "grow.google", + "gs-discuss.com", + "gsearch.media", + "gstatic.com", + "gtricks.com", + "gts-vpn.com", + "gtv.org", + "gtv1.org", + "gu-chu-sum.org", + "guaguass.com", + "guaguass.org", + "guancha.org", + "guaneryu.com", + "guangming.com.my", + "guangnianvpn.com", + "guardster.com", + "guishan.org", + "gumroad.com", + "gun-world.net", + "gunsamerica.com", + "gunsandammo.com", + "guo.media", + "guruonline.hk", + "gutteruncensored.com", + "gvlib.com", + "gvm.com.tw", + "gvt0.com", + "gvt1.com", + "gvt3.com", + "gwins.org", + "gwtproject.org", + "gyalwarinpoche.com", + "gyatsostudio.com", + "gzm.tv", + "gzone-anime.info", + "h-china.org", + "h-moe.com", + "h1n1china.org", + "h528.com", + "h5dm.com", + "h5galgame.me", + "hacg.club", + "hacg.in", + "hacg.li", + "hacg.me", + "hacg.red", + "hacken.cc", + "hacker.org", + "hackmd.io", + "hackthatphone.net", + "hahlo.com", + "hakkatv.org.tw", + "handcraftedsoftware.org", + "hanime.tv", + "hanminzu.org", + "hanunyi.com", + "hao.news", + "hao123.com", + "hao123img.com", + "happy-vpn.com", + "haproxy.org", + "hardsextube.com", + "harunyahya.com", + "hasi.wang", + "hautelook.com", + "hautelookcdn.com", + "have8.com", + "hbg.com", + "hbo.com", + "hclips.com", + "hdlt.me", + "hdtvb.net", + "hdzog.com", + "he.net", + "heartyit.com", + "heavy-r.com", + "hec.su", + "hecaitou.net", + "hechaji.com", + "heeact.edu.tw", + "hegre-art.com", + "helixstudios.net", + "helloandroid.com", + "helloqueer.com", + "helloss.pw", + "hellotxt.com", + "hellouk.org", + "helpeachpeople.com", + "helplinfen.com", + "helpster.de", + "helpuyghursnow.org", + "helpzhuling.org", + "hentai.to", + "hentaitube.tv", + "hentaivideoworld.com", + "heqinglian.net", + "here.com", + "heritage.org", + "heroku.com", + "heungkongdiscuss.com", + "hexieshe.com", + "hexieshe.xyz", + "hexxeh.net", + "heyuedi.com", + "heywire.com", + "heyzo.com", + "hgseav.com", + "hhdcb3office.org", + "hhthesakyatrizin.org", + "hi-on.org.tw", + "hidden-advent.org", + "hide.me", + "hidecloud.com", + "hidein.net", + "hideipvpn.com", + "hideman.net", + "hideme.nl", + "hidemy.name", + "hidemyass.com", + "hidemycomp.com", + "higfw.com", + "highpeakspureearth.com", + "highrockmedia.com", + "hightail.com", + "hihiforum.com", + "hihistory.net", + "hiitch.com", + "hikinggfw.org", + "hilive.tv", + "himalayan-foundation.org", + "himalayanglacier.com", + "himemix.com", + "himemix.net", + "hinet.net", + "hitbtc.com", + "hitomi.la", + "hiwifi.com", + "hizb-ut-tahrir.info", + "hizb-ut-tahrir.org", + "hizbuttahrir.org", + "hjclub.info", + "hk-pub.com", + "hk01.com", + "hk32168.com", + "hkacg.com", + "hkacg.net", + "hkatvnews.com", + "hkbc.net", + "hkbf.org", + "hkbookcity.com", + "hkchronicles.com", + "hkchurch.org", + "hkci.org.hk", + "hkcmi.edu", + "hkcnews.com", + "hkcoc.com", + "hkctu.org.hk", + "hkdailynews.com.hk", + "hkday.net", + "hkdc.us", + "hkdf.org", + "hkej.com", + "hkepc.com", + "hket.com", + "hkfaa.com", + "hkfreezone.com", + "hkfront.org", + "hkgalden.com", + "hkgolden.com", + "hkgpao.com", + "hkgreenradio.org", + "hkheadline.com", + "hkhkhk.com", + "hkhrc.org.hk", + "hkhrm.org.hk", + "hkip.org.uk", + "hkja.org.hk", + "hkjc.com", + "hkjp.org", + "hklft.com", + "hklts.org.hk", + "hkmap.live", + "hkopentv.com", + "hkpeanut.com", + "hkptu.org", + "hkreporter.com", + "hku.hk", + "hkusu.net", + "hkvwet.com", + "hkwcc.org.hk", + "hkzone.org", + "hmoegirl.com", + "hmonghot.com", + "hmv.co.jp", + "hmvdigital.ca", + "hmvdigital.com", + "hnjhj.com", + "hnntube.com", + "hola.com", + "hola.org", + "holymountaincn.com", + "holyspiritspeaks.org", + "homedepot.com", + "homeip.net", + "homeperversion.com", + "homeservershow.com", + "honeynet.org", + "hongkongfp.com", + "hongmeimei.com", + "hongzhi.li", + "honven.xyz", + "hootsuite.com", + "hoover.org", + "hoovers.com", + "hopedialogue.org", + "hopto.org", + "hornygamer.com", + "hornytrip.com", + "horrorporn.com", + "hotair.com", + "hotav.tv", + "hotels.cn", + "hotfrog.com.tw", + "hotgoo.com", + "hotpornshow.com", + "hotpot.hk", + "hotshame.com", + "hotspotshield.com", + "hottg.com", + "hotvpn.com", + "hougaige.com", + "howtoforge.com", + "hoxx.com", + "hpa.gov.tw", + "hqcdp.org", + "hqjapanesesex.com", + "hqmovies.com", + "hrcchina.org", + "hrcir.com", + "hrea.org", + "hrichina.org", + "hrtsea.com", + "hrw.org", + "hrweb.org", + "hsjp.net", + "hsselite.com", + "hst.net.tw", + "hstern.net", + "hstt.net", + "ht.ly", + "htkou.net", + "htl.li", + "html5rocks.com", + "https443.net", + "https443.org", + "hua-yue.net", + "huaglad.com", + "huanghuagang.org", + "huangyiyu.com", + "huaren.us", + "huaren4us.com", + "huashangnews.com", + "huasing.org", + "huaxia-news.com", + "huaxiabao.org", + "huaxin.ph", + "huayuworld.org", + "hudatoriq.web.id", + "hudson.org", + "huffingtonpost.com", + "hugoroy.eu", + "huhaitai.com", + "huhamhire.com", + "huhangfei.com", + "huiyi.in", + "hulkshare.com", + "hulu.com", + "huluim.com", + "hung-ya.com", + "hungerstrikeforaids.org", + "huobi.co", + "huobi.com", + "huobi.me", + "huobi.pro", + "huobi.sc", + "huobipro.com", + "huping.net", + "hurgokbayrak.com", + "hurriyet.com.tr", + "hustler.com", + "hustlercash.com", + "hut2.ru", + "hutianyi.net", + "hutong9.net", + "huyandex.com", + "hwadzan.tw", + "hwayue.org.tw", + "hwinfo.com", + "hxwk.org", + "hxwq.org", + "hybrid-analysis.com", + "hyperrate.com", + "hyread.com.tw", + "i-cable.com", + "i-part.com.tw", + "i-scmp.com", + "i1.hk", + "i2p2.de", + "i2runner.com", + "i818hk.com", + "iam.soy", + "iamtopone.com", + "iask.bz", + "iask.ca", + "iav19.com", + "ibiblio.org", + "ibit.am", + "iblist.com", + "iblogserv-f.net", + "ibros.org", + "ibtimes.com", + "ibvpn.com", + "icams.com", + "icerocket.com", + "icij.org", + "icl-fi.org", + "icoco.com", + "iconfactory.net", + "iconpaper.org", + "icu-project.org", + "idaiwan.com", + "idemocracy.asia", + "identi.ca", + "idiomconnection.com", + "idlcoyote.com", + "idouga.com", + "idreamx.com", + "idsam.com", + "ieasy5.com", + "ied2k.net", + "ienergy1.com", + "iepl.us", + "ifanqiang.com", + "ifcss.org", + "ifjc.org", + "ifreewares.com", + "ift.tt", + "igcd.net", + "igfw.net", + "igfw.tech", + "igmg.de", + "ignitedetroit.net", + "igoogle.com", + "igotmail.com.tw", + "igvita.com", + "ihakka.net", + "ihao.org", + "iicns.com", + "ikstar.com", + "ikwb.com", + "ilbe.com", + "ilhamtohtiinstitute.org", + "illusionfactory.com", + "ilove80.be", + "ilovelongtoes.com", + "im.tv", + "im88.tw", + "imageab.com", + "imagefap.com", + "imageflea.com", + "images-gaytube.com", + "imageshack.us", + "imagevenue.com", + "imagezilla.net", + "imb.org", + "imdb.com", + "img.ly", + "imgchili.net", + "imgmega.com", + "imgur.com", + "imkev.com", + "imlive.com", + "immigration.gov.tw", + "immoral.jp", + "impact.org.au", + "impp.mn", + "in-disguise.com", + "in.com", + "in99.org", + "incapdns.net", + "incloak.com", + "incredibox.fr", + "independent.co.uk", + "indiablooms.com", + "indianarrative.com", + "indiandefensenews.in", + "indiatimes.com", + "indiemerch.com", + "info-graf.fr", + "informer.com", + "initiativesforchina.org", + "inkui.com", + "inmediahk.net", + "innermongolia.org", + "inoreader.com", + "inote.tw", + "insecam.org", + "inside.com.tw", + "insidevoa.com", + "instagram.com", + "instanthq.com", + "institut-tibetain.org", + "internet.org", + "internetdefenseleague.org", + "internetfreedom.org", + "internetpopculture.com", + "inthenameofconfuciusmovie.com", + "inxian.com", + "iownyour.biz", + "iownyour.org", + "ipalter.com", + "ipfire.org", + "ipfs.io", + "iphone4hongkong.com", + "iphonehacks.com", + "iphonetaiwan.org", + "iphonix.fr", + "ipicture.ru", + "ipjetable.net", + "ipobar.com", + "ipoock.com", + "iportal.me", + "ippotv.com", + "ipredator.se", + "iptv.com.tw", + "iptvbin.com", + "ipvanish.com", + "iqiyi.com", + "iredmail.org", + "irib.ir", + "ironpython.net", + "ironsocket.com", + "is-a-hunter.com", + "is.gd", + "isaacmao.com", + "isasecret.com", + "isgreat.org", + "islahhaber.net", + "islam.org.hk", + "islamawareness.net", + "islamhouse.com", + "islamicity.com", + "islamicpluralism.org", + "islamtoday.net", + "ismaelan.com", + "ismalltits.com", + "ismprofessional.net", + "isohunt.com", + "israbox.com", + "issuu.com", + "istars.co.nz", + "istarshine.com", + "istef.info", + "istiqlalhewer.com", + "istockphoto.com", + "isunaffairs.com", + "isuntv.com", + "isupportuyghurs.org", + "itaboo.info", + "itaiwan.gov.tw", + "italiatibet.org", + "itasoftware.com", + "itemdb.com", + "ithome.com.tw", + "itsaol.com", + "itshidden.com", + "itsky.it", + "itweet.net", + "iu45.com", + "iuhrdf.org", + "iuksky.com", + "ivacy.com", + "iverycd.com", + "ivpn.net", + "ixquick.com", + "ixxx.com", + "iyouport.com", + "iyouport.org", + "izaobao.us", + "izihost.org", + "izles.net", + "izlesem.org", + "j.mp", + "jable.tv", + "jackjia.com", + "jamaat.org", + "jamestown.org", + "jamyangnorbu.com", + "jandyx.com", + "janwongphoto.com", + "japan-whores.com", + "japantimes.co.jp", + "jav.com", + "jav101.com", + "jav2be.com", + "jav68.tv", + "javakiba.org", + "javbus.com", + "javfor.me", + "javhd.com", + "javhip.com", + "javhub.net", + "javhuge.com", + "javlibrary.com", + "javmobile.net", + "javmoo.com", + "javmoo.xyz", + "javseen.com", + "javtag.com", + "javzoo.com", + "jbtalks.cc", + "jbtalks.com", + "jbtalks.my", + "jcpenney.com", + "jdwsy.com", + "jeanyim.com", + "jetos.com", + "jex.com", + "jfqu36.club", + "jfqu37.xyz", + "jgoodies.com", + "jiangweiping.com", + "jiaoyou8.com", + "jichangtj.com", + "jiehua.cz", + "jiepang.com", + "jieshibaobao.com", + "jigglegifs.com", + "jigong1024.com", + "jigsy.com", + "jihadology.net", + "jiji.com", + "jims.net", + "jinbushe.org", + "jingpin.org", + "jingsim.org", + "jinhai.de", + "jinpianwang.com", + "jinroukong.com", + "jintian.net", + "jinx.com", + "jiruan.net", + "jitouch.com", + "jizzthis.com", + "jjgirls.com", + "jkb.cc", + "jkforum.net", + "jkub.com", + "jma.go.jp", + "jmscult.com", + "joachims.org", + "jobso.tv", + "joinbbs.net", + "joinclubhouse.com", + "joinmastodon.org", + "joins.com", + "jornaldacidadeonline.com.br", + "journalchretien.net", + "journalofdemocracy.org", + "joymiihub.com", + "joyourself.com", + "jp.net", + "jpopforum.net", + "jqueryui.com", + "jsdelivr.net", + "jshell.net", + "jtvnw.net", + "jubushoushen.com", + "juhuaren.com", + "jukujo-club.com", + "juliepost.com", + "juliereyc.com", + "junauza.com", + "june4commemoration.org", + "junefourth-20.net", + "jungleheart.com", + "junglobal.net", + "juoaa.com", + "justdied.com", + "justfreevpn.com", + "justicefortenzin.org", + "justmysocks1.net", + "justpaste.it", + "justtristan.com", + "juyuange.org", + "juziyue.com", + "jwmusic.org", + "jyxf.net", + "k-doujin.net", + "ka-wai.com", + "kadokawa.co.jp", + "kagyu.org", + "kagyu.org.za", + "kagyumonlam.org", + "kagyunews.com.hk", + "kagyuoffice.org", + "kagyuoffice.org.tw", + "kaiyuan.de", + "kakao.com", + "kalachakralugano.org", + "kangye.org", + "kankan.today", + "kannewyork.com", + "kanshifang.com", + "kantie.org", + "kanzhongguo.com", + "kanzhongguo.eu", + "kaotic.com", + "karayou.com", + "karkhung.com", + "karmapa-teachings.org", + "karmapa.org", + "kawaiikawaii.jp", + "kawase.com", + "kba-tx.org", + "kcoolonline.com", + "kebrum.com", + "kechara.com", + "keepandshare.com", + "keezmovies.com", + "kendatire.com", + "kendincos.net", + "kenengba.com", + "keontech.net", + "kepard.com", + "keso.cn", + "kex.com", + "keycdn.com", + "khabdha.org", + "khatrimaza.org", + "khmusic.com.tw", + "kichiku-doujinko.com", + "kik.com", + "killwall.com", + "kimy.com.tw", + "kindleren.com", + "kingdomsalvation.org", + "kinghost.com", + "kingstone.com.tw", + "kink.com", + "kinmen.org.tw", + "kinmen.travel", + "kinokuniya.com", + "kir.jp", + "kissbbao.cn", + "kiwi.kz", + "kk-whys.co.jp", + "kkbox.com", + "kknews.cc", + "klip.me", + "kmuh.org.tw", + "knowledgerush.com", + "knowyourmeme.com", + "kobo.com", + "kobobooks.com", + "kodingen.com", + "kompozer.net", + "konachan.com", + "kone.com", + "koolsolutions.com", + "koornk.com", + "koranmandarin.com", + "korenan2.com", + "kqes.net", + "krtco.com.tw", + "ksdl.org", + "ksnews.com.tw", + "kspcoin.com", + "ktzhk.com", + "kucoin.com", + "kui.name", + "kukuku.uk", + "kun.im", + "kurashsultan.com", + "kurtmunger.com", + "kusocity.com", + "kwcg.ca", + "kwok7.com", + "kwongwah.com.my", + "kxsw.life", + "kyofun.com", + "kyohk.net", + "kyoyue.com", + "kyzyhello.com", + "kzeng.info", + "la-forum.org", + "labiennale.org", + "ladbrokes.com", + "lagranepoca.com", + "lala.im", + "lalulalu.com", + "lama.com.tw", + "lamayeshe.com", + "lamenhu.com", + "lamnia.co.uk", + "lamrim.com", + "landofhope.tv", + "lanterncn.cn", + "lantosfoundation.org", + "laod.cn", + "laogai.org", + "laogairesearch.org", + "laomiu.com", + "laoyang.info", + "laptoplockdown.com", + "laqingdan.net", + "larsgeorge.com", + "lastcombat.com", + "lastfm.es", + "latelinenews.com", + "lausan.hk", + "law.com", + "lbank.info", + "le-vpn.com", + "leafyvpn.net", + "lecloud.net", + "leeao.com.cn", + "lefora.com", + "left21.hk", + "legalporno.com", + "legsjapan.com", + "leirentv.ca", + "leisurecafe.ca", + "leisurepro.com", + "lematin.ch", + "lemonde.fr", + "lenwhite.com", + "leorockwell.com", + "lerosua.org", + "lers.google", + "lesoir.be", + "lester850.info", + "letou.com", + "letscorp.net", + "letsencrypt.org", + "levyhsu.com", + "lflink.com", + "lflinkup.com", + "lflinkup.net", + "lflinkup.org", + "lfpcontent.com", + "lhakar.org", + "lhasocialwork.org", + "liangyou.net", + "liangzhichuanmei.com", + "lianyue.net", + "liaowangxizang.net", + "liberal.org.hk", + "libertytimes.com.tw", + "libraryinformationtechnology.com", + "libredd.it", + "lifemiles.com", + "lighten.org.tw", + "lighti.me", + "lightnovel.cn", + "lightyearvpn.com", + "lihkg.com", + "like.com", + "limiao.net", + "line-apps.com", + "line-scdn.net", + "line.me", + "linglingfa.com", + "lingvodics.com", + "link-o-rama.com", + "linkedin.com", + "linkideo.com", + "linksalpha.com", + "linkuswell.com", + "linpie.com", + "linux.org.hk", + "linuxtoy.org", + "lionsroar.com", + "lipuman.com", + "liquiditytp.com", + "liquidvpn.com", + "list-manage.com", + "listennotes.com", + "listentoyoutube.com", + "listorious.com", + "lithium.com", + "liu-xiaobo.org", + "liudejun.com", + "liuhanyu.com", + "liujianshu.com", + "liuxiaobo.net", + "liuxiaotong.com", + "live.com", + "livecoin.net", + "livedoor.jp", + "liveleak.com", + "livemint.com", + "livestation.com", + "livestream.com", + "livevideo.com", + "livingonline.us", + "livingstream.com", + "liwangyang.com", + "lizhizhuangbi.com", + "lkcn.net", + "llss.me", + "lncn.org", + "load.to", + "lobsangwangyal.com", + "localbitcoins.com", + "localdomain.ws", + "localpresshk.com", + "lockestek.com", + "logbot.net", + "logiqx.com", + "logmein.com", + "logos.com.hk", + "londonchinese.ca", + "longhair.hk", + "longmusic.com", + "longtermly.net", + "longtoes.com", + "lookpic.com", + "looktoronto.com", + "lotsawahouse.org", + "lotuslight.org.hk", + "lotuslight.org.tw", + "loved.hk", + "lovetvshow.com", + "lpsg.com", + "lrfz.com", + "lrip.org", + "lsd.org.hk", + "lsforum.net", + "lsm.org", + "lsmchinese.org", + "lsmkorean.org", + "lsmradio.com", + "lsmwebcast.com", + "lsxszzg.com", + "ltn.com.tw", + "luckydesigner.space", + "luke54.com", + "luke54.org", + "lupm.org", + "lushstories.com", + "luxebc.com", + "lvhai.org", + "lvv2.com", + "lyfhk.net", + "lzjscript.com", + "lzmtnews.org", + "m-sport.co.uk", + "m-team.cc", + "m.me", + "macgamestore.com", + "macrovpn.com", + "macts.com.tw", + "mad-ar.ch", + "madewithcode.com", + "madonna-av.com", + "madrau.com", + "madthumbs.com", + "magic-net.info", + "mahabodhi.org", + "maiio.net", + "mail-archive.com", + "mail.ru", + "mailchimp.com", + "maildns.xyz", + "maiplus.com", + "maizhong.org", + "makemymood.com", + "makkahnewspaper.com", + "malaysiakini.com", + "mamingzhe.com", + "manchukuo.net", + "mandiant.com", + "mangafox.com", + "mangafox.me", + "maniash.com", + "manicur4ik.ru", + "mansion.com", + "mansionpoker.com", + "manta.com", + "manyvoices.news", + "maplew.com", + "marc.info", + "marguerite.su", + "martau.com", + "martincartoons.com", + "martinoei.com", + "martsangkagyuofficial.org", + "maruta.be", + "marxist.com", + "marxist.net", + "marxists.org", + "mash.to", + "maskedip.com", + "mastodon.cloud", + "mastodon.host", + "mastodon.social", + "mastodon.xyz", + "matainja.com", + "material.io", + "mathable.io", + "mathiew-badimon.com", + "matome-plus.com", + "matome-plus.net", + "matrix.org", + "matsushimakaede.com", + "matters.news", + "mattwilcox.net", + "maturejp.com", + "maxing.jp", + "mayimayi.com", + "mcadforums.com", + "mcaf.ee", + "mcfog.com", + "mcreasite.com", + "md-t.org", + "me.me", + "meansys.com", + "media.org.hk", + "mediachinese.com", + "mediafire.com", + "mediafreakcity.com", + "medium.com", + "meetav.com", + "meetup.com", + "mefeedia.com", + "meforum.org", + "mefound.com", + "mega.co.nz", + "mega.io", + "mega.nz", + "megaproxy.com", + "megarotic.com", + "megavideo.com", + "megurineluka.com", + "meizhong.blog", + "meizhong.report", + "meltoday.com", + "memehk.com", + "memorybbs.com", + "memri.org", + "memrijttm.org", + "mercatox.com", + "mercdn.net", + "mercyprophet.org", + "mergersandinquisitions.org", + "meridian-trust.org", + "meripet.biz", + "meripet.com", + "merit-times.com.tw", + "meshrep.com", + "mesotw.com", + "messenger.com", + "metacafe.com", + "metafilter.com", + "metart.com", + "metarthunter.com", + "meteorshowersonline.com", + "metro.taipei", + "metrohk.com.hk", + "metrolife.ca", + "metroradio.com.hk", + "mewe.com", + "meyou.jp", + "meyul.com", + "mfxmedia.com", + "mgoon.com", + "mgstage.com", + "mh4u.org", + "mhradio.org", + "michaelanti.com", + "michaelmarketl.com", + "microvpn.com", + "middle-way.net", + "mihk.hk", + "mihr.com", + "mihua.org", + "mikesoltys.com", + "mikocon.com", + "milph.net", + "milsurps.com", + "mimiai.net", + "mimivip.com", + "mimivv.com", + "mindrolling.org", + "mingdemedia.org", + "minghui-a.org", + "minghui-b.org", + "minghui-school.org", + "minghui.or.kr", + "minghui.org", + "mingjinglishi.com", + "mingjingnews.com", + "mingjingtimes.com", + "mingpao.com", + "mingpaocanada.com", + "mingpaomonthly.com", + "mingpaonews.com", + "mingpaony.com", + "mingpaosf.com", + "mingpaotor.com", + "mingpaovan.com", + "mingshengbao.com", + "minhhue.net", + "miniforum.org", + "ministrybooks.org", + "minzhuhua.net", + "minzhuzhanxian.com", + "minzhuzhongguo.org", + "miroguide.com", + "mirrorbooks.com", + "mirrormedia.mg", + "mist.vip", + "mit.edu", + "mitao.com.tw", + "mitbbs.com", + "mitbbsau.com", + "mixero.com", + "mixi.jp", + "mixpod.com", + "mixx.com", + "mizzmona.com", + "mjib.gov.tw", + "mk5000.com", + "mlcool.com", + "mlzs.work", + "mm-cg.com", + "mmaaxx.com", + "mmmca.com", + "mnewstv.com", + "mobatek.net", + "mobile01.com", + "mobileways.de", + "moby.to", + "mobypicture.com", + "mod.io", + "modernchinastudies.org", + "moeaic.gov.tw", + "moeerolibrary.com", + "moegirl.org", + "mofa.gov.tw", + "mofaxiehui.com", + "mofos.com", + "mog.com", + "mohu.club", + "mohu.ml", + "mohu.rocks", + "mojim.com", + "mol.gov.tw", + "molihua.org", + "monar.ch", + "mondex.org", + "money-link.com.tw", + "moneyhome.biz", + "monitorchina.org", + "monitorware.com", + "monlamit.org", + "monster.com", + "moodyz.com", + "moon.fm", + "moonbbs.com", + "moonbingo.com", + "moptt.tw", + "morbell.com", + "morningsun.org", + "moroneta.com", + "mos.ru", + "motherless.com", + "motiyun.com", + "motor4ik.ru", + "mousebreaker.com", + "movements.org", + "moviefap.com", + "moztw.org", + "mp3buscador.com", + "mpettis.com", + "mpfinance.com", + "mpinews.com", + "mponline.hk", + "mqxd.org", + "mrbasic.com", + "mrbonus.com", + "mrface.com", + "mrslove.com", + "mrtweet.com", + "msa-it.org", + "msguancha.com", + "msha.gov", + "msn.com", + "msn.com.tw", + "mswe1.org", + "mthruf.com", + "mtw.tl", + "mubi.com", + "muchosucko.com", + "mullvad.net", + "multiply.com", + "multiproxy.org", + "multiupload.com", + "mummysgold.com", + "murmur.tw", + "musicade.net", + "muslimvideo.com", + "muzi.com", + "muzi.net", + "muzu.tv", + "mvdis.gov.tw", + "mvg.jp", + "mx981.com", + "my-formosa.com", + "my-private-network.co.uk", + "my-proxy.com", + "my03.com", + "my903.com", + "myactimes.com", + "myanniu.com", + "myaudiocast.com", + "myav.com.tw", + "mybbs.us", + "mybet.com", + "myca168.com", + "mycanadanow.com", + "mychat.to", + "mychinamyhome.com", + "mychinanet.com", + "mychinanews.com", + "mychinese.news", + "mycnnews.com", + "mycould.com", + "mydad.info", + "myddns.com", + "myeasytv.com", + "myeclipseide.com", + "myforum.com.hk", + "myfreecams.com", + "myfreepaysite.com", + "myfreshnet.com", + "myftp.info", + "myftp.name", + "myiphide.com", + "mykomica.org", + "mylftv.com", + "mymaji.com", + "mymediarom.com", + "mymoe.moe", + "mymom.info", + "mymusic.net.tw", + "mynetav.net", + "mynetav.org", + "mynumber.org", + "myparagliding.com", + "mypicture.info", + "mypikpak.com", + "mypop3.net", + "mypop3.org", + "mypopescu.com", + "myradio.hk", + "myreadingmanga.info", + "mysecondarydns.com", + "mysinablog.com", + "myspace.com", + "myspacecdn.com", + "mytalkbox.com", + "mytizi.com", + "mywww.biz", + "myz.info", + "naacoalition.org", + "nabble.com", + "naitik.net", + "nakido.com", + "nakuz.com", + "nalandabodhi.org", + "nalandawest.org", + "namgyal.org", + "namgyalmonastery.org", + "namsisi.com", + "nanyang.com", + "nanyangpost.com", + "nanzao.com", + "naol.ca", + "naol.cc", + "narod.ru", + "nasa.gov", + "nat.gov.tw", + "nat.moe", + "natado.com", + "national-lottery.co.uk", + "nationalawakening.org", + "nationalgeographic.com", + "nationalinterest.org", + "nationalreview.com", + "nationsonline.org", + "nationwide.com", + "naughtyamerica.com", + "naver.jp", + "navy.mil", + "naweeklytimes.com", + "nbc.com", + "nbcnews.com", + "nbtvpn.com", + "nccwatch.org.tw", + "nch.com.tw", + "nchrd.org", + "ncn.org", + "ncol.com", + "nde.de", + "ndi.org", + "ndr.de", + "ned.org", + "nekoslovakia.net", + "neo-miracle.com", + "neowin.net", + "nepusoku.com", + "nesnode.com", + "net-fits.pro", + "netalert.me", + "netbig.com", + "netbirds.com", + "netcolony.com", + "netfirms.com", + "netflav.com", + "netflix.com", + "netflix.net", + "netme.cc", + "netsarang.com", + "netsneak.com", + "network54.com", + "networkedblogs.com", + "networktunnel.net", + "neverforget8964.org", + "new-3lunch.net", + "new-akiba.com", + "new96.ca", + "newcenturymc.com", + "newcenturynews.com", + "newchen.com", + "newgrounds.com", + "newhighlandvision.com", + "newipnow.com", + "newlandmagazine.com.au", + "newnews.ca", + "news100.com.tw", + "newsancai.com", + "newschinacomment.org", + "newscn.org", + "newsdetox.ca", + "newsdh.com", + "newsmagazine.asia", + "newsmax.com", + "newspeak.cc", + "newstamago.com", + "newstapa.org", + "newstarnet.com", + "newstatesman.com", + "newsweek.com", + "newtaiwan.com.tw", + "newtalk.tw", + "newyorker.com", + "newyorktimes.com", + "nexon.com", + "next11.co.jp", + "nextdigital.com.hk", + "nextmag.com.tw", + "nextmedia.com", + "nexton-net.jp", + "nexttv.com.tw", + "nf.id.au", + "nfjtyd.com", + "nflxext.com", + "nflximg.com", + "nflximg.net", + "nflxso.net", + "nflxvideo.net", + "ng.mil", + "nga.mil", + "ngensis.com", + "ngodupdongchung.com", + "nhentai.net", + "nhi.gov.tw", + "nhk-ondemand.jp", + "nic.google", + "nic.gov", + "nicovideo.jp", + "nighost.org", + "nightlife141.com", + "nike.com", + "nikkei.com", + "ninecommentaries.com", + "ning.com", + "ninjacloak.com", + "ninjaproxy.ninja", + "nintendium.com", + "ninth.biz", + "nitter.cc", + "nitter.net", + "niu.moe", + "niusnews.com", + "njactb.org", + "njuice.com", + "nlfreevpn.com", + "nmsl.website", + "nnews.eu", + "no-ip.com", + "no-ip.org", + "nobel.se", + "nobelprize.org", + "nobodycanstop.us", + "nodesnoop.com", + "nofile.io", + "nokogiri.org", + "nokola.com", + "noodlevpn.com", + "norbulingka.org", + "nordstrom.com", + "nordstromimage.com", + "nordstromrack.com", + "nordvpn.com", + "notepad-plus-plus.org", + "nottinghampost.com", + "novelasia.com", + "now.com", + "now.im", + "nownews.com", + "nowtorrents.com", + "noxinfluencer.com", + "noypf.com", + "npa.go.jp", + "npa.gov.tw", + "npnt.me", + "nps.gov", + "npsboost.com", + "nradio.me", + "nrk.no", + "ns01.biz", + "ns01.info", + "ns01.us", + "ns02.biz", + "ns02.info", + "ns02.us", + "ns1.name", + "ns2.name", + "ns3.name", + "nsc.gov.tw", + "ntbk.gov.tw", + "ntbna.gov.tw", + "ntbt.gov.tw", + "ntd.tv", + "ntdtv.ca", + "ntdtv.co.kr", + "ntdtv.com", + "ntdtv.com.tw", + "ntdtv.cz", + "ntdtv.org", + "ntdtv.ru", + "ntdtvla.com", + "ntrfun.com", + "ntsna.gov.tw", + "ntu.edu.tw", + "nu.nl", + "nubiles.net", + "nudezz.com", + "nuexpo.com", + "nukistream.com", + "nurgo-software.com", + "nusatrip.com", + "nutaku.net", + "nutsvpn.work", + "nuuvem.com", + "nuvid.com", + "nuzcom.com", + "nvdst.com", + "nvquan.org", + "nvtongzhisheng.org", + "nwtca.org", + "nyaa.eu", + "nyaa.si", + "nybooks.com", + "nydus.ca", + "nylon-angel.com", + "nylonstockingsonline.com", + "nypost.com", + "nyt.com", + "nytchina.com", + "nytcn.me", + "nytco.com", + "nyti.ms", + "nytimes.com", + "nytimg.com", + "nytlog.com", + "nytstyle.com", + "nzchinese.com", + "nzchinese.net.nz", + "oanda.com", + "oann.com", + "oauth.net", + "observechina.net", + "obutu.com", + "ocaspro.com", + "occupytiananmen.com", + "oclp.hk", + "ocreampies.com", + "ocry.com", + "october-review.org", + "oculus.com", + "oculuscdn.com", + "odysee.com", + "oex.com", + "offbeatchina.com", + "officeoftibet.com", + "ofile.org", + "ogaoga.org", + "ogate.org", + "ohchr.org", + "ohmyrss.com", + "oikos.com.tw", + "oiktv.com", + "oizoblog.com", + "ok.ru", + "okayfreedom.com", + "okex.com", + "okk.tw", + "okx.com", + "olabloga.pl", + "old-cat.net", + "olevod.com", + "olumpo.com", + "olympicwatch.org", + "omct.org", + "omgili.com", + "omni7.jp", + "omnitalk.com", + "omnitalk.org", + "omny.fm", + "omy.sg", + "on.cc", + "on2.com", + "onapp.com", + "onedumb.com", + "onejav.com", + "onion.city", + "onion.ly", + "onlinecha.com", + "onlineyoutube.com", + "onlygayvideo.com", + "onlytweets.com", + "onmoon.com", + "onmoon.net", + "onmypc.biz", + "onmypc.info", + "onmypc.net", + "onmypc.org", + "onmypc.us", + "onthehunt.com", + "ontrac.com", + "oopsforum.com", + "open.com.hk", + "openallweb.com", + "opendemocracy.net", + "opendn.xyz", + "openervpn.in", + "openid.net", + "openleaks.org", + "opensea.io", + "opensource.google", + "opentech.fund", + "openvpn.net", + "openvpn.org", + "openwebster.com", + "openwrt.org.cn", + "opera-mini.net", + "opera.com", + "opus-gaming.com", + "orchidbbs.com", + "organcare.org.tw", + "organharvestinvestigation.net", + "organiccrap.com", + "orgasm.com", + "orgfree.com", + "oricon.co.jp", + "orient-doll.com", + "orientaldaily.com.my", + "orn.jp", + "orzdream.com", + "orzistic.org", + "osfoora.com", + "otcbtc.com", + "otnd.org", + "otto.de", + "otzo.com", + "ourdearamy.com", + "ourhobby.com", + "oursogo.com", + "oursteps.com.au", + "oursweb.net", + "ourtv.hk", + "over-blog.com", + "overcast.fm", + "overdaily.org", + "overplay.net", + "ovi.com", + "ovpn.com", + "ow.ly", + "owind.com", + "owl.li", + "owltail.com", + "oxfordscholarship.com", + "oxid.it", + "oyax.com", + "oyghan.com", + "ozchinese.com", + "ozvoice.org", + "ozxw.com", + "ozyoyo.com", + "pachosting.com", + "pacificpoker.com", + "packetix.net", + "pacopacomama.com", + "padmanet.com", + "page.tl", + "page2rss.com", + "pages.dev", + "pagodabox.com", + "palacemoon.com", + "paldengyal.com", + "paljorpublications.com", + "palmislife.com", + "paltalk.com", + "pandapow.co", + "pandapow.net", + "pandavpn-jp.com", + "pandavpnpro.com", + "pandora.com", + "pandora.tv", + "panluan.net", + "panoramio.com", + "pao-pao.net", + "paper.li", + "paperb.us", + "paradisehill.cc", + "paradisepoker.com", + "parkansky.com", + "parler.com", + "parse.com", + "parsevideo.com", + "partycasino.com", + "partypoker.com", + "passion.com", + "passiontimes.hk", + "paste.ee", + "pastebin.com", + "pastie.org", + "pathtosharepoint.com", + "patreon.com", + "paxful.com", + "pbs.org", + "pbwiki.com", + "pbworks.com", + "pbxes.com", + "pbxes.org", + "pcanywhere.net", + "pcc.gov.tw", + "pcdvd.com.tw", + "pchome.com.tw", + "pcij.org", + "pcloud.com", + "pcstore.com.tw", + "pct.org.tw", + "pdetails.com", + "pdproxy.com", + "peace.ca", + "peacefire.org", + "peacehall.com", + "pearlher.org", + "peeasian.com", + "peing.net", + "pekingduck.org", + "pemulihan.or.id", + "pen.io", + "penchinese.com", + "penchinese.net", + "pengyulong.com", + "penisbot.com", + "pentalogic.net", + "penthouse.com", + "pentoy.hk", + "peoplebookcafe.com", + "peoplenews.tw", + "peopo.org", + "percy.in", + "perfect-privacy.com", + "perfectgirls.net", + "periscope.tv", + "persecutionblog.com", + "persiankitty.com", + "phapluan.org", + "phayul.com", + "philborges.com", + "philly.com", + "phmsociety.org", + "phncdn.com", + "phonegap.com", + "photodharma.net", + "photofocus.com", + "phuquocservices.com", + "picacomic.com", + "picacomiccn.com", + "picasaweb.com", + "picidae.net", + "picturedip.com", + "picturesocial.com", + "pimg.tw", + "pin-cong.com", + "pin6.com", + "pincong.rocks", + "ping.fm", + "pinimg.com", + "pinkrod.com", + "pinoy-n.com", + "pinterest.at", + "pinterest.ca", + "pinterest.co.kr", + "pinterest.co.uk", + "pinterest.com", + "pinterest.com.mx", + "pinterest.de", + "pinterest.dk", + "pinterest.fr", + "pinterest.jp", + "pinterest.nl", + "pinterest.se", + "pipii.tv", + "piposay.com", + "piraattilahti.org", + "piring.com", + "pixelqi.com", + "pixiv.net", + "pixnet.in", + "pixnet.net", + "pk.com", + "pki.goog", + "placemix.com", + "playboy.com", + "playboyplus.com", + "player.fm", + "playno1.com", + "playpcesor.com", + "plays.com.tw", + "plexvpn.pro", + "plixi.com", + "plm.org.hk", + "plunder.com", + "plurk.com", + "plus.codes", + "plus28.com", + "plusbb.com", + "pmatehunter.com", + "pmates.com", + "po2b.com", + "pobieramy.top", + "podbean.com", + "podcast.co", + "podictionary.com", + "pokerstars.com", + "pokerstars.net", + "pokerstrategy.com", + "politicalchina.org", + "politicalconsultation.org", + "politiscales.net", + "poloniex.com", + "polymer-project.org", + "polymerhk.com", + "poolin.com", + "popo.tw", + "popvote.hk", + "popxi.click", + "popyard.com", + "popyard.org", + "porn.com", + "porn2.com", + "porn5.com", + "pornbase.org", + "pornerbros.com", + "pornhd.com", + "pornhost.com", + "pornhub.com", + "pornhubdeutsch.net", + "pornmm.net", + "pornoxo.com", + "pornrapidshare.com", + "pornsharing.com", + "pornsocket.com", + "pornstarclub.com", + "porntube.com", + "porntubenews.com", + "porntvblog.com", + "pornvisit.com", + "port25.biz", + "portablevpn.nl", + "poskotanews.com", + "post01.com", + "post76.com", + "post852.com", + "postadult.com", + "postimg.org", + "potato.im", + "potvpn.com", + "power.com", + "powerapple.com", + "powercx.com", + "powerphoto.org", + "powerpointninja.com", + "pp.ru", + "prayforchina.net", + "premeforwindows7.com", + "premproxy.com", + "presentationzen.com", + "presidentlee.tw", + "prestige-av.com", + "pride.google", + "printfriendly.com", + "prism-break.org", + "prisoneralert.com", + "pritunl.com", + "privacybox.de", + "private.com", + "privateinternetaccess.com", + "privatepaste.com", + "privatetunnel.com", + "privatevpn.com", + "privoxy.org", + "procopytips.com", + "project-syndicate.org", + "prosiben.de", + "proton.me", + "protonvpn.com", + "provideocoalition.com", + "provpnaccounts.com", + "proxfree.com", + "proxifier.com", + "proxlet.com", + "proxomitron.info", + "proxpn.com", + "proxyanonimo.es", + "proxydns.com", + "proxylist.org.uk", + "proxynetwork.org.uk", + "proxypy.net", + "proxyroad.com", + "proxytunnel.net", + "proyectoclubes.com", + "prozz.net", + "psblog.name", + "pscp.tv", + "pshvpn.com", + "psiphon.ca", + "psiphon3.com", + "psiphontoday.com", + "pt.im", + "pts.org.tw", + "ptt.cc", + "pttgame.com", + "pttvan.org", + "pubu.com.tw", + "puffinbrowser.com", + "puffstore.com", + "pullfolio.com", + "punyu.com", + "pure18.com", + "pureapk.com", + "pureconcepts.net", + "pureinsight.org", + "purepdf.com", + "purevpn.com", + "purplelotus.org", + "pursuestar.com", + "pushchinawall.com", + "pussthecat.org", + "pussyspace.com", + "putihome.org", + "putlocker.com", + "putty.org", + "puuko.com", + "pwned.com", + "pximg.net", + "python.com", + "python.com.tw", + "pythonhackers.com", + "pythonic.life", + "pytorch.org", + "qanote.com", + "qgirl.com.tw", + "qhigh.com", + "qi-gong.me", + "qianbai.tw", + "qiandao.today", + "qiangwaikan.com", + "qiangyou.org", + "qidian.ca", + "qienkuen.org", + "qiwen.lu", + "qixianglu.cn", + "qkshare.com", + "qmzdd.com", + "qoos.com", + "qooza.hk", + "qpoe.com", + "qq.co.za", + "qstatus.com", + "qtrac.eu", + "qtweeter.com", + "quannengshen.org", + "quantumbooter.net", + "questvisual.com", + "quitccp.net", + "quitccp.org", + "quora.com", + "quoracdn.net", + "quran.com", + "quranexplorer.com", + "qusi8.net", + "qvodzy.org", + "qx.net", + "qxbbs.org", + "qz.com", + "r0.ru", + "r18.com", + "ra.gg", + "radicalparty.org", + "radiko.jp", + "radio.garden", + "radioaustralia.net.au", + "radiohilight.net", + "radioline.co", + "radiotime.com", + "radiovaticana.org", + "radiovncr.com", + "rael.org", + "raggedbanner.com", + "raidcall.com.tw", + "raidtalk.com.tw", + "rainbowplan.org", + "raindrop.io", + "raizoji.or.jp", + "ramcity.com.au", + "rangwang.biz", + "rangzen.com", + "rangzen.net", + "rangzen.org", + "ranxiang.com", + "ranyunfei.com", + "rapbull.net", + "rapidgator.net", + "rapidmoviez.com", + "rapidvpn.com", + "rarbgprx.org", + "raremovie.cc", + "raremovie.net", + "rateyourmusic.com", + "rationalwiki.org", + "rawgit.com", + "rawgithub.com", + "raxcdn.com", + "razyboard.com", + "rcinet.ca", + "rd.com", + "rdio.com", + "read01.com", + "read100.com", + "readingtimes.com.tw", + "readmoo.com", + "readydown.com", + "realcourage.org", + "realitykings.com", + "realraptalk.com", + "realsexpass.com", + "reason.com", + "rebatesrule.net", + "recaptcha.net", + "recordhistory.org", + "recovery.org.tw", + "recoveryversion.com.tw", + "recoveryversion.org", + "red-lang.org", + "redballoonsolidarity.org", + "redbubble.com", + "redchinacn.net", + "redchinacn.org", + "redd.it", + "reddit.com", + "redditlist.com", + "redditmedia.com", + "redditstatic.com", + "redhotlabs.com", + "redtube.com", + "referer.us", + "reflectivecode.com", + "registry.google", + "relaxbbs.com", + "relay.com.tw", + "releaseinternational.org", + "religionnews.com", + "religioustolerance.org", + "renminbao.com", + "renyurenquan.org", + "rerouted.org", + "research.google", + "resilio.com", + "resistchina.org", + "retweeteffect.com", + "retweetist.com", + "retweetrank.com", + "reuters.com", + "reutersmedia.net", + "revleft.com", + "revocationcheck.com", + "revver.com", + "rfa.org", + "rfachina.com", + "rfamobile.org", + "rfaweb.org", + "rferl.org", + "rfi.fr", + "rfi.my", + "rightbtc.com", + "rightster.com", + "rigpa.org", + "riku.me", + "rileyguide.com", + "riseup.net", + "ritouki.jp", + "ritter.vg", + "rixcloud.com", + "rixcloud.us", + "rlwlw.com", + "rmjdw.com", + "rmjdw132.info", + "roadshow.hk", + "roboforex.com", + "robustnessiskey.com", + "rocket-inc.net", + "rocketbbs.com", + "rocksdb.org", + "rojo.com", + "rolfoundation.org", + "rolia.net", + "rolsociety.org", + "ronjoneswriter.com", + "roodo.com", + "rosechina.net", + "rotten.com", + "rsdlmonitor.com", + "rsf-chinese.org", + "rsf.org", + "rsgamen.org", + "rsshub.app", + "rssing.com", + "rssmeme.com", + "rtalabel.org", + "rthk.hk", + "rthk.org.hk", + "rti.org.tw", + "rti.tw", + "rtycminnesota.org", + "ruanyifeng.com", + "rukor.org", + "rule34.xxx", + "runbtx.com", + "rushbee.com", + "rusvpn.com", + "ruten.com.tw", + "rutracker.net", + "rutube.ru", + "ruyiseek.com", + "rxhj.net", + "s-cute.com", + "s-dragon.org", + "s1heng.com", + "s1s1s1.com", + "s4miniarchive.com", + "s8forum.com", + "sa.com", + "saboom.com", + "sacks.com", + "sacom.hk", + "sadistic-v.com", + "sadpanda.us", + "safechat.com", + "safeguarddefenders.com", + "safervpn.com", + "safety.google", + "saintyculture.com", + "saiq.me", + "sakuralive.com", + "sakya.org", + "salvation.org.hk", + "samair.ru", + "sambhota.org", + "sandscotaicentral.com", + "sankei.com", + "sanmin.com.tw", + "sans.edu", + "sapikachu.net", + "saveliuxiaobo.com", + "savemedia.com", + "savethedate.foo", + "savethesounds.info", + "savetibet.de", + "savetibet.fr", + "savetibet.nl", + "savetibet.org", + "savetibet.ru", + "savetibetstore.org", + "saveuighur.org", + "savevid.com", + "say2.info", + "sbme.me", + "sbs.com.au", + "scasino.com", + "schema.org", + "sciencemag.org", + "sciencenets.com", + "scieron.com", + "scmp.com", + "scmpchinese.com", + "scramble.io", + "scribd.com", + "scriptspot.com", + "search.com", + "search.xxx", + "searchtruth.com", + "searx.me", + "seatguru.com", + "seattlefdc.com", + "secretchina.com", + "secretgarden.no", + "secretsline.biz", + "secureservercdn.net", + "securetunnel.com", + "securityinabox.org", + "securitykiss.com", + "seed4.me", + "seehua.com", + "seesmic.com", + "seevpn.com", + "seezone.net", + "sejie.com", + "sellclassics.com", + "sendsmtp.com", + "sendspace.com", + "sensortower.com", + "seraph.me", + "servehttp.com", + "serveuser.com", + "serveusers.com", + "sesawe.net", + "sesawe.org", + "sethwklein.net", + "setn.com", + "settv.com.tw", + "setty.com.tw", + "sevenload.com", + "sex-11.com", + "sex.com", + "sex3.com", + "sex8.cc", + "sexandsubmission.com", + "sexbot.com", + "sexhu.com", + "sexhuang.com", + "sexidude.com", + "sexinsex.net", + "sextvx.com", + "sexxxy.biz", + "sf.net", + "sfileydy.com", + "sfshibao.com", + "sftindia.org", + "sftuk.org", + "shadeyouvpn.com", + "shadow.ma", + "shadowsky.xyz", + "shadowsocks-r.com", + "shadowsocks.asia", + "shadowsocks.be", + "shadowsocks.com", + "shadowsocks.com.hk", + "shadowsocks.org", + "shadowsocks9.com", + "shafaqna.com", + "shahit.biz", + "shambalapost.com", + "shambhalasun.com", + "shangfang.org", + "shapeservices.com", + "sharebee.com", + "sharecool.org", + "sharpdaily.com.hk", + "sharpdaily.hk", + "sharpdaily.tw", + "shat-tibet.com", + "shattered.io", + "sheikyermami.com", + "shellfire.de", + "shemalez.com", + "shenshou.org", + "shenyun.com", + "shenyunperformingarts.org", + "shenyunshop.com", + "shenzhoufilm.com", + "shenzhouzhengdao.org", + "sherabgyaltsen.com", + "shiatv.net", + "shicheng.org", + "shiksha.com", + "shinychan.com", + "shipcamouflage.com", + "shireyishunjian.com", + "shitaotv.org", + "shixiao.org", + "shizhao.org", + "shkspr.mobi", + "shodanhq.com", + "shooshtime.com", + "shop2000.com.tw", + "shopee.tw", + "shopping.com", + "showhaotu.com", + "showtime.jp", + "showwe.tw", + "shutterstock.com", + "shvoong.com", + "shwchurch.org", + "shwchurch3.com", + "siddharthasintent.org", + "sidelinesnews.com", + "sidelinessportseatery.com", + "sierrafriendsoftibet.org", + "signal.org", + "sijihuisuo.club", + "sijihuisuo.com", + "silkbook.com", + "simbolostwitter.com", + "simplecd.org", + "simpleproductivityblog.com", + "sina.com", + "sina.com.hk", + "sina.com.tw", + "sinchew.com.my", + "singaporepools.com.sg", + "singfortibet.com", + "singpao.com.hk", + "singtao.ca", + "singtao.com", + "singtaousa.com", + "sino-monthly.com", + "sinoants.com", + "sinoca.com", + "sinocast.com", + "sinocism.com", + "sinoinsider.com", + "sinomontreal.ca", + "sinonet.ca", + "sinopitt.info", + "sinoquebec.com", + "sipml5.org", + "sis.xxx", + "sis001.com", + "sis001.us", + "site2unblock.com", + "site90.net", + "sitebro.tw", + "sitekreator.com", + "sitemaps.org", + "six-degrees.io", + "sixth.biz", + "sjrt.org", + "sjum.cn", + "sketchappsources.com", + "skimtube.com", + "skk.moe", + "skybet.com", + "skyking.com.tw", + "skykiwi.com", + "skynet.be", + "skype.com", + "skyvegas.com", + "skyxvpn.com", + "slacker.com", + "slandr.net", + "slaytizle.com", + "sleazydream.com", + "slheng.com", + "slickvpn.com", + "slideshare.net", + "slime.com.tw", + "slinkset.com", + "slutload.com", + "slutmoonbeam.com", + "slyip.com", + "slyip.net", + "sm-miracle.com", + "smartdnsproxy.com", + "smarthide.com", + "smartmailcloud.com", + "smchbooks.com", + "smh.com.au", + "smhric.org", + "smith.edu", + "smyxy.org", + "snapchat.com", + "snaptu.com", + "sndcdn.com", + "sneakme.net", + "snowlionpub.com", + "so-net.net.tw", + "sobees.com", + "soc.mil", + "socialblade.com", + "socialwhale.com", + "socks-proxy.net", + "sockscap64.com", + "sockslist.net", + "socrec.org", + "sod.co.jp", + "softether-download.com", + "softether.co.jp", + "softether.org", + "softfamous.com", + "softlayer.net", + "softnology.biz", + "softsmirror.cf", + "softwarebychuck.com", + "sogclub.com", + "sogoo.org", + "sogrady.me", + "soh.tw", + "sohcradio.com", + "sohfrance.org", + "soifind.com", + "sokamonline.com", + "sokmil.com", + "solana.com", + "solidaritetibet.org", + "solidfiles.com", + "solv.finance", + "somee.com", + "songjianjun.com", + "sonicbbs.cc", + "sonidodelaesperanza.org", + "sopcast.com", + "sopcast.org", + "sophos.com", + "sorazone.net", + "sorting-algorithms.com", + "sos.org", + "sosreader.com", + "sostibet.org", + "sou-tong.org", + "soubory.com", + "soul-plus.net", + "soulcaliburhentai.net", + "soumo.info", + "soundcloud.com", + "soundofhope.kr", + "soundofhope.org", + "soup.io", + "soupofmedia.com", + "sourceforge.net", + "sourcewadio.com", + "south-plus.org", + "southnews.com.tw", + "sowers.org.hk", + "sowiki.net", + "soylent.com", + "soylentnews.org", + "spankbang.com", + "spankingtube.com", + "spankwire.com", + "spb.com", + "speakerdeck.com", + "speedify.com", + "spem.at", + "spencertipping.com", + "spendee.com", + "spicevpn.com", + "spideroak.com", + "spike.com", + "spotflux.com", + "spotify.com", + "spreadshirt.es", + "spring4u.info", + "springboardplatform.com", + "sprite.org", + "sproutcore.com", + "sproxy.info", + "squirly.info", + "squirrelvpn.com", + "srocket.us", + "ss-link.com", + "ssglobal.co", + "ssglobal.me", + "ssh91.com", + "ssl443.org", + "sspanel.net", + "sspro.ml", + "ssr.tools", + "ssrshare.com", + "sss.camp", + "sstm.moe", + "sstmlt.moe", + "sstmlt.net", + "stackoverflow.com", + "stage64.hk", + "standupfortibet.org", + "standwithhk.org", + "stanford.edu", + "starfishfx.com", + "starp2p.com", + "startpage.com", + "startuplivingchina.com", + "stat.gov.tw", + "state.gov", + "static-economist.com", + "staticflickr.com", + "statueofdemocracy.org", + "stboy.net", + "stc.com.sa", + "steamcommunity.com", + "steampowered.com", + "steel-storm.com", + "steemit.com", + "steganos.com", + "steganos.net", + "stepchina.com", + "stephaniered.com", + "stgloballink.com", + "stheadline.com", + "sthoo.com", + "stickam.com", + "stickeraction.com", + "stileproject.com", + "sto.cc", + "stoporganharvesting.org", + "stoptibetcrisis.net", + "storagenewsletter.com", + "stories.google", + "storify.com", + "storm.mg", + "stormmediagroup.com", + "stoweboyd.com", + "straitstimes.com", + "stranabg.com", + "straplessdildo.com", + "streamable.com", + "streamate.com", + "streamingthe.net", + "streema.com", + "streetvoice.com", + "strikingly.com", + "strongvpn.com", + "strongwindpress.com", + "student.tw", + "studentsforafreetibet.org", + "stumbleupon.com", + "stupidvideos.com", + "substack.com", + "successfn.com", + "sueddeutsche.de", + "sugarsync.com", + "sugobbs.com", + "sugumiru18.com", + "suissl.com", + "sulian.me", + "summify.com", + "sumrando.com", + "sun1911.com", + "sundayguardianlive.com", + "sunmedia.ca", + "sunporno.com", + "sunskyforum.com", + "sunta.com.tw", + "sunvpn.net", + "suoluo.org", + "supchina.com", + "superfreevpn.com", + "superokayama.com", + "superpages.com", + "supervpn.net", + "superzooi.com", + "suppig.net", + "suprememastertv.com", + "surfeasy.com", + "surfeasy.com.au", + "surfshark.com", + "suroot.com", + "surrenderat20.net", + "sustainability.google", + "svsfx.com", + "swagbucks.com", + "swissinfo.ch", + "swissvpn.net", + "switch1.jp", + "switchvpn.net", + "sydneytoday.com", + "sylfoundation.org", + "syncback.com", + "synergyse.com", + "sysresccd.org", + "sytes.net", + "syx86.cn", + "syx86.com", + "szbbs.net", + "szetowah.org.hk", + "t-g.com", + "t.co", + "t.me", + "t35.com", + "t66y.com", + "t91y.com", + "taa-usa.org", + "taaze.tw", + "tablesgenerator.com", + "tabtter.jp", + "tacem.org", + "taconet.com.tw", + "taedp.org.tw", + "tafm.org", + "tagwa.org.au", + "tagwalk.com", + "tahr.org.tw", + "taipei.gov.tw", + "taipeisociety.org", + "taipeitimes.com", + "taiwan-sex.com", + "taiwanbible.com", + "taiwancon.com", + "taiwandaily.net", + "taiwandc.org", + "taiwanhot.net", + "taiwanjobs.gov.tw", + "taiwanjustice.com", + "taiwanjustice.net", + "taiwankiss.com", + "taiwannation.com", + "taiwannation.com.tw", + "taiwanncf.org.tw", + "taiwannews.com.tw", + "taiwanonline.cc", + "taiwantp.net", + "taiwantt.org.tw", + "taiwanus.net", + "taiwanyes.com", + "talk853.com", + "talkboxapp.com", + "talkcc.com", + "talkonly.net", + "tamiaode.tk", + "tampabay.com", + "tanc.org", + "tangben.com", + "tangren.us", + "taoism.net", + "taolun.info", + "tapanwap.com", + "tapatalk.com", + "taragana.com", + "target.com", + "tascn.com.au", + "taup.net", + "taup.org.tw", + "taweet.com", + "tbcollege.org", + "tbi.org.hk", + "tbicn.org", + "tbjyt.org", + "tbpic.info", + "tbrc.org", + "tbs-rainbow.org", + "tbsec.org", + "tbsmalaysia.org", + "tbsn.org", + "tbsseattle.org", + "tbssqh.org", + "tbswd.org", + "tbtemple.org.uk", + "tbthouston.org", + "tccwonline.org", + "tcewf.org", + "tchrd.org", + "tcnynj.org", + "tcpspeed.co", + "tcpspeed.com", + "tcsofbc.org", + "tcsovi.org", + "tdesktop.com", + "tdm.com.mo", + "teachparentstech.org", + "teamamericany.com", + "technews.tw", + "techspot.com", + "techviz.net", + "teck.in", + "teco-hk.org", + "teco-mo.org", + "teddysun.com", + "teeniefuck.net", + "teensinasia.com", + "tehrantimes.com", + "telecomspace.com", + "telegra.ph", + "telegram.dog", + "telegram.me", + "telegram.org", + "telegramdownload.com", + "telegraph.co.uk", + "telesco.pe", + "tellme.pw", + "tenacy.com", + "tensorflow.org", + "tenzinpalmo.com", + "terabox.com", + "tew.org", + "textnow.me", + "tfhub.dev", + "tfiflve.com", + "thaicn.com", + "thb.gov.tw", + "theatlantic.com", + "theatrum-belli.com", + "theaustralian.com.au", + "thebcomplex.com", + "theblaze.com", + "theblemish.com", + "thebobs.com", + "thebodyshop-usa.com", + "thechinabeat.org", + "thechinacollection.org", + "thechinastory.org", + "theconversation.com", + "thedalailamamovie.com", + "thediplomat.com", + "thedw.us", + "theepochtimes.com", + "thefacebook.com", + "thefrontier.hk", + "thegay.com", + "thegioitinhoc.vn", + "thegly.com", + "theguardian.com", + "thehots.info", + "thehousenews.com", + "thehun.net", + "theinitium.com", + "themoviedb.org", + "thenewslens.com", + "thepiratebay.org", + "theporndude.com", + "theportalwiki.com", + "theprint.in", + "thereallove.kr", + "therock.net.nz", + "thesaturdaypaper.com.au", + "thestandnews.com", + "thetibetcenter.org", + "thetibetconnection.org", + "thetibetmuseum.org", + "thetibetpost.com", + "thetinhat.com", + "thetrotskymovie.com", + "thetvdb.com", + "thevivekspot.com", + "thewgo.org", + "theync.com", + "thinkgeek.com", + "thinkingtaiwan.com", + "thinkwithgoogle.com", + "thisav.com", + "thlib.org", + "thomasbernhard.org", + "thongdreams.com", + "threatchaos.com", + "throughnightsfire.com", + "thumbzilla.com", + "thywords.com", + "thywords.com.tw", + "tiananmenduizhi.com", + "tiananmenmother.org", + "tiananmenuniv.com", + "tiananmenuniv.net", + "tiandixing.org", + "tianhuayuan.com", + "tianlawoffice.com", + "tianti.io", + "tiantibooks.org", + "tianyantong.org.cn", + "tianzhu.org", + "tibet-envoy.eu", + "tibet-foundation.org", + "tibet-house-trust.co.uk", + "tibet-initiative.de", + "tibet-munich.de", + "tibet.a.se", + "tibet.at", + "tibet.ca", + "tibet.com", + "tibet.fr", + "tibet.net", + "tibet.nu", + "tibet.org", + "tibet.org.tw", + "tibet.sk", + "tibet.to", + "tibet3rdpole.org", + "tibetaction.net", + "tibetaid.org", + "tibetalk.com", + "tibetan-alliance.org", + "tibetan.fr", + "tibetanaidproject.org", + "tibetanarts.org", + "tibetanbuddhistinstitute.org", + "tibetancommunity.org", + "tibetancommunityuk.net", + "tibetanculture.org", + "tibetanentrepreneurs.org", + "tibetanfeministcollective.org", + "tibetanhealth.org", + "tibetanjournal.com", + "tibetanlanguage.org", + "tibetanliberation.org", + "tibetanpaintings.com", + "tibetanphotoproject.com", + "tibetanpoliticalreview.org", + "tibetanreview.net", + "tibetansports.org", + "tibetanwomen.org", + "tibetanyouth.org", + "tibetanyouthcongress.org", + "tibetcharity.dk", + "tibetcharity.in", + "tibetchild.org", + "tibetcity.com", + "tibetcollection.com", + "tibetcorps.org", + "tibetexpress.net", + "tibetfocus.com", + "tibetfund.org", + "tibetgermany.com", + "tibetgermany.de", + "tibethaus.com", + "tibetheritagefund.org", + "tibethouse.jp", + "tibethouse.org", + "tibethouse.us", + "tibetinfonet.net", + "tibetjustice.org", + "tibetkomite.dk", + "tibetmuseum.org", + "tibetnetwork.org", + "tibetoffice.ch", + "tibetoffice.com.au", + "tibetoffice.eu", + "tibetoffice.org", + "tibetonline.com", + "tibetonline.tv", + "tibetoralhistory.org", + "tibetpolicy.eu", + "tibetrelieffund.co.uk", + "tibetsites.com", + "tibetsociety.com", + "tibetsun.com", + "tibetsupportgroup.org", + "tibetswiss.ch", + "tibettelegraph.com", + "tibettimes.net", + "tibetwrites.org", + "ticket.com.tw", + "tigervpn.com", + "tiktok.com", + "tiltbrush.com", + "timdir.com", + "time.com", + "timesnownews.com", + "timsah.com", + "timtales.com", + "tinc-vpn.org", + "tiney.com", + "tineye.com", + "tintuc101.com", + "tiny.cc", + "tinychat.com", + "tinypaste.com", + "tipas.net", + "tipo.gov.tw", + "tistory.com", + "tkcs-collins.com", + "tl.gd", + "tma.co.jp", + "tmagazine.com", + "tmdfish.com", + "tmi.me", + "tmpp.org", + "tnaflix.com", + "tngrnow.com", + "tngrnow.net", + "tnp.org", + "to-porno.com", + "togetter.com", + "toh.info", + "tokyo-247.com", + "tokyo-hot.com", + "tokyo-porn-tube.com", + "tokyocn.com", + "tomonews.net", + "tongil.or.kr", + "tono-oka.jp", + "tonyyan.net", + "toodoc.com", + "toonel.net", + "top.tv", + "top10vpn.com", + "top81.ws", + "topbtc.com", + "topnews.in", + "toppornsites.com", + "topshareware.com", + "topsy.com", + "toptip.ca", + "tora.to", + "torcn.com", + "torguard.net", + "torlock.com", + "torproject.org", + "torrentkitty.tv", + "torrentprivacy.com", + "torrentproject.se", + "torrenty.org", + "torrentz.eu", + "torvpn.com", + "totalvpn.com", + "toutiaoabc.com", + "towngain.com", + "toypark.in", + "toythieves.com", + "toytractorshow.com", + "tparents.org", + "tpi.org.tw", + "tracfone.com", + "tradingview.com", + "translate.goog", + "translate.google", + "transparency.org", + "treemall.com.tw", + "trendsmap.com", + "trialofccp.org", + "trickip.net", + "trickip.org", + "trimondi.de", + "tronscan.org", + "trouw.nl", + "trt.net.tr", + "trtc.com.tw", + "truebuddha-md.org", + "trulyergonomic.com", + "truthontour.org", + "truthsocial.com", + "truveo.com", + "tryheart.jp", + "tsctv.net", + "tsemtulku.com", + "tsquare.tv", + "tsu.org.tw", + "tsunagarumon.com", + "tt1069.com", + "tttan.com", + "ttv.com.tw", + "ttvnw.net", + "tu8964.com", + "tubaholic.com", + "tube.com", + "tube8.com", + "tube911.com", + "tubecup.com", + "tubegals.com", + "tubeislam.com", + "tubepornclassic.com", + "tubestack.com", + "tubewolf.com", + "tuibeitu.net", + "tuidang.net", + "tuidang.org", + "tuidang.se", + "tuitui.info", + "tuitwit.com", + "tumblr.com", + "tumutanzi.com", + "tumview.com", + "tunein.com", + "tunnelbear.com", + "tunnelblick.net", + "tunnelr.com", + "tunsafe.com", + "turansam.org", + "turbobit.net", + "turbohide.com", + "turbotwitter.com", + "turkistantimes.com", + "turntable.fm", + "tushycash.com", + "tutanota.com", + "tuvpn.com", + "tuzaijidi.com", + "tv.com", + "tv.google", + "tvants.com", + "tvb.com", + "tvboxnow.com", + "tvbs.com.tw", + "tvider.com", + "tvmost.com.hk", + "tvplayvideos.com", + "tvunetworks.com", + "tw-blog.com", + "tw-npo.org", + "tw01.org", + "twaitter.com", + "twapperkeeper.com", + "twaud.io", + "twavi.com", + "twbbs.net.tw", + "twbbs.org", + "twbbs.tw", + "twblogger.com", + "tweepguide.com", + "tweeplike.me", + "tweepmag.com", + "tweepml.org", + "tweetbackup.com", + "tweetboard.com", + "tweetboner.biz", + "tweetcs.com", + "tweetdeck.com", + "tweetedtimes.com", + "tweetmylast.fm", + "tweetphoto.com", + "tweetrans.com", + "tweetree.com", + "tweettunnel.com", + "tweetwally.com", + "tweetymail.com", + "tweez.net", + "twelve.today", + "twerkingbutt.com", + "twftp.org", + "twgreatdaily.com", + "twibase.com", + "twibble.de", + "twibbon.com", + "twibs.com", + "twicountry.org", + "twicsy.com", + "twiends.com", + "twifan.com", + "twiffo.com", + "twiggit.org", + "twilightsex.com", + "twilio.com", + "twilog.org", + "twimbow.com", + "twimg.com", + "twindexx.com", + "twip.me", + "twipple.jp", + "twishort.com", + "twistar.cc", + "twister.net.co", + "twisterio.com", + "twisternow.com", + "twistory.net", + "twit2d.com", + "twitbrowser.net", + "twitcause.com", + "twitch.tv", + "twitchcdn.net", + "twitgether.com", + "twitgoo.com", + "twitiq.com", + "twitlonger.com", + "twitmania.com", + "twitoaster.com", + "twitonmsn.com", + "twitpic.com", + "twitstat.com", + "twittbot.net", + "twitter.com", + "twitter.jp", + "twitter4j.org", + "twittercounter.com", + "twitterfeed.com", + "twittergadget.com", + "twitterkr.com", + "twittermail.com", + "twitterrific.com", + "twittertim.es", + "twitthat.com", + "twitturk.com", + "twitturly.com", + "twitvid.com", + "twitzap.com", + "twiyia.com", + "twnorth.org.tw", + "twreporter.org", + "twskype.com", + "twstar.net", + "twt.tl", + "twtkr.com", + "twtrland.com", + "twttr.com", + "twurl.nl", + "twyac.org", + "txxx.com", + "tycool.com", + "typepad.com", + "typora.io", + "u15.info", + "u9un.com", + "ub0.cc", + "ubddns.org", + "uberproxy.net", + "uc-japan.org", + "ucam.org", + "ucanews.com", + "ucdc1998.org", + "uchicago.edu", + "uderzo.it", + "udn.com", + "udn.com.tw", + "udnbkk.com", + "uforadio.com.tw", + "ufreevpn.com", + "ugo.com", + "uhdwallpapers.org", + "uhrp.org", + "uighur.nl", + "uighurbiz.net", + "uk.to", + "ukcdp.co.uk", + "ukliferadio.co.uk", + "uku.im", + "ulike.net", + "ulop.net", + "ultravpn.fr", + "ultraxs.com", + "umich.edu", + "unblock-us.com", + "unblockdmm.com", + "unblocker.yt", + "unblocksit.es", + "uncyclomedia.org", + "uncyclopedia.hk", + "uncyclopedia.tw", + "underwoodammo.com", + "unholyknight.com", + "uni.cc", + "unicode.org", + "unification.net", + "unification.org.tw", + "unirule.cloud", + "unitedsocialpress.com", + "unix100.com", + "unknownspace.org", + "unodedos.com", + "unpo.org", + "unseen.is", + "unstable.icu", + "untraceable.us", + "uocn.org", + "updatestar.com", + "upghsbc.com", + "upholdjustice.org", + "upload4u.info", + "uploaded.net", + "uploaded.to", + "uploadstation.com", + "upmedia.mg", + "upornia.com", + "uproxy.org", + "uptodown.com", + "upwill.org", + "ur7s.com", + "uraban.me", + "urbandictionary.com", + "urbansurvival.com", + "urchin.com", + "url.com.tw", + "url.tw", + "urlborg.com", + "urlparser.com", + "us.to", + "usacn.com", + "usaip.eu", + "usc.edu", + "uscnpm.org", + "usembassy.gov", + "usfk.mil", + "usma.edu", + "usmc.mil", + "usocctn.com", + "uspto.gov", + "ustibetcommittee.org", + "ustream.tv", + "usus.cc", + "utopianpal.com", + "uu-gg.com", + "uukanshu.com", + "uvwxyz.xyz", + "uwants.com", + "uwants.net", + "uyghur-j.org", + "uyghur.co.uk", + "uyghuraa.org", + "uyghuramerican.org", + "uyghurbiz.org", + "uyghurcanadian.ca", + "uyghurcongress.org", + "uyghurpen.org", + "uyghurpress.com", + "uyghurstudies.org", + "uyghurtribunal.com", + "uygur.org", + "uymaarip.com", + "v2ex.com", + "v2fly.org", + "v2ray.com", + "v2raycn.com", + "v2raytech.com", + "valeursactuelles.com", + "van001.com", + "van698.com", + "vanemu.cn", + "vanilla-jp.com", + "vanpeople.com", + "vansky.com", + "vaticannews.va", + "vatn.org", + "vcf-online.org", + "vcfbuilder.org", + "vegasred.com", + "velkaepocha.sk", + "venbbs.com", + "venchina.com", + "venetianmacao.com", + "ventureswell.com", + "veoh.com", + "vercel.app", + "verizon.net", + "vermonttibet.org", + "versavpn.com", + "verybs.com", + "vevo.com", + "vft.com.tw", + "viber.com", + "vica.info", + "victimsofcommunism.org", + "vid.me", + "vidble.com", + "videobam.com", + "videodetective.com", + "videomega.tv", + "videomo.com", + "videopediaworld.com", + "videopress.com", + "vidinfo.org", + "vietdaikynguyen.com", + "vijayatemple.org", + "vilavpn.com", + "vimeo.com", + "vimperator.org", + "vincnd.com", + "vine.co", + "vinniev.com", + "vip-enterprise.com", + "virginia.edu", + "virtualrealporn.com", + "visibletweets.com", + "visiontimes.com", + "vital247.org", + "viu.com", + "viu.tv", + "vivahentai4u.net", + "vivaldi.com", + "vivatube.com", + "vivthomas.com", + "vizvaz.com", + "vjav.com", + "vjmedia.com.hk", + "vllcs.org", + "vmixcore.com", + "vmpsoft.com", + "vnet.link", + "voa.mobi", + "voacambodia.com", + "voacantonese.com", + "voachinese.com", + "voachineseblog.com", + "voagd.com", + "voaindonesia.com", + "voanews.com", + "voatibetan.com", + "voatibetanenglish.com", + "vocativ.com", + "vocn.tv", + "vocus.cc", + "voicettank.org", + "vot.org", + "vovo2000.com", + "voxer.com", + "voy.com", + "vpn.ac", + "vpn4all.com", + "vpnaccount.org", + "vpnaccounts.com", + "vpnbook.com", + "vpncomparison.org", + "vpncoupons.com", + "vpncup.com", + "vpndada.com", + "vpnfan.com", + "vpnfire.com", + "vpnfires.biz", + "vpnforgame.net", + "vpngate.jp", + "vpngate.net", + "vpngratis.net", + "vpnhq.com", + "vpnhub.com", + "vpninja.net", + "vpnintouch.com", + "vpnintouch.net", + "vpnjack.com", + "vpnmaster.com", + "vpnmentor.com", + "vpnpick.com", + "vpnpop.com", + "vpnpronet.com", + "vpnreactor.com", + "vpnreviewz.com", + "vpnsecure.me", + "vpnshazam.com", + "vpnshieldapp.com", + "vpnsp.com", + "vpntraffic.com", + "vpntunnel.com", + "vpnuk.info", + "vpnunlimitedapp.com", + "vpnvip.com", + "vpnworldwide.com", + "vporn.com", + "vpser.net", + "vraiesagesse.net", + "vrmtr.com", + "vrsmash.com", + "vs.com", + "vtunnel.com", + "vuku.cc", + "vultryhw.com", + "vzw.com", + "w3.org", + "w3schools.com", + "waffle1999.com", + "wahas.com", + "waigaobu.com", + "waikeung.org", + "wailaike.net", + "wainao.me", + "waiwaier.com", + "wallmama.com", + "wallornot.org", + "wallpapercasa.com", + "wallproxy.com", + "wallsttv.com", + "waltermartin.com", + "waltermartin.org", + "wan-press.org", + "wanderinghorse.net", + "wangafu.net", + "wangjinbo.org", + "wanglixiong.com", + "wango.org", + "wangruoshui.net", + "wangruowang.org", + "want-daily.com", + "wanz-factory.com", + "wapedia.mobi", + "warehouse333.com", + "warroom.org", + "waselpro.com", + "washeng.net", + "washingtonpost.com", + "watch8x.com", + "watchinese.com", + "watchmygf.net", + "watchout.tw", + "wattpad.com", + "wav.tv", + "waveprotocol.org", + "waymo.com", + "wda.gov.tw", + "wdf5.com", + "wealth.com.tw", + "wearehairy.com", + "wearn.com", + "weather.com.hk", + "web.dev", + "web2project.net", + "webbang.net", + "webevader.org", + "webfreer.com", + "webjb.org", + "weblagu.com", + "webmproject.org", + "webpack.de", + "webrtc.org", + "webrush.net", + "webs-tv.net", + "websitepulse.com", + "websnapr.com", + "webwarper.net", + "webworkerdaily.com", + "wechatlawsuit.com", + "weekmag.info", + "wefightcensorship.org", + "wefong.com", + "weiboleak.com", + "weihuo.org", + "weijingsheng.org", + "weiming.info", + "weiquanwang.org", + "weisuo.ws", + "welovecock.com", + "welt.de", + "wemigrate.org", + "wengewang.com", + "wengewang.org", + "wenhui.ch", + "wenweipo.com", + "wenxuecity.com", + "wenyunchao.com", + "wenzhao.ca", + "westca.com", + "westernshugdensociety.org", + "westernwolves.com", + "westkit.net", + "westpoint.edu", + "wetplace.com", + "wetpussygames.com", + "wexiaobo.org", + "wezhiyong.org", + "wezone.net", + "wforum.com", + "wha.la", + "whatblocked.com", + "whatbrowser.org", + "whatsapp.com", + "whatsapp.net", + "whatsonweibo.com", + "wheatseeds.org", + "wheelockslatin.com", + "whereiswerner.com", + "wheretowatch.com", + "whippedass.com", + "whispersystems.org", + "whodns.xyz", + "whoer.net", + "whotalking.com", + "whylover.com", + "whyx.org", + "widevine.com", + "wikaba.com", + "wikia.com", + "wikileaks-forum.com", + "wikileaks.ch", + "wikileaks.com", + "wikileaks.de", + "wikileaks.eu", + "wikileaks.lu", + "wikileaks.org", + "wikileaks.pl", + "wikilivres.info", + "wikimapia.org", + "wikimedia.org", + "wikinews.org", + "wikipedia.org", + "wikiquote.org", + "wikisource.org", + "wikiwand.com", + "wikiwiki.jp", + "wildammo.com", + "williamhill.com", + "willw.net", + "windowsphoneme.com", + "windscribe.com", + "windy.com", + "wingamestore.com", + "wingy.site", + "winning11.com", + "winwhispers.info", + "wionews.com", + "wire.com", + "wiredbytes.com", + "wiredpen.com", + "wireguard.com", + "wisdompubs.org", + "wisevid.com", + "wistia.com", + "withgoogle.com", + "withyoutube.com", + "witnessleeteaching.com", + "witopia.net", + "wizcrafts.net", + "wjbk.org", + "wn.com", + "wnacg.com", + "wnacg.org", + "wo.tc", + "woeser.com", + "woesermiddle-way.net", + "wokar.org", + "wolfax.com", + "wombo.ai", + "woolyss.com", + "woopie.jp", + "woopie.tv", + "wordpress.com", + "workatruna.com", + "workerdemo.org.hk", + "workerempowerment.org", + "workers.dev", + "workersthebig.net", + "workflow.is", + "worldcat.org", + "worldjournal.com", + "worldvpn.net", + "wow-life.net", + "wow.com", + "wowgirls.com", + "wowhead.com", + "wowlegacy.ml", + "wowporn.com", + "wowrk.com", + "woxinghuiguo.com", + "woyaolian.org", + "wozy.in", + "wp.com", + "wpoforum.com", + "wqyd.org", + "wrchina.org", + "wretch.cc", + "wsj.com", + "wsj.net", + "wsjhk.com", + "wtbn.org", + "wtfpeople.com", + "wuerkaixi.com", + "wufafangwen.com", + "wufi.org.tw", + "wuguoguang.com", + "wujie.net", + "wujieliulan.com", + "wukangrui.net", + "wuw.red", + "wuyanblog.com", + "wwe.com", + "wwitv.com", + "www1.biz", + "wwwhost.biz", + "wzyboy.im", + "x-art.com", + "x-berry.com", + "x-wall.org", + "x.co", + "x.company", + "x1949x.com", + "x24hr.com", + "x365x.com", + "xanga.com", + "xbabe.com", + "xbookcn.com", + "xbtce.com", + "xcafe.in", + "xcity.jp", + "xcritic.com", + "xda-developers.com", + "xerotica.com", + "xfiles.to", + "xfinity.com", + "xgmyd.com", + "xhamster.com", + "xianba.net", + "xianchawang.net", + "xianjian.tw", + "xianqiao.net", + "xiaobaiwu.com", + "xiaochuncnjp.com", + "xiaod.in", + "xiaohexie.com", + "xiaolan.me", + "xiaoma.org", + "xiaxiaoqiang.net", + "xiezhua.com", + "xihua.es", + "xinbao.de", + "xing.com", + "xinhuanet.org", + "xinjiangpolicefiles.org", + "xinmiao.com.hk", + "xinsheng.net", + "xinshijue.com", + "xinyubbs.net", + "xiongpian.com", + "xiuren.org", + "xixicui.icu", + "xizang-zhiye.org", + "xjp.cc", + "xjtravelguide.com", + "xkiwi.tk", + "xlfmtalk.com", + "xlfmwz.info", + "xm.com", + "xml-training-guide.com", + "xmovies.com", + "xn--4gq171p.com", + "xn--9pr62r24a.com", + "xn--czq75pvv1aj5c.org", + "xn--i2ru8q2qg.com", + "xn--ngstr-lra8j.com", + "xn--oiq.cc", + "xn--p8j9a0d9c9a.xn--q9jyb4c", + "xnxx.com", + "xpdo.net", + "xpud.org", + "xrentdvd.com", + "xsden.info", + "xskywalker.com", + "xskywalker.net", + "xtube.com", + "xuchao.net", + "xuchao.org", + "xuehua.us", + "xuite.net", + "xuzhiyong.net", + "xvbelink.com", + "xvideo.cc", + "xvideos-cdn.com", + "xvideos.com", + "xvideos.es", + "xvinlink.com", + "xxbbx.com", + "xxlmovies.com", + "xxuz.com", + "xxx.com", + "xxx.xxx", + "xxxfuckmom.com", + "xxxx.com.au", + "xxxy.biz", + "xxxy.info", + "xxxymovies.com", + "xys.org", + "xysblogs.org", + "xyy69.com", + "xyy69.info", + "y2mate.com", + "yadi.sk", + "yahoo.co.jp", + "yahoo.com", + "yahoo.com.hk", + "yahoo.com.tw", + "yahoo.net", + "yakbutterblues.com", + "yam.com", + "yam.org.tw", + "yande.re", + "yandex.com", + "yanghengjun.com", + "yangjianli.com", + "yasni.co.uk", + "yayabay.com", + "ycombinator.com", + "ydy.com", + "yeahteentube.com", + "yecl.net", + "yeelou.com", + "yeeyi.com", + "yegle.net", + "yes-news.com", + "yes.xxx", + "yes123.com.tw", + "yesasia.com", + "yesasia.com.hk", + "yespornplease.com", + "yeyeclub.com", + "ygto.com", + "yhcw.net", + "yibada.com", + "yibaochina.com", + "yidio.com", + "yigeni.com", + "yilubbs.com", + "yimg.com", + "yingsuoss.com", + "yinlei.org", + "yipub.com", + "yiyechat.com", + "yizhihongxing.com", + "yobit.net", + "yobt.com", + "yobt.tv", + "yogichen.org", + "yolasite.com", + "yomiuri.co.jp", + "yong.hu", + "yorkbbs.ca", + "you-get.org", + "youdontcare.com", + "youjizz.com", + "youmaker.com", + "youngpornvideos.com", + "youngspiration.hk", + "youpai.org", + "youporn.com", + "youporngay.com", + "your-freedom.net", + "yourepeat.com", + "yourlisten.com", + "yourlust.com", + "yourprivatevpn.com", + "yourtrap.com", + "yousendit.com", + "youshun12.com", + "youthforfreechina.org", + "youthnetradio.org", + "youthwant.com.tw", + "youtu.be", + "youtube-nocookie.com", + "youtube.com", + "youtubecn.com", + "youtubeeducation.com", + "youtubegaming.com", + "youtubekids.com", + "youversion.com", + "youwin.com", + "youxu.info", + "yt.be", + "ytht.net", + "ytimg.com", + "ytn.co.kr", + "yuanming.net", + "yuanzhengtang.org", + "yulghun.com", + "yunchao.net", + "yuntipub.com", + "yuvutu.com", + "yvesgeleyn.com", + "ywpw.com", + "yx51.net", + "yyii.org", + "yyjlymb.xyz", + "yzzk.com", + "z-lib.org", + "zacebook.com", + "zalmos.com", + "zannel.com", + "zaobao.com", + "zaobao.com.sg", + "zaozon.com", + "zapto.org", + "zattoo.com", + "zb.com", + "zdnet.com.tw", + "zello.com", + "zengjinyan.org", + "zenmate.com", + "zerohedge.com", + "zeronet.io", + "zeutch.com", + "zfreet.com", + "zgsddh.com", + "zgzcjj.net", + "zhanbin.net", + "zhangboli.net", + "zhangtianliang.com", + "zhanlve.org", + "zhenghui.org", + "zhengjian.org", + "zhengwunet.org", + "zhenlibu.info", + "zhenlibu1984.com", + "zhenxiang.biz", + "zhinengluyou.com", + "zhongguo.ca", + "zhongguorenquan.org", + "zhongguotese.net", + "zhongmeng.org", + "zhoushuguang.com", + "zhreader.com", + "zhuangbi.me", + "zhuanxing.cn", + "zhuatieba.com", + "zhuichaguoji.org", + "zi.media", + "zi5.me", + "ziddu.com", + "zillionk.com", + "zim.vn", + "zinio.com", + "ziporn.com", + "zippyshare.com", + "zkaip.com", + "zkiz.com", + "zmw.cn", + "zodgame.us", + "zoho.com", + "zomobo.net", + "zonaeuropa.com", + "zonghexinwen.com", + "zonghexinwen.net", + "zoogvpn.com", + "zootool.com", + "zoozle.net", + "zophar.net", + "zorrovpn.com", + "zozotown.com", + "zpn.im", + "zspeeder.me", + "zsrhao.com", + "zuo.la", + "zuobiao.me", + "zuola.com", + "zvereff.com", + "zynaima.com", + "zynamics.com", + "zyns.com", + "zyxel.com", + "zyzc9.com", + "zzcartoon.com", + "zzcloud.me", + "zzux.com", + "chat.openai.com" + ] + ] +]; + +var lastRule = ''; + +function FindProxyForURL(url, host) { + for (var i = 0; i < rules.length; i++) { + ret = testHost(host, i); + if (ret != undefined) + return ret; + } + return 'DIRECT'; +} + +function testHost(host, index) { + for (var i = 0; i < rules[index].length; i++) { + for (var j = 0; j < rules[index][i].length; j++) { + lastRule = rules[index][i][j]; + if (host == lastRule || host.endsWith('.' + lastRule)) + return i % 2 == 0 ? 'DIRECT' : proxy; + } + } + lastRule = ''; +} + +// REF: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith +if (!String.prototype.endsWith) { + String.prototype.endsWith = function(searchString, position) { + var subjectString = this.toString(); + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { + position = subjectString.length; + } + position -= searchString.length; + var lastIndex = subjectString.indexOf(searchString, position); + return lastIndex !== -1 && lastIndex === position; + }; +} diff --git a/v2rayMiniConsole/ProtosLib/ProtosLib.csproj b/v2rayMiniConsole/ProtosLib/ProtosLib.csproj new file mode 100644 index 00000000..81cc41f9 --- /dev/null +++ b/v2rayMiniConsole/ProtosLib/ProtosLib.csproj @@ -0,0 +1,20 @@ + + + net8.0 + enable + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/v2rayMiniConsole/ProtosLib/Statistics.proto b/v2rayMiniConsole/ProtosLib/Statistics.proto new file mode 100644 index 00000000..db96ac02 --- /dev/null +++ b/v2rayMiniConsole/ProtosLib/Statistics.proto @@ -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 {} diff --git a/v2rayMiniConsole/ProtosLib/Tests.cs b/v2rayMiniConsole/ProtosLib/Tests.cs new file mode 100644 index 00000000..e1eb85b1 --- /dev/null +++ b/v2rayMiniConsole/ProtosLib/Tests.cs @@ -0,0 +1,13 @@ +using ProtosLib.Statistics; + +namespace ProtosLib +{ + public class Tests + { + private StatsService.StatsServiceClient client_; + + public Tests() + { + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/build.ps1 b/v2rayMiniConsole/build.ps1 new file mode 100644 index 00000000..506888d7 --- /dev/null +++ b/v2rayMiniConsole/build.ps1 @@ -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 \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole.sln b/v2rayMiniConsole/v2rayMiniConsole.sln new file mode 100644 index 00000000..167e3870 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole.sln @@ -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 diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/DownloaderHelper.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/DownloaderHelper.cs new file mode 100644 index 00000000..335727eb --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/DownloaderHelper.cs @@ -0,0 +1,186 @@ +using Downloader; +using System.IO; +using System.Net; + +namespace v2rayN +{ + internal class DownloaderHelper + { + private static readonly Lazy _instance = new(() => new()); + public static DownloaderHelper Instance => _instance.Value; + + public async Task DownloadStringAsync(IWebProxy? webProxy, string url, string? userAgent, int timeout) + { + if (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 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 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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/FileManager.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/FileManager.cs new file mode 100644 index 00000000..b721f5db --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/FileManager.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/HttpClientHelper.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/HttpClientHelper.cs new file mode 100644 index 00000000..60f9ad2b --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/HttpClientHelper.cs @@ -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 +{ + /// + /// + public class HttpClientHelper + { + private static readonly Lazy _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 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 GetAsync(string url) + { + if (Utils.IsNullOrEmpty(url)) return null; + return await httpClient.GetStringAsync(url); + } + + public async Task 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 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 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? 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 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); + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/Job.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/Job.cs new file mode 100644 index 00000000..c25a2221 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/Job.cs @@ -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 +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/JsonUtils.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/JsonUtils.cs new file mode 100644 index 00000000..ab81d41e --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/JsonUtils.cs @@ -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 + { + /// + /// DeepCopy + /// + /// + /// + /// + public static T DeepCopy(T obj) + { + return Deserialize(Serialize(obj, false))!; + } + + /// + /// Deserialize to object + /// + /// + /// + /// + public static T? Deserialize(string? strJson) + { + try + { + if (string.IsNullOrWhiteSpace(strJson)) + { + return default; + } + return JsonSerializer.Deserialize(strJson); + } + catch + { + return default; + } + } + + /// + /// parse + /// + /// + /// + public static JsonNode? ParseJson(string strJson) + { + try + { + if (string.IsNullOrWhiteSpace(strJson)) + { + return null; + } + return JsonNode.Parse(strJson); + } + catch + { + //SaveLog(ex.Message, ex); + return null; + } + } + + /// + /// Serialize Object to Json string + /// + /// + /// + /// + 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; + } + + /// + /// SerializeToNode + /// + /// + /// + public static JsonNode? SerializeToNode(object? obj) => JsonSerializer.SerializeToNode(obj); + + /// + /// Save as json file + /// + /// + /// + /// + /// + 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; + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/Logging.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/Logging.cs new file mode 100644 index 00000000..ba5b97e7 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/Logging.cs @@ -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); + } + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/QueryableExtension.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/QueryableExtension.cs new file mode 100644 index 00000000..113f2824 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/QueryableExtension.cs @@ -0,0 +1,50 @@ +using System.Linq.Expressions; +using System.Reflection; + +namespace v2rayN +{ + public static class QueryableExtension + { + public static IOrderedQueryable OrderBy(this IQueryable query, string propertyName) + { + return _OrderBy(query, propertyName, false); + } + + public static IOrderedQueryable OrderByDescending(this IQueryable query, string propertyName) + { + return _OrderBy(query, propertyName, true); + } + + private static IOrderedQueryable _OrderBy(IQueryable 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)method.Invoke(null, new object[] { query, memberProp }); + } + + public static IOrderedQueryable OrderByInternal(IQueryable query, PropertyInfo memberProperty) + {//public + return query.OrderBy(_GetLambda(memberProperty)); + } + + public static IOrderedQueryable OrderByDescendingInternal(IQueryable query, PropertyInfo memberProperty) + {//public + return query.OrderByDescending(_GetLambda(memberProperty)); + } + + private static Expression> _GetLambda(PropertyInfo memberProperty) + { + if (memberProperty.PropertyType != typeof(TProp)) throw new Exception(); + + var thisArg = Expression.Parameter(typeof(T)); + var lambda = Expression.Lambda>(Expression.Property(thisArg, memberProperty), thisArg); + + return lambda; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/SemanticVersion.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/SemanticVersion.cs new file mode 100644 index 00000000..597ed970 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/SemanticVersion.cs @@ -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(); + } + + /// + /// Use ToVersionString(string? prefix) instead if possible. + /// + /// major.minor.patch + 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 + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/SqliteHelper.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/SqliteHelper.cs new file mode 100644 index 00000000..70beb810 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/SqliteHelper.cs @@ -0,0 +1,123 @@ +using SQLite; +using System.Collections; + +namespace v2rayN +{ + public sealed class SQLiteHelper + { + private static readonly Lazy _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() + { + return _db.CreateTable(); + } + + public int Insert(object model) + { + return _db.Insert(model); + } + + public int InsertAll(IEnumerable models) + { + lock (objLock) + { + return _db.InsertAll(models); + } + } + + public async Task InsertAsync(object model) + { + return await _dbAsync.InsertAsync(model); + } + + public int Replace(object model) + { + lock (objLock) + { + return _db.InsertOrReplace(model); + } + } + + public async Task ReplaceAsync(object model) + { + return await _dbAsync.InsertOrReplaceAsync(model); + } + + public int Update(object model) + { + lock (objLock) + { + return _db.Update(model); + } + } + + public async Task 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 DeleteAsync(object model) + { + return await _dbAsync.DeleteAsync(model); + } + + public List Query(string sql) where T : new() + { + return _db.Query(sql); + } + + public async Task> QueryAsync(string sql) where T : new() + { + return await _dbAsync.QueryAsync(sql); + } + + public int Execute(string sql) + { + return _db.Execute(sql); + } + + public async Task ExecuteAsync(string sql) + { + return await _dbAsync.ExecuteAsync(sql); + } + + public TableQuery Table() where T : new() + { + return _db.Table(); + } + + public AsyncTableQuery TableAsync() where T : new() + { + return _dbAsync.Table(); + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/StringEx.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/StringEx.cs new file mode 100644 index 00000000..9a751ce9 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/StringEx.cs @@ -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 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 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}\""; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/Utils.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/Utils.cs new file mode 100644 index 00000000..c430370a --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/Utils.cs @@ -0,0 +1,1127 @@ +using Microsoft.Win32; +using Microsoft.Win32.TaskScheduler; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Principal; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows.Forms; + + +namespace v2rayN +{ + internal class Utils + { + #region 资源Json操作 + + /// + /// 获取嵌入文本资源 + /// + /// + /// + public static string GetEmbedText(string res) + { + string result = string.Empty; + + try + { + Assembly assembly = Assembly.GetExecutingAssembly(); + using Stream? stream = assembly.GetManifestResourceStream(res); + ArgumentNullException.ThrowIfNull(stream); + using StreamReader reader = new(stream); + result = reader.ReadToEnd(); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return result; + } + + /// + /// 取得存储资源 + /// + /// + public static string? LoadResource(string? res) + { + try + { + if (!File.Exists(res)) + { + return null; + } + return File.ReadAllText(res); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return null; + } + + #endregion 资源Json操作 + + #region 转换函数 + + /// + /// List转逗号分隔的字符串 + /// + /// + /// + public static string List2String(List? lst, bool wrap = false) + { + try + { + if (lst == null) + { + return string.Empty; + } + if (wrap) + { + return string.Join("," + Environment.NewLine, lst); + } + else + { + return string.Join(",", lst); + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + return string.Empty; + } + } + + /// + /// 逗号分隔的字符串,转List + /// + /// + /// + public static List String2List(string str) + { + try + { + str = str.Replace(Environment.NewLine, ""); + return new List(str.Split(',', StringSplitOptions.RemoveEmptyEntries)); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + return new List(); + } + } + + /// + /// 逗号分隔的字符串,先排序后转List + /// + /// + /// + public static List String2ListSorted(string str) + { + try + { + str = str.Replace(Environment.NewLine, ""); + List list = new(str.Split(',', StringSplitOptions.RemoveEmptyEntries)); + list.Sort(); + return list; + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + return new List(); + } + } + + /// + /// Base64编码 + /// + /// + /// + public static string Base64Encode(string plainText) + { + try + { + byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); + return Convert.ToBase64String(plainTextBytes); + } + catch (Exception ex) + { + Logging.SaveLog("Base64Encode", ex); + return string.Empty; + } + } + + /// + /// Base64解码 + /// + /// + /// + public static string Base64Decode(string plainText) + { + try + { + plainText = plainText.Trim() + .Replace(Environment.NewLine, "") + .Replace("\n", "") + .Replace("\r", "") + .Replace('_', '/') + .Replace('-', '+') + .Replace(" ", ""); + + if (plainText.Length % 4 > 0) + { + plainText = plainText.PadRight(plainText.Length + 4 - plainText.Length % 4, '='); + } + + byte[] data = Convert.FromBase64String(plainText); + return Encoding.UTF8.GetString(data); + } + catch (Exception ex) + { + Logging.SaveLog("Base64Decode", ex); + return string.Empty; + } + } + + /// + /// 转Int + /// + /// + /// + public static int ToInt(object? obj) + { + try + { + return Convert.ToInt32(obj ?? string.Empty); + } + catch //(Exception ex) + { + //SaveLog(ex.Message, ex); + return 0; + } + } + + public static bool ToBool(object obj) + { + try + { + return Convert.ToBoolean(obj); + } + catch //(Exception ex) + { + //SaveLog(ex.Message, ex); + return false; + } + } + + public static string ToString(object obj) + { + try + { + return obj?.ToString() ?? string.Empty; + } + catch// (Exception ex) + { + //SaveLog(ex.Message, ex); + return string.Empty; + } + } + + /// + /// byte 转成 有两位小数点的 方便阅读的数据 + /// 比如 2.50 MB + /// + /// bytes + /// 转换之后的数据 + /// 单位 + public static void ToHumanReadable(long amount, out double result, out string unit) + { + uint factor = 1024u; + //long KBs = amount / factor; + long KBs = amount; + if (KBs > 0) + { + // multi KB + long MBs = KBs / factor; + if (MBs > 0) + { + // multi MB + long GBs = MBs / factor; + if (GBs > 0) + { + // multi GB + long TBs = GBs / factor; + if (TBs > 0) + { + result = TBs + ((GBs % factor) / (factor + 0.0)); + unit = "TB"; + return; + } + result = GBs + ((MBs % factor) / (factor + 0.0)); + unit = "GB"; + return; + } + result = MBs + ((KBs % factor) / (factor + 0.0)); + unit = "MB"; + return; + } + result = KBs + ((amount % factor) / (factor + 0.0)); + unit = "KB"; + return; + } + else + { + result = amount; + unit = "B"; + } + } + + public static string HumanFy(long amount) + { + ToHumanReadable(amount, out double result, out string unit); + return $"{string.Format("{0:f1}", result)} {unit}"; + } + + public static string UrlEncode(string url) + { + return Uri.EscapeDataString(url); + //return HttpUtility.UrlEncode(url); + } + + public static string UrlDecode(string url) + { + return Uri.UnescapeDataString(url); + //return HttpUtility.UrlDecode(url); + } + + public static NameValueCollection ParseQueryString(string query) + { + var result = new NameValueCollection(StringComparer.OrdinalIgnoreCase); + if (IsNullOrEmpty(query)) + { + return result; + } + + var parts = query[1..].Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var part in parts) + { + var keyValue = part.Split(['=']); + if (keyValue.Length != 2) + { + continue; + } + var key = Uri.UnescapeDataString(keyValue[0]); + var val = Uri.UnescapeDataString(keyValue[1]); + + if (result[key] is null) + { + result.Add(key, val); + } + } + + return result; + } + + public static string GetMD5(string str) + { + byte[] byteOld = Encoding.UTF8.GetBytes(str); + byte[] byteNew = MD5.HashData(byteOld); + StringBuilder sb = new(32); + foreach (byte b in byteNew) + { + sb.Append(b.ToString("x2")); + } + return sb.ToString(); + } + + /// + /// idn to idc + /// + /// + /// + public static string GetPunycode(string url) + { + if (Utils.IsNullOrEmpty(url)) + { + return url; + } + try + { + Uri uri = new(url); + if (uri.Host == uri.IdnHost || uri.Host == $"[{uri.IdnHost}]") + { + return url; + } + else + { + return url.Replace(uri.Host, uri.IdnHost); + } + } + catch + { + return url; + } + } + + public static bool IsBase64String(string plainText) + { + var buffer = new Span(new byte[plainText.Length]); + return Convert.TryFromBase64String(plainText, buffer, out int _); + } + + public static string Convert2Comma(string text) + { + if (Utils.IsNullOrEmpty(text)) + { + return text; + } + return text.Replace(",", ",").Replace(Environment.NewLine, ","); + } + + #endregion 转换函数 + + #region 数据检查 + + /// + /// 判断输入的是否是数字 + /// + /// + /// + public static bool IsNumeric(string oText) + { + try + { + int var1 = ToInt(oText); + return true; + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + return false; + } + } + + /// + /// 文本 + /// + /// + /// + public static bool IsNullOrEmpty(string? text) + { + if (string.IsNullOrWhiteSpace(text)) + { + return true; + } + if (text == "null") + { + return true; + } + return false; + } + + /// + /// 验证IP地址是否合法 + /// + /// + public static bool IsIP(string ip) + { + //如果为空 + if (IsNullOrEmpty(ip)) + { + return false; + } + + //清除要验证字符串中的空格 + //ip = ip.TrimEx(); + //可能是CIDR + if (ip.IndexOf(@"/") > 0) + { + string[] cidr = ip.Split('/'); + if (cidr.Length == 2) + { + if (!IsNumeric(cidr[0])) + { + return false; + } + ip = cidr[0]; + } + } + + //模式字符串 + string pattern = @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$"; + + //验证 + return IsMatch(ip, pattern); + } + + /// + /// 验证Domain地址是否合法 + /// + /// + public static bool IsDomain(string? domain) + { + //如果为空 + if (IsNullOrEmpty(domain)) + { + return false; + } + + return Uri.CheckHostName(domain) == UriHostNameType.Dns; + } + + public static bool IsValidUrl(string? url) + { + if (string.IsNullOrWhiteSpace(url)) + { + return false; + } + + Uri result; + // 尝试将字符串转换为Uri实例 + bool success = Uri.TryCreate(url, UriKind.Absolute, out result) + && (result.Scheme == Uri.UriSchemeHttp || result.Scheme == Uri.UriSchemeHttps); + + // 注意:这里还检查了URL是否是HTTP或HTTPS协议 + // 如果需要接受其他协议,请删除或修改此检查 + return success; + } + + /// + /// 验证输入字符串是否与模式字符串匹配,匹配返回true + /// + /// 输入字符串 + /// 模式字符串 + public static bool IsMatch(string input, string pattern) + { + return Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase); + } + + public static bool IsIpv6(string ip) + { + if (IPAddress.TryParse(ip, out IPAddress? address)) + { + return address.AddressFamily switch + { + AddressFamily.InterNetwork => false, + AddressFamily.InterNetworkV6 => true, + _ => false, + }; + } + return false; + } + + #endregion 数据检查 + + #region 测速 + + /// + /// 取得本机 IP Address + /// + /// + //public static List GetHostIPAddress() + //{ + // List lstIPAddress = new List(); + // try + // { + // IPHostEntry IpEntry = Dns.GetHostEntry(Dns.GetHostName()); + // foreach (IPAddress ipa in IpEntry.AddressList) + // { + // if (ipa.AddressFamily == AddressFamily.InterNetwork) + // lstIPAddress.Add(ipa.ToString()); + // } + // } + // catch (Exception ex) + // { + // SaveLog(ex.Message, ex); + // } + // return lstIPAddress; + //} + + public static void SetSecurityProtocol(bool enableSecurityProtocolTls13) + { + if (enableSecurityProtocolTls13) + { + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13; + } + else + { + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; + } + ServicePointManager.DefaultConnectionLimit = 256; + } + + public static bool PortInUse(int port) + { + bool inUse = false; + try + { + IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties(); + IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners(); + + var lstIpEndPoints = new List(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); + + foreach (IPEndPoint endPoint in ipEndPoints) + { + if (endPoint.Port == port) + { + inUse = true; + break; + } + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return inUse; + } + + public static int GetFreePort(int defaultPort = 9090) + { + try + { + if (!Utils.PortInUse(defaultPort)) + { + return defaultPort; + } + + TcpListener l = new(IPAddress.Loopback, 0); + l.Start(); + int port = ((IPEndPoint)l.LocalEndpoint).Port; + l.Stop(); + return port; + } + catch + { + } + return 59090; + } + + #endregion 测速 + + #region 杂项 + + /// + /// 取得版本 + /// + /// + public static string GetVersion(bool blFull = true) + { + try + { + string location = GetExePath(); + if (blFull) + { + return string.Format("v2rayMiniConsole - V{0} - {1}", + FileVersionInfo.GetVersionInfo(location).FileVersion?.ToString(), + File.GetLastWriteTime(location).ToString("yyyy/MM/dd")); + } + else + { + return string.Format("v2rayMiniConsole - V{0}", + FileVersionInfo.GetVersionInfo(location).FileVersion?.ToString()); + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + return string.Empty; + } + } + + /// + /// 获取剪贴板数 + /// + /// + public static string? GetClipboardData() + { + string? strData = string.Empty; + try + { + IDataObject data = Clipboard.GetDataObject(); + if (data.GetDataPresent(DataFormats.UnicodeText)) + { + strData = data.GetData(DataFormats.UnicodeText)?.ToString(); + } + return strData; + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return strData; + } + + /// + /// 拷贝至剪贴板 + /// + /// + public static void SetClipboardData(string strData) + { + try + { + Clipboard.SetText(strData); + } + catch + { + } + } + + /// + /// 取得GUID + /// + /// + public static string GetGUID(bool full = true) + { + try + { + if (full) + { + return Guid.NewGuid().ToString("D"); + } + else + { + return BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0).ToString(); + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return string.Empty; + } + + /// + /// IsAdministrator + /// + /// + public static bool IsAdministrator() + { + try + { + WindowsIdentity current = WindowsIdentity.GetCurrent(); + WindowsPrincipal windowsPrincipal = new WindowsPrincipal(current); + //WindowsBuiltInRole可以枚举出很多权限,例如系统用户、User、Guest等等 + return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + return false; + } + } + + public static string GetDownloadFileName(string url) + { + var fileName = Path.GetFileName(url); + fileName += "_temp"; + + return fileName; + } + + public static IPAddress? GetDefaultGateway() + { + return NetworkInterface + .GetAllNetworkInterfaces() + .Where(n => n.OperationalStatus == OperationalStatus.Up) + .Where(n => n.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .SelectMany(n => n.GetIPProperties()?.GatewayAddresses) + .Select(g => g?.Address) + .Where(a => a != null) + // .Where(a => a.AddressFamily == AddressFamily.InterNetwork) + // .Where(a => Array.FindIndex(a.GetAddressBytes(), b => b != 0) >= 0) + .FirstOrDefault(); + } + + public static bool IsGuidByParse(string strSrc) + { + return Guid.TryParse(strSrc, out Guid g); + } + + public static void ProcessStart(string fileName, string arguments = "") + { + try + { + Process.Start(new ProcessStartInfo(fileName, arguments) { UseShellExecute = true }); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + } + + public static bool IsLightTheme() + { + using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"); + var value = key?.GetValue("AppsUseLightTheme"); + return value is int i && i > 0; + } + + /// + /// 获取系统hosts + /// + /// + public static Dictionary GetSystemHosts() + { + var systemHosts = new Dictionary(); + var hostfile = @"C:\Windows\System32\drivers\etc\hosts"; + try + { + if (File.Exists(hostfile)) + { + var hosts = File.ReadAllText(hostfile).Replace("\r", ""); + var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var host in hostsList) + { + if (host.StartsWith("#")) continue; + var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + if (hostItem.Length < 2) continue; + systemHosts.Add(hostItem[1], hostItem[0]); + } + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return systemHosts; + } + + #endregion 杂项 + + #region TempPath + + /// + /// 获取启动了应用程序的可执行文件的路径 + /// + /// + public static string GetPath(string fileName) + { + string startupPath = StartupPath(); + if (IsNullOrEmpty(fileName)) + { + return startupPath; + } + return Path.Combine(startupPath, fileName); + } + + /// + /// 获取启动了应用程序的可执行文件的路径及文件名 + /// + /// + public static string GetExePath() + { + return Environment.ProcessPath ?? string.Empty; + } + + public static string StartupPath() + { + return AppDomain.CurrentDomain.BaseDirectory; + } + + public static string GetTempPath(string filename = "") + { + string _tempPath = Path.Combine(StartupPath(), "guiTemps"); + if (!Directory.Exists(_tempPath)) + { + Directory.CreateDirectory(_tempPath); + } + if (Utils.IsNullOrEmpty(filename)) + { + return _tempPath; + } + else + { + return Path.Combine(_tempPath, filename); + } + } + + public static string UnGzip(byte[] buf) + { + using MemoryStream sb = new(); + using GZipStream input = new(new MemoryStream(buf), CompressionMode.Decompress, false); + input.CopyTo(sb); + sb.Position = 0; + return new StreamReader(sb, Encoding.UTF8).ReadToEnd(); + } + + public static string GetBackupPath(string filename) + { + string _tempPath = Path.Combine(StartupPath(), "guiBackups"); + if (!Directory.Exists(_tempPath)) + { + Directory.CreateDirectory(_tempPath); + } + return Path.Combine(_tempPath, filename); + } + + public static string GetConfigPath(string filename = "") + { + string _tempPath = Path.Combine(StartupPath(), "guiConfigs"); + if (!Directory.Exists(_tempPath)) + { + Directory.CreateDirectory(_tempPath); + } + if (Utils.IsNullOrEmpty(filename)) + { + return _tempPath; + } + else + { + return Path.Combine(_tempPath, filename); + } + } + + public static string GetBinPath(string filename, string? coreType = null) + { + string _tempPath = Path.Combine(StartupPath(), "bin"); + if (!Directory.Exists(_tempPath)) + { + Directory.CreateDirectory(_tempPath); + } + if (coreType != null) + { + _tempPath = Path.Combine(_tempPath, coreType.ToString()!); + if (!Directory.Exists(_tempPath)) + { + Directory.CreateDirectory(_tempPath); + } + } + if (Utils.IsNullOrEmpty(filename)) + { + return _tempPath; + } + else + { + return Path.Combine(_tempPath, filename); + } + } + + public static string GetLogPath(string filename = "") + { + string _tempPath = Path.Combine(StartupPath(), "guiLogs"); + if (!Directory.Exists(_tempPath)) + { + Directory.CreateDirectory(_tempPath); + } + if (Utils.IsNullOrEmpty(filename)) + { + return _tempPath; + } + else + { + return Path.Combine(_tempPath, filename); + } + } + + public static string GetFontsPath(string filename = "") + { + string _tempPath = Path.Combine(StartupPath(), "guiFonts"); + if (!Directory.Exists(_tempPath)) + { + Directory.CreateDirectory(_tempPath); + } + if (Utils.IsNullOrEmpty(filename)) + { + return _tempPath; + } + else + { + return Path.Combine(_tempPath, filename); + } + } + + #endregion TempPath + + #region 开机自动启动等 + + /// + /// 开机自动启动 + /// + /// + /// + public static void SetAutoRun(string AutoRunRegPath, string AutoRunName, bool run) + { + try + { + var autoRunName = $"{AutoRunName}_{GetMD5(StartupPath())}"; + + //delete first + RegWriteValue(AutoRunRegPath, autoRunName, ""); + if (IsAdministrator()) + { + AutoStart(autoRunName, "", ""); + } + + if (run) + { + string exePath = GetExePath(); + if (IsAdministrator()) + { + AutoStart(autoRunName, exePath, ""); + } + else + { + RegWriteValue(AutoRunRegPath, autoRunName, exePath.AppendQuotes()); + } + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + } + + public static string? RegReadValue(string path, string name, string def) + { + RegistryKey? regKey = null; + try + { + regKey = Registry.CurrentUser.OpenSubKey(path, false); + string? value = regKey?.GetValue(name) as string; + if (IsNullOrEmpty(value)) + { + return def; + } + else + { + return value; + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + finally + { + regKey?.Close(); + } + return def; + } + + public static void RegWriteValue(string path, string name, object value) + { + RegistryKey? regKey = null; + try + { + regKey = Registry.CurrentUser.CreateSubKey(path); + if (IsNullOrEmpty(value.ToString())) + { + regKey?.DeleteValue(name, false); + } + else + { + regKey?.SetValue(name, value); + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + finally + { + regKey?.Close(); + } + } + + /// + /// Auto Start via TaskService + /// + /// + /// + /// + /// + public static void AutoStart(string taskName, string fileName, string description) + { + if (Utils.IsNullOrEmpty(taskName)) + { + return; + } + string TaskName = taskName; + var logonUser = WindowsIdentity.GetCurrent().Name; + string taskDescription = description; + string deamonFileName = fileName; + + using var taskService = new TaskService(); + var tasks = taskService.RootFolder.GetTasks(new Regex(TaskName)); + foreach (var t in tasks) + { + taskService.RootFolder.DeleteTask(t.Name); + } + if (Utils.IsNullOrEmpty(fileName)) + { + return; + } + + var task = taskService.NewTask(); + task.RegistrationInfo.Description = taskDescription; + task.Settings.DisallowStartIfOnBatteries = false; + task.Settings.StopIfGoingOnBatteries = false; + task.Settings.RunOnlyIfIdle = false; + task.Settings.IdleSettings.StopOnIdleEnd = false; + task.Settings.ExecutionTimeLimit = TimeSpan.Zero; + task.Triggers.Add(new LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(10) }); + task.Principal.RunLevel = TaskRunLevel.Highest; + task.Actions.Add(new ExecAction(deamonFileName.AppendQuotes(), null, Path.GetDirectoryName(deamonFileName))); + + taskService.RootFolder.RegisterTaskDefinition(TaskName, task); + } + + public static void RemoveTunDevice() + { + try + { + var sum = MD5.HashData(Encoding.UTF8.GetBytes("wintunsingbox_tun")); + var guid = new Guid(sum); + string pnputilPath = @"C:\Windows\System32\pnputil.exe"; + string arg = $$""" /remove-device "SWD\Wintun\{{{guid}}}" """; + + // Try to remove the device + Process proc = new() + { + StartInfo = new() + { + FileName = pnputilPath, + Arguments = arg, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + + proc.Start(); + var output = proc.StandardOutput.ReadToEnd(); + proc.WaitForExit(); + } + catch + { + } + } + + #endregion 开机自动启动等 + + #region Windows API + + [Flags] + public enum DWMWINDOWATTRIBUTE : uint + { + DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19, + DWMWA_USE_IMMERSIVE_DARK_MODE = 20, + } + + [DllImport("dwmapi.dll")] + public static extern int DwmSetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE attribute, ref int attributeValue, uint attributeSize); + + #endregion Windows API + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Common/YamlUtils.cs b/v2rayMiniConsole/v2rayMiniConsole/Common/YamlUtils.cs new file mode 100644 index 00000000..57af2119 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Common/YamlUtils.cs @@ -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 + + /// + /// 反序列化成对象 + /// + /// + /// + /// + public static T FromYaml(string str) + { + var deserializer = new DeserializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .Build(); + try + { + T obj = deserializer.Deserialize(str); + return obj; + } + catch (Exception ex) + { + Logging.SaveLog("FromYaml", ex); + return deserializer.Deserialize(""); + } + } + + /// + /// 序列化 + /// + /// + /// + 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 + } +} diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/EConfigType.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/EConfigType.cs new file mode 100644 index 00000000..60ef796a --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/EConfigType.cs @@ -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 + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/ECoreType.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/ECoreType.cs new file mode 100644 index 00000000..54c07887 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/ECoreType.cs @@ -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 + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/EGlobalHotkey.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/EGlobalHotkey.cs new file mode 100644 index 00000000..f4a92bdb --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/EGlobalHotkey.cs @@ -0,0 +1,11 @@ +namespace v2rayN.Enums +{ + public enum EGlobalHotkey + { + ShowForm = 0, + SystemProxyClear = 1, + SystemProxySet = 2, + SystemProxyUnchanged = 3, + SystemProxyPac = 4, + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/EInboundProtocol.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/EInboundProtocol.cs new file mode 100644 index 00000000..07cd1369 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/EInboundProtocol.cs @@ -0,0 +1,14 @@ +namespace v2rayN.Enums +{ + public enum EInboundProtocol + { + socks = 0, + http, + socks2, + http2, + pac, + api, + api2, + speedtest = 21 + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/EMove.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/EMove.cs new file mode 100644 index 00000000..fcbc23dc --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/EMove.cs @@ -0,0 +1,11 @@ +namespace v2rayN.Enums +{ + public enum EMove + { + Top = 1, + Up = 2, + Down = 3, + Bottom = 4, + Position = 5 + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/ERuleMode.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/ERuleMode.cs new file mode 100644 index 00000000..59a01430 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/ERuleMode.cs @@ -0,0 +1,10 @@ +namespace v2rayN.Enums +{ + public enum ERuleMode + { + Rule = 0, + Global = 1, + Direct = 2, + Unchanged = 3 + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/EServerColName.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/EServerColName.cs new file mode 100644 index 00000000..453dbe88 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/EServerColName.cs @@ -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 + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/ESpeedActionType.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/ESpeedActionType.cs new file mode 100644 index 00000000..e0663d92 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/ESpeedActionType.cs @@ -0,0 +1,10 @@ +namespace v2rayN.Enums +{ + public enum ESpeedActionType + { + Tcping, + Realping, + Speedtest, + Mixedtest + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/ESysProxyType.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/ESysProxyType.cs new file mode 100644 index 00000000..3991940e --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/ESysProxyType.cs @@ -0,0 +1,10 @@ +namespace v2rayN.Enums +{ + public enum ESysProxyType + { + ForcedClear = 0, // "清除系统代理" + ForcedChange = 1, // "自动配置系统代理" + Unchanged = 2, // "不改变系统代理" + Pac = 3 // "pac" + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/ETransport.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/ETransport.cs new file mode 100644 index 00000000..349d68e7 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/ETransport.cs @@ -0,0 +1,15 @@ +namespace v2rayN.Enums +{ + public enum ETransport + { + tcp, + kcp, + ws, + httpupgrade, + splithttp, + h2, + http, + quic, + grpc + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Enums/EViewAction.cs b/v2rayMiniConsole/v2rayMiniConsole/Enums/EViewAction.cs new file mode 100644 index 00000000..8a5a4641 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Enums/EViewAction.cs @@ -0,0 +1,8 @@ +namespace v2rayN.Enums +{ + public enum EViewAction + { + AdjustMainLvColWidth, + ProfilesFocus + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Global.cs b/v2rayMiniConsole/v2rayMiniConsole/Global.cs new file mode 100644 index 00000000..2b012c70 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Global.cs @@ -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 = ""; + 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 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 SubConvertUrls = new List { + @"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 SubConvertConfig = new List { + @"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini" + }; + + public static readonly List SubConvertTargets = new List { + "", + "mixed", + "v2ray", + "clash", + "ss", + }; + + public static readonly List 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 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 UserAgentTexts = new() + { + {"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" }, + {"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" }, + {"safari","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" }, + {"edge","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70" }, + {"none",""} + }; + + public const string Hysteria2ProtocolShare = "hy2://"; + + public static readonly Dictionary ProtocolShares = new() + { + {EConfigType.VMess,"vmess://"}, + {EConfigType.Shadowsocks,"ss://"}, + {EConfigType.Socks,"socks://"}, + {EConfigType.VLESS,"vless://"}, + {EConfigType.Trojan,"trojan://"}, + {EConfigType.Hysteria2,"hysteria2://"}, + {EConfigType.Tuic,"tuic://"}, + {EConfigType.Wireguard,"wireguard://"} + }; + + public static readonly Dictionary ProtocolTypes = new() + { + {EConfigType.VMess,"vmess"}, + {EConfigType.Shadowsocks,"shadowsocks"}, + {EConfigType.Socks,"socks"}, + {EConfigType.Http,"http"}, + {EConfigType.VLESS,"vless"}, + {EConfigType.Trojan,"trojan"}, + {EConfigType.Hysteria2,"hysteria2"}, + {EConfigType.Tuic,"tuic"}, + {EConfigType.Wireguard,"wireguard"} + }; + + public static readonly List VmessSecurities = new() { "aes-128-gcm", "chacha20-poly1305", "auto", "none", "zero" }; + public static readonly List SsSecurities = new() { "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "none", "plain" }; + public static readonly List 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 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 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 Flows = new() { "", "xtls-rprx-vision", "xtls-rprx-vision-udp443" }; + public static readonly List Networks = new() { "tcp", "kcp", "ws", "httpupgrade", "splithttp", "h2", "quic", "grpc" }; + public static readonly List KcpHeaderTypes = new() { "srtp", "utp", "wechat-video", "dtls", "wireguard" }; + public static readonly List CoreTypes = new() { "v2fly", "SagerNet", "Xray", "sing_box" }; + public static readonly List CoreTypes4VLESS = new() { "Xray", "sing_box" }; + public static readonly List DomainStrategies = new() { "AsIs", "IPIfNonMatch", "IPOnDemand" }; + public static readonly List DomainStrategies4Singbox = new() { "ipv4_only", "ipv6_only", "prefer_ipv4", "prefer_ipv6", "" }; + public static readonly List DomainMatchers = new() { "linear", "mph", "" }; + public static readonly List Fingerprints = new() { "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random", "randomized", "" }; + public static readonly List UserAgent = new() { "chrome", "firefox", "safari", "edge", "none" }; + + public static readonly List AllowInsecure = new() { "true", "false", "" }; + public static readonly List DomainStrategy4Freedoms = new() { "AsIs", "UseIP", "UseIPv4", "UseIPv6", "" }; + public static readonly List Languages = new() { "zh-Hans", "zh-Hant", "en", "fa-Ir", "ru" }; + public static readonly List Alpns = new() { "h3", "h2", "http/1.1", "h3,h2,http/1.1", "h3,h2", "h2,http/1.1", "" }; + public static readonly List LogLevels = new() { "debug", "info", "warning", "error", "none" }; + public static readonly List InboundTags = new() { "socks", "http", "socks2", "http2" }; + public static readonly List RuleProtocols = new() { "http", "tls", "bittorrent" }; + public static readonly List RuleNetworks = new() { "", "tcp", "udp", "tcp,udp" }; + public static readonly List destOverrideProtocols = ["http", "tls", "quic", "fakedns", "fakedns+others"]; + public static readonly List TunMtus = new() { "1280", "1408", "1500", "9000" }; + public static readonly List TunStacks = new() { "gvisor", "system" }; + public static readonly List PresetMsgFilters = new() { "proxy", "direct", "block", "" }; + public static readonly List SingboxMuxs = new() { "h2mux", "smux", "yamux", "" }; + public static readonly List TuicCongestionControls = new() { "cubic", "new_reno", "bbr" }; + + public static readonly List allowSelectType = new List { "selector", "urltest", "loadbalance", "fallback" }; + public static readonly List notAllowTestType = new List { "selector", "urltest", "direct", "reject", "compatible", "pass", "loadbalance", "fallback" }; + public static readonly List proxyVehicleType = new List { "file", "http" }; + + #endregion const + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/ClashApiHandler.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/ClashApiHandler.cs new file mode 100644 index 00000000..1e04bf1e --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/ClashApiHandler.cs @@ -0,0 +1,218 @@ +using v2rayN.Models; +using static v2rayN.Models.ClashProxies; + +namespace v2rayN.Handler +{ + public sealed class ClashApiHandler + { + private static readonly Lazy instance = new(() => new()); + public static ClashApiHandler Instance => instance.Value; + + private Dictionary _proxies; + public Dictionary ProfileContent { get; set; } + + public void SetProxies(Dictionary proxies) + { + _proxies = proxies; + } + + public Dictionary GetProxies() + { + return _proxies; + } + + public void GetClashProxies(Config config, Action update) + { + Task.Run(() => GetClashProxiesAsync(config, update)); + } + + private async Task GetClashProxiesAsync(Config config, Action update) + { + for (var i = 0; i < 5; i++) + { + var url = $"{GetApiUrl()}/proxies"; + var result = await HttpClientHelper.Instance.TryGetAsync(url); + var clashProxies = JsonUtils.Deserialize(result); + + var url2 = $"{GetApiUrl()}/providers/proxies"; + var result2 = await HttpClientHelper.Instance.TryGetAsync(url2); + var clashProviders = JsonUtils.Deserialize(result2); + + if (clashProxies != null || clashProviders != null) + { + update(clashProxies, clashProviders); + return; + } + Thread.Sleep(5000); + } + update(null, null); + } + + public void ClashProxiesDelayTest(bool blAll, List lstProxy, Action 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(); + foreach (KeyValuePair 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 tasks = new List(); + foreach (var it in lstProxy) + { + if (Global.notAllowTestType.Contains(it.type.ToLower())) + { + continue; + } + var name = it.name; + var url = string.Format(urlBase, name); + tasks.Add(Task.Run(async () => + { + var result = await HttpClientHelper.Instance.TryGetAsync(url); + update(it, result); + })); + } + Task.WaitAll(tasks.ToArray()); + + Thread.Sleep(1000); + update(null, ""); + }); + } + + public List? GetClashProxyGroups() + { + try + { + var fileContent = ProfileContent; + if (fileContent is null || fileContent?.ContainsKey("proxy-groups") == false) + { + return null; + } + return JsonUtils.Deserialize>(JsonUtils.Serialize(fileContent["proxy-groups"])); + } + catch (Exception ex) + { + Logging.SaveLog("GetClashProxyGroups", ex); + return null; + } + } + + public async void ClashSetActiveProxy(string name, string nameNode) + { + try + { + var url = $"{GetApiUrl()}/proxies/{name}"; + Dictionary headers = new Dictionary(); + headers.Add("name", nameNode); + await HttpClientHelper.Instance.PutAsync(url, headers); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + } + + public void ClashConfigUpdate(Dictionary 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 headers = new Dictionary(); + headers.Add("path", filePath); + await HttpClientHelper.Instance.PutAsync(url, headers); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + } + + public void GetClashConnections(Config config, Action update) + { + Task.Run(() => GetClashConnectionsAsync(config, update)); + } + + private async Task GetClashConnectionsAsync(Config config, Action update) + { + try + { + var url = $"{GetApiUrl()}/connections"; + var result = await HttpClientHelper.Instance.TryGetAsync(url); + var clashConnections = JsonUtils.Deserialize(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}"; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/ConfigHandler.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/ConfigHandler.cs new file mode 100644 index 00000000..0cae007a --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/ConfigHandler.cs @@ -0,0 +1,1730 @@ +using System.Data; +using System.IO; +using System.Text.RegularExpressions; +using System.Web; +using v2rayMiniConsole; +using v2rayN.Enums; +using v2rayN.Handler.Fmt; +using v2rayN.Models; + +namespace v2rayN.Handler +{ + /// + /// 本软件配置文件处理类 + /// + internal class ConfigHandler + { + private static string configRes = Global.ConfigFileName; + private static readonly object objLock = new(); + + #region ConfigHandler + + /// + /// 载入配置文件 + /// + /// + /// + public static int LoadConfig(ref Config? config) + { + //载入配置文件 + var result = Utils.LoadResource(Utils.GetConfigPath(configRes)); + if (!Utils.IsNullOrEmpty(result)) + { + //转成Json + config = JsonUtils.Deserialize(result); + } + else + { + if (File.Exists(Utils.GetConfigPath(configRes))) + { + Logging.SaveLog("LoadConfig Exception"); + return -1; + } + } + + if (config == null) + { + config = new Config + { + }; + } + if (config.coreBasicItem == null) + { + config.coreBasicItem = new() + { + logEnabled = false, + loglevel = "warning", + muxEnabled = false, + }; + } + + //本地监听 + if (config.inbound == null) + { + config.inbound = new List(); + InItem inItem = new() + { + protocol = EInboundProtocol.socks.ToString(), + localPort = 10808, + udpEnabled = true, + sniffingEnabled = true, + routeOnly = false, + }; + + config.inbound.Add(inItem); + } + else + { + if (config.inbound.Count > 0) + { + config.inbound[0].protocol = EInboundProtocol.socks.ToString(); + } + } + if (config.routingBasicItem == null) + { + config.routingBasicItem = new() + { + enableRoutingAdvanced = true + }; + } + //路由规则 + if (Utils.IsNullOrEmpty(config.routingBasicItem.domainStrategy)) + { + config.routingBasicItem.domainStrategy = Global.DomainStrategies[0];//"IPIfNonMatch"; + } + //if (Utile.IsNullOrEmpty(config.domainMatcher)) + //{ + // config.domainMatcher = "linear"; + //} + + //kcp + if (config.kcpItem == null) + { + config.kcpItem = new KcpItem + { + mtu = 1350, + tti = 50, + uplinkCapacity = 12, + downlinkCapacity = 100, + readBufferSize = 2, + writeBufferSize = 2, + congestion = false + }; + } + if (config.grpcItem == null) + { + config.grpcItem = new GrpcItem + { + idle_timeout = 60, + health_check_timeout = 20, + permit_without_stream = false, + initial_windows_size = 0, + }; + } + if (config.tunModeItem == null) + { + config.tunModeItem = new TunModeItem + { + enableTun = false, + mtu = 9000, + }; + } + if (config.guiItem == null) + { + config.guiItem = new() + { + enableStatistics = false, + }; + } + if (config.uiItem == null) + { + config.uiItem = new UIItem() + { + enableAutoAdjustMainLvColWidth = true + }; + } + if (config.uiItem.mainColumnItem == null) + { + config.uiItem.mainColumnItem = new(); + } + if (Utils.IsNullOrEmpty(config.uiItem.currentLanguage)) + { + config.uiItem.currentLanguage = Global.Languages[0]; + } + + if (config.constItem == null) + { + config.constItem = new ConstItem(); + } + if (Utils.IsNullOrEmpty(config.constItem.defIEProxyExceptions)) + { + config.constItem.defIEProxyExceptions = Global.IEProxyExceptions; + } + + if (config.speedTestItem == null) + { + config.speedTestItem = new(); + } + if (config.speedTestItem.speedTestTimeout < 10) + { + config.speedTestItem.speedTestTimeout = 10; + } + if (Utils.IsNullOrEmpty(config.speedTestItem.speedTestUrl)) + { + config.speedTestItem.speedTestUrl = Global.SpeedTestUrls[0]; + } + if (Utils.IsNullOrEmpty(config.speedTestItem.speedPingTestUrl)) + { + //config.speedTestItem.speedPingTestUrl = Global.SpeedPingTestUrl; + config.speedTestItem.speedPingTestUrl = Global.SpeedPingTestUrls[1]; + } + + if (config.mux4SboxItem == null) + { + config.mux4SboxItem = new() + { + protocol = Global.SingboxMuxs[0], + max_connections = 8 + }; + } + + if (config.hysteriaItem == null) + { + config.hysteriaItem = new() + { + up_mbps = 100, + down_mbps = 100 + }; + } + config.clashUIItem ??= new(); + + LazyConfig.Instance.SetConfig(config); + return 0; + } + + /// + /// 保参数 + /// + /// + /// + public static int SaveConfig(Config config, bool reload = true) + { + ToJsonFile(config); + + return 0; + } + + /// + /// 存储文件 + /// + /// + private static void ToJsonFile(Config config) + { + lock (objLock) + { + try + { + //save temp file + var resPath = Utils.GetConfigPath(configRes); + var tempPath = $"{resPath}_temp"; + if (JsonUtils.ToFile(config, tempPath) != 0) + { + return; + } + + if (File.Exists(resPath)) + { + File.Delete(resPath); + } + //rename + File.Move(tempPath, resPath); + } + catch (Exception ex) + { + Logging.SaveLog("ToJsonFile", ex); + } + } + } + + //public static int ImportOldGuiConfig(Config config, string fileName) + //{ + // var result = Utils.LoadResource(fileName); + // if (Utils.IsNullOrEmpty(result)) + // { + // return -1; + // } + + // var configOld = JsonUtils.Deserialize(result); + // if (configOld == null) + // { + // return -1; + // } + + // var subItem = JsonUtils.Deserialize>(JsonUtils.Serialize(configOld.subItem)); + // foreach (var it in subItem) + // { + // if (Utils.IsNullOrEmpty(it.id)) + // { + // it.id = Utils.GetGUID(false); + // } + // SQLiteHelper.Instance.Replace(it); + // } + + // var profileItems = JsonUtils.Deserialize>(JsonUtils.Serialize(configOld.vmess)); + // foreach (var it in profileItems) + // { + // if (Utils.IsNullOrEmpty(it.indexId)) + // { + // it.indexId = Utils.GetGUID(false); + // } + // SQLiteHelper.Instance.Replace(it); + // } + + // foreach (var it in configOld.routings) + // { + // if (it.locked) + // { + // continue; + // } + // var routing = JsonUtils.Deserialize(JsonUtils.Serialize(it)); + // foreach (var it2 in it.rules) + // { + // it2.id = Utils.GetGUID(false); + // } + // routing.ruleNum = it.rules.Count; + // routing.ruleSet = JsonUtils.Serialize(it.rules, false); + + // if (Utils.IsNullOrEmpty(routing.id)) + // { + // routing.id = Utils.GetGUID(false); + // } + // SQLiteHelper.Instance.Replace(routing); + // } + + // config = JsonUtils.Deserialize(JsonUtils.Serialize(configOld)); + + // if (config.coreBasicItem == null) + // { + // config.coreBasicItem = new() + // { + // logEnabled = configOld.logEnabled, + // loglevel = configOld.loglevel, + // muxEnabled = configOld.muxEnabled, + // }; + // } + + // if (config.routingBasicItem == null) + // { + // config.routingBasicItem = new() + // { + // enableRoutingAdvanced = configOld.enableRoutingAdvanced, + // domainStrategy = configOld.domainStrategy + // }; + // } + + // if (config.guiItem == null) + // { + // config.guiItem = new() + // { + // enableStatistics = configOld.enableStatistics, + // keepOlderDedupl = configOld.keepOlderDedupl, + // ignoreGeoUpdateCore = configOld.ignoreGeoUpdateCore, + // autoUpdateInterval = configOld.autoUpdateInterval, + // checkPreReleaseUpdate = configOld.checkPreReleaseUpdate, + // enableSecurityProtocolTls13 = configOld.enableSecurityProtocolTls13, + // trayMenuServersLimit = configOld.trayMenuServersLimit, + // }; + // } + + // GetDefaultServer(config); + // GetDefaultRouting(config); + // SaveConfig(config); + // LoadConfig(ref config); + + // return 0; + //} + + #endregion ConfigHandler + + #region Server + + /// + /// Add or edit server + /// + /// + /// + /// + public static int AddServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.configType = EConfigType.VMess; + + profileItem.address = profileItem.address.TrimEx(); + profileItem.id = profileItem.id.TrimEx(); + profileItem.security = profileItem.security.TrimEx(); + profileItem.network = profileItem.network.TrimEx(); + profileItem.headerType = profileItem.headerType.TrimEx(); + profileItem.requestHost = profileItem.requestHost.TrimEx(); + profileItem.path = profileItem.path.TrimEx(); + profileItem.streamSecurity = profileItem.streamSecurity.TrimEx(); + + if (!Global.VmessSecurities.Contains(profileItem.security)) + { + return -1; + } + if (profileItem.id.IsNullOrEmpty()) + { + return -1; + } + + AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// 移除服务器 + /// + /// + /// + /// + public static int RemoveServer(Config config, List indexes) + { + var subid = "TempRemoveSubId"; + foreach (var item in indexes) + { + item.subid = subid; + } + + SQLiteHelper.Instance.UpdateAll(indexes); + RemoveServerViaSubid(config, subid, false); + + return 0; + } + + /// + /// 克隆服务器 + /// + /// + /// + /// + public static int CopyServer(Config config, List indexes) + { + foreach (var it in indexes) + { + var item = LazyConfig.Instance.GetProfileItem(it.indexId); + if (item is null) + { + continue; + } + + ProfileItem profileItem = JsonUtils.DeepCopy(item); + profileItem.indexId = string.Empty; + profileItem.remarks = $"{item.remarks}-clone"; + + if (profileItem.configType == EConfigType.Custom) + { + profileItem.address = Utils.GetConfigPath(profileItem.address); + if (AddCustomServer(config, profileItem, false) == 0) + { + } + } + else + { + AddServerCommon(config, profileItem, true); + } + } + + return 0; + } + + /// + /// 设置活动服务器 + /// + /// + /// + /// + public static int SetDefaultServerIndex(Config config, string? indexId) + { + if (Utils.IsNullOrEmpty(indexId)) + { + return -1; + } + + config.indexId = indexId; + + ToJsonFile(config); + + return 0; + } + + public static int SetDefaultServer(Config config, List lstProfile) + { + if (lstProfile.Exists(t => t.indexId == config.indexId)) + { + return 0; + } + if (SQLiteHelper.Instance.Table().Where(t => t.indexId == config.indexId).Any()) + { + return 0; + } + if (lstProfile.Count > 0) + { + return SetDefaultServerIndex(config, lstProfile.Where(t => t.port > 0).FirstOrDefault()?.indexId); + } + return SetDefaultServerIndex(config, SQLiteHelper.Instance.Table().Where(t => t.port > 0).Select(t => t.indexId).FirstOrDefault()); + } + + public static ProfileItem? GetDefaultServer(Config config) + { + var item = LazyConfig.Instance.GetProfileItem(config.indexId); + if (item is null) + { + var item2 = SQLiteHelper.Instance.Table().FirstOrDefault(); + SetDefaultServerIndex(config, item2?.indexId); + return item2; + } + + return item; + } + + /// + /// 移动服务器 + /// + /// + /// + /// + /// + /// + public static int MoveServer(Config config, ref List lstProfile, int index, EMove eMove, int pos = -1) + { + int count = lstProfile.Count; + if (index < 0 || index > lstProfile.Count - 1) + { + return -1; + } + + for (int i = 0; i < lstProfile.Count; i++) + { + ProfileExHandler.Instance.SetSort(lstProfile[i].indexId, (i + 1) * 10); + } + + var sort = 0; + switch (eMove) + { + case EMove.Top: + { + if (index == 0) + { + return 0; + } + sort = ProfileExHandler.Instance.GetSort(lstProfile[0].indexId) - 1; + + break; + } + case EMove.Up: + { + if (index == 0) + { + return 0; + } + sort = ProfileExHandler.Instance.GetSort(lstProfile[index - 1].indexId) - 1; + + break; + } + + case EMove.Down: + { + if (index == count - 1) + { + return 0; + } + sort = ProfileExHandler.Instance.GetSort(lstProfile[index + 1].indexId) + 1; + + break; + } + case EMove.Bottom: + { + if (index == count - 1) + { + return 0; + } + sort = ProfileExHandler.Instance.GetSort(lstProfile[^1].indexId) + 1; + + break; + } + case EMove.Position: + sort = pos * 10 + 1; + break; + } + + ProfileExHandler.Instance.SetSort(lstProfile[index].indexId, sort); + return 0; + } + + /// + /// 添加自定义服务器 + /// + /// + /// + /// + public static int AddCustomServer(Config config, ProfileItem profileItem, bool blDelete) + { + var fileName = profileItem.address; + if (!File.Exists(fileName)) + { + return -1; + } + var ext = Path.GetExtension(fileName); + string newFileName = $"{Utils.GetGUID()}{ext}"; + //newFileName = Path.Combine(Utile.GetTempPath(), newFileName); + + try + { + File.Copy(fileName, Utils.GetConfigPath(newFileName)); + if (blDelete) + { + File.Delete(fileName); + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + return -1; + } + + profileItem.address = newFileName; + profileItem.configType = EConfigType.Custom; + if (Utils.IsNullOrEmpty(profileItem.remarks)) + { + profileItem.remarks = $"import custom@{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}"; + } + + AddServerCommon(config, profileItem, true); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static int EditCustomServer(Config config, ProfileItem profileItem) + { + if (SQLiteHelper.Instance.Update(profileItem) > 0) + { + return 0; + } + else + { + return -1; + } + + //ToJsonFile(config); + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static int AddShadowsocksServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.configType = EConfigType.Shadowsocks; + + profileItem.address = profileItem.address.TrimEx(); + profileItem.id = profileItem.id.TrimEx(); + profileItem.security = profileItem.security.TrimEx(); + + if (!LazyConfig.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.security)) + { + return -1; + } + if (profileItem.id.IsNullOrEmpty()) + { + return -1; + } + + AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static int AddSocksServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.configType = EConfigType.Socks; + + profileItem.address = profileItem.address.TrimEx(); + + AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static int AddHttpServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.configType = EConfigType.Http; + + profileItem.address = profileItem.address.TrimEx(); + + AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static int AddTrojanServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.configType = EConfigType.Trojan; + + profileItem.address = profileItem.address.TrimEx(); + profileItem.id = profileItem.id.TrimEx(); + if (Utils.IsNullOrEmpty(profileItem.streamSecurity)) + { + profileItem.streamSecurity = Global.StreamSecurity; + } + if (profileItem.id.IsNullOrEmpty()) + { + return -1; + } + + AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static int AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.configType = EConfigType.Hysteria2; + profileItem.coreType = ECoreType.sing_box; + + profileItem.address = profileItem.address.TrimEx(); + profileItem.id = profileItem.id.TrimEx(); + profileItem.path = profileItem.path.TrimEx(); + profileItem.network = string.Empty; + + if (Utils.IsNullOrEmpty(profileItem.streamSecurity)) + { + profileItem.streamSecurity = Global.StreamSecurity; + } + if (profileItem.id.IsNullOrEmpty()) + { + return -1; + } + + AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static int AddTuicServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.configType = EConfigType.Tuic; + profileItem.coreType = ECoreType.sing_box; + + profileItem.address = profileItem.address.TrimEx(); + profileItem.id = profileItem.id.TrimEx(); + profileItem.security = profileItem.security.TrimEx(); + profileItem.network = string.Empty; + + if (!Global.TuicCongestionControls.Contains(profileItem.headerType)) + { + profileItem.headerType = Global.TuicCongestionControls.FirstOrDefault()!; + } + + if (Utils.IsNullOrEmpty(profileItem.streamSecurity)) + { + profileItem.streamSecurity = Global.StreamSecurity; + } + if (Utils.IsNullOrEmpty(profileItem.alpn)) + { + profileItem.alpn = "h3"; + } + if (profileItem.id.IsNullOrEmpty()) + { + return -1; + } + + AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static int AddWireguardServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.configType = EConfigType.Wireguard; + profileItem.coreType = ECoreType.sing_box; + + profileItem.address = profileItem.address.TrimEx(); + profileItem.id = profileItem.id.TrimEx(); + profileItem.publicKey = profileItem.publicKey.TrimEx(); + profileItem.path = profileItem.path.TrimEx(); + profileItem.requestHost = profileItem.requestHost.TrimEx(); + profileItem.network = string.Empty; + if (profileItem.shortId.IsNullOrEmpty()) + { + profileItem.shortId = Global.TunMtus.FirstOrDefault(); + } + + if (profileItem.id.IsNullOrEmpty()) + { + return -1; + } + + AddServerCommon(config, profileItem, toFile); + + return 0; + } + + public static int SortServers(Config config, string subId, string colName, bool asc) + { + var lstModel = LazyConfig.Instance.ProfileItems(subId, ""); + if (lstModel.Count <= 0) + { + return -1; + } + var lstProfileExs = ProfileExHandler.Instance.ProfileExs; + var lstProfile = (from t in lstModel + join t3 in lstProfileExs on t.indexId equals t3.indexId into t3b + from t33 in t3b.DefaultIfEmpty() + select new ProfileItemModel + { + indexId = t.indexId, + configType = t.configType, + remarks = t.remarks, + address = t.address, + port = t.port, + security = t.security, + network = t.network, + streamSecurity = t.streamSecurity, + delay = t33 == null ? 0 : t33.delay, + speed = t33 == null ? 0 : t33.speed, + sort = t33 == null ? 0 : t33.sort + }).ToList(); + + Enum.TryParse(colName, true, out EServerColName name); + var propertyName = string.Empty; + switch (name) + { + case EServerColName.configType: + case EServerColName.remarks: + case EServerColName.address: + case EServerColName.port: + case EServerColName.network: + case EServerColName.streamSecurity: + propertyName = name.ToString(); + break; + + case EServerColName.delayVal: + propertyName = "delay"; + break; + + case EServerColName.speedVal: + propertyName = "speed"; + break; + + case EServerColName.subRemarks: + propertyName = "subid"; + break; + + default: + return -1; + } + + var items = lstProfile.AsQueryable(); + + if (asc) + { + lstProfile = items.OrderBy(propertyName).ToList(); + } + else + { + lstProfile = items.OrderByDescending(propertyName).ToList(); + } + for (int i = 0; i < lstProfile.Count; i++) + { + ProfileExHandler.Instance.SetSort(lstProfile[i].indexId, (i + 1) * 10); + } + if (name == EServerColName.delayVal) + { + var maxSort = lstProfile.Max(t => t.sort) + 10; + foreach (var item in lstProfile) + { + if (item.delay <= 0) + { + ProfileExHandler.Instance.SetSort(item.indexId, maxSort); + } + } + } + if (name == EServerColName.speedVal) + { + var maxSort = lstProfile.Max(t => t.sort) + 10; + foreach (var item in lstProfile) + { + if (item.speed <= 0) + { + ProfileExHandler.Instance.SetSort(item.indexId, maxSort); + } + } + } + + return 0; + } + + /// + /// Add or edit server + /// + /// + /// + /// + public static int AddVlessServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.configType = EConfigType.VLESS; + + profileItem.address = profileItem.address.TrimEx(); + profileItem.id = profileItem.id.TrimEx(); + profileItem.security = profileItem.security.TrimEx(); + profileItem.network = profileItem.network.TrimEx(); + profileItem.headerType = profileItem.headerType.TrimEx(); + profileItem.requestHost = profileItem.requestHost.TrimEx(); + profileItem.path = profileItem.path.TrimEx(); + profileItem.streamSecurity = profileItem.streamSecurity.TrimEx(); + + if (!Global.Flows.Contains(profileItem.flow)) + { + profileItem.flow = Global.Flows.First(); + } + if (profileItem.id.IsNullOrEmpty()) + { + return -1; + } + if (!Utils.IsNullOrEmpty(profileItem.security) && profileItem.security != Global.None) + { + profileItem.security = Global.None; + } + + AddServerCommon(config, profileItem, toFile); + + return 0; + } + + public static List DedupServerList(List profiles) + { + var availProfiles = LazyConfig.Instance.ProfileItems(); + int profile_nums = profiles.Count; + + profiles.RemoveAll(t => availProfiles.Exists(item => CompareProfileItem(t, item, false))); + + return profiles; + } + + public static int AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.configVersion = 2; + + if (!Utils.IsNullOrEmpty(profileItem.streamSecurity)) + { + if (profileItem.streamSecurity != Global.StreamSecurity + && profileItem.streamSecurity != Global.StreamSecurityReality) + { + profileItem.streamSecurity = string.Empty; + } + else + { + if (Utils.IsNullOrEmpty(profileItem.allowInsecure)) + { + profileItem.allowInsecure = config.coreBasicItem.defAllowInsecure.ToString().ToLower(); + } + if (Utils.IsNullOrEmpty(profileItem.fingerprint) && profileItem.streamSecurity == Global.StreamSecurityReality) + { + profileItem.fingerprint = config.coreBasicItem.defFingerprint; + } + } + } + + if (!Utils.IsNullOrEmpty(profileItem.network) && !Global.Networks.Contains(profileItem.network)) + { + profileItem.network = Global.DefaultNetwork; + } + + var maxSort = -1; + if (Utils.IsNullOrEmpty(profileItem.indexId)) + { + profileItem.indexId = Utils.GetGUID(false); + maxSort = ProfileExHandler.Instance.GetMaxSort(); + } + if (!toFile && maxSort < 0) + { + maxSort = ProfileExHandler.Instance.GetMaxSort(); + } + if (maxSort > 0) + { + ProfileExHandler.Instance.SetSort(profileItem.indexId, maxSort + 1); + } + + if (toFile) + { + //SQLiteHelper.Instance.Replace(profileItem); + if (!RunningObjects.Instance.ProfileItems.Contains(profileItem)) + { + RunningObjects.Instance.ProfileItems.Add(profileItem); + } + + } + return 0; + } + + private static bool CompareProfileItem(ProfileItem o, ProfileItem n, bool remarks) + { + if (o == null || n == null) + { + return false; + } + + return o.configType == n.configType + && o.address == n.address + && o.port == n.port + && o.id == n.id + && o.alterId == n.alterId + && o.security == n.security + && o.network == n.network + && o.headerType == n.headerType + && o.requestHost == n.requestHost + && o.path == n.path + && (o.configType == EConfigType.Trojan || o.streamSecurity == n.streamSecurity) + && o.flow == n.flow + && o.sni == n.sni + && (!remarks || o.remarks == n.remarks); + } + + private static int RemoveProfileItem(Config config, string indexId) + { + try + { + var item = LazyConfig.Instance.GetProfileItem(indexId); + if (item == null) + { + return 0; + } + if (item.configType == EConfigType.Custom) + { + File.Delete(Utils.GetConfigPath(item.address)); + } + + SQLiteHelper.Instance.Delete(item); + } + catch (Exception ex) + { + Logging.SaveLog("Remove Item", ex); + } + + return 0; + } + + #endregion Server + + #region Batch add servers + + /// + /// 批量添加服务器 + /// + /// + /// + /// + /// 成功导入的数量 + private static int AddBatchServers(Config config, string strData, string subid, bool isSub, List lstOriSub) + { + if (Utils.IsNullOrEmpty(strData)) + { + return -1; + } + + string subFilter = string.Empty; + //remove sub items + if (isSub && !Utils.IsNullOrEmpty(subid)) + { + //RemoveServerViaSubid(config, subid, isSub); + subFilter = LazyConfig.Instance.GetSubItem(subid)?.filter ?? ""; + } + + int countServers = 0; + //Check for duplicate indexId + List? lstDbIndexId = null; + List lstAdd = new(); + var arrData = strData.Split(Environment.NewLine.ToCharArray()).Where(t => !t.IsNullOrEmpty()); + if (isSub) + { + arrData = arrData.Distinct(); + } + foreach (string str in arrData) + { + //maybe sub + if (!isSub && (str.StartsWith(Global.HttpsProtocol) || str.StartsWith(Global.HttpProtocol))) + { + if (AddSubItem(config, str) == 0) + { + countServers++; + } + continue; + } + var profileItem = FmtHandler.ResolveConfig(str, out string msg); + if (profileItem is null) + { + continue; + } + + //exist sub items + if (isSub && !Utils.IsNullOrEmpty(subid)) + { + var existItem = lstOriSub?.FirstOrDefault(t => t.isSub == isSub + && config.uiItem.enableUpdateSubOnlyRemarksExist ? t.remarks == profileItem.remarks : CompareProfileItem(t, profileItem, true)); + if (existItem != null) + { + //Check for duplicate indexId + if (lstDbIndexId is null) + { + lstDbIndexId = LazyConfig.Instance.ProfileItemIndexes(""); + } + if (lstAdd.Any(t => t.indexId == existItem.indexId) + || lstDbIndexId.Any(t => t == existItem.indexId)) + { + profileItem.indexId = string.Empty; + } + else + { + profileItem.indexId = existItem.indexId; + } + } + //filter + if (!Utils.IsNullOrEmpty(subFilter)) + { + if (!Regex.IsMatch(profileItem.remarks, subFilter)) + { + continue; + } + } + } + profileItem.subid = subid; + profileItem.isSub = isSub; + + var addStatus = profileItem.configType switch + { + EConfigType.VMess => AddServer(config, profileItem, false), + EConfigType.Shadowsocks => AddShadowsocksServer(config, profileItem, false), + EConfigType.Socks => AddSocksServer(config, profileItem, false), + EConfigType.Trojan => AddTrojanServer(config, profileItem, false), + EConfigType.VLESS => AddVlessServer(config, profileItem, false), + EConfigType.Hysteria2 => AddHysteria2Server(config, profileItem, false), + EConfigType.Tuic => AddTuicServer(config, profileItem, false), + EConfigType.Wireguard => AddWireguardServer(config, profileItem, false), + _ => -1, + }; + + if (addStatus == 0) + { + countServers++; + lstAdd.Add(profileItem); + } + } + + + foreach (var item in lstAdd) + { + RunningObjects.Instance.ProfileItems.Add(item); + } + + ToJsonFile(config); + return countServers; + } + + private static int AddBatchServers4Custom(Config config, string strData, string subid, bool isSub, List lstOriSub) + { + if (Utils.IsNullOrEmpty(strData)) + { + return -1; + } + var subRemarks = LazyConfig.Instance.GetSubItem(subid)?.remarks; + + List? lstProfiles = null; + //Is sing-box array configuration + if (lstProfiles is null || lstProfiles.Count <= 0) + { + lstProfiles = SingboxFmt.ResolveFullArray(strData, subRemarks); + } + //Is v2ray array configuration + if (lstProfiles is null || lstProfiles.Count <= 0) + { + lstProfiles = V2rayFmt.ResolveFullArray(strData, subRemarks); + } + if (lstProfiles != null && lstProfiles.Count > 0) + { + //if (isSub && !Utils.IsNullOrEmpty(subid)) + //{ + // RemoveServerViaSubid(config, subid, isSub); + //} + int count = 0; + foreach (var it in lstProfiles) + { + it.subid = subid; + it.isSub = isSub; + if (AddCustomServer(config, it, true) == 0) + { + count++; + } + } + if (count > 0) + { + return count; + } + } + + ProfileItem? profileItem = null; + //Is sing-box configuration + if (profileItem is null) + { + profileItem = SingboxFmt.ResolveFull(strData, subRemarks); + } + //Is v2ray configuration + if (profileItem is null) + { + profileItem = V2rayFmt.ResolveFull(strData, subRemarks); + } + //Is Clash configuration + if (profileItem is null) + { + profileItem = ClashFmt.ResolveFull(strData, subRemarks); + } + //Is hysteria configuration + if (profileItem is null) + { + profileItem = Hysteria2Fmt.ResolveFull2(strData, subRemarks); + } + if (profileItem is null) + { + profileItem = Hysteria2Fmt.ResolveFull(strData, subRemarks); + } + //Is naiveproxy configuration + if (profileItem is null) + { + profileItem = NaiveproxyFmt.ResolveFull(strData, subRemarks); + } + if (profileItem is null || Utils.IsNullOrEmpty(profileItem.address)) + { + return -1; + } + + //if (isSub && !Utils.IsNullOrEmpty(subid)) + //{ + // RemoveServerViaSubid(config, subid, isSub); + //} + if (isSub && lstOriSub?.Count == 1) + { + profileItem.indexId = lstOriSub[0].indexId; + } + profileItem.subid = subid; + profileItem.isSub = isSub; + if (AddCustomServer(config, profileItem, true) == 0) + { + return 1; + } + else + { + return -1; + } + } + + private static int AddBatchServers4SsSIP008(Config config, string strData, string subid, bool isSub, List lstOriSub) + { + if (Utils.IsNullOrEmpty(strData)) + { + return -1; + } + + //if (isSub && !Utils.IsNullOrEmpty(subid)) + //{ + // RemoveServerViaSubid(config, subid, isSub); + //} + + var lstSsServer = ShadowsocksFmt.ResolveSip008(strData); + if (lstSsServer?.Count > 0) + { + int counter = 0; + foreach (var ssItem in lstSsServer) + { + ssItem.subid = subid; + ssItem.isSub = isSub; + if (AddShadowsocksServer(config, ssItem) == 0) + { + counter++; + } + } + ToJsonFile(config); + return counter; + } + + return -1; + } + + public static int AddBatchServers(Config config, string strData, string subid, bool isSub) + { + List? lstOriSub = null; + if (isSub && !Utils.IsNullOrEmpty(subid)) + { + lstOriSub = LazyConfig.Instance.ProfileItems(subid); + } + + var counter = 0; + if (Utils.IsBase64String(strData)) + { + counter = AddBatchServers(config, Utils.Base64Decode(strData), subid, isSub, lstOriSub); + } + if (counter < 1) + { + counter = AddBatchServers(config, strData, subid, isSub, lstOriSub); + } + if (counter < 1) + { + counter = AddBatchServers(config, Utils.Base64Decode(strData), subid, isSub, lstOriSub); + } + + if (counter < 1) + { + counter = AddBatchServers4SsSIP008(config, strData, subid, isSub, lstOriSub); + } + + //maybe other sub + if (counter < 1) + { + counter = AddBatchServers4Custom(config, strData, subid, isSub, lstOriSub); + } + + return counter; + } + + #endregion Batch add servers + + #region Sub & Group + + /// + /// add sub + /// + /// + /// + /// + public static int AddSubItem(Config config, string url) + { + //already exists + if (SQLiteHelper.Instance.Table().Any(e => e.url == url)) + { + return 0; + } + SubItem subItem = new() + { + id = string.Empty, + url = url + }; + + try + { + var uri = new Uri(url); + var queryVars = HttpUtility.ParseQueryString(uri.Query); + subItem.remarks = queryVars["remarks"] ?? "import_sub"; + } + catch (UriFormatException) + { + return 0; + } + + return AddSubItem(config, subItem); + } + + public static int AddSubItem(Config config, SubItem subItem) + { + if (Utils.IsNullOrEmpty(subItem.id)) + { + subItem.id = Utils.GetGUID(false); + + if (subItem.sort <= 0) + { + var maxSort = 0; + if (SQLiteHelper.Instance.Table().Count() > 0) + { + maxSort = SQLiteHelper.Instance.Table().Max(t => t == null ? 0 : t.sort); + } + subItem.sort = maxSort + 1; + } + } + if (SQLiteHelper.Instance.Replace(subItem) > 0) + { + return 0; + } + else + { + return -1; + } + } + + /// + /// 移除服务器 + /// + /// + /// + /// + public static int RemoveServerViaSubid(Config config, string subid, bool isSub) + { + if (Utils.IsNullOrEmpty(subid)) + { + return -1; + } + var customProfile = SQLiteHelper.Instance.Table().Where(t => t.subid == subid && t.configType == EConfigType.Custom).ToList(); + if (isSub) + { + SQLiteHelper.Instance.Execute($"delete from ProfileItem where isSub = 1 and subid = '{subid}'"); + } + else + { + SQLiteHelper.Instance.Execute($"delete from ProfileItem where subid = '{subid}'"); + } + foreach (var item in customProfile) + { + File.Delete(Utils.GetConfigPath(item.address)); + } + + return 0; + } + + public static int DeleteSubItem(Config config, string id) + { + var item = LazyConfig.Instance.GetSubItem(id); + if (item is null) + { + return 0; + } + SQLiteHelper.Instance.Delete(item); + RemoveServerViaSubid(config, id, false); + + return 0; + } + + public static int MoveToGroup(Config config, List lstProfile, string subid) + { + foreach (var item in lstProfile) + { + item.subid = subid; + } + SQLiteHelper.Instance.UpdateAll(lstProfile); + + return 0; + } + + #endregion Sub & Group + + #region Routing + + public static int SaveRoutingItem(Config config, RoutingItem item) + { + if (Utils.IsNullOrEmpty(item.id)) + { + item.id = Utils.GetGUID(false); + } + + if (SQLiteHelper.Instance.Replace(item) > 0) + { + return 0; + } + else + { + return -1; + } + } + + /// + /// AddBatchRoutingRules + /// + /// + /// + /// + public static int AddBatchRoutingRules(ref RoutingItem routingItem, string strData) + { + if (Utils.IsNullOrEmpty(strData)) + { + return -1; + } + + var lstRules = JsonUtils.Deserialize>(strData); + if (lstRules == null) + { + return -1; + } + + foreach (var item in lstRules) + { + item.id = Utils.GetGUID(false); + } + routingItem.ruleNum = lstRules.Count; + routingItem.ruleSet = JsonUtils.Serialize(lstRules, false); + + if (Utils.IsNullOrEmpty(routingItem.id)) + { + routingItem.id = Utils.GetGUID(false); + } + + if (SQLiteHelper.Instance.Replace(routingItem) > 0) + { + return 0; + } + else + { + return -1; + } + } + + /// + /// MoveRoutingRule + /// + /// + /// + /// + /// + public static int MoveRoutingRule(List rules, int index, EMove eMove, int pos = -1) + { + int count = rules.Count; + if (index < 0 || index > rules.Count - 1) + { + return -1; + } + switch (eMove) + { + case EMove.Top: + { + if (index == 0) + { + return 0; + } + var item = JsonUtils.DeepCopy(rules[index]); + rules.RemoveAt(index); + rules.Insert(0, item); + + break; + } + case EMove.Up: + { + if (index == 0) + { + return 0; + } + var item = JsonUtils.DeepCopy(rules[index]); + rules.RemoveAt(index); + rules.Insert(index - 1, item); + + break; + } + + case EMove.Down: + { + if (index == count - 1) + { + return 0; + } + var item = JsonUtils.DeepCopy(rules[index]); + rules.RemoveAt(index); + rules.Insert(index + 1, item); + + break; + } + case EMove.Bottom: + { + if (index == count - 1) + { + return 0; + } + var item = JsonUtils.DeepCopy(rules[index]); + rules.RemoveAt(index); + rules.Add(item); + + break; + } + case EMove.Position: + { + var removeItem = rules[index]; + var item = JsonUtils.DeepCopy(rules[index]); + rules.Insert(pos, item); + rules.Remove(removeItem); + break; + } + } + return 0; + } + + public static int SetDefaultRouting(Config config, RoutingItem routingItem) + { + if (SQLiteHelper.Instance.Table().Where(t => t.id == routingItem.id).Count() > 0) + { + config.routingBasicItem.routingIndexId = routingItem.id; + } + + ToJsonFile(config); + + return 0; + } + + public static RoutingItem GetDefaultRouting(Config config) + { + var item = LazyConfig.Instance.GetRoutingItem(config.routingBasicItem.routingIndexId); + if (item is null) + { + var item2 = SQLiteHelper.Instance.Table().FirstOrDefault(t => t.locked == false); + SetDefaultRouting(config, item2); + return item2; + } + + return item; + } + + public static int InitBuiltinRouting(Config config, bool blImportAdvancedRules = false) + { + var ver = "V3-"; + var items = LazyConfig.Instance.RoutingItems(); + if (blImportAdvancedRules || items.Where(t => t.remarks.StartsWith(ver)).ToList().Count <= 0) + { + var maxSort = items.Count; + //Bypass the mainland + var item2 = new RoutingItem() + { + remarks = $"{ver}绕过大陆(Whitelist)", + url = string.Empty, + sort = maxSort + 1, + }; + AddBatchRoutingRules(ref item2, Utils.GetEmbedText(Global.CustomRoutingFileName + "white")); + + //Blacklist + var item3 = new RoutingItem() + { + remarks = $"{ver}黑名单(Blacklist)", + url = string.Empty, + sort = maxSort + 2, + }; + AddBatchRoutingRules(ref item3, Utils.GetEmbedText(Global.CustomRoutingFileName + "black")); + + //Global + var item1 = new RoutingItem() + { + remarks = $"{ver}全局(Global)", + url = string.Empty, + sort = maxSort + 3, + }; + AddBatchRoutingRules(ref item1, Utils.GetEmbedText(Global.CustomRoutingFileName + "global")); + + if (!blImportAdvancedRules) + { + SetDefaultRouting(config, item2); + } + } + + if (GetLockedRoutingItem(config) == null) + { + var item1 = new RoutingItem() + { + remarks = "locked", + url = string.Empty, + locked = true, + }; + AddBatchRoutingRules(ref item1, Utils.GetEmbedText(Global.CustomRoutingFileName + "locked")); + } + return 0; + } + + public static RoutingItem GetLockedRoutingItem(Config config) + { + return SQLiteHelper.Instance.Table().FirstOrDefault(it => it.locked == true); + } + + public static void RemoveRoutingItem(RoutingItem routingItem) + { + SQLiteHelper.Instance.Delete(routingItem); + } + + #endregion Routing + + #region DNS + + public static int InitBuiltinDNS(Config config) + { + var items = LazyConfig.Instance.DNSItems(); + if (items.Count <= 0) + { + var item = new DNSItem() + { + remarks = "V2ray", + coreType = ECoreType.Xray, + }; + SaveDNSItems(config, item); + + var item2 = new DNSItem() + { + remarks = "sing-box", + coreType = ECoreType.sing_box, + }; + SaveDNSItems(config, item2); + } + + return 0; + } + + public static int SaveDNSItems(Config config, DNSItem item) + { + if (Utils.IsNullOrEmpty(item.id)) + { + item.id = Utils.GetGUID(false); + } + + if (SQLiteHelper.Instance.Replace(item) > 0) + { + return 0; + } + else + { + return -1; + } + } + + #endregion DNS + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigClash.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigClash.cs new file mode 100644 index 00000000..1ea4c5d3 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigClash.cs @@ -0,0 +1,271 @@ +using System.IO; +using v2rayN.Common; +using v2rayN.Enums; +using v2rayN.Models; +using v2rayMiniConsole.Resx; + +namespace v2rayN.Handler.CoreConfig +{ + /// + /// Core configuration file processing class + /// + internal class CoreConfigClash + { + private Config _config; + + public CoreConfigClash(Config config) + { + _config = config; + } + + /// + /// 生成配置文件 + /// + /// + /// + /// + /// + 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 = "!"; + string tagYamlStr2 = "__strn__"; + string tagYamlStr3 = "!!str"; + var txtFile = File.ReadAllText(addressFileName); + txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2); + + var fileContent = YamlUtils.FromYaml>(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>(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 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>(txtFile); + if (mixinContent == null) + { + return; + } + foreach (var item in mixinContent) + { + if (!_config.tunModeItem.enableTun && item.Key == "tun") + { + continue; + } + + if (item.Key.StartsWith("prepend-") + || item.Key.StartsWith("append-") + || item.Key.StartsWith("removed-")) + { + ModifyContentMerge(fileContent, item.Key, item.Value); + } + else + { + fileContent[item.Key] = item.Value; + } + } + return; + } + + private void ModifyContentMerge(Dictionary fileContent, string key, object value) + { + 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)fileContent[key]; + var lstValue = (List)value; + + if (blRemoved) + { + foreach (var item in lstValue) + { + lstOri.RemoveAll(t => t.ToString().StartsWith(item.ToString())); + } + return; + } + + if (blPrepend) + { + lstValue.Reverse(); + 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; + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigHandler.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigHandler.cs new file mode 100644 index 00000000..d2ee786b --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigHandler.cs @@ -0,0 +1,183 @@ +using System.IO; +using v2rayN.Enums; +using v2rayN.Models; +using v2rayMiniConsole.Resx; + +namespace v2rayN.Handler.CoreConfig +{ + /// + /// Core configuration file processing class + /// + 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 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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigSingbox.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigSingbox.cs new file mode 100644 index 00000000..bdc6c31c --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigSingbox.cs @@ -0,0 +1,1160 @@ +using System.Data; +using System.Net; +using System.Net.NetworkInformation; +using v2rayN.Enums; +using v2rayN.Models; +using v2rayMiniConsole.Resx; + +namespace v2rayN.Handler.CoreConfig +{ + internal class CoreConfigSingbox + { + private Config _config; + + public CoreConfigSingbox(Config config) + { + _config = config; + } + + public int GenerateClientConfigContent(ProfileItem node, out SingboxConfig? singboxConfig, out string msg) + { + singboxConfig = null; + try + { + if (node == null + || node.port <= 0) + { + msg = ResUI.CheckServerSettings; + return -1; + } + if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.splithttp)) + { + msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + return -1; + } + + msg = ResUI.InitialConfiguration; + + string result = Utils.GetEmbedText(Global.SingboxSampleClient); + if (Utils.IsNullOrEmpty(result)) + { + msg = ResUI.FailedGetDefaultConfiguration; + return -1; + } + + singboxConfig = JsonUtils.Deserialize(result); + if (singboxConfig == null) + { + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + + GenLog(singboxConfig); + + GenInbounds(singboxConfig); + + GenOutbound(node, singboxConfig.outbounds[0]); + + GenMoreOutbounds(node, singboxConfig); + + GenRouting(singboxConfig); + + GenDns(node, singboxConfig); + + GenExperimental(singboxConfig); + + ConvertGeo2Ruleset(singboxConfig); + + msg = string.Format(ResUI.SuccessfulConfiguration, ""); + } + catch (Exception ex) + { + Logging.SaveLog("GenerateClientConfig4Singbox", ex); + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + return 0; + } + + #region private gen function + + private int GenLog(SingboxConfig singboxConfig) + { + try + { + switch (_config.coreBasicItem.loglevel) + { + case "debug": + case "info": + case "error": + singboxConfig.log.level = _config.coreBasicItem.loglevel; + break; + + case "warning": + singboxConfig.log.level = "warn"; + break; + + default: + break; + } + if (_config.coreBasicItem.loglevel == Global.None) + { + singboxConfig.log.disabled = true; + } + if (_config.coreBasicItem.logEnabled) + { + var dtNow = DateTime.Now; + singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt"); + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenInbounds(SingboxConfig singboxConfig) + { + try + { + var listen = "::"; + singboxConfig.inbounds = []; + + if (!_config.tunModeItem.enableTun + || (_config.tunModeItem.enableTun && _config.tunModeItem.enableExInbound && _config.runningCoreType == ECoreType.sing_box)) + { + var inbound = new Inbound4Sbox() + { + type = EInboundProtocol.socks.ToString(), + tag = EInboundProtocol.socks.ToString(), + listen = Global.Loopback, + }; + singboxConfig.inbounds.Add(inbound); + + inbound.listen_port = LazyConfig.Instance.GetLocalPort(EInboundProtocol.socks); + inbound.sniff = _config.inbound[0].sniffingEnabled; + inbound.sniff_override_destination = _config.inbound[0].routeOnly ? false : _config.inbound[0].sniffingEnabled; + inbound.domain_strategy = Utils.IsNullOrEmpty(_config.routingBasicItem.domainStrategy4Singbox) ? null : _config.routingBasicItem.domainStrategy4Singbox; + + if (_config.routingBasicItem.enableRoutingAdvanced) + { + var routing = ConfigHandler.GetDefaultRouting(_config); + if (!Utils.IsNullOrEmpty(routing.domainStrategy4Singbox)) + { + inbound.domain_strategy = routing.domainStrategy4Singbox; + } + } + + //http + var inbound2 = GetInbound(inbound, EInboundProtocol.http, false); + singboxConfig.inbounds.Add(inbound2); + + if (_config.inbound[0].allowLANConn) + { + if (_config.inbound[0].newPort4LAN) + { + var inbound3 = GetInbound(inbound, EInboundProtocol.socks2, true); + inbound3.listen = listen; + singboxConfig.inbounds.Add(inbound3); + + var inbound4 = GetInbound(inbound, EInboundProtocol.http2, false); + inbound4.listen = listen; + singboxConfig.inbounds.Add(inbound4); + + //auth + if (!Utils.IsNullOrEmpty(_config.inbound[0].user) && !Utils.IsNullOrEmpty(_config.inbound[0].pass)) + { + inbound3.users = new() { new() { username = _config.inbound[0].user, password = _config.inbound[0].pass } }; + inbound4.users = new() { new() { username = _config.inbound[0].user, password = _config.inbound[0].pass } }; + } + } + else + { + inbound.listen = listen; + inbound2.listen = listen; + } + } + } + + if (_config.tunModeItem.enableTun) + { + if (_config.tunModeItem.mtu <= 0) + { + _config.tunModeItem.mtu = Utils.ToInt(Global.TunMtus[0]); + } + if (Utils.IsNullOrEmpty(_config.tunModeItem.stack)) + { + _config.tunModeItem.stack = Global.TunStacks[0]; + } + + var tunInbound = JsonUtils.Deserialize(Utils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { }; + tunInbound.mtu = _config.tunModeItem.mtu; + tunInbound.strict_route = _config.tunModeItem.strictRoute; + tunInbound.stack = _config.tunModeItem.stack; + tunInbound.sniff = _config.inbound[0].sniffingEnabled; + //tunInbound.sniff_override_destination = _config.inbound[0].routeOnly ? false : _config.inbound[0].sniffingEnabled; + if (_config.tunModeItem.enableIPv6Address == false) + { + tunInbound.inet6_address = null; + } + + singboxConfig.inbounds.Add(tunInbound); + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks) + { + var inbound = JsonUtils.DeepCopy(inItem); + inbound.tag = protocol.ToString(); + inbound.listen_port = inItem.listen_port + (int)protocol; + inbound.type = bSocks ? EInboundProtocol.socks.ToString() : EInboundProtocol.http.ToString(); + return inbound; + } + + private int GenOutbound(ProfileItem node, Outbound4Sbox outbound) + { + try + { + outbound.server = node.address; + outbound.server_port = node.port; + outbound.type = Global.ProtocolTypes[node.configType]; + + switch (node.configType) + { + case EConfigType.VMess: + { + outbound.uuid = node.id; + outbound.alter_id = node.alterId; + if (Global.VmessSecurities.Contains(node.security)) + { + outbound.security = node.security; + } + else + { + outbound.security = Global.DefaultSecurity; + } + + GenOutboundMux(node, outbound); + break; + } + case EConfigType.Shadowsocks: + { + outbound.method = LazyConfig.Instance.GetShadowsocksSecurities(node).Contains(node.security) ? node.security : Global.None; + outbound.password = node.id; + + GenOutboundMux(node, outbound); + break; + } + case EConfigType.Socks: + { + outbound.version = "5"; + if (!Utils.IsNullOrEmpty(node.security) + && !Utils.IsNullOrEmpty(node.id)) + { + outbound.username = node.security; + outbound.password = node.id; + } + break; + } + case EConfigType.Http: + { + if (!Utils.IsNullOrEmpty(node.security) + && !Utils.IsNullOrEmpty(node.id)) + { + outbound.username = node.security; + outbound.password = node.id; + } + break; + } + case EConfigType.VLESS: + { + outbound.uuid = node.id; + + outbound.packet_encoding = "xudp"; + + if (Utils.IsNullOrEmpty(node.flow)) + { + GenOutboundMux(node, outbound); + } + else + { + outbound.flow = node.flow; + } + break; + } + case EConfigType.Trojan: + { + outbound.password = node.id; + + GenOutboundMux(node, outbound); + break; + } + case EConfigType.Hysteria2: + { + outbound.password = node.id; + + if (!Utils.IsNullOrEmpty(node.path)) + { + outbound.obfs = new() + { + type = "salamander", + password = node.path.TrimEx(), + }; + } + + outbound.up_mbps = _config.hysteriaItem.up_mbps > 0 ? _config.hysteriaItem.up_mbps : null; + outbound.down_mbps = _config.hysteriaItem.down_mbps > 0 ? _config.hysteriaItem.down_mbps : null; + break; + } + case EConfigType.Tuic: + { + outbound.uuid = node.id; + outbound.password = node.security; + outbound.congestion_control = node.headerType; + break; + } + case EConfigType.Wireguard: + { + outbound.private_key = node.id; + outbound.peer_public_key = node.publicKey; + outbound.reserved = Utils.String2List(node.path).Select(int.Parse).ToArray(); + outbound.local_address = [.. Utils.String2List(node.requestHost)]; + outbound.mtu = Utils.ToInt(node.shortId.IsNullOrEmpty() ? Global.TunMtus.FirstOrDefault() : node.shortId); + break; + } + } + + GenOutboundTls(node, outbound); + + GenOutboundTransport(node, outbound); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenOutboundMux(ProfileItem node, Outbound4Sbox outbound) + { + try + { + if (_config.coreBasicItem.muxEnabled && !Utils.IsNullOrEmpty(_config.mux4SboxItem.protocol)) + { + var mux = new Multiplex4Sbox() + { + enabled = true, + protocol = _config.mux4SboxItem.protocol, + max_connections = _config.mux4SboxItem.max_connections, + }; + outbound.multiplex = mux; + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenOutboundTls(ProfileItem node, Outbound4Sbox outbound) + { + try + { + if (node.streamSecurity == Global.StreamSecurityReality || node.streamSecurity == Global.StreamSecurity) + { + var server_name = string.Empty; + if (!Utils.IsNullOrEmpty(node.sni)) + { + server_name = node.sni; + } + else if (!Utils.IsNullOrEmpty(node.requestHost)) + { + server_name = Utils.String2List(node.requestHost)[0]; + } + var tls = new Tls4Sbox() + { + enabled = true, + server_name = server_name, + insecure = Utils.ToBool(node.allowInsecure.IsNullOrEmpty() ? _config.coreBasicItem.defAllowInsecure.ToString().ToLower() : node.allowInsecure), + alpn = node.GetAlpn(), + }; + if (!Utils.IsNullOrEmpty(node.fingerprint)) + { + tls.utls = new Utls4Sbox() + { + enabled = true, + fingerprint = node.fingerprint.IsNullOrEmpty() ? _config.coreBasicItem.defFingerprint : node.fingerprint + }; + } + if (node.streamSecurity == Global.StreamSecurityReality) + { + tls.reality = new Reality4Sbox() + { + enabled = true, + public_key = node.publicKey, + short_id = node.shortId + }; + tls.insecure = false; + } + outbound.tls = tls; + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenOutboundTransport(ProfileItem node, Outbound4Sbox outbound) + { + try + { + var transport = new Transport4Sbox(); + + switch (node.GetNetwork()) + { + case nameof(ETransport.h2): + transport.type = nameof(ETransport.http); + transport.host = Utils.IsNullOrEmpty(node.requestHost) ? null : Utils.String2List(node.requestHost); + transport.path = Utils.IsNullOrEmpty(node.path) ? null : node.path; + break; + + case nameof(ETransport.tcp): //http + if (node.headerType == Global.TcpHeaderHttp) + { + if (node.configType == EConfigType.Shadowsocks) + { + outbound.plugin = "obfs-local"; + outbound.plugin_opts = $"obfs=http;obfs-host={node.requestHost};"; + } + else + { + transport.type = nameof(ETransport.http); + transport.host = Utils.IsNullOrEmpty(node.requestHost) ? null : Utils.String2List(node.requestHost); + transport.path = Utils.IsNullOrEmpty(node.path) ? null : node.path; + } + } + break; + + case nameof(ETransport.ws): + transport.type = nameof(ETransport.ws); + transport.path = Utils.IsNullOrEmpty(node.path) ? null : node.path; + if (!Utils.IsNullOrEmpty(node.requestHost)) + { + transport.headers = new() + { + Host = node.requestHost + }; + } + break; + + case nameof(ETransport.httpupgrade): + transport.type = nameof(ETransport.httpupgrade); + transport.path = Utils.IsNullOrEmpty(node.path) ? null : node.path; + transport.host = Utils.IsNullOrEmpty(node.requestHost) ? null : node.requestHost; + + break; + + case nameof(ETransport.quic): + transport.type = nameof(ETransport.quic); + break; + + case nameof(ETransport.grpc): + transport.type = nameof(ETransport.grpc); + transport.service_name = node.path; + transport.idle_timeout = _config.grpcItem.idle_timeout.ToString("##s"); + transport.ping_timeout = _config.grpcItem.health_check_timeout.ToString("##s"); + transport.permit_without_stream = _config.grpcItem.permit_without_stream; + break; + + default: + break; + } + if (transport.type != null) + { + outbound.transport = transport; + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig) + { + if (node.subid.IsNullOrEmpty()) + { + return 0; + } + try + { + var subItem = LazyConfig.Instance.GetSubItem(node.subid); + if (subItem is null) + { + return 0; + } + + //current proxy + var outbound = singboxConfig.outbounds[0]; + var txtOutbound = Utils.GetEmbedText(Global.SingboxSampleOutbound); + + //Previous proxy + var prevNode = LazyConfig.Instance.GetProfileItemViaRemarks(subItem.prevProfile!); + if (prevNode is not null + && prevNode.configType != EConfigType.Custom) + { + var prevOutbound = JsonUtils.Deserialize(txtOutbound); + GenOutbound(prevNode, prevOutbound); + prevOutbound.tag = $"{Global.ProxyTag}2"; + singboxConfig.outbounds.Add(prevOutbound); + + outbound.detour = prevOutbound.tag; + } + + //Next proxy + var nextNode = LazyConfig.Instance.GetProfileItemViaRemarks(subItem.nextProfile!); + if (nextNode is not null + && nextNode.configType != EConfigType.Custom) + { + var nextOutbound = JsonUtils.Deserialize(txtOutbound); + GenOutbound(nextNode, nextOutbound); + nextOutbound.tag = Global.ProxyTag; + singboxConfig.outbounds.Insert(0, nextOutbound); + + outbound.tag = $"{Global.ProxyTag}1"; + nextOutbound.detour = outbound.tag; + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + + return 0; + } + + private int GenRouting(SingboxConfig singboxConfig) + { + try + { + var dnsOutbound = "dns_out"; + if (!_config.inbound[0].sniffingEnabled) + { + singboxConfig.route.rules.Add(new() + { + port = [53], + network = ["udp"], + outbound = dnsOutbound + }); + } + + if (_config.tunModeItem.enableTun) + { + singboxConfig.route.auto_detect_interface = true; + + var tunRules = JsonUtils.Deserialize>(Utils.GetEmbedText(Global.TunSingboxRulesFileName)); + if (tunRules != null) + { + singboxConfig.route.rules.AddRange(tunRules); + } + + GenRoutingDirectExe(out List lstDnsExe, out List lstDirectExe); + singboxConfig.route.rules.Add(new() + { + port = new() { 53 }, + outbound = dnsOutbound, + process_name = lstDnsExe + }); + + singboxConfig.route.rules.Add(new() + { + outbound = Global.DirectTag, + process_name = lstDirectExe + }); + } + + if (_config.routingBasicItem.enableRoutingAdvanced) + { + var routing = ConfigHandler.GetDefaultRouting(_config); + if (routing != null) + { + var rules = JsonUtils.Deserialize>(routing.ruleSet); + foreach (var item in rules!) + { + if (item.enabled) + { + GenRoutingUserRule(item, singboxConfig.route.rules); + } + } + } + } + else + { + var lockedItem = ConfigHandler.GetLockedRoutingItem(_config); + if (lockedItem != null) + { + var rules = JsonUtils.Deserialize>(lockedItem.ruleSet); + foreach (var item in rules!) + { + GenRoutingUserRule(item, singboxConfig.route.rules); + } + } + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private void GenRoutingDirectExe(out List lstDnsExe, out List lstDirectExe) + { + lstDnsExe = new(); + lstDirectExe = new(); + var coreInfo = LazyConfig.Instance.GetCoreInfo(); + foreach (var it in coreInfo) + { + if (it.coreType == ECoreType.v2rayN) + { + continue; + } + foreach (var it2 in it.coreExes) + { + if (!lstDnsExe.Contains(it2) && it.coreType != ECoreType.sing_box) + { + lstDnsExe.Add($"{it2}.exe"); + } + + if (!lstDirectExe.Contains(it2)) + { + lstDirectExe.Add($"{it2}.exe"); + } + } + } + } + + private int GenRoutingUserRule(RulesItem item, List rules) + { + try + { + if (item == null) + { + return 0; + } + + var rule = new Rule4Sbox() + { + outbound = item.outboundTag, + }; + + if (!Utils.IsNullOrEmpty(item.port)) + { + if (item.port.Contains("-")) + { + rule.port_range = new List { item.port.Replace("-", ":") }; + } + else + { + rule.port = new List { Utils.ToInt(item.port) }; + } + } + if (!Utils.IsNullOrEmpty(item.network)) + { + rule.network = Utils.String2List(item.network); + } + if (item.protocol?.Count > 0) + { + rule.protocol = item.protocol; + } + if (item.inboundTag?.Count >= 0) + { + rule.inbound = item.inboundTag; + } + var rule1 = JsonUtils.DeepCopy(rule); + var rule2 = JsonUtils.DeepCopy(rule); + var rule3 = JsonUtils.DeepCopy(rule); + + var hasDomainIp = false; + if (item.domain?.Count > 0) + { + var countDomain = 0; + foreach (var it in item.domain) + { + if (ParseV2Domain(it, rule1)) countDomain++; + } + if (countDomain > 0) + { + rules.Add(rule1); + hasDomainIp = true; + } + } + + if (item.ip?.Count > 0) + { + var countIp = 0; + foreach (var it in item.ip) + { + if (ParseV2Address(it, rule2)) countIp++; + } + if (countIp > 0) + { + rules.Add(rule2); + hasDomainIp = true; + } + } + + if (_config.tunModeItem.enableTun && item.process?.Count > 0) + { + rule3.process_name = item.process; + rules.Add(rule3); + hasDomainIp = true; + } + + if (!hasDomainIp + && (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null)) + { + rules.Add(rule); + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private bool ParseV2Domain(string domain, Rule4Sbox rule) + { + if (domain.StartsWith("#") || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:")) + { + return false; + } + else if (domain.StartsWith("geosite:")) + { + rule.geosite ??= []; + rule.geosite?.Add(domain.Substring(8)); + } + else if (domain.StartsWith("regexp:")) + { + rule.domain_regex ??= []; + rule.domain_regex?.Add(domain.Replace(Global.RoutingRuleComma, ",").Substring(7)); + } + else if (domain.StartsWith("domain:")) + { + rule.domain ??= []; + rule.domain_suffix ??= []; + rule.domain?.Add(domain.Substring(7)); + rule.domain_suffix?.Add("." + domain.Substring(7)); + } + else if (domain.StartsWith("full:")) + { + rule.domain ??= []; + rule.domain?.Add(domain.Substring(5)); + } + else if (domain.StartsWith("keyword:")) + { + rule.domain_keyword ??= []; + rule.domain_keyword?.Add(domain.Substring(8)); + } + else + { + rule.domain_keyword ??= []; + rule.domain_keyword?.Add(domain); + } + return true; + } + + private bool ParseV2Address(string address, Rule4Sbox rule) + { + if (address.StartsWith("ext:") || address.StartsWith("ext-ip:")) + { + return false; + } + else if (address.StartsWith("geoip:!")) + { + return false; + } + else if (address.Equals("geoip:private")) + { + rule.ip_is_private = true; + } + else if (address.StartsWith("geoip:")) + { + if (rule.geoip is null) { rule.geoip = new(); } + rule.geoip?.Add(address.Substring(6)); + } + else + { + if (rule.ip_cidr is null) { rule.ip_cidr = new(); } + rule.ip_cidr?.Add(address); + } + return true; + } + + private int GenDns(ProfileItem node, SingboxConfig singboxConfig) + { + try + { + var item = LazyConfig.Instance.GetDNSItem(ECoreType.sing_box); + var strDNS = string.Empty; + if (_config.tunModeItem.enableTun) + { + strDNS = Utils.IsNullOrEmpty(item?.tunDNS) ? Utils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.tunDNS; + } + else + { + strDNS = Utils.IsNullOrEmpty(item?.normalDNS) ? Utils.GetEmbedText(Global.DNSSingboxNormalFileName) : item?.normalDNS; + } + + var dns4Sbox = JsonUtils.Deserialize(strDNS); + if (dns4Sbox is null) + { + return 0; + } + singboxConfig.dns = dns4Sbox; + + GenDnsDomains(node, singboxConfig); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenDnsDomains(ProfileItem? node, SingboxConfig singboxConfig) + { + var dns4Sbox = singboxConfig.dns ?? new(); + dns4Sbox.servers ??= []; + dns4Sbox.rules ??= []; + + var tag = "local_local"; + dns4Sbox.servers.Add(new() + { + tag = tag, + address = "223.5.5.5", + detour = Global.DirectTag, + //strategy = strategy + }); + + var lstDomain = singboxConfig.outbounds + .Where(t => !Utils.IsNullOrEmpty(t.server) && Utils.IsDomain(t.server)) + .Select(t => t.server) + .ToList(); + if (lstDomain != null && lstDomain.Count > 0) + { + dns4Sbox.rules.Insert(0, new() + { + server = tag, + domain = lstDomain + }); + } + + //Tun2SocksAddress + if (_config.tunModeItem.enableTun && node?.configType == EConfigType.Socks && Utils.IsDomain(node?.sni)) + { + dns4Sbox.rules.Insert(0, new() + { + server = tag, + domain = [node?.sni] + }); + } + + singboxConfig.dns = dns4Sbox; + return 0; + } + + private int GenExperimental(SingboxConfig singboxConfig) + { + if (_config.guiItem.enableStatistics) + { + singboxConfig.experimental ??= new Experimental4Sbox(); + singboxConfig.experimental.clash_api = new Clash_Api4Sbox() + { + external_controller = $"{Global.Loopback}:{LazyConfig.Instance.StatePort2}", + }; + } + + if (_config.coreBasicItem.enableCacheFile4Sbox) + { + singboxConfig.experimental ??= new Experimental4Sbox(); + singboxConfig.experimental.cache_file = new CacheFile4Sbox() + { + enabled = true + }; + } + + return 0; + } + + private int ConvertGeo2Ruleset(SingboxConfig singboxConfig) + { + static void AddRuleSets(List ruleSets, List? rule_set) + { + if (rule_set != null) ruleSets.AddRange(rule_set); + } + var geosite = "geosite"; + var geoip = "geoip"; + var ruleSets = new List(); + + //convert route geosite & geoip to ruleset + foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) + { + rule.rule_set = rule?.geosite?.Select(t => $"{geosite}-{t}").ToList(); + rule.geosite = null; + AddRuleSets(ruleSets, rule.rule_set); + } + foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) + { + rule.rule_set = rule?.geoip?.Select(t => $"{geoip}-{t}").ToList(); + rule.geoip = null; + AddRuleSets(ruleSets, rule.rule_set); + } + + //convert dns geosite & geoip to ruleset + foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) + { + rule.rule_set = rule?.geosite?.Select(t => $"{geosite}-{t}").ToList(); + rule.geosite = null; + } + foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) + { + rule.rule_set = rule?.geoip?.Select(t => $"{geoip}-{t}").ToList(); + rule.geoip = null; + } + foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) + { + AddRuleSets(ruleSets, dnsRule.rule_set); + } + //rules in rules + foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? []) + { + foreach (var item2 in item ?? []) + { + AddRuleSets(ruleSets, item2.rule_set); + } + } + + //load custom ruleset file + List customRulesets = []; + if (_config.routingBasicItem.enableRoutingAdvanced) + { + var routing = ConfigHandler.GetDefaultRouting(_config); + if (!Utils.IsNullOrEmpty(routing.customRulesetPath4Singbox)) + { + var result = Utils.LoadResource(routing.customRulesetPath4Singbox); + if (!Utils.IsNullOrEmpty(result)) + { + customRulesets = (JsonUtils.Deserialize>(result) ?? []) + .Where(t => t.tag != null) + .Where(t => t.type != null) + .Where(t => t.format != null) + .ToList(); + } + } + } + + //Add ruleset srs + singboxConfig.route.rule_set = []; + foreach (var item in new HashSet(ruleSets)) + { + if (Utils.IsNullOrEmpty(item)) { continue; } + var customRuleset = customRulesets.FirstOrDefault(t => t.tag != null && t.tag.Equals(item)); + if (customRuleset != null) + { + singboxConfig.route.rule_set.Add(customRuleset); + } + else + { + singboxConfig.route.rule_set.Add(new() + { + type = "remote", + format = "binary", + tag = item, + url = item.StartsWith(geosite) ? + string.Format(Global.SingboxRulesetUrlGeosite, item) : + string.Format(Global.SingboxRulesetUrlGeoip, item.Replace($"{geoip}-", "")), + download_detour = Global.ProxyTag + }); + } + } + + return 0; + } + + #endregion private gen function + + #region Gen speedtest config + + public int GenerateClientSpeedtestConfig(List selecteds, out SingboxConfig? singboxConfig, out string msg) + { + singboxConfig = null; + try + { + if (_config == null) + { + msg = ResUI.CheckServerSettings; + return -1; + } + + msg = ResUI.InitialConfiguration; + + string result = Utils.GetEmbedText(Global.SingboxSampleClient); + string txtOutbound = Utils.GetEmbedText(Global.SingboxSampleOutbound); + if (Utils.IsNullOrEmpty(result) || txtOutbound.IsNullOrEmpty()) + { + msg = ResUI.FailedGetDefaultConfiguration; + return -1; + } + + singboxConfig = JsonUtils.Deserialize(result); + if (singboxConfig == null) + { + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + List lstIpEndPoints = new(); + List lstTcpConns = new(); + try + { + lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); + lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); + lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + + GenLog(singboxConfig); + //GenDns(new(), singboxConfig); + singboxConfig.inbounds.Clear(); // Remove "proxy" service for speedtest, avoiding port conflicts. + singboxConfig.outbounds.RemoveAt(0); + + int httpPort = LazyConfig.Instance.GetLocalPort(EInboundProtocol.speedtest); + + foreach (var it in selecteds) + { + if (it.configType == EConfigType.Custom) + { + continue; + } + if (it.port <= 0) + { + continue; + } + if (it.configType is EConfigType.VMess or EConfigType.VLESS) + { + var item2 = LazyConfig.Instance.GetProfileItem(it.indexId); + if (item2 is null || Utils.IsNullOrEmpty(item2.id) || !Utils.IsGuidByParse(item2.id)) + { + continue; + } + } + + //find unused port + var port = httpPort; + for (int k = httpPort; k < Global.MaxPort; k++) + { + if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0) + { + continue; + } + if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0) + { + continue; + } + //found + port = k; + httpPort = port + 1; + break; + } + + //Port In Used + if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0) + { + continue; + } + it.port = port; + it.allowTest = true; + + //inbound + Inbound4Sbox inbound = new() + { + listen = Global.Loopback, + listen_port = port, + type = EInboundProtocol.http.ToString(), + }; + inbound.tag = inbound.type + inbound.listen_port.ToString(); + singboxConfig.inbounds.Add(inbound); + + //outbound + var item = LazyConfig.Instance.GetProfileItem(it.indexId); + if (item is null) + { + continue; + } + if (item.configType == EConfigType.Shadowsocks + && !Global.SsSecuritiesInSingbox.Contains(item.security)) + { + continue; + } + if (item.configType == EConfigType.VLESS + && !Global.Flows.Contains(item.flow)) + { + continue; + } + + var outbound = JsonUtils.Deserialize(txtOutbound); + GenOutbound(item, outbound); + outbound.tag = Global.ProxyTag + inbound.listen_port.ToString(); + singboxConfig.outbounds.Add(outbound); + + //rule + Rule4Sbox rule = new() + { + inbound = new List { inbound.tag }, + outbound = outbound.tag + }; + singboxConfig.route.rules.Add(rule); + } + + GenDnsDomains(null, singboxConfig); + //var dnsServer = singboxConfig.dns?.servers.FirstOrDefault(); + //if (dnsServer != null) + //{ + // dnsServer.detour = singboxConfig.route.rules.LastOrDefault()?.outbound; + //} + //var dnsRule = singboxConfig.dns?.rules.Where(t => t.outbound != null).FirstOrDefault(); + //if (dnsRule != null) + //{ + // singboxConfig.dns.rules = []; + // singboxConfig.dns.rules.Add(dnsRule); + //} + + //msg = string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); + return 0; + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + } + + #endregion Gen speedtest config + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigV2ray.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigV2ray.cs new file mode 100644 index 00000000..f7543bb1 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreConfig/CoreConfigV2ray.cs @@ -0,0 +1,1126 @@ +using System.Net; +using System.Net.NetworkInformation; +using System.Text.Json.Nodes; +using v2rayN.Enums; +using v2rayN.Models; +using v2rayMiniConsole.Resx; +using v2rayMiniConsole; + +namespace v2rayN.Handler.CoreConfig +{ + internal class CoreConfigV2ray + { + private Config _config; + + public CoreConfigV2ray(Config config) + { + _config = config; + } + + public int GenerateClientConfigContent(ProfileItem node, out V2rayConfig? v2rayConfig, out string msg) + { + v2rayConfig = null; + try + { + if (node == null + || node.port <= 0) + { + msg = ResUI.CheckServerSettings; + return -1; + } + + msg = ResUI.InitialConfiguration; + + string result = Utils.GetEmbedText(Global.V2raySampleClient); + if (Utils.IsNullOrEmpty(result)) + { + msg = ResUI.FailedGetDefaultConfiguration; + return -1; + } + + v2rayConfig = JsonUtils.Deserialize(result); + if (v2rayConfig == null) + { + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + + GenLog(v2rayConfig); + + GenInbounds(v2rayConfig); + + GenRouting(v2rayConfig); + + GenOutbound(node, v2rayConfig.outbounds[0]); + + GenMoreOutbounds(node, v2rayConfig); + + GenDns(node, v2rayConfig); + + GenStatistic(v2rayConfig); + + msg = string.Format(ResUI.SuccessfulConfiguration, ""); + } + catch (Exception ex) + { + Logging.SaveLog("GenerateClientConfig4V2ray", ex); + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + return 0; + } + + #region private gen function + + private int GenLog(V2rayConfig v2rayConfig) + { + try + { + if (_config.coreBasicItem.logEnabled) + { + var dtNow = DateTime.Now; + v2rayConfig.log.loglevel = _config.coreBasicItem.loglevel; + v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt"); + v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt"); + } + else + { + v2rayConfig.log.loglevel = _config.coreBasicItem.loglevel; + v2rayConfig.log.access = ""; + v2rayConfig.log.error = ""; + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenInbounds(V2rayConfig v2rayConfig) + { + try + { + var listen = "0.0.0.0"; + v2rayConfig.inbounds = []; + + Inbounds4Ray? inbound = GetInbound(_config.inbound[0], EInboundProtocol.socks, true); + v2rayConfig.inbounds.Add(inbound); + + //http + Inbounds4Ray? inbound2 = GetInbound(_config.inbound[0], EInboundProtocol.http, false); + v2rayConfig.inbounds.Add(inbound2); + + if (_config.inbound[0].allowLANConn) + { + if (_config.inbound[0].newPort4LAN) + { + var inbound3 = GetInbound(_config.inbound[0], EInboundProtocol.socks2, true); + inbound3.listen = listen; + v2rayConfig.inbounds.Add(inbound3); + + var inbound4 = GetInbound(_config.inbound[0], EInboundProtocol.http2, false); + inbound4.listen = listen; + v2rayConfig.inbounds.Add(inbound4); + + //auth + if (!Utils.IsNullOrEmpty(_config.inbound[0].user) && !Utils.IsNullOrEmpty(_config.inbound[0].pass)) + { + inbound3.settings.auth = "password"; + inbound3.settings.accounts = new List { new AccountsItem4Ray() { user = _config.inbound[0].user, pass = _config.inbound[0].pass } }; + + inbound4.settings.auth = "password"; + inbound4.settings.accounts = new List { new AccountsItem4Ray() { user = _config.inbound[0].user, pass = _config.inbound[0].pass } }; + } + } + else + { + inbound.listen = listen; + inbound2.listen = listen; + } + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks) + { + string result = Utils.GetEmbedText(Global.V2raySampleInbound); + if (Utils.IsNullOrEmpty(result)) + { + return new(); + } + + var inbound = JsonUtils.Deserialize(result); + if (inbound == null) + { + return new(); + } + inbound.tag = protocol.ToString(); + inbound.port = inItem.localPort + (int)protocol; + inbound.protocol = bSocks ? EInboundProtocol.socks.ToString() : EInboundProtocol.http.ToString(); + inbound.settings.udp = inItem.udpEnabled; + inbound.sniffing.enabled = inItem.sniffingEnabled; + inbound.sniffing.destOverride = inItem.destOverride; + inbound.sniffing.routeOnly = inItem.routeOnly; + + return inbound; + } + + private int GenRouting(V2rayConfig v2rayConfig) + { + try + { + if (v2rayConfig.routing?.rules != null) + { + v2rayConfig.routing.domainStrategy = _config.routingBasicItem.domainStrategy; + v2rayConfig.routing.domainMatcher = Utils.IsNullOrEmpty(_config.routingBasicItem.domainMatcher) ? null : _config.routingBasicItem.domainMatcher; + + if (_config.routingBasicItem.enableRoutingAdvanced) + { + var routing = ConfigHandler.GetDefaultRouting(_config); + if (routing != null) + { + if (!Utils.IsNullOrEmpty(routing.domainStrategy)) + { + v2rayConfig.routing.domainStrategy = routing.domainStrategy; + } + var rules = JsonUtils.Deserialize>(routing.ruleSet); + foreach (var item in rules) + { + if (item.enabled) + { + var item2 = JsonUtils.Deserialize(JsonUtils.Serialize(item)); + GenRoutingUserRule(item2, v2rayConfig); + } + } + } + } + else + { + var lockedItem = ConfigHandler.GetLockedRoutingItem(_config); + if (lockedItem != null) + { + var rules = JsonUtils.Deserialize>(lockedItem.ruleSet); + foreach (var item in rules) + { + var item2 = JsonUtils.Deserialize(JsonUtils.Serialize(item)); + GenRoutingUserRule(item2, v2rayConfig); + } + } + } + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig) + { + try + { + if (rule == null) + { + return 0; + } + if (Utils.IsNullOrEmpty(rule.port)) + { + rule.port = null; + } + if (Utils.IsNullOrEmpty(rule.network)) + { + rule.network = null; + } + if (rule.domain?.Count == 0) + { + rule.domain = null; + } + if (rule.ip?.Count == 0) + { + rule.ip = null; + } + if (rule.protocol?.Count == 0) + { + rule.protocol = null; + } + if (rule.inboundTag?.Count == 0) + { + rule.inboundTag = null; + } + + var hasDomainIp = false; + if (rule.domain?.Count > 0) + { + var it = JsonUtils.DeepCopy(rule); + it.ip = null; + it.type = "field"; + for (int k = it.domain.Count - 1; k >= 0; k--) + { + if (it.domain[k].StartsWith("#")) + { + it.domain.RemoveAt(k); + } + it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ","); + } + v2rayConfig.routing.rules.Add(it); + hasDomainIp = true; + } + if (rule.ip?.Count > 0) + { + var it = JsonUtils.DeepCopy(rule); + it.domain = null; + it.type = "field"; + v2rayConfig.routing.rules.Add(it); + hasDomainIp = true; + } + if (!hasDomainIp) + { + if (!Utils.IsNullOrEmpty(rule.port) + || rule.protocol?.Count > 0 + || rule.inboundTag?.Count > 0 + ) + { + var it = JsonUtils.DeepCopy(rule); + it.type = "field"; + v2rayConfig.routing.rules.Add(it); + } + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenOutbound(ProfileItem node, Outbounds4Ray outbound) + { + try + { + switch (node.configType) + { + case EConfigType.VMess: + { + VnextItem4Ray vnextItem; + if (outbound.settings.vnext.Count <= 0) + { + vnextItem = new VnextItem4Ray(); + outbound.settings.vnext.Add(vnextItem); + } + else + { + vnextItem = outbound.settings.vnext[0]; + } + vnextItem.address = node.address; + vnextItem.port = node.port; + + UsersItem4Ray usersItem; + if (vnextItem.users.Count <= 0) + { + usersItem = new UsersItem4Ray(); + vnextItem.users.Add(usersItem); + } + else + { + usersItem = vnextItem.users[0]; + } + //远程服务器用户ID + usersItem.id = node.id; + usersItem.alterId = node.alterId; + usersItem.email = Global.UserEMail; + if (Global.VmessSecurities.Contains(node.security)) + { + usersItem.security = node.security; + } + else + { + usersItem.security = Global.DefaultSecurity; + } + + GenOutboundMux(node, outbound, _config.coreBasicItem.muxEnabled); + + outbound.settings.servers = null; + break; + } + case EConfigType.Shadowsocks: + { + ServersItem4Ray serversItem; + if (outbound.settings.servers.Count <= 0) + { + serversItem = new ServersItem4Ray(); + outbound.settings.servers.Add(serversItem); + } + else + { + serversItem = outbound.settings.servers[0]; + } + serversItem.address = node.address; + serversItem.port = node.port; + serversItem.password = node.id; + serversItem.method = LazyConfig.Instance.GetShadowsocksSecurities(node).Contains(node.security) ? node.security : "none"; + + serversItem.ota = false; + serversItem.level = 1; + + GenOutboundMux(node, outbound, false); + + outbound.settings.vnext = null; + break; + } + case EConfigType.Socks: + case EConfigType.Http: + { + ServersItem4Ray serversItem; + if (outbound.settings.servers.Count <= 0) + { + serversItem = new ServersItem4Ray(); + outbound.settings.servers.Add(serversItem); + } + else + { + serversItem = outbound.settings.servers[0]; + } + serversItem.address = node.address; + serversItem.port = node.port; + serversItem.method = null; + serversItem.password = null; + + if (!Utils.IsNullOrEmpty(node.security) + && !Utils.IsNullOrEmpty(node.id)) + { + SocksUsersItem4Ray socksUsersItem = new() + { + user = node.security, + pass = node.id, + level = 1 + }; + + serversItem.users = new List() { socksUsersItem }; + } + + GenOutboundMux(node, outbound, false); + + outbound.settings.vnext = null; + break; + } + case EConfigType.VLESS: + { + VnextItem4Ray vnextItem; + if (outbound.settings.vnext?.Count <= 0) + { + vnextItem = new VnextItem4Ray(); + outbound.settings.vnext.Add(vnextItem); + } + else + { + vnextItem = outbound.settings.vnext[0]; + } + vnextItem.address = node.address; + vnextItem.port = node.port; + + UsersItem4Ray usersItem; + if (vnextItem.users.Count <= 0) + { + usersItem = new UsersItem4Ray(); + vnextItem.users.Add(usersItem); + } + else + { + usersItem = vnextItem.users[0]; + } + usersItem.id = node.id; + usersItem.email = Global.UserEMail; + usersItem.encryption = node.security; + + GenOutboundMux(node, outbound, _config.coreBasicItem.muxEnabled); + + if (node.streamSecurity == Global.StreamSecurityReality + || node.streamSecurity == Global.StreamSecurity) + { + if (!Utils.IsNullOrEmpty(node.flow)) + { + usersItem.flow = node.flow; + + GenOutboundMux(node, outbound, false); + } + } + if (node.streamSecurity == Global.StreamSecurityReality && Utils.IsNullOrEmpty(node.flow)) + { + GenOutboundMux(node, outbound, _config.coreBasicItem.muxEnabled); + } + + outbound.settings.servers = null; + break; + } + case EConfigType.Trojan: + { + ServersItem4Ray serversItem; + if (outbound.settings.servers.Count <= 0) + { + serversItem = new ServersItem4Ray(); + outbound.settings.servers.Add(serversItem); + } + else + { + serversItem = outbound.settings.servers[0]; + } + serversItem.address = node.address; + serversItem.port = node.port; + serversItem.password = node.id; + + serversItem.ota = false; + serversItem.level = 1; + + GenOutboundMux(node, outbound, false); + + outbound.settings.vnext = null; + break; + } + } + + outbound.protocol = Global.ProtocolTypes[node.configType]; + GenBoundStreamSettings(node, outbound.streamSettings); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenOutboundMux(ProfileItem node, Outbounds4Ray outbound, bool enabled) + { + try + { + if (enabled) + { + outbound.mux.enabled = true; + outbound.mux.concurrency = 8; + } + else + { + outbound.mux.enabled = false; + outbound.mux.concurrency = -1; + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenBoundStreamSettings(ProfileItem node, StreamSettings4Ray streamSettings) + { + try + { + streamSettings.network = node.GetNetwork(); + string host = node.requestHost.TrimEx(); + string sni = node.sni; + string useragent = ""; + if (!_config.coreBasicItem.defUserAgent.IsNullOrEmpty()) + { + try + { + useragent = Global.UserAgentTexts[_config.coreBasicItem.defUserAgent]; + } + catch (KeyNotFoundException) + { + useragent = _config.coreBasicItem.defUserAgent; + } + } + + //if tls + if (node.streamSecurity == Global.StreamSecurity) + { + streamSettings.security = node.streamSecurity; + + TlsSettings4Ray tlsSettings = new() + { + allowInsecure = Utils.ToBool(node.allowInsecure.IsNullOrEmpty() ? _config.coreBasicItem.defAllowInsecure.ToString().ToLower() : node.allowInsecure), + alpn = node.GetAlpn(), + fingerprint = node.fingerprint.IsNullOrEmpty() ? _config.coreBasicItem.defFingerprint : node.fingerprint + }; + if (!Utils.IsNullOrEmpty(sni)) + { + tlsSettings.serverName = sni; + } + else if (!Utils.IsNullOrEmpty(host)) + { + tlsSettings.serverName = Utils.String2List(host)[0]; + } + streamSettings.tlsSettings = tlsSettings; + } + + //if Reality + if (node.streamSecurity == Global.StreamSecurityReality) + { + streamSettings.security = node.streamSecurity; + + TlsSettings4Ray realitySettings = new() + { + fingerprint = node.fingerprint.IsNullOrEmpty() ? _config.coreBasicItem.defFingerprint : node.fingerprint, + serverName = sni, + publicKey = node.publicKey, + shortId = node.shortId, + spiderX = node.spiderX, + show = false, + }; + + streamSettings.realitySettings = realitySettings; + } + + //streamSettings + switch (node.GetNetwork()) + { + case nameof(ETransport.kcp): + KcpSettings4Ray kcpSettings = new() + { + mtu = _config.kcpItem.mtu, + tti = _config.kcpItem.tti + }; + + kcpSettings.uplinkCapacity = _config.kcpItem.uplinkCapacity; + kcpSettings.downlinkCapacity = _config.kcpItem.downlinkCapacity; + + kcpSettings.congestion = _config.kcpItem.congestion; + kcpSettings.readBufferSize = _config.kcpItem.readBufferSize; + kcpSettings.writeBufferSize = _config.kcpItem.writeBufferSize; + kcpSettings.header = new Header4Ray + { + type = node.headerType + }; + if (!Utils.IsNullOrEmpty(node.path)) + { + kcpSettings.seed = node.path; + } + streamSettings.kcpSettings = kcpSettings; + break; + //ws + case nameof(ETransport.ws): + WsSettings4Ray wsSettings = new(); + wsSettings.headers = new Headers4Ray(); + string path = node.path; + if (!Utils.IsNullOrEmpty(host)) + { + wsSettings.headers.Host = host; + } + if (!Utils.IsNullOrEmpty(path)) + { + wsSettings.path = path; + } + if (!Utils.IsNullOrEmpty(useragent)) + { + wsSettings.headers.UserAgent = useragent; + } + streamSettings.wsSettings = wsSettings; + + break; + //httpupgrade + case nameof(ETransport.httpupgrade): + HttpupgradeSettings4Ray httpupgradeSettings = new(); + + if (!Utils.IsNullOrEmpty(node.path)) + { + httpupgradeSettings.path = node.path; + } + if (!Utils.IsNullOrEmpty(host)) + { + httpupgradeSettings.host = host; + } + streamSettings.httpupgradeSettings = httpupgradeSettings; + + break; + //splithttp + case nameof(ETransport.splithttp): + SplithttpSettings4Ray splithttpSettings = new() + { + maxUploadSize = 1000000, + maxConcurrentUploads = 10 + }; + + if (!Utils.IsNullOrEmpty(node.path)) + { + splithttpSettings.path = node.path; + } + if (!Utils.IsNullOrEmpty(host)) + { + splithttpSettings.host = host; + } + streamSettings.splithttpSettings = splithttpSettings; + + break; + //h2 + case nameof(ETransport.h2): + HttpSettings4Ray httpSettings = new(); + + if (!Utils.IsNullOrEmpty(host)) + { + httpSettings.host = Utils.String2List(host); + } + httpSettings.path = node.path; + + streamSettings.httpSettings = httpSettings; + + break; + //quic + case nameof(ETransport.quic): + QuicSettings4Ray quicsettings = new() + { + security = host, + key = node.path, + header = new Header4Ray + { + type = node.headerType + } + }; + streamSettings.quicSettings = quicsettings; + if (node.streamSecurity == Global.StreamSecurity) + { + if (!Utils.IsNullOrEmpty(sni)) + { + streamSettings.tlsSettings.serverName = sni; + } + else + { + streamSettings.tlsSettings.serverName = node.address; + } + } + break; + + case nameof(ETransport.grpc): + GrpcSettings4Ray grpcSettings = new() + { + authority = Utils.IsNullOrEmpty(host) ? null : host, + serviceName = node.path, + multiMode = node.headerType == Global.GrpcMultiMode, + idle_timeout = _config.grpcItem.idle_timeout, + health_check_timeout = _config.grpcItem.health_check_timeout, + permit_without_stream = _config.grpcItem.permit_without_stream, + initial_windows_size = _config.grpcItem.initial_windows_size, + }; + streamSettings.grpcSettings = grpcSettings; + break; + + default: + //tcp + if (node.headerType == Global.TcpHeaderHttp) + { + TcpSettings4Ray tcpSettings = new() + { + header = new Header4Ray + { + type = node.headerType + } + }; + + //request Host + string request = Utils.GetEmbedText(Global.V2raySampleHttpRequestFileName); + string[] arrHost = host.Split(','); + string host2 = string.Join("\",\"", arrHost); + request = request.Replace("$requestHost$", $"\"{host2}\""); + //request = request.Replace("$requestHost$", string.Format("\"{0}\"", config.requestHost())); + request = request.Replace("$requestUserAgent$", $"\"{useragent}\""); + //Path + string pathHttp = @"/"; + if (!Utils.IsNullOrEmpty(node.path)) + { + string[] arrPath = node.path.Split(','); + pathHttp = string.Join("\",\"", arrPath); + } + request = request.Replace("$requestPath$", $"\"{pathHttp}\""); + tcpSettings.header.request = JsonUtils.Deserialize(request); + + streamSettings.tcpSettings = tcpSettings; + } + break; + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenDns(ProfileItem node, V2rayConfig v2rayConfig) + { + try + { + var item = LazyConfig.Instance.GetDNSItem(ECoreType.Xray); + var normalDNS = item?.normalDNS; + var domainStrategy4Freedom = item?.domainStrategy4Freedom; + if (Utils.IsNullOrEmpty(normalDNS)) + { + normalDNS = Utils.GetEmbedText(Global.DNSV2rayNormalFileName); + } + + //Outbound Freedom domainStrategy + if (!Utils.IsNullOrEmpty(domainStrategy4Freedom)) + { + var outbound = v2rayConfig.outbounds[1]; + outbound.settings.domainStrategy = domainStrategy4Freedom; + outbound.settings.userLevel = 0; + } + + var obj = JsonUtils.ParseJson(normalDNS); + if (obj is null) + { + List servers = []; + string[] arrDNS = normalDNS.Split(','); + foreach (string str in arrDNS) + { + servers.Add(str); + } + obj = JsonUtils.ParseJson("{}"); + obj["servers"] = JsonUtils.SerializeToNode(servers); + } + + // 追加至 dns 设置 + if (item.useSystemHosts) + { + var systemHosts = Utils.GetSystemHosts(); + if (systemHosts.Count > 0) + { + var normalHost = obj["hosts"]; + if (normalHost != null) + { + foreach (var host in systemHosts) + { + if (normalHost[host.Key] != null) + continue; + normalHost[host.Key] = host.Value; + } + } + } + } + + GenDnsDomains(node, obj); + + v2rayConfig.dns = obj; + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return 0; + } + + private int GenDnsDomains(ProfileItem node, JsonNode dns) + { + var servers = dns["servers"]; + if (servers != null) + { + if (Utils.IsDomain(node.address)) + { + var dnsServer = new DnsServer4Ray() + { + address = "223.5.5.5", + domains = [node.address] + }; + servers.AsArray().Insert(0, JsonUtils.SerializeToNode(dnsServer)); + } + } + return 0; + } + + private int GenStatistic(V2rayConfig v2rayConfig) + { + if (_config.guiItem.enableStatistics) + { + string tag = EInboundProtocol.api.ToString(); + API4Ray apiObj = new(); + Policy4Ray policyObj = new(); + SystemPolicy4Ray policySystemSetting = new(); + + string[] services = { "StatsService" }; + + v2rayConfig.stats = new Stats4Ray(); + + apiObj.tag = tag; + apiObj.services = services.ToList(); + v2rayConfig.api = apiObj; + + policySystemSetting.statsOutboundDownlink = true; + policySystemSetting.statsOutboundUplink = true; + policyObj.system = policySystemSetting; + v2rayConfig.policy = policyObj; + + if (!v2rayConfig.inbounds.Exists(item => item.tag == tag)) + { + Inbounds4Ray apiInbound = new(); + Inboundsettings4Ray apiInboundSettings = new(); + apiInbound.tag = tag; + apiInbound.listen = Global.Loopback; + apiInbound.port = LazyConfig.Instance.StatePort; + apiInbound.protocol = Global.InboundAPIProtocol; + apiInboundSettings.address = Global.Loopback; + apiInbound.settings = apiInboundSettings; + v2rayConfig.inbounds.Add(apiInbound); + } + + if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag)) + { + RulesItem4Ray apiRoutingRule = new() + { + inboundTag = new List { tag }, + outboundTag = tag, + type = "field" + }; + + v2rayConfig.routing.rules.Add(apiRoutingRule); + } + } + return 0; + } + + private int GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig) + { + //fragment proxy + if (_config.coreBasicItem.enableFragment + && !Utils.IsNullOrEmpty(v2rayConfig.outbounds[0].streamSettings?.security)) + { + var fragmentOutbound = new Outbounds4Ray + { + protocol = "freedom", + tag = $"{Global.ProxyTag}3", + settings = new() + { + fragment = new() + { + packets = "tlshello", + length = "100-200", + interval = "10-20" + } + } + }; + + v2rayConfig.outbounds.Add(fragmentOutbound); + v2rayConfig.outbounds[0].streamSettings.sockopt = new() + { + dialerProxy = fragmentOutbound.tag + }; + return 0; + } + + if (node.subid.IsNullOrEmpty()) + { + return 0; + } + try + { + var subItem = LazyConfig.Instance.GetSubItem(node.subid); + if (subItem is null) + { + return 0; + } + + //current proxy + var outbound = v2rayConfig.outbounds[0]; + var txtOutbound = Utils.GetEmbedText(Global.V2raySampleOutbound); + + //Previous proxy + var prevNode = LazyConfig.Instance.GetProfileItemViaRemarks(subItem.prevProfile!); + if (prevNode is not null + && prevNode.configType != EConfigType.Custom + && prevNode.configType != EConfigType.Hysteria2 + && prevNode.configType != EConfigType.Tuic + && prevNode.configType != EConfigType.Wireguard) + { + var prevOutbound = JsonUtils.Deserialize(txtOutbound); + GenOutbound(prevNode, prevOutbound); + prevOutbound.tag = $"{Global.ProxyTag}2"; + v2rayConfig.outbounds.Add(prevOutbound); + + outbound.streamSettings.sockopt = new() + { + dialerProxy = prevOutbound.tag + }; + } + + //Next proxy + var nextNode = LazyConfig.Instance.GetProfileItemViaRemarks(subItem.nextProfile!); + if (nextNode is not null + && nextNode.configType != EConfigType.Custom + && nextNode.configType != EConfigType.Hysteria2 + && nextNode.configType != EConfigType.Tuic + && nextNode.configType != EConfigType.Wireguard) + { + var nextOutbound = JsonUtils.Deserialize(txtOutbound); + GenOutbound(nextNode, nextOutbound); + nextOutbound.tag = Global.ProxyTag; + v2rayConfig.outbounds.Insert(0, nextOutbound); + + outbound.tag = $"{Global.ProxyTag}1"; + nextOutbound.streamSettings.sockopt = new() + { + dialerProxy = outbound.tag + }; + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + + return 0; + } + + #endregion private gen function + + #region Gen speedtest config + + public int GenerateClientSpeedtestConfig(List selecteds, out V2rayConfig? v2rayConfig, out string msg) + { + v2rayConfig = null; + try + { + if (_config == null) + { + msg = ResUI.CheckServerSettings; + return -1; + } + + msg = ResUI.InitialConfiguration; + + string result = Utils.GetEmbedText(Global.V2raySampleClient); + string txtOutbound = Utils.GetEmbedText(Global.V2raySampleOutbound); + if (Utils.IsNullOrEmpty(result) || txtOutbound.IsNullOrEmpty()) + { + msg = ResUI.FailedGetDefaultConfiguration; + return -1; + } + + v2rayConfig = JsonUtils.Deserialize(result); + if (v2rayConfig == null) + { + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + List lstIpEndPoints = new(); + List lstTcpConns = new(); + try + { + lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); + lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); + lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + + GenLog(v2rayConfig); + v2rayConfig.inbounds.Clear(); // Remove "proxy" service for speedtest, avoiding port conflicts. + v2rayConfig.outbounds.RemoveAt(0); + + int httpPort = LazyConfig.Instance.GetLocalPort(EInboundProtocol.speedtest); + + foreach (var it in selecteds) + { + if (it.configType == EConfigType.Custom) + { + continue; + } + if (it.port <= 0) + { + continue; + } + if (it.configType is EConfigType.VMess or EConfigType.VLESS) + { + var item2 = RunningObjects.Instance.ProfileItems.Where(t => t.indexId == it.indexId).FirstOrDefault(); + if (item2 is null || Utils.IsNullOrEmpty(item2.id) || !Utils.IsGuidByParse(item2.id)) + { + continue; + } + } + + //find unused port + var port = httpPort; + for (int k = httpPort; k < Global.MaxPort; k++) + { + if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0) + { + continue; + } + if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0) + { + continue; + } + //found + port = k; + httpPort = port + 1; + break; + } + + //Port In Used + if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0) + { + continue; + } + it.port = port; + it.allowTest = true; + + //inbound + Inbounds4Ray inbound = new() + { + listen = Global.Loopback, + port = port, + protocol = EInboundProtocol.http.ToString(), + }; + inbound.tag = inbound.protocol + inbound.port.ToString(); + v2rayConfig.inbounds.Add(inbound); + + //outbound + var item = RunningObjects.Instance.ProfileItems.Where(t=> t.indexId == it.indexId).FirstOrDefault(); + if (item is null) + { + continue; + } + if (item.configType == EConfigType.Shadowsocks + && !Global.SsSecuritiesInXray.Contains(item.security)) + { + continue; + } + if (item.configType == EConfigType.VLESS + && !Global.Flows.Contains(item.flow)) + { + continue; + } + + var outbound = JsonUtils.Deserialize(txtOutbound); + GenOutbound(item, outbound); + outbound.tag = Global.ProxyTag + inbound.port.ToString(); + v2rayConfig.outbounds.Add(outbound); + + //rule + RulesItem4Ray rule = new() + { + inboundTag = new List { inbound.tag }, + outboundTag = outbound.tag, + type = "field" + }; + v2rayConfig.routing.rules.Add(rule); + } + + //msg = string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); + return 0; + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + } + + #endregion Gen speedtest config + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreHandler.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreHandler.cs new file mode 100644 index 00000000..13b41850 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/CoreHandler.cs @@ -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 +{ + /// + /// Core process processing class + /// + internal class CoreHandler + { + private Config _config; + private Process? _process; + private Process? _processPre; + private Action _updateFunc; + + public CoreHandler(Config config, Action 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 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 + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/DownloadHandle.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/DownloadHandle.cs new file mode 100644 index 00000000..a174f424 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/DownloadHandle.cs @@ -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 +{ + /// + ///Download + /// + internal class DownloadHandle + { + public event EventHandler? 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 DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Action update) + { + try + { + Utils.SetSecurityProtocol(LazyConfig.Instance.GetConfig().guiItem.enableSecurityProtocolTls13); + + var progress = new Progress(); + 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(); + 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 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 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; + } + + /// + /// DownloadString + /// + /// + public async Task 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; + } + + /// + /// DownloadString + /// + /// + public async Task 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 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 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; + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/BaseFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/BaseFmt.cs new file mode 100644 index 00000000..cc867c36 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/BaseFmt.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/ClashFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/ClashFmt.cs new file mode 100644 index 00000000..90d96749 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/ClashFmt.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/FmtHandler.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/FmtHandler.cs new file mode 100644 index 00000000..dd8c5467 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/FmtHandler.cs @@ -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; + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/Hysteria2Fmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/Hysteria2Fmt.cs new file mode 100644 index 00000000..9ff29fd6 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/Hysteria2Fmt.cs @@ -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(); + 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", "", "")) + { + 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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/NaiveproxyFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/NaiveproxyFmt.cs new file mode 100644 index 00000000..3e523170 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/NaiveproxyFmt.cs @@ -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", "", "")) + { + var fileName = WriteAllText(strData); + + var profileItem = new ProfileItem + { + coreType = ECoreType.naiveproxy, + address = fileName, + remarks = subRemarks ?? "naiveproxy_custom" + }; + return profileItem; + } + + return null; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/ShadowsocksFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/ShadowsocksFmt.cs new file mode 100644 index 00000000..d890e298 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/ShadowsocksFmt.cs @@ -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://(?[A-Za-z0-9+-/=_]+)(?:#(?\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex DetailsParser = new(@"^((?.+?):(?.*)@(?.+?):(?\d+?))$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static ProfileItem? ResolveSSLegacy(string result) + { + var match = UrlFinder.Match(result); + if (!match.Success) + return null; + + ProfileItem item = new(); + var base64 = match.Groups["base64"].Value.TrimEnd('/'); + var tag = match.Groups["tag"].Value; + if (!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? ResolveSip008(string result) + { + //SsSIP008 + var lstSsServer = JsonUtils.Deserialize>(result); + if (lstSsServer?.Count <= 0) + { + var ssSIP008 = JsonUtils.Deserialize(result); + if (ssSIP008?.servers?.Count > 0) + { + lstSsServer = ssSIP008.servers; + } + } + + if (lstSsServer?.Count > 0) + { + List lst = []; + foreach (var it in lstSsServer) + { + var ssItem = new ProfileItem() + { + remarks = it.remarks, + security = it.method, + id = it.password, + address = it.server, + port = Utils.ToInt(it.server_port) + }; + lst.Add(ssItem); + } + return lst; + } + return null; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/SingboxFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/SingboxFmt.cs new file mode 100644 index 00000000..c8221afe --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/SingboxFmt.cs @@ -0,0 +1,58 @@ +using v2rayN.Enums; +using v2rayN.Models; + +namespace v2rayN.Handler.Fmt +{ + internal class SingboxFmt : BaseFmt + { + public static List? ResolveFullArray(string strData, string? subRemarks) + { + var configObjects = JsonUtils.Deserialize(strData); + if (configObjects != null && configObjects.Length > 0) + { + List lstResult = []; + foreach (var configObject in configObjects) + { + var objectString = JsonUtils.Serialize(configObject); + var singboxCon = JsonUtils.Deserialize(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(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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/SocksFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/SocksFmt.cs new file mode 100644 index 00000000..404b80f7 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/SocksFmt.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/TrojanFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/TrojanFmt.cs new file mode 100644 index 00000000..e1637f61 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/TrojanFmt.cs @@ -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(); + 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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/TuicFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/TuicFmt.cs new file mode 100644 index 00000000..83cd0a33 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/TuicFmt.cs @@ -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(); + 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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/V2rayFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/V2rayFmt.cs new file mode 100644 index 00000000..ed34c9a2 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/V2rayFmt.cs @@ -0,0 +1,59 @@ +using v2rayN.Enums; +using v2rayN.Models; + +namespace v2rayN.Handler.Fmt +{ + internal class V2rayFmt : BaseFmt + { + public static List? ResolveFullArray(string strData, string? subRemarks) + { + var configObjects = JsonUtils.Deserialize(strData); + if (configObjects != null && configObjects.Length > 0) + { + List lstResult = []; + foreach (var configObject in configObjects) + { + var objectString = JsonUtils.Serialize(configObject); + var v2rayCon = JsonUtils.Deserialize(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(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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/VLESSFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/VLESSFmt.cs new file mode 100644 index 00000000..588602fc --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/VLESSFmt.cs @@ -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(); + 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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/VmessFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/VmessFmt.cs new file mode 100644 index 00000000..1af16852 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/VmessFmt.cs @@ -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( + @"^(?[a-z]+)(\+(?[a-z]+))?:(?[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(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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/WireguardFmt.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/WireguardFmt.cs new file mode 100644 index 00000000..3362eb8c --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/Fmt/WireguardFmt.cs @@ -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(); + 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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/LazyConfig.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/LazyConfig.cs new file mode 100644 index 00000000..fe80c2a0 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/LazyConfig.cs @@ -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 _instance = new(() => new()); + private Config _config; + private List 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(); + SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); + } + + #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 SubItems() + { + return SQLiteHelper.Instance.Table().ToList(); + } + + public SubItem GetSubItem(string subid) + { + return SQLiteHelper.Instance.Table().FirstOrDefault(t => t.id == subid); + } + + public List ProfileItems(string subid = null) + { + if (Utils.IsNullOrEmpty(subid)) + { + return SQLiteHelper.Instance.Table().ToList(); + } + else + { + return SQLiteHelper.Instance.Table().Where(t => t.subid == subid).ToList(); + } + } + + public List ProfileItemIndexes(string subid) + { + if (Utils.IsNullOrEmpty(subid)) + { + return SQLiteHelper.Instance.Table().Select(t => t.indexId).ToList(); + } + else + { + return SQLiteHelper.Instance.Table().Where(t => t.subid == subid).Select(t => t.indexId).ToList(); + } + } + + public List 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(sql).ToList(); + } + + public ProfileItem? GetProfileItem(string indexId) + { + if (Utils.IsNullOrEmpty(indexId)) + { + return null; + } + return SQLiteHelper.Instance.Table().FirstOrDefault(it => it.indexId == indexId); + } + + public ProfileItem? GetProfileItemViaRemarks(string remarks) + { + if (Utils.IsNullOrEmpty(remarks)) + { + return null; + } + return SQLiteHelper.Instance.Table().FirstOrDefault(it => it.remarks == remarks); + } + + public List RoutingItems() + { + return SQLiteHelper.Instance.Table().Where(it => it.locked == false).OrderBy(t => t.sort).ToList(); + } + + public RoutingItem GetRoutingItem(string id) + { + return SQLiteHelper.Instance.Table().FirstOrDefault(it => it.locked == false && it.id == id); + } + + public List DNSItems() + { + return SQLiteHelper.Instance.Table().ToList(); + } + + public DNSItem GetDNSItem(ECoreType eCoreType) + { + return SQLiteHelper.Instance.Table().FirstOrDefault(it => it.coreType == eCoreType); + } + + #endregion SqliteHelper + + #region Core Type + + public List GetShadowsocksSecurities(ProfileItem profileItem) + { + var coreType = GetCoreType(profileItem, EConfigType.Shadowsocks); + switch (coreType) + { + case ECoreType.v2fly: + return Global.SsSecurities; + + case ECoreType.Xray: + return Global.SsSecuritiesInXray; + + case ECoreType.sing_box: + return Global.SsSecuritiesInSingbox; + } + return Global.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 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 { "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 { "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 { "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 { "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 { "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 { "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 { $"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 { "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 { "naiveproxy", "naive" }, + arguments = "config.json", + coreUrl = Global.NaiveproxyCoreUrl, + redirectInfo = false, + }); + + coreInfo.Add(new CoreInfo + { + coreType = ECoreType.tuic, + coreExes = new List { "tuic-client", "tuic" }, + arguments = "-c config.json", + coreUrl = Global.TuicCoreUrl, + redirectInfo = true, + }); + + coreInfo.Add(new CoreInfo + { + coreType = ECoreType.sing_box, + coreExes = new List { "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 { "juicity-client", "juicity" }, + arguments = "run -c config.json", + coreUrl = Global.JuicityCoreUrl + }); + + coreInfo.Add(new CoreInfo + { + coreType = ECoreType.hysteria2, + coreExes = new List { "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 + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/MainFormHandler.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/MainFormHandler.cs new file mode 100644 index 00000000..4e102a42 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/MainFormHandler.cs @@ -0,0 +1,112 @@ +using v2rayN.Models; +using v2rayMiniConsole; +using System.Collections.Concurrent; + +namespace v2rayN.Handler +{ + public sealed class MainFormHandler + { + private static readonly Lazy instance = new(() => new()); + public static MainFormHandler Instance => instance.Value; + + // 使用ConcurrentBag来存储任务,因为它提供了线程安全的集合 + public readonly ConcurrentBag MainFormTasks = new ConcurrentBag(); + + + public void UpdateTask(Config config, Action 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 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 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); + } + } + + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/NoticeHandler.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/NoticeHandler.cs new file mode 100644 index 00000000..556c1bcd --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/NoticeHandler.cs @@ -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); + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/ProfileExHandler.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/ProfileExHandler.cs new file mode 100644 index 00000000..6c91e402 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/ProfileExHandler.cs @@ -0,0 +1,153 @@ +using System.Collections.Concurrent; +using System.Linq; +using v2rayN.Models; + +namespace v2rayN.Handler +{ + internal class ProfileExHandler + { + private static readonly Lazy _instance = new(() => new()); + private ConcurrentBag _lstProfileEx = []; + private Queue _queIndexIds = new(); + public ConcurrentBag 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(); + List lstInserts = []; + List 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); + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/ProxySetting.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/ProxySetting.cs new file mode 100644 index 00000000..a6ce2454 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/ProxySetting.cs @@ -0,0 +1,323 @@ +using System.Runtime.InteropServices; +using static v2rayN.Handler.ProxySetting.InternetConnectionOption; + +namespace v2rayN.Handler +{ + internal class ProxySetting + { + /// + // set to use no proxy + /// + /// Error message with win32 error code + public static bool UnsetProxy() + { + return SetProxy(null, null, 1); + } + + /// + /// Set system proxy settings + /// + /// proxy address + /// exception addresses that do not use proxy + /// type of proxy defined in PerConnFlags + /// PROXY_TYPE_DIRECT = 0x00000001, // direct connection (no proxy) + /// PROXY_TYPE_PROXY = 0x00000002, // via named proxy + /// PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy script URL + /// PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection + /// + /// Error message with win32 error code + /// true: one of connection is successfully updated proxy settings + public static bool SetProxy(string? strProxy, string? exceptions, int type) + { + // 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; + } + + /// + /// Retrieve list of connections including LAN and WAN to support PPPoE connection + /// + /// A list of RAS connection names. May be empty list if no dial up connection. + /// Error message with win32 error code + private static IEnumerable EnumerateRasEntries() + { + 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(); + 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 + ); + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/SpeedtestHandler.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/SpeedtestHandler.cs new file mode 100644 index 00000000..3b0243ea --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/SpeedtestHandler.cs @@ -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 _selecteds; + private ESpeedActionType _actionType; + private Action _updateFunc; + + public SpeedtestHandler(Config config) + { + _config = config; + } + + public SpeedtestHandler(Config config, CoreHandler coreHandler, List selecteds, ESpeedActionType actionType, Action update) + { + _config = config; + _coreHandler = coreHandler; + _actionType = actionType; + _updateFunc = update; + + _selecteds = new List(); + 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 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 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 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); + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/StatisticsHandler.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/StatisticsHandler.cs new file mode 100644 index 00000000..f99a461b --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/StatisticsHandler.cs @@ -0,0 +1,135 @@ +using v2rayN.Models; + +namespace v2rayN.Handler +{ + internal class StatisticsHandler + { + private Config _config; + private ServerStatItem? _serverStatItem; + private List _lstServerStat; + private Action _updateFunc; + private StatisticsV2ray? _statisticsV2Ray; + private StatisticsSingbox? _statisticsSingbox; + + public List ServerStat => _lstServerStat; + public bool Enable { get; set; } + + public StatisticsHandler(Config config, Action 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().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; + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/StatisticsSingbox.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/StatisticsSingbox.cs new file mode 100644 index 00000000..d7a8b97e --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/StatisticsSingbox.cs @@ -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 _updateFunc; + + public StatisticsSingbox(Config config, Action 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(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(buffer), CancellationToken.None); + } + } + } + catch + { + } + } + } + + private void ParseOutput(string source, out ulong up, out ulong down) + { + up = 0; down = 0; + try + { + var trafficItem = JsonUtils.Deserialize(source); + if (trafficItem != null) + { + up = trafficItem.up; + down = trafficItem.down; + } + } + catch + { + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/StatisticsV2ray.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/StatisticsV2ray.cs new file mode 100644 index 00000000..0be8a617 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/StatisticsV2ray.cs @@ -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 _updateFunc; + + public StatisticsV2ray(Models.Config config, Action 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 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 + { + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/SysProxyHandle.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/SysProxyHandle.cs new file mode 100644 index 00000000..59aed628 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/SysProxyHandle.cs @@ -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: + // + // + // + // + + 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 = $";{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 + { + } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Handler/UpdateHandle.cs b/v2rayMiniConsole/v2rayMiniConsole/Handler/UpdateHandle.cs new file mode 100644 index 00000000..2e71dffb --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Handler/UpdateHandle.cs @@ -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 _updateFunc; + private Config _config; + + public event EventHandler 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 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 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 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(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 update) + { + Task.Run(async () => + { + await UpdateGeoFile("geosite", _config, update); + await UpdateGeoFile("geoip", _config, update); + }); + } + + public void RunAvailabilityCheck(Action 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); + } + } + + /// + /// 获取V2RayCore版本 + /// + 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>(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 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 + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/MainTask.cs b/v2rayMiniConsole/v2rayMiniConsole/MainTask.cs new file mode 100644 index 00000000..4d4eb4bc --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/MainTask.cs @@ -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 _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 proxyModes = new List(){ + ResUI.menuSystemProxyClear, + ResUI.menuSystemProxySet, + ResUI.menuSystemProxyNothing, + ResUI.menuSystemProxyPac + }; + List eSysProxyTypes = new List() { + 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( + 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 RemoveDuplicateServer(List 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 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 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 options, Action 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"); + } + } + } + } +} diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ClashConnectionModel.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ClashConnectionModel.cs new file mode 100644 index 00000000..94911d58 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ClashConnectionModel.cs @@ -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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ClashConnections.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ClashConnections.cs new file mode 100644 index 00000000..439e3944 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ClashConnections.cs @@ -0,0 +1,37 @@ +namespace v2rayN.Models +{ + public class ClashConnections + { + public ulong downloadTotal { get; set; } + public ulong uploadTotal { get; set; } + public List? connections { get; set; } + } + + public class ConnectionItem + { + public string id { get; set; } = string.Empty; + public MetadataItem metadata { get; set; } + public ulong upload { get; set; } + public ulong download { get; set; } + public DateTime start { get; set; } + public List? chains { get; set; } + public string rule { get; set; } + public string rulePayload { get; set; } + } + + public class MetadataItem + { + public string network { get; set; } + public string type { get; set; } + public string sourceIP { get; set; } + public string destinationIP { get; set; } + public string sourcePort { get; set; } + public string destinationPort { get; set; } + public string host { get; set; } + public string nsMode { get; set; } + public object uid { get; set; } + public string process { get; set; } + public string processPath { get; set; } + public string remoteDestination { get; set; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ClashProviders.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ClashProviders.cs new file mode 100644 index 00000000..e59ac58d --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ClashProviders.cs @@ -0,0 +1,19 @@ + + +using static v2rayN.Models.ClashProxies; + +namespace v2rayN.Models +{ + public class ClashProviders + { + public Dictionary 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; } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ClashProxies.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ClashProxies.cs new file mode 100644 index 00000000..4e8560c8 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ClashProxies.cs @@ -0,0 +1,24 @@ +namespace v2rayN.Models +{ + public class ClashProxies + { + public Dictionary proxies { get; set; } + + public class ProxiesItem + { + public string[] all { get; set; } + public List history { get; set; } + public string name { get; set; } + public string type { get; set; } + public bool udp { get; set; } + public string now { get; set; } + public int delay { get; set; } + } + + public class HistoryItem + { + public string time { get; set; } + public int delay { get; set; } + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ClashProxyModel.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ClashProxyModel.cs new file mode 100644 index 00000000..5c83e8ff --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ClashProxyModel.cs @@ -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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ComboItem.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ComboItem.cs new file mode 100644 index 00000000..836d88bd --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ComboItem.cs @@ -0,0 +1,15 @@ +namespace v2rayN.Models +{ + public class ComboItem + { + public string ID + { + get; set; + } + + public string Text + { + get; set; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/Config.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/Config.cs new file mode 100644 index 00000000..ea921008 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/Config.cs @@ -0,0 +1,43 @@ +using v2rayN.Enums; + +namespace v2rayN.Models +{ + /// + /// 本软件配置文件实体类 + /// + [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 inbound { get; set; } + public List globalHotkeys { get; set; } + public List coreTypeItem { get; set; } + + #endregion other entities + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ConfigItems.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ConfigItems.cs new file mode 100644 index 00000000..19d18ff6 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ConfigItems.cs @@ -0,0 +1,229 @@ +using System.Windows.Forms; +using v2rayN.Enums; + +namespace v2rayN.Models +{ + [Serializable] + public class CoreBasicItem + { + /// + /// 允许日志 + /// + public bool logEnabled { get; set; } + + /// + /// 日志等级 + /// + public string loglevel { get; set; } + + /// + /// 允许Mux多路复用 + /// + public bool muxEnabled { get; set; } + + /// + /// 是否允许不安全连接 + /// + public bool defAllowInsecure { get; set; } + + public string defFingerprint { get; set; } + + /// + /// 默认用户代理 + /// + public string defUserAgent { get; set; } + + public bool enableFragment { get; set; } + + public bool enableCacheFile4Sbox { get; set; } = true; + } + + [Serializable] + public class InItem + { + public int localPort { get; set; } + + public string protocol { get; set; } + + public bool udpEnabled { get; set; } + + public bool sniffingEnabled { get; set; } = true; + public List? destOverride { get; set; } = ["http", "tls"]; + public bool routeOnly { get; set; } + public bool allowLANConn { get; set; } + + public bool newPort4LAN { get; set; } + + public string user { get; set; } + + public string pass { get; set; } + } + + [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 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; + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/CoreInfo.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/CoreInfo.cs new file mode 100644 index 00000000..ad773c8c --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/CoreInfo.cs @@ -0,0 +1,29 @@ +using v2rayN.Enums; + +namespace v2rayN.Models +{ + [Serializable] + public class CoreInfo + { + public ECoreType coreType { get; set; } + + public List 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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/DNSItem.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/DNSItem.cs new file mode 100644 index 00000000..c8c27738 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/DNSItem.cs @@ -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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/GitHubRelease.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/GitHubRelease.cs new file mode 100644 index 00000000..84520d14 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/GitHubRelease.cs @@ -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 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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ProfileExItem.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ProfileExItem.cs new file mode 100644 index 00000000..b572bd50 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ProfileExItem.cs @@ -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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ProfileItem.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ProfileItem.cs new file mode 100644 index 00000000..adcfa179 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ProfileItem.cs @@ -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 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; } + + /// + /// config type(1=normal,2=custom) + /// + public EConfigType configType { get; set; } + + /// + /// 版本(现在=2) + /// + public int configVersion { get; set; } + + /// + /// 远程服务器地址 + /// + public string address { get; set; } + + /// + /// 远程服务器端口 + /// + public int port { get; set; } + + /// + /// 远程服务器ID + /// + public string id { get; set; } + + /// + /// 远程服务器额外ID + /// + public int alterId { get; set; } + + /// + /// 本地安全策略 + /// + public string security { get; set; } + + /// + /// tcp,kcp,ws,h2,quic + /// + public string network { get; set; } + + /// + /// + /// + public string remarks { get; set; } + + /// + /// 伪装类型 + /// + public string headerType { get; set; } + + /// + /// 伪装的域名 + /// + public string requestHost { get; set; } + + /// + /// ws h2 path + /// + public string path { get; set; } + + /// + /// 传输层安全 + /// + public string streamSecurity { get; set; } + + /// + /// 是否允许不安全连接(用于客户端) + /// + public string allowInsecure { get; set; } + + /// + /// SubItem id + /// + public string subid { get; set; } + + public bool isSub { get; set; } = true; + + /// + /// VLESS flow + /// + public string flow { get; set; } + + /// + /// tls sni + /// + public string sni { get; set; } + + /// + /// tls alpn + /// + 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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ProfileItemModel.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ProfileItemModel.cs new file mode 100644 index 00000000..8208d576 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ProfileItemModel.cs @@ -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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/RoutingItem.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/RoutingItem.cs new file mode 100644 index 00000000..de5cfb2a --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/RoutingItem.cs @@ -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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/RoutingItemModel.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/RoutingItemModel.cs new file mode 100644 index 00000000..3e2fd4fc --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/RoutingItemModel.cs @@ -0,0 +1,8 @@ +namespace v2rayN.Models +{ + [Serializable] + public class RoutingItemModel : RoutingItem + { + public bool isActive { get; set; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/RulesItem.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/RulesItem.cs new file mode 100644 index 00000000..78c4e846 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/RulesItem.cs @@ -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? inboundTag { get; set; } + + public string? outboundTag { get; set; } + + public List? ip { get; set; } + + public List? domain { get; set; } + + public List? protocol { get; set; } + + public List? process { get; set; } + + public bool enabled { get; set; } = true; + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/RulesItemModel.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/RulesItemModel.cs new file mode 100644 index 00000000..fbe7c57a --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/RulesItemModel.cs @@ -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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ServerSpeedItem.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ServerSpeedItem.cs new file mode 100644 index 00000000..274fa493 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ServerSpeedItem.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ServerStatItem.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ServerStatItem.cs new file mode 100644 index 00000000..a24b78b0 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ServerStatItem.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/ServerTestItem.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/ServerTestItem.cs new file mode 100644 index 00000000..8faea95e --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/ServerTestItem.cs @@ -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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/SingboxConfig.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/SingboxConfig.cs new file mode 100644 index 00000000..e7b531ad --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/SingboxConfig.cs @@ -0,0 +1,252 @@ +namespace v2rayN.Models +{ + public class SingboxConfig + { + public Log4Sbox log { get; set; } + public Dns4Sbox? dns { get; set; } + public List inbounds { get; set; } + public List outbounds { get; set; } + public Route4Sbox route { get; set; } + public Experimental4Sbox? experimental { get; set; } + } + + public class Log4Sbox + { + public bool? disabled { get; set; } + public string level { get; set; } + public string output { get; set; } + public bool? timestamp { get; set; } + } + + public class Dns4Sbox + { + public List servers { get; set; } + public List rules { get; set; } + public string? final { get; set; } + public string? strategy { get; set; } + public bool? disable_cache { get; set; } + public bool? disable_expire { get; set; } + public bool? independent_cache { get; set; } + public 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 rules { get; set; } + public List? 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? inbound { get; set; } + public List? protocol { get; set; } + public List? network { get; set; } + public List? port { get; set; } + public List? port_range { get; set; } + public List? geosite { get; set; } + public List? domain { get; set; } + public List? domain_suffix { get; set; } + public List? domain_keyword { get; set; } + public List? domain_regex { get; set; } + public List? geoip { get; set; } + public List? ip_cidr { get; set; } + public List? source_ip_cidr { get; set; } + public List? process_name { get; set; } + public List? rule_set { get; set; } + public List? 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 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 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? inbounds { get; set; } + public List? outbounds { get; set; } + public List? 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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/SsSIP008.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/SsSIP008.cs new file mode 100644 index 00000000..3600a438 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/SsSIP008.cs @@ -0,0 +1,18 @@ +namespace v2rayN.Models +{ + public class SsSIP008 + { + public List servers { get; set; } + } + + [Serializable] + public class SsServer + { + public string remarks { get; set; } + public string server { get; set; } + public string server_port { get; set; } + public string method { get; set; } + public string password { get; set; } + public string plugin { get; set; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/SubItem.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/SubItem.cs new file mode 100644 index 00000000..1f36bb9a --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/SubItem.cs @@ -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; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/SysProxyConfig.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/SysProxyConfig.cs new file mode 100644 index 00000000..95c2221b --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/SysProxyConfig.cs @@ -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 = ""; + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/V2rayConfig.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/V2rayConfig.cs new file mode 100644 index 00000000..750aea84 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/V2rayConfig.cs @@ -0,0 +1,700 @@ +using System.Text.Json.Serialization; + +namespace v2rayN.Models +{ + /// + /// v2ray配置文件实体类 例子SampleConfig.txt + /// + public class V2rayConfig + { + /// + /// Properties that do not belong to Ray + /// + public string? remarks { get; set; } + + /// + /// 日志配置 + /// + public Log4Ray log { get; set; } + + /// + /// 传入连接配置 + /// + public List inbounds { get; set; } + + /// + /// 传出连接配置 + /// + public List outbounds { get; set; } + + /// + /// 统计需要, 空对象 + /// + public Stats4Ray stats { get; set; } + + /// + public API4Ray api { get; set; } + + /// + public Policy4Ray policy { get; set; } + + /// + /// DNS 配置 + /// + public object dns { get; set; } + + /// + /// 路由配置 + /// + public Routing4Ray routing { get; set; } + } + + public class Stats4Ray + { }; + + public class API4Ray + { + public string tag { get; set; } + public List 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 + { + /// + /// + /// + public string access { get; set; } + + /// + /// + /// + public string error { get; set; } + + /// + /// + /// + public string loglevel { get; set; } + } + + public class Inbounds4Ray + { + public string tag { get; set; } + + /// + /// + /// + public int port { get; set; } + + /// + /// + /// + public string listen { get; set; } + + /// + /// + /// + public string protocol { get; set; } + + /// + /// + /// + public Sniffing4Ray sniffing { get; set; } + + /// + /// + /// + public Inboundsettings4Ray settings { get; set; } + + /// + /// + /// + public StreamSettings4Ray streamSettings { get; set; } + } + + public class Inboundsettings4Ray + { + /// + /// + /// + public string auth { get; set; } + + /// + /// + /// + public bool udp { get; set; } + + /// + /// + /// + public string ip { get; set; } + + /// + /// api 使用 + /// + public string address { get; set; } + + /// + /// + /// + public List clients { get; set; } + + /// + /// VLESS + /// + public string decryption { get; set; } + + public bool allowTransparent { get; set; } + + public List accounts { get; set; } + } + + public class UsersItem4Ray + { + /// + /// + /// + public string id { get; set; } + + /// + /// + /// + public int alterId { get; set; } + + /// + /// + /// + public string email { get; set; } + + /// + /// + /// + public string security { get; set; } + + /// + /// VLESS + /// + public string encryption { get; set; } + + /// + /// VLESS + /// + public string? flow { get; set; } + } + + public class Sniffing4Ray + { + public bool enabled { get; set; } + public List? destOverride { get; set; } + public bool routeOnly { get; set; } + } + + public class Outbounds4Ray + { + /// + /// 默认值agentout + /// + public string tag { get; set; } + + /// + /// + /// + public string protocol { get; set; } + + /// + /// + /// + public Outboundsettings4Ray settings { get; set; } + + /// + /// + /// + public StreamSettings4Ray streamSettings { get; set; } + + /// + /// + /// + public Mux4Ray mux { get; set; } + } + + public class Outboundsettings4Ray + { + /// + /// + /// + public List? vnext { get; set; } + + /// + /// + /// + public List servers { get; set; } + + /// + /// + /// + public Response4Ray response { get; set; } + + /// + /// + /// + public string domainStrategy { get; set; } + + /// + /// + /// + public int? userLevel { get; set; } + + public FragmentItem4Ray? fragment { get; set; } + } + + public class VnextItem4Ray + { + /// + /// + /// + public string address { get; set; } + + /// + /// + /// + public int port { get; set; } + + /// + /// + /// + public List users { get; set; } + } + + public class ServersItem4Ray + { + /// + /// + /// + public string email { get; set; } + + /// + /// + /// + public string address { get; set; } + + /// + /// + /// + public string? method { get; set; } + + /// + /// + /// + public bool? ota { get; set; } + + /// + /// + /// + public string? password { get; set; } + + /// + /// + /// + public int port { get; set; } + + /// + /// + /// + public int? level { get; set; } + + /// + /// trojan + /// + public string flow { get; set; } + + /// + /// + /// + public List users { get; set; } + } + + public class SocksUsersItem4Ray + { + /// + /// + /// + public string user { get; set; } + + /// + /// + /// + public string pass { get; set; } + + /// + /// + /// + public int? level { get; set; } + } + + public class Mux4Ray + { + /// + /// + /// + public bool enabled { get; set; } + + /// + /// + /// + public int concurrency { get; set; } + } + + public class Response4Ray + { + /// + /// + /// + public string type { get; set; } + } + + public class Dns4Ray + { + /// + /// + /// + public List servers { get; set; } + } + + public class DnsServer4Ray + { + public string? address { get; set; } + public List? domains { get; set; } + } + + public class Routing4Ray + { + /// + /// + /// + public string domainStrategy { get; set; } + + /// + /// + /// + public string? domainMatcher { get; set; } + + /// + /// + /// + public List rules { get; set; } + } + + [Serializable] + public class RulesItem4Ray + { + public string? type { get; set; } + + public string? port { get; set; } + public string? network { get; set; } + + public List? inboundTag { get; set; } + + public string? outboundTag { get; set; } + + public List? ip { get; set; } + + public List? domain { get; set; } + + public List? protocol { get; set; } + } + + public class StreamSettings4Ray + { + /// + /// + /// + public string network { get; set; } + + /// + /// + /// + public string security { get; set; } + + /// + /// + /// + public TlsSettings4Ray? tlsSettings { get; set; } + + /// + /// Tcp传输额外设置 + /// + public TcpSettings4Ray? tcpSettings { get; set; } + + /// + /// Kcp传输额外设置 + /// + public KcpSettings4Ray? kcpSettings { get; set; } + + /// + /// ws传输额外设置 + /// + public WsSettings4Ray? wsSettings { get; set; } + + /// + /// + /// + public HttpupgradeSettings4Ray? httpupgradeSettings { get; set; } + + /// + /// + /// + public SplithttpSettings4Ray? splithttpSettings { get; set; } + + /// + /// h2传输额外设置 + /// + public HttpSettings4Ray? httpSettings { get; set; } + + /// + /// QUIC + /// + public QuicSettings4Ray? quicSettings { get; set; } + + /// + /// VLESS only + /// + public TlsSettings4Ray? realitySettings { get; set; } + + /// + /// grpc + /// + public GrpcSettings4Ray? grpcSettings { get; set; } + + /// + /// sockopt + /// + public Sockopt4Ray? sockopt { get; set; } + } + + public class TlsSettings4Ray + { + /// + /// 是否允许不安全连接(用于客户端) + /// + public bool? allowInsecure { get; set; } + + /// + /// + /// + public string? serverName { get; set; } + + /// + /// + /// + public List? alpn { get; set; } + + public string? fingerprint { get; set; } + + public bool? show { get; set; } + public string? publicKey { get; set; } + public string? shortId { get; set; } + public string? spiderX { get; set; } + } + + public class TcpSettings4Ray + { + /// + /// 数据包头部伪装设置 + /// + public Header4Ray header { get; set; } + } + + public class Header4Ray + { + /// + /// 伪装 + /// + public string type { get; set; } + + /// + /// 结构复杂,直接存起来 + /// + public object request { get; set; } + + /// + /// 结构复杂,直接存起来 + /// + public object response { get; set; } + } + + public class KcpSettings4Ray + { + /// + /// + /// + public int mtu { get; set; } + + /// + /// + /// + public int tti { get; set; } + + /// + /// + /// + public int uplinkCapacity { get; set; } + + /// + /// + /// + public int downlinkCapacity { get; set; } + + /// + /// + /// + public bool congestion { get; set; } + + /// + /// + /// + public int readBufferSize { get; set; } + + /// + /// + /// + public int writeBufferSize { get; set; } + + /// + /// + /// + public Header4Ray header { get; set; } + + /// + /// + /// + public string seed { get; set; } + } + + public class WsSettings4Ray + { + /// + /// + /// + public string path { get; set; } + + /// + /// + /// + public Headers4Ray headers { get; set; } + } + + public class Headers4Ray + { + /// + /// + /// + public string Host { get; set; } + + /// + /// 用户代理 + /// + [JsonPropertyName("User-Agent")] + public string UserAgent { get; set; } + } + + public class HttpupgradeSettings4Ray + { + /// + /// + /// + public string? path { get; set; } + + /// + /// + /// + 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 + { + /// + /// + /// + public string path { get; set; } + + /// + /// + /// + public List host { get; set; } + } + + public class QuicSettings4Ray + { + /// + /// + /// + public string security { get; set; } + + /// + /// + /// + public string key { get; set; } + + /// + /// + /// + public Header4Ray header { get; set; } + } + + public class GrpcSettings4Ray + { + public string? authority { get; set; } + public string? serviceName { get; set; } + public bool multiMode { get; set; } + public int idle_timeout { get; set; } + public int health_check_timeout { get; set; } + public bool permit_without_stream { get; set; } + public int initial_windows_size { get; set; } + } + + public class AccountsItem4Ray + { + /// + /// + /// + public string user { get; set; } + + /// + /// + /// + public string pass { get; set; } + } + + public class Sockopt4Ray + { + public string? dialerProxy { get; set; } + } + + public class FragmentItem4Ray + { + public string? packets { get; set; } + public string? length { get; set; } + public string? interval { get; set; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/V2rayTcpRequest.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/V2rayTcpRequest.cs new file mode 100644 index 00000000..89158f19 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/V2rayTcpRequest.cs @@ -0,0 +1,21 @@ +namespace v2rayN.Models +{ + /// + /// Tcp伪装http的Request,只要Host + /// + public class V2rayTcpRequest + { + /// + /// + /// + public RequestHeaders headers { get; set; } + } + + public class RequestHeaders + { + /// + /// + /// + public List Host { get; set; } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Models/VmessQRCode.cs b/v2rayMiniConsole/v2rayMiniConsole/Models/VmessQRCode.cs new file mode 100644 index 00000000..e37d5c62 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Models/VmessQRCode.cs @@ -0,0 +1,44 @@ +using System.Text.Json.Serialization; + +namespace v2rayN.Models +{ + /// + /// https://github.com/2dust/v2rayN/wiki/ + /// + [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; + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Program.cs b/v2rayMiniConsole/v2rayMiniConsole/Program.cs new file mode 100644 index 00000000..a346a07a --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Program.cs @@ -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(); + } +} + + + + + + + diff --git a/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.Designer.cs b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.Designer.cs new file mode 100644 index 00000000..730e3873 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.Designer.cs @@ -0,0 +1,3619 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace v2rayMiniConsole.Resx { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class ResUI { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ResUI() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("v2rayMiniConsole.Resx.ResUI", typeof(ResUI).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 Do you want to append rules? Choose yes to append, choose otherwise to replace 的本地化字符串。 + /// + public static string AddBatchRoutingRulesYesNo { + get { + return ResourceManager.GetString("AddBatchRoutingRulesYesNo", resourceCulture); + } + } + + /// + /// 查找类似 All 的本地化字符串。 + /// + public static string AllGroupServers { + get { + return ResourceManager.GetString("AllGroupServers", resourceCulture); + } + } + + /// + /// 查找类似 Batch export subscription to clipboard successfully 的本地化字符串。 + /// + public static string BatchExportSubscriptionSuccessfully { + get { + return ResourceManager.GetString("BatchExportSubscriptionSuccessfully", resourceCulture); + } + } + + /// + /// 查找类似 Batch export share URL to clipboard successfully 的本地化字符串。 + /// + public static string BatchExportURLSuccessfully { + get { + return ResourceManager.GetString("BatchExportURLSuccessfully", resourceCulture); + } + } + + /// + /// 查找类似 Please check the server settings first 的本地化字符串。 + /// + public static string CheckServerSettings { + get { + return ResourceManager.GetString("CheckServerSettings", resourceCulture); + } + } + + /// + /// 查找类似 Invalid configuration format 的本地化字符串。 + /// + public static string ConfigurationFormatIncorrect { + get { + return ResourceManager.GetString("ConfigurationFormatIncorrect", resourceCulture); + } + } + + /// + /// 查找类似 Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. 的本地化字符串。 + /// + public static string CustomServerTips { + get { + return ResourceManager.GetString("CustomServerTips", resourceCulture); + } + } + + /// + /// 查找类似 Downloading... 的本地化字符串。 + /// + public static string Downloading { + get { + return ResourceManager.GetString("Downloading", resourceCulture); + } + } + + /// + /// 查找类似 Download 的本地化字符串。 + /// + public static string downloadSpeed { + get { + return ResourceManager.GetString("downloadSpeed", resourceCulture); + } + } + + /// + /// 查找类似 Whether to download? {0} 的本地化字符串。 + /// + public static string DownloadYesNo { + get { + return ResourceManager.GetString("DownloadYesNo", resourceCulture); + } + } + + /// + /// 查找类似 Failed to convert configuration file 的本地化字符串。 + /// + public static string FailedConversionConfiguration { + get { + return ResourceManager.GetString("FailedConversionConfiguration", resourceCulture); + } + } + + /// + /// 查找类似 Failed to generate default configuration file 的本地化字符串。 + /// + public static string FailedGenDefaultConfiguration { + get { + return ResourceManager.GetString("FailedGenDefaultConfiguration", resourceCulture); + } + } + + /// + /// 查找类似 Failed to get the default configuration 的本地化字符串。 + /// + public static string FailedGetDefaultConfiguration { + get { + return ResourceManager.GetString("FailedGetDefaultConfiguration", resourceCulture); + } + } + + /// + /// 查找类似 Failed to import custom configuration server 的本地化字符串。 + /// + public static string FailedImportedCustomServer { + get { + return ResourceManager.GetString("FailedImportedCustomServer", resourceCulture); + } + } + + /// + /// 查找类似 Failed to read configuration file 的本地化字符串。 + /// + public static string FailedReadConfiguration { + get { + return ResourceManager.GetString("FailedReadConfiguration", resourceCulture); + } + } + + /// + /// 查找类似 Failed to run Core, please see the log 的本地化字符串。 + /// + public static string FailedToRunCore { + get { + return ResourceManager.GetString("FailedToRunCore", resourceCulture); + } + } + + /// + /// 查找类似 Please fill in the correct custom DNS 的本地化字符串。 + /// + public static string FillCorrectDNSText { + get { + return ResourceManager.GetString("FillCorrectDNSText", resourceCulture); + } + } + + /// + /// 查找类似 Please fill in the correct format server port 的本地化字符串。 + /// + public static string FillCorrectServerPort { + get { + return ResourceManager.GetString("FillCorrectServerPort", resourceCulture); + } + } + + /// + /// 查找类似 Please fill in the KCP parameters correctly 的本地化字符串。 + /// + public static string FillKcpParameters { + get { + return ResourceManager.GetString("FillKcpParameters", resourceCulture); + } + } + + /// + /// 查找类似 Please fill in the local listening port 的本地化字符串。 + /// + public static string FillLocalListeningPort { + get { + return ResourceManager.GetString("FillLocalListeningPort", resourceCulture); + } + } + + /// + /// 查找类似 Please fill in the password 的本地化字符串。 + /// + public static string FillPassword { + get { + return ResourceManager.GetString("FillPassword", resourceCulture); + } + } + + /// + /// 查找类似 Please fill in the server address 的本地化字符串。 + /// + public static string FillServerAddress { + get { + return ResourceManager.GetString("FillServerAddress", resourceCulture); + } + } + + /// + /// 查找类似 Please browse to import server configuration 的本地化字符串。 + /// + public static string FillServerAddressCustom { + get { + return ResourceManager.GetString("FillServerAddressCustom", resourceCulture); + } + } + + /// + /// 查找类似 Please fill in the user ID 的本地化字符串。 + /// + public static string FillUUID { + get { + return ResourceManager.GetString("FillUUID", resourceCulture); + } + } + + /// + /// 查找类似 Transport 的本地化字符串。 + /// + public static string GbTransport { + get { + return ResourceManager.GetString("GbTransport", resourceCulture); + } + } + + /// + /// 查找类似 Available commands:\n\tswitch_server - Switch server\n\tshow_current_subs - Display current subscriptions\n\tadd_subscription - Add subscription\n\tremove_subscription - Remove subscription\n\tchange_proxy_mode - Change system proxy mode\n\tchange_routing - Change routing rules\n\tctrl + T - Toggle information display\n\tq - Exit the program\n\nEnter 'h' at any time to view this help information.\n\n 的本地化字符串。 + /// + public static string HelpInfo { + get { + return ResourceManager.GetString("HelpInfo", resourceCulture); + } + } + + /// + /// 查找类似 Is not the correct client configuration file, please check 的本地化字符串。 + /// + public static string IncorrectClientConfiguration { + get { + return ResourceManager.GetString("IncorrectClientConfiguration", resourceCulture); + } + } + + /// + /// 查找类似 Is not the correct configuration, please check 的本地化字符串。 + /// + public static string Incorrectconfiguration { + get { + return ResourceManager.GetString("Incorrectconfiguration", resourceCulture); + } + } + + /// + /// 查找类似 Is not the correct server configuration file, please check 的本地化字符串。 + /// + public static string IncorrectServerConfiguration { + get { + return ResourceManager.GetString("IncorrectServerConfiguration", resourceCulture); + } + } + + /// + /// 查找类似 Initial Configuration 的本地化字符串。 + /// + public static string InitialConfiguration { + get { + return ResourceManager.GetString("InitialConfiguration", resourceCulture); + } + } + + /// + /// 查找类似 {0} {1} already up to date. 的本地化字符串。 + /// + public static string IsLatestCore { + get { + return ResourceManager.GetString("IsLatestCore", resourceCulture); + } + } + + /// + /// 查找类似 {0} {1} already up to date. 的本地化字符串。 + /// + public static string IsLatestN { + get { + return ResourceManager.GetString("IsLatestN", resourceCulture); + } + } + + /// + /// 查找类似 LAN 的本地化字符串。 + /// + public static string LabLAN { + get { + return ResourceManager.GetString("LabLAN", resourceCulture); + } + } + + /// + /// 查找类似 Local 的本地化字符串。 + /// + public static string LabLocal { + get { + return ResourceManager.GetString("LabLocal", resourceCulture); + } + } + + /// + /// 查找类似 Address 的本地化字符串。 + /// + public static string LvAddress { + get { + return ResourceManager.GetString("LvAddress", resourceCulture); + } + } + + /// + /// 查找类似 Automatic update interval(minutes) 的本地化字符串。 + /// + public static string LvAutoUpdateInterval { + get { + return ResourceManager.GetString("LvAutoUpdateInterval", resourceCulture); + } + } + + /// + /// 查找类似 Convert target type 的本地化字符串。 + /// + public static string LvConvertTarget { + get { + return ResourceManager.GetString("LvConvertTarget", resourceCulture); + } + } + + /// + /// 查找类似 Please leave blank if no conversion is required 的本地化字符串。 + /// + public static string LvConvertTargetTip { + get { + return ResourceManager.GetString("LvConvertTargetTip", resourceCulture); + } + } + + /// + /// 查找类似 Count 的本地化字符串。 + /// + public static string LvCount { + get { + return ResourceManager.GetString("LvCount", resourceCulture); + } + } + + /// + /// 查找类似 Custom Icon 的本地化字符串。 + /// + public static string LvCustomIcon { + get { + return ResourceManager.GetString("LvCustomIcon", resourceCulture); + } + } + + /// + /// 查找类似 Custom the rule-set of sing-box 的本地化字符串。 + /// + public static string LvCustomRulesetPath4Singbox { + get { + return ResourceManager.GetString("LvCustomRulesetPath4Singbox", resourceCulture); + } + } + + /// + /// 查找类似 Enabled Update 的本地化字符串。 + /// + public static string LvEnabled { + get { + return ResourceManager.GetString("LvEnabled", resourceCulture); + } + } + + /// + /// 查找类似 Security 的本地化字符串。 + /// + public static string LvEncryptionMethod { + get { + return ResourceManager.GetString("LvEncryptionMethod", resourceCulture); + } + } + + /// + /// 查找类似 Remarks regular filter 的本地化字符串。 + /// + public static string LvFilter { + get { + return ResourceManager.GetString("LvFilter", resourceCulture); + } + } + + /// + /// 查找类似 More urls, separated by commas;Subscription conversion will be invalid 的本地化字符串。 + /// + public static string LvMoreUrl { + get { + return ResourceManager.GetString("LvMoreUrl", resourceCulture); + } + } + + /// + /// 查找类似 Next proxy remarks 的本地化字符串。 + /// + public static string LvNextProfile { + get { + return ResourceManager.GetString("LvNextProfile", resourceCulture); + } + } + + /// + /// 查找类似 Port 的本地化字符串。 + /// + public static string LvPort { + get { + return ResourceManager.GetString("LvPort", resourceCulture); + } + } + + /// + /// 查找类似 Previous proxy remarks 的本地化字符串。 + /// + public static string LvPrevProfile { + get { + return ResourceManager.GetString("LvPrevProfile", resourceCulture); + } + } + + /// + /// 查找类似 Please make sure the remarks exists and is unique 的本地化字符串。 + /// + public static string LvPrevProfileTip { + get { + return ResourceManager.GetString("LvPrevProfileTip", resourceCulture); + } + } + + /// + /// 查找类似 Remarks 的本地化字符串。 + /// + public static string LvRemarks { + get { + return ResourceManager.GetString("LvRemarks", resourceCulture); + } + } + + /// + /// 查找类似 Type 的本地化字符串。 + /// + public static string LvServiceType { + get { + return ResourceManager.GetString("LvServiceType", resourceCulture); + } + } + + /// + /// 查找类似 Sort 的本地化字符串。 + /// + public static string LvSort { + get { + return ResourceManager.GetString("LvSort", resourceCulture); + } + } + + /// + /// 查找类似 Subs group 的本地化字符串。 + /// + public static string LvSubscription { + get { + return ResourceManager.GetString("LvSubscription", resourceCulture); + } + } + + /// + /// 查找类似 Delay(ms) 的本地化字符串。 + /// + public static string LvTestDelay { + get { + return ResourceManager.GetString("LvTestDelay", resourceCulture); + } + } + + /// + /// 查找类似 Speed(M/s) 的本地化字符串。 + /// + public static string LvTestSpeed { + get { + return ResourceManager.GetString("LvTestSpeed", resourceCulture); + } + } + + /// + /// 查找类似 TLS 的本地化字符串。 + /// + public static string LvTLS { + get { + return ResourceManager.GetString("LvTLS", resourceCulture); + } + } + + /// + /// 查找类似 Download traffic today 的本地化字符串。 + /// + public static string LvTodayDownloadDataAmount { + get { + return ResourceManager.GetString("LvTodayDownloadDataAmount", resourceCulture); + } + } + + /// + /// 查找类似 Upload traffic today 的本地化字符串。 + /// + public static string LvTodayUploadDataAmount { + get { + return ResourceManager.GetString("LvTodayUploadDataAmount", resourceCulture); + } + } + + /// + /// 查找类似 Total download traffic 的本地化字符串。 + /// + public static string LvTotalDownloadDataAmount { + get { + return ResourceManager.GetString("LvTotalDownloadDataAmount", resourceCulture); + } + } + + /// + /// 查找类似 Total upload traffic 的本地化字符串。 + /// + public static string LvTotalUploadDataAmount { + get { + return ResourceManager.GetString("LvTotalUploadDataAmount", resourceCulture); + } + } + + /// + /// 查找类似 Transport 的本地化字符串。 + /// + public static string LvTransportProtocol { + get { + return ResourceManager.GetString("LvTransportProtocol", resourceCulture); + } + } + + /// + /// 查找类似 URL(Optional) 的本地化字符串。 + /// + public static string LvUrl { + get { + return ResourceManager.GetString("LvUrl", resourceCulture); + } + } + + /// + /// 查找类似 User Agent 的本地化字符串。 + /// + public static string LvUserAgent { + get { + return ResourceManager.GetString("LvUserAgent", resourceCulture); + } + } + + /// + /// 查找类似 Add a custom configuration server 的本地化字符串。 + /// + public static string menuAddCustomServer { + get { + return ResourceManager.GetString("menuAddCustomServer", resourceCulture); + } + } + + /// + /// 查找类似 Add [Http] server 的本地化字符串。 + /// + public static string menuAddHttpServer { + get { + return ResourceManager.GetString("menuAddHttpServer", resourceCulture); + } + } + + /// + /// 查找类似 Add [Hysteria2] server 的本地化字符串。 + /// + public static string menuAddHysteria2Server { + get { + return ResourceManager.GetString("menuAddHysteria2Server", resourceCulture); + } + } + + /// + /// 查找类似 Import bulk URL from clipboard (Ctrl+V) 的本地化字符串。 + /// + public static string menuAddServerViaClipboard { + get { + return ResourceManager.GetString("menuAddServerViaClipboard", resourceCulture); + } + } + + /// + /// 查找类似 Scan QR code on the screen (Ctrl+S) 的本地化字符串。 + /// + public static string menuAddServerViaScan { + get { + return ResourceManager.GetString("menuAddServerViaScan", resourceCulture); + } + } + + /// + /// 查找类似 Add [Shadowsocks] server 的本地化字符串。 + /// + public static string menuAddShadowsocksServer { + get { + return ResourceManager.GetString("menuAddShadowsocksServer", resourceCulture); + } + } + + /// + /// 查找类似 Add [Socks] server 的本地化字符串。 + /// + public static string menuAddSocksServer { + get { + return ResourceManager.GetString("menuAddSocksServer", resourceCulture); + } + } + + /// + /// 查找类似 Add [Trojan] server 的本地化字符串。 + /// + public static string menuAddTrojanServer { + get { + return ResourceManager.GetString("menuAddTrojanServer", resourceCulture); + } + } + + /// + /// 查找类似 Add [Tuic] server 的本地化字符串。 + /// + public static string menuAddTuicServer { + get { + return ResourceManager.GetString("menuAddTuicServer", resourceCulture); + } + } + + /// + /// 查找类似 Add [VLESS] server 的本地化字符串。 + /// + public static string menuAddVlessServer { + get { + return ResourceManager.GetString("menuAddVlessServer", resourceCulture); + } + } + + /// + /// 查找类似 Add [VMess] server 的本地化字符串。 + /// + public static string menuAddVmessServer { + get { + return ResourceManager.GetString("menuAddVmessServer", resourceCulture); + } + } + + /// + /// 查找类似 Add [Wireguard] server 的本地化字符串。 + /// + public static string menuAddWireguardServer { + get { + return ResourceManager.GetString("menuAddWireguardServer", resourceCulture); + } + } + + /// + /// 查找类似 Check Update 的本地化字符串。 + /// + public static string menuCheckUpdate { + get { + return ResourceManager.GetString("menuCheckUpdate", resourceCulture); + } + } + + /// + /// 查找类似 Clear all service statistics 的本地化字符串。 + /// + public static string menuClearServerStatistics { + get { + return ResourceManager.GetString("menuClearServerStatistics", resourceCulture); + } + } + + /// + /// 查找类似 Close 的本地化字符串。 + /// + public static string menuClose { + get { + return ResourceManager.GetString("menuClose", resourceCulture); + } + } + + /// + /// 查找类似 Close Connection 的本地化字符串。 + /// + public static string menuConnectionClose { + get { + return ResourceManager.GetString("menuConnectionClose", resourceCulture); + } + } + + /// + /// 查找类似 Close All Connection 的本地化字符串。 + /// + public static string menuConnectionCloseAll { + get { + return ResourceManager.GetString("menuConnectionCloseAll", resourceCulture); + } + } + + /// + /// 查找类似 Clone selected server 的本地化字符串。 + /// + public static string menuCopyServer { + get { + return ResourceManager.GetString("menuCopyServer", resourceCulture); + } + } + + /// + /// 查找类似 DNS Settings 的本地化字符串。 + /// + public static string menuDNSSetting { + get { + return ResourceManager.GetString("menuDNSSetting", resourceCulture); + } + } + + /// + /// 查找类似 Edit Server (Ctrl+D) 的本地化字符串。 + /// + public static string menuEditServer { + get { + return ResourceManager.GetString("menuEditServer", resourceCulture); + } + } + + /// + /// 查找类似 Exit 的本地化字符串。 + /// + public static string menuExit { + get { + return ResourceManager.GetString("menuExit", resourceCulture); + } + } + + /// + /// 查找类似 Export selected server for client configuration 的本地化字符串。 + /// + public static string menuExport2ClientConfig { + get { + return ResourceManager.GetString("menuExport2ClientConfig", resourceCulture); + } + } + + /// + /// 查找类似 Export share URLs to clipboard (Ctrl+C) 的本地化字符串。 + /// + public static string menuExport2ShareUrl { + get { + return ResourceManager.GetString("menuExport2ShareUrl", resourceCulture); + } + } + + /// + /// 查找类似 Export subscription (base64) share to clipboard 的本地化字符串。 + /// + public static string menuExport2SubContent { + get { + return ResourceManager.GetString("menuExport2SubContent", resourceCulture); + } + } + + /// + /// 查找类似 Global Hotkey Setting 的本地化字符串。 + /// + public static string menuGlobalHotkeySetting { + get { + return ResourceManager.GetString("menuGlobalHotkeySetting", resourceCulture); + } + } + + /// + /// 查找类似 Help 的本地化字符串。 + /// + public static string menuHelp { + get { + return ResourceManager.GetString("menuHelp", resourceCulture); + } + } + + /// + /// 查找类似 Import old config (guiNConfig) 的本地化字符串。 + /// + public static string menuImportOldGuiConfig { + get { + return ResourceManager.GetString("menuImportOldGuiConfig", resourceCulture); + } + } + + /// + /// 查找类似 Import Rules From Clipboard 的本地化字符串。 + /// + public static string menuImportRulesFromClipboard { + get { + return ResourceManager.GetString("menuImportRulesFromClipboard", resourceCulture); + } + } + + /// + /// 查找类似 Import Rules From File 的本地化字符串。 + /// + public static string menuImportRulesFromFile { + get { + return ResourceManager.GetString("menuImportRulesFromFile", resourceCulture); + } + } + + /// + /// 查找类似 Import Rules From Subscription URL 的本地化字符串。 + /// + public static string menuImportRulesFromUrl { + get { + return ResourceManager.GetString("menuImportRulesFromUrl", resourceCulture); + } + } + + /// + /// 查找类似 One-click multi test Latency and speed (Ctrl+E) 的本地化字符串。 + /// + public static string menuMixedTestServer { + get { + return ResourceManager.GetString("menuMixedTestServer", resourceCulture); + } + } + + /// + /// 查找类似 Direct 的本地化字符串。 + /// + public static string menuModeDirect { + get { + return ResourceManager.GetString("menuModeDirect", resourceCulture); + } + } + + /// + /// 查找类似 Global 的本地化字符串。 + /// + public static string menuModeGlobal { + get { + return ResourceManager.GetString("menuModeGlobal", resourceCulture); + } + } + + /// + /// 查找类似 Do not change 的本地化字符串。 + /// + public static string menuModeNothing { + get { + return ResourceManager.GetString("menuModeNothing", resourceCulture); + } + } + + /// + /// 查找类似 Rule 的本地化字符串。 + /// + public static string menuModeRule { + get { + return ResourceManager.GetString("menuModeRule", resourceCulture); + } + } + + /// + /// 查找类似 Move to bottom (B) 的本地化字符串。 + /// + public static string menuMoveBottom { + get { + return ResourceManager.GetString("menuMoveBottom", resourceCulture); + } + } + + /// + /// 查找类似 Down (D) 的本地化字符串。 + /// + public static string menuMoveDown { + get { + return ResourceManager.GetString("menuMoveDown", resourceCulture); + } + } + + /// + /// 查找类似 Move up and down 的本地化字符串。 + /// + public static string menuMoveTo { + get { + return ResourceManager.GetString("menuMoveTo", resourceCulture); + } + } + + /// + /// 查找类似 Move to group 的本地化字符串。 + /// + public static string menuMoveToGroup { + get { + return ResourceManager.GetString("menuMoveToGroup", resourceCulture); + } + } + + /// + /// 查找类似 Move to top (T) 的本地化字符串。 + /// + public static string menuMoveTop { + get { + return ResourceManager.GetString("menuMoveTop", resourceCulture); + } + } + + /// + /// 查找类似 Up (U) 的本地化字符串。 + /// + public static string menuMoveUp { + get { + return ResourceManager.GetString("menuMoveUp", resourceCulture); + } + } + + /// + /// 查找类似 Clear all 的本地化字符串。 + /// + public static string menuMsgViewClear { + get { + return ResourceManager.GetString("menuMsgViewClear", resourceCulture); + } + } + + /// + /// 查找类似 Copy (Ctrl+C) 的本地化字符串。 + /// + public static string menuMsgViewCopy { + get { + return ResourceManager.GetString("menuMsgViewCopy", resourceCulture); + } + } + + /// + /// 查找类似 Copy all 的本地化字符串。 + /// + public static string menuMsgViewCopyAll { + get { + return ResourceManager.GetString("menuMsgViewCopyAll", resourceCulture); + } + } + + /// + /// 查找类似 Set message filters 的本地化字符串。 + /// + public static string menuMsgViewFilter { + get { + return ResourceManager.GetString("menuMsgViewFilter", resourceCulture); + } + } + + /// + /// 查找类似 Select all (Ctrl+A) 的本地化字符串。 + /// + public static string menuMsgViewSelectAll { + get { + return ResourceManager.GetString("menuMsgViewSelectAll", resourceCulture); + } + } + + /// + /// 查找类似 Open the storage location 的本地化字符串。 + /// + public static string menuOpenTheFileLocation { + get { + return ResourceManager.GetString("menuOpenTheFileLocation", resourceCulture); + } + } + + /// + /// 查找类似 Option Setting 的本地化字符串。 + /// + public static string menuOptionSetting { + get { + return ResourceManager.GetString("menuOptionSetting", resourceCulture); + } + } + + /// + /// 查找类似 Promotion 的本地化字符串。 + /// + public static string menuPromotion { + get { + return ResourceManager.GetString("menuPromotion", resourceCulture); + } + } + + /// + /// 查找类似 All Node Latency Test 的本地化字符串。 + /// + public static string menuProxiesDelaytest { + get { + return ResourceManager.GetString("menuProxiesDelaytest", resourceCulture); + } + } + + /// + /// 查找类似 Part Node Latency Test 的本地化字符串。 + /// + public static string menuProxiesDelaytestPart { + get { + return ResourceManager.GetString("menuProxiesDelaytestPart", resourceCulture); + } + } + + /// + /// 查找类似 Refresh Proxies (F5) 的本地化字符串。 + /// + public static string menuProxiesReload { + get { + return ResourceManager.GetString("menuProxiesReload", resourceCulture); + } + } + + /// + /// 查找类似 Select active node (Enter) 的本地化字符串。 + /// + public static string menuProxiesSelectActivity { + get { + return ResourceManager.GetString("menuProxiesSelectActivity", resourceCulture); + } + } + + /// + /// 查找类似 Test servers real delay (Ctrl+R) 的本地化字符串。 + /// + public static string menuRealPingServer { + get { + return ResourceManager.GetString("menuRealPingServer", resourceCulture); + } + } + + /// + /// 查找类似 Restart as Administrator 的本地化字符串。 + /// + public static string menuRebootAsAdmin { + get { + return ResourceManager.GetString("menuRebootAsAdmin", resourceCulture); + } + } + + /// + /// 查找类似 Reload 的本地化字符串。 + /// + public static string menuReload { + get { + return ResourceManager.GetString("menuReload", resourceCulture); + } + } + + /// + /// 查找类似 Remove duplicate servers 的本地化字符串。 + /// + public static string menuRemoveDuplicateServer { + get { + return ResourceManager.GetString("menuRemoveDuplicateServer", resourceCulture); + } + } + + /// + /// 查找类似 Remove selected servers (Delete) 的本地化字符串。 + /// + public static string menuRemoveServer { + get { + return ResourceManager.GetString("menuRemoveServer", resourceCulture); + } + } + + /// + /// 查找类似 Routing 的本地化字符串。 + /// + public static string menuRouting { + get { + return ResourceManager.GetString("menuRouting", resourceCulture); + } + } + + /// + /// 查找类似 Advanced Function 的本地化字符串。 + /// + public static string menuRoutingAdvanced { + get { + return ResourceManager.GetString("menuRoutingAdvanced", resourceCulture); + } + } + + /// + /// 查找类似 Add 的本地化字符串。 + /// + public static string menuRoutingAdvancedAdd { + get { + return ResourceManager.GetString("menuRoutingAdvancedAdd", resourceCulture); + } + } + + /// + /// 查找类似 Import Advanced Rules 的本地化字符串。 + /// + public static string menuRoutingAdvancedImportRules { + get { + return ResourceManager.GetString("menuRoutingAdvancedImportRules", resourceCulture); + } + } + + /// + /// 查找类似 Remove selected (Delete) 的本地化字符串。 + /// + public static string menuRoutingAdvancedRemove { + get { + return ResourceManager.GetString("menuRoutingAdvancedRemove", resourceCulture); + } + } + + /// + /// 查找类似 Set as active rule(Enter) 的本地化字符串。 + /// + public static string menuRoutingAdvancedSetDefault { + get { + return ResourceManager.GetString("menuRoutingAdvancedSetDefault", resourceCulture); + } + } + + /// + /// 查找类似 Basic Function 的本地化字符串。 + /// + public static string menuRoutingBasic { + get { + return ResourceManager.GetString("menuRoutingBasic", resourceCulture); + } + } + + /// + /// 查找类似 Import Basic Rules 的本地化字符串。 + /// + public static string menuRoutingBasicImportRules { + get { + return ResourceManager.GetString("menuRoutingBasicImportRules", resourceCulture); + } + } + + /// + /// 查找类似 RoutingRuleDetailsSetting 的本地化字符串。 + /// + public static string menuRoutingRuleDetailsSetting { + get { + return ResourceManager.GetString("menuRoutingRuleDetailsSetting", resourceCulture); + } + } + + /// + /// 查找类似 Rule Settings 的本地化字符串。 + /// + public static string menuRoutingRuleSetting { + get { + return ResourceManager.GetString("menuRoutingRuleSetting", resourceCulture); + } + } + + /// + /// 查找类似 Routing Setting 的本地化字符串。 + /// + public static string menuRoutingSetting { + get { + return ResourceManager.GetString("menuRoutingSetting", resourceCulture); + } + } + + /// + /// 查找类似 Add Rule 的本地化字符串。 + /// + public static string menuRuleAdd { + get { + return ResourceManager.GetString("menuRuleAdd", resourceCulture); + } + } + + /// + /// 查找类似 Export Selected Rules 的本地化字符串。 + /// + public static string menuRuleExportSelected { + get { + return ResourceManager.GetString("menuRuleExportSelected", resourceCulture); + } + } + + /// + /// 查找类似 Rule List 的本地化字符串。 + /// + public static string menuRuleList { + get { + return ResourceManager.GetString("menuRuleList", resourceCulture); + } + } + + /// + /// 查找类似 Rule mode 的本地化字符串。 + /// + public static string menuRulemode { + get { + return ResourceManager.GetString("menuRulemode", resourceCulture); + } + } + + /// + /// 查找类似 Remove Rule (Delete) 的本地化字符串。 + /// + public static string menuRuleRemove { + get { + return ResourceManager.GetString("menuRuleRemove", resourceCulture); + } + } + + /// + /// 查找类似 Select all (Ctrl+A) 的本地化字符串。 + /// + public static string menuSelectAll { + get { + return ResourceManager.GetString("menuSelectAll", resourceCulture); + } + } + + /// + /// 查找类似 Servers 的本地化字符串。 + /// + public static string menuServers { + get { + return ResourceManager.GetString("menuServers", resourceCulture); + } + } + + /// + /// 查找类似 Set as active server (Enter) 的本地化字符串。 + /// + public static string menuSetDefaultServer { + get { + return ResourceManager.GetString("menuSetDefaultServer", resourceCulture); + } + } + + /// + /// 查找类似 Settings 的本地化字符串。 + /// + public static string menuSetting { + get { + return ResourceManager.GetString("menuSetting", resourceCulture); + } + } + + /// + /// 查找类似 Share Server (Ctrl+F) 的本地化字符串。 + /// + public static string menuShareServer { + get { + return ResourceManager.GetString("menuShareServer", resourceCulture); + } + } + + /// + /// 查找类似 Sort by test result 的本地化字符串。 + /// + public static string menuSortServerResult { + get { + return ResourceManager.GetString("menuSortServerResult", resourceCulture); + } + } + + /// + /// 查找类似 Test servers download speed (Ctrl+T) 的本地化字符串。 + /// + public static string menuSpeedServer { + get { + return ResourceManager.GetString("menuSpeedServer", resourceCulture); + } + } + + /// + /// 查找类似 Add 的本地化字符串。 + /// + public static string menuSubAdd { + get { + return ResourceManager.GetString("menuSubAdd", resourceCulture); + } + } + + /// + /// 查找类似 Delete 的本地化字符串。 + /// + public static string menuSubDelete { + get { + return ResourceManager.GetString("menuSubDelete", resourceCulture); + } + } + + /// + /// 查找类似 Edit 的本地化字符串。 + /// + public static string menuSubEdit { + get { + return ResourceManager.GetString("menuSubEdit", resourceCulture); + } + } + + /// + /// 查找类似 Update current subscription without proxy 的本地化字符串。 + /// + public static string menuSubGroupUpdate { + get { + return ResourceManager.GetString("menuSubGroupUpdate", resourceCulture); + } + } + + /// + /// 查找类似 Update current subscription with proxy 的本地化字符串。 + /// + public static string menuSubGroupUpdateViaProxy { + get { + return ResourceManager.GetString("menuSubGroupUpdateViaProxy", resourceCulture); + } + } + + /// + /// 查找类似 Subscription group 的本地化字符串。 + /// + public static string menuSubscription { + get { + return ResourceManager.GetString("menuSubscription", resourceCulture); + } + } + + /// + /// 查找类似 Subscription group settings 的本地化字符串。 + /// + public static string menuSubSetting { + get { + return ResourceManager.GetString("menuSubSetting", resourceCulture); + } + } + + /// + /// 查找类似 Share 的本地化字符串。 + /// + public static string menuSubShare { + get { + return ResourceManager.GetString("menuSubShare", resourceCulture); + } + } + + /// + /// 查找类似 Update subscription without proxy 的本地化字符串。 + /// + public static string menuSubUpdate { + get { + return ResourceManager.GetString("menuSubUpdate", resourceCulture); + } + } + + /// + /// 查找类似 Update subscription with proxy 的本地化字符串。 + /// + public static string menuSubUpdateViaProxy { + get { + return ResourceManager.GetString("menuSubUpdateViaProxy", resourceCulture); + } + } + + /// + /// 查找类似 System proxy 的本地化字符串。 + /// + public static string menuSystemproxy { + get { + return ResourceManager.GetString("menuSystemproxy", resourceCulture); + } + } + + /// + /// 查找类似 Clear system proxy 的本地化字符串。 + /// + public static string menuSystemProxyClear { + get { + return ResourceManager.GetString("menuSystemProxyClear", resourceCulture); + } + } + + /// + /// 查找类似 Do not change system proxy 的本地化字符串。 + /// + public static string menuSystemProxyNothing { + get { + return ResourceManager.GetString("menuSystemProxyNothing", resourceCulture); + } + } + + /// + /// 查找类似 PAC mode 的本地化字符串。 + /// + public static string menuSystemProxyPac { + get { + return ResourceManager.GetString("menuSystemProxyPac", resourceCulture); + } + } + + /// + /// 查找类似 Set system proxy 的本地化字符串。 + /// + public static string menuSystemProxySet { + get { + return ResourceManager.GetString("menuSystemProxySet", resourceCulture); + } + } + + /// + /// 查找类似 Test servers with tcping (Ctrl+O) 的本地化字符串。 + /// + public static string menuTcpingServer { + get { + return ResourceManager.GetString("menuTcpingServer", resourceCulture); + } + } + + /// + /// 查找类似 Test current service status 的本地化字符串。 + /// + public static string menuTestMe { + get { + return ResourceManager.GetString("menuTestMe", resourceCulture); + } + } + + /// + /// 查找类似 {0} Website 的本地化字符串。 + /// + public static string menuWebsiteItem { + get { + return ResourceManager.GetString("menuWebsiteItem", resourceCulture); + } + } + + /// + /// 查找类似 Clear original subscription content 的本地化字符串。 + /// + public static string MsgClearSubscription { + get { + return ResourceManager.GetString("MsgClearSubscription", resourceCulture); + } + } + + /// + /// 查找类似 Download GeoFile: {0} successfully 的本地化字符串。 + /// + public static string MsgDownloadGeoFileSuccessfully { + get { + return ResourceManager.GetString("MsgDownloadGeoFileSuccessfully", resourceCulture); + } + } + + /// + /// 查找类似 Download Core successfully 的本地化字符串。 + /// + public static string MsgDownloadV2rayCoreSuccessfully { + get { + return ResourceManager.GetString("MsgDownloadV2rayCoreSuccessfully", resourceCulture); + } + } + + /// + /// 查找类似 Failed to import subscription content 的本地化字符串。 + /// + public static string MsgFailedImportSubscription { + get { + return ResourceManager.GetString("MsgFailedImportSubscription", resourceCulture); + } + } + + /// + /// 查找类似 Filter, support regular 的本地化字符串。 + /// + public static string MsgFilterTitle { + get { + return ResourceManager.GetString("MsgFilterTitle", resourceCulture); + } + } + + /// + /// 查找类似 Get subscription content successfully 的本地化字符串。 + /// + public static string MsgGetSubscriptionSuccessfully { + get { + return ResourceManager.GetString("MsgGetSubscriptionSuccessfully", resourceCulture); + } + } + + /// + /// 查找类似 Information 的本地化字符串。 + /// + public static string MsgInformationTitle { + get { + return ResourceManager.GetString("MsgInformationTitle", resourceCulture); + } + } + + /// + /// 查找类似 Please fill in the URL 的本地化字符串。 + /// + public static string MsgNeedUrl { + get { + return ResourceManager.GetString("MsgNeedUrl", resourceCulture); + } + } + + /// + /// 查找类似 No valid subscriptions set 的本地化字符串。 + /// + public static string MsgNoValidSubscription { + get { + return ResourceManager.GetString("MsgNoValidSubscription", resourceCulture); + } + } + + /// + /// 查找类似 Resolve {0} successfully 的本地化字符串。 + /// + public static string MsgParsingSuccessfully { + get { + return ResourceManager.GetString("MsgParsingSuccessfully", resourceCulture); + } + } + + /// + /// 查找类似 Servers Filter, press Enter to execute 的本地化字符串。 + /// + public static string MsgServerTitle { + get { + return ResourceManager.GetString("MsgServerTitle", resourceCulture); + } + } + + /// + /// 查找类似 Updates are not enabled, skip this subscription 的本地化字符串。 + /// + public static string MsgSkipSubscriptionUpdate { + get { + return ResourceManager.GetString("MsgSkipSubscriptionUpdate", resourceCulture); + } + } + + /// + /// 查找类似 Start getting subscriptions 的本地化字符串。 + /// + public static string MsgStartGettingSubscriptions { + get { + return ResourceManager.GetString("MsgStartGettingSubscriptions", resourceCulture); + } + } + + /// + /// 查找类似 Start updating {0}... 的本地化字符串。 + /// + public static string MsgStartUpdating { + get { + return ResourceManager.GetString("MsgStartUpdating", resourceCulture); + } + } + + /// + /// 查找类似 Invalid subscription content 的本地化字符串。 + /// + public static string MsgSubscriptionDecodingFailed { + get { + return ResourceManager.GetString("MsgSubscriptionDecodingFailed", resourceCulture); + } + } + + /// + /// 查找类似 Is unpacking...... 的本地化字符串。 + /// + public static string MsgUnpacking { + get { + return ResourceManager.GetString("MsgUnpacking", resourceCulture); + } + } + + /// + /// 查找类似 Update subscription end 的本地化字符串。 + /// + public static string MsgUpdateSubscriptionEnd { + get { + return ResourceManager.GetString("MsgUpdateSubscriptionEnd", resourceCulture); + } + } + + /// + /// 查找类似 Update subscription starts 的本地化字符串。 + /// + public static string MsgUpdateSubscriptionStart { + get { + return ResourceManager.GetString("MsgUpdateSubscriptionStart", resourceCulture); + } + } + + /// + /// 查找类似 Update Core successfully 的本地化字符串。 + /// + public static string MsgUpdateV2rayCoreSuccessfully { + get { + return ResourceManager.GetString("MsgUpdateV2rayCoreSuccessfully", resourceCulture); + } + } + + /// + /// 查找类似 Update Core successfully! Restarting service... 的本地化字符串。 + /// + public static string MsgUpdateV2rayCoreSuccessfullyMore { + get { + return ResourceManager.GetString("MsgUpdateV2rayCoreSuccessfullyMore", resourceCulture); + } + } + + /// + /// 查找类似 Successful operation. Click the settings menu to reboot the app. 的本地化字符串。 + /// + public static string NeedRebootTips { + get { + return ResourceManager.GetString("NeedRebootTips", resourceCulture); + } + } + + /// + /// 查找类似 Non-VMess or ss protocol 的本地化字符串。 + /// + public static string NonvmessOrssProtocol { + get { + return ResourceManager.GetString("NonvmessOrssProtocol", resourceCulture); + } + } + + /// + /// 查找类似 Non-standard service, this feature is invalid 的本地化字符串。 + /// + public static string NonVmessService { + get { + return ResourceManager.GetString("NonVmessService", resourceCulture); + } + } + + /// + /// 查找类似 The Core file (file name: {1}) was not found under the folder ({0}), please download and put it in the folder, download address: {2} 的本地化字符串。 + /// + public static string NotFoundCore { + get { + return ResourceManager.GetString("NotFoundCore", resourceCulture); + } + } + + /// + /// 查找类似 Not run as Admin 的本地化字符串。 + /// + public static string NotRunAsAdmin { + get { + return ResourceManager.GetString("NotRunAsAdmin", resourceCulture); + } + } + + /// + /// 查找类似 Scan completed, no valid QR code found 的本地化字符串。 + /// + public static string NoValidQRcodeFound { + get { + return ResourceManager.GetString("NoValidQRcodeFound", resourceCulture); + } + } + + /// + /// 查找类似 Operation failed, please check and retry 的本地化字符串。 + /// + public static string OperationFailed { + get { + return ResourceManager.GetString("OperationFailed", resourceCulture); + } + } + + /// + /// 查找类似 Operation success 的本地化字符串。 + /// + public static string OperationSuccess { + get { + return ResourceManager.GetString("OperationSuccess", resourceCulture); + } + } + + /// + /// 查找类似 Please Fill Remarks 的本地化字符串。 + /// + public static string PleaseFillRemarks { + get { + return ResourceManager.GetString("PleaseFillRemarks", resourceCulture); + } + } + + /// + /// 查找类似 Please select the encryption method 的本地化字符串。 + /// + public static string PleaseSelectEncryption { + get { + return ResourceManager.GetString("PleaseSelectEncryption", resourceCulture); + } + } + + /// + /// 查找类似 Please select a protocol 的本地化字符串。 + /// + public static string PleaseSelectProtocol { + get { + return ResourceManager.GetString("PleaseSelectProtocol", resourceCulture); + } + } + + /// + /// 查找类似 Please select rules 的本地化字符串。 + /// + public static string PleaseSelectRules { + get { + return ResourceManager.GetString("PleaseSelectRules", resourceCulture); + } + } + + /// + /// 查找类似 Please select the server first 的本地化字符串。 + /// + public static string PleaseSelectServer { + get { + return ResourceManager.GetString("PleaseSelectServer", resourceCulture); + } + } + + /// + /// 查找类似 Global hotkey {0} registered failed, reason {1} 的本地化字符串。 + /// + public static string RegisterGlobalHotkeyFailed { + get { + return ResourceManager.GetString("RegisterGlobalHotkeyFailed", resourceCulture); + } + } + + /// + /// 查找类似 Global hotkey {0} registered successfully 的本地化字符串。 + /// + public static string RegisterGlobalHotkeySuccessfully { + get { + return ResourceManager.GetString("RegisterGlobalHotkeySuccessfully", resourceCulture); + } + } + + /// + /// 查找类似 Servers deduplication completed. Old: {0}, New: {1}. 的本地化字符串。 + /// + public static string RemoveDuplicateServerResult { + get { + return ResourceManager.GetString("RemoveDuplicateServerResult", resourceCulture); + } + } + + /// + /// 查找类似 Are you sure to remove the rules? 的本地化字符串。 + /// + public static string RemoveRules { + get { + return ResourceManager.GetString("RemoveRules", resourceCulture); + } + } + + /// + /// 查找类似 Are you sure to remove the server? 的本地化字符串。 + /// + public static string RemoveServer { + get { + return ResourceManager.GetString("RemoveServer", resourceCulture); + } + } + + /// + /// 查找类似 {0},One of the required. 的本地化字符串。 + /// + public static string RoutingRuleDetailRequiredTips { + get { + return ResourceManager.GetString("RoutingRuleDetailRequiredTips", resourceCulture); + } + } + + /// + /// 查找类似 Run as Admin 的本地化字符串。 + /// + public static string RunAsAdmin { + get { + return ResourceManager.GetString("RunAsAdmin", resourceCulture); + } + } + + /// + /// 查找类似 The client configuration file is saved at: {0} 的本地化字符串。 + /// + public static string SaveClientConfigurationIn { + get { + return ResourceManager.GetString("SaveClientConfigurationIn", resourceCulture); + } + } + + /// + /// 查找类似 {0} : {1}/s↑ | {2}/s↓ 的本地化字符串。 + /// + public static string SpeedDisplayText { + get { + return ResourceManager.GetString("SpeedDisplayText", resourceCulture); + } + } + + /// + /// 查找类似 Testing... 的本地化字符串。 + /// + public static string Speedtesting { + get { + return ResourceManager.GetString("Speedtesting", resourceCulture); + } + } + + /// + /// 查找类似 Test completed 的本地化字符串。 + /// + public static string SpeedtestingCompleted { + get { + return ResourceManager.GetString("SpeedtestingCompleted", resourceCulture); + } + } + + /// + /// 查找类似 Skip test 的本地化字符串。 + /// + public static string SpeedtestingSkip { + get { + return ResourceManager.GetString("SpeedtestingSkip", resourceCulture); + } + } + + /// + /// 查找类似 Test terminating... 的本地化字符串。 + /// + public static string SpeedtestingStop { + get { + return ResourceManager.GetString("SpeedtestingStop", resourceCulture); + } + } + + /// + /// 查找类似 Waiting for testing (press ESC to terminate)... 的本地化字符串。 + /// + public static string SpeedtestingWait { + get { + return ResourceManager.GetString("SpeedtestingWait", resourceCulture); + } + } + + /// + /// 查找类似 Start service ({0})... 的本地化字符串。 + /// + public static string StartService { + get { + return ResourceManager.GetString("StartService", resourceCulture); + } + } + + /// + /// 查找类似 A simple secondary development was done on the basis of v2rayN to automate speed measurement and node switching to avoid manual intervention to the greatest extent in unstable node environments. Please enter 'h' to view help information. 的本地化字符串。 + /// + public static string StartupInfo { + get { + return ResourceManager.GetString("StartupInfo", resourceCulture); + } + } + + /// + /// 查找类似 Group please leave blank here 的本地化字符串。 + /// + public static string SubUrlTips { + get { + return ResourceManager.GetString("SubUrlTips", resourceCulture); + } + } + + /// + /// 查找类似 Configuration successful + ///{0} 的本地化字符串。 + /// + public static string SuccessfulConfiguration { + get { + return ResourceManager.GetString("SuccessfulConfiguration", resourceCulture); + } + } + + /// + /// 查找类似 Custom configuration server imported successfully 的本地化字符串。 + /// + public static string SuccessfullyImportedCustomServer { + get { + return ResourceManager.GetString("SuccessfullyImportedCustomServer", resourceCulture); + } + } + + /// + /// 查找类似 {0} servers have been imported from clipboard 的本地化字符串。 + /// + public static string SuccessfullyImportedServerViaClipboard { + get { + return ResourceManager.GetString("SuccessfullyImportedServerViaClipboard", resourceCulture); + } + } + + /// + /// 查找类似 Scan import URL successfully 的本地化字符串。 + /// + public static string SuccessfullyImportedServerViaScan { + get { + return ResourceManager.GetString("SuccessfullyImportedServerViaScan", resourceCulture); + } + } + + /// + /// 查找类似 System proxy 的本地化字符串。 + /// + public static string SystemProxy { + get { + return ResourceManager.GetString("SystemProxy", resourceCulture); + } + } + + /// + /// 查找类似 Address 的本地化字符串。 + /// + public static string TbAddress { + get { + return ResourceManager.GetString("TbAddress", resourceCulture); + } + } + + /// + /// 查找类似 AllowInsecure 的本地化字符串。 + /// + public static string TbAllowInsecure { + get { + return ResourceManager.GetString("TbAllowInsecure", resourceCulture); + } + } + + /// + /// 查找类似 ALPN 的本地化字符串。 + /// + public static string TbAlpn { + get { + return ResourceManager.GetString("TbAlpn", resourceCulture); + } + } + + /// + /// 查找类似 AlterID 的本地化字符串。 + /// + public static string TbAlterId { + get { + return ResourceManager.GetString("TbAlterId", resourceCulture); + } + } + + /// + /// 查找类似 AutoRefresh 的本地化字符串。 + /// + public static string TbAutoRefresh { + get { + return ResourceManager.GetString("TbAutoRefresh", resourceCulture); + } + } + + /// + /// 查找类似 Auto ScrollToEnd 的本地化字符串。 + /// + public static string TbAutoScrollToEnd { + get { + return ResourceManager.GetString("TbAutoScrollToEnd", resourceCulture); + } + } + + /// + /// 查找类似 Domain, ip, process are auto sorted when saving 的本地化字符串。 + /// + public static string TbAutoSort { + get { + return ResourceManager.GetString("TbAutoSort", resourceCulture); + } + } + + /// + /// 查找类似 Browse 的本地化字符串。 + /// + public static string TbBrowse { + get { + return ResourceManager.GetString("TbBrowse", resourceCulture); + } + } + + /// + /// 查找类似 Cancel 的本地化字符串。 + /// + public static string TbCancel { + get { + return ResourceManager.GetString("TbCancel", resourceCulture); + } + } + + /// + /// 查找类似 Clear system proxy 的本地化字符串。 + /// + public static string TbClearSystemProxy { + get { + return ResourceManager.GetString("TbClearSystemProxy", resourceCulture); + } + } + + /// + /// 查找类似 Confirm 的本地化字符串。 + /// + public static string TbConfirm { + get { + return ResourceManager.GetString("TbConfirm", resourceCulture); + } + } + + /// + /// 查找类似 Connections 的本地化字符串。 + /// + public static string TbConnections { + get { + return ResourceManager.GetString("TbConnections", resourceCulture); + } + } + + /// + /// 查找类似 Core Type 的本地化字符串。 + /// + public static string TbCoreType { + get { + return ResourceManager.GetString("TbCoreType", resourceCulture); + } + } + + /// + /// 查找类似 Display GUI 的本地化字符串。 + /// + public static string TbDisplayGUI { + get { + return ResourceManager.GetString("TbDisplayGUI", resourceCulture); + } + } + + /// + /// 查找类似 Display Log 的本地化字符串。 + /// + public static string TbDisplayLog { + get { + return ResourceManager.GetString("TbDisplayLog", resourceCulture); + } + } + + /// + /// 查找类似 Support DnsObject, Click to view the document 的本地化字符串。 + /// + public static string TbDnsObjectDoc { + get { + return ResourceManager.GetString("TbDnsObjectDoc", resourceCulture); + } + } + + /// + /// 查找类似 Please fill in DNS Structure, Click to view the document 的本地化字符串。 + /// + public static string TbDnsSingboxObjectDoc { + get { + return ResourceManager.GetString("TbDnsSingboxObjectDoc", resourceCulture); + } + } + + /// + /// 查找类似 Domain Matcher 的本地化字符串。 + /// + public static string TbdomainMatcher { + get { + return ResourceManager.GetString("TbdomainMatcher", resourceCulture); + } + } + + /// + /// 查找类似 Domain strategy 的本地化字符串。 + /// + public static string TbdomainStrategy { + get { + return ResourceManager.GetString("TbdomainStrategy", resourceCulture); + } + } + + /// + /// 查找类似 sing-box domain strategy 的本地化字符串。 + /// + public static string TbdomainStrategy4Singbox { + get { + return ResourceManager.GetString("TbdomainStrategy4Singbox", resourceCulture); + } + } + + /// + /// 查找类似 Edit 的本地化字符串。 + /// + public static string TbEdit { + get { + return ResourceManager.GetString("TbEdit", resourceCulture); + } + } + + /// + /// 查找类似 Enable advanced function 的本地化字符串。 + /// + public static string TbenableRoutingAdvanced { + get { + return ResourceManager.GetString("TbenableRoutingAdvanced", resourceCulture); + } + } + + /// + /// 查找类似 Enable Tun 的本地化字符串。 + /// + public static string TbEnableTunAs { + get { + return ResourceManager.GetString("TbEnableTunAs", resourceCulture); + } + } + + /// + /// 查找类似 Fingerprint 的本地化字符串。 + /// + public static string TbFingerprint { + get { + return ResourceManager.GetString("TbFingerprint", resourceCulture); + } + } + + /// + /// 查找类似 Flow 的本地化字符串。 + /// + public static string TbFlow5 { + get { + return ResourceManager.GetString("TbFlow5", resourceCulture); + } + } + + /// + /// 查找类似 Global Hotkey Settings 的本地化字符串。 + /// + public static string TbGlobalHotkeySetting { + get { + return ResourceManager.GetString("TbGlobalHotkeySetting", resourceCulture); + } + } + + /// + /// 查找类似 Set directly by pressing the keyboard, take effect after restart 的本地化字符串。 + /// + public static string TbGlobalHotkeySettingTip { + get { + return ResourceManager.GetString("TbGlobalHotkeySettingTip", resourceCulture); + } + } + + /// + /// 查找类似 Generate 的本地化字符串。 + /// + public static string TbGUID { + get { + return ResourceManager.GetString("TbGUID", resourceCulture); + } + } + + /// + /// 查找类似 Camouflage type 的本地化字符串。 + /// + public static string TbHeaderType { + get { + return ResourceManager.GetString("TbHeaderType", resourceCulture); + } + } + + /// + /// 查找类似 Congestion control 的本地化字符串。 + /// + public static string TbHeaderType8 { + get { + return ResourceManager.GetString("TbHeaderType8", resourceCulture); + } + } + + /// + /// 查找类似 UUID(id) 的本地化字符串。 + /// + public static string TbId { + get { + return ResourceManager.GetString("TbId", resourceCulture); + } + } + + /// + /// 查找类似 Password 的本地化字符串。 + /// + public static string TbId3 { + get { + return ResourceManager.GetString("TbId3", resourceCulture); + } + } + + /// + /// 查找类似 Password(Optional) 的本地化字符串。 + /// + public static string TbId4 { + get { + return ResourceManager.GetString("TbId4", resourceCulture); + } + } + + /// + /// 查找类似 UUID(id) 的本地化字符串。 + /// + public static string TbId5 { + get { + return ResourceManager.GetString("TbId5", resourceCulture); + } + } + + /// + /// 查找类似 Address(Ip,Ipv6) 的本地化字符串。 + /// + public static string TbLocalAddress { + get { + return ResourceManager.GetString("TbLocalAddress", resourceCulture); + } + } + + /// + /// 查找类似 Transport protocol(network) 的本地化字符串。 + /// + public static string TbNetwork { + get { + return ResourceManager.GetString("TbNetwork", resourceCulture); + } + } + + /// + /// 查找类似 Do not change system proxy 的本地化字符串。 + /// + public static string TbNotChangeSystemProxy { + get { + return ResourceManager.GetString("TbNotChangeSystemProxy", resourceCulture); + } + } + + /// + /// 查找类似 Path 的本地化字符串。 + /// + public static string TbPath { + get { + return ResourceManager.GetString("TbPath", resourceCulture); + } + } + + /// + /// 查找类似 obfs password 的本地化字符串。 + /// + public static string TbPath7 { + get { + return ResourceManager.GetString("TbPath7", resourceCulture); + } + } + + /// + /// 查找类似 Port 的本地化字符串。 + /// + public static string TbPort { + get { + return ResourceManager.GetString("TbPort", resourceCulture); + } + } + + /// + /// 查找类似 txtPreSocksPort 的本地化字符串。 + /// + public static string TbPreSocksPort { + get { + return ResourceManager.GetString("TbPreSocksPort", resourceCulture); + } + } + + /// + /// 查找类似 PrivateKey 的本地化字符串。 + /// + public static string TbPrivateKey { + get { + return ResourceManager.GetString("TbPrivateKey", resourceCulture); + } + } + + /// + /// 查找类似 Proxies 的本地化字符串。 + /// + public static string TbProxies { + get { + return ResourceManager.GetString("TbProxies", resourceCulture); + } + } + + /// + /// 查找类似 PublicKey 的本地化字符串。 + /// + public static string TbPublicKey { + get { + return ResourceManager.GetString("TbPublicKey", resourceCulture); + } + } + + /// + /// 查找类似 Alias (remarks) 的本地化字符串。 + /// + public static string TbRemarks { + get { + return ResourceManager.GetString("TbRemarks", resourceCulture); + } + } + + /// + /// 查找类似 Camouflage domain(host) 的本地化字符串。 + /// + public static string TbRequestHost { + get { + return ResourceManager.GetString("TbRequestHost", resourceCulture); + } + } + + /// + /// 查找类似 Reserved(2,3,4) 的本地化字符串。 + /// + public static string TbReserved { + get { + return ResourceManager.GetString("TbReserved", resourceCulture); + } + } + + /// + /// 查找类似 Reset 的本地化字符串。 + /// + public static string TbReset { + get { + return ResourceManager.GetString("TbReset", resourceCulture); + } + } + + /// + /// 查找类似 Domain 的本地化字符串。 + /// + public static string TbRoutingRuleDomain { + get { + return ResourceManager.GetString("TbRoutingRuleDomain", resourceCulture); + } + } + + /// + /// 查找类似 IP or IP CIDR 的本地化字符串。 + /// + public static string TbRoutingRuleIP { + get { + return ResourceManager.GetString("TbRoutingRuleIP", resourceCulture); + } + } + + /// + /// 查找类似 Full process name (Tun mode) 的本地化字符串。 + /// + public static string TbRoutingRuleProcess { + get { + return ResourceManager.GetString("TbRoutingRuleProcess", resourceCulture); + } + } + + /// + /// 查找类似 3.Block Domain or IP 的本地化字符串。 + /// + public static string TbRoutingTabBlock { + get { + return ResourceManager.GetString("TbRoutingTabBlock", resourceCulture); + } + } + + /// + /// 查找类似 2.Direct Domain or IP 的本地化字符串。 + /// + public static string TbRoutingTabDirect { + get { + return ResourceManager.GetString("TbRoutingTabDirect", resourceCulture); + } + } + + /// + /// 查找类似 1.Proxy Domain or IP 的本地化字符串。 + /// + public static string TbRoutingTabProxy { + get { + return ResourceManager.GetString("TbRoutingTabProxy", resourceCulture); + } + } + + /// + /// 查找类似 Pre-defined Rule Set List 的本地化字符串。 + /// + public static string TbRoutingTabRuleList { + get { + return ResourceManager.GetString("TbRoutingTabRuleList", resourceCulture); + } + } + + /// + /// 查找类似 *Set the rules, separated by commas (,); The comma in the regular is replaced by <COMMA> 的本地化字符串。 + /// + public static string TbRoutingTips { + get { + return ResourceManager.GetString("TbRoutingTips", resourceCulture); + } + } + + /// + /// 查找类似 (Domain or IP or ProcName) and Port and Protocol and InboundTag => OutboundTag 的本地化字符串。 + /// + public static string TbRuleMatchingTips { + get { + return ResourceManager.GetString("TbRuleMatchingTips", resourceCulture); + } + } + + /// + /// 查找类似 Ruleobject Doc 的本地化字符串。 + /// + public static string TbRuleobjectDoc { + get { + return ResourceManager.GetString("TbRuleobjectDoc", resourceCulture); + } + } + + /// + /// 查找类似 Encryption method (security) 的本地化字符串。 + /// + public static string TbSecurity { + get { + return ResourceManager.GetString("TbSecurity", resourceCulture); + } + } + + /// + /// 查找类似 Encryption 的本地化字符串。 + /// + public static string TbSecurity3 { + get { + return ResourceManager.GetString("TbSecurity3", resourceCulture); + } + } + + /// + /// 查找类似 User(Optional) 的本地化字符串。 + /// + public static string TbSecurity4 { + get { + return ResourceManager.GetString("TbSecurity4", resourceCulture); + } + } + + /// + /// 查找类似 Encryption 的本地化字符串。 + /// + public static string TbSecurity5 { + get { + return ResourceManager.GetString("TbSecurity5", resourceCulture); + } + } + + /// + /// 查找类似 Set system proxy 的本地化字符串。 + /// + public static string TbSetSystemProxy { + get { + return ResourceManager.GetString("TbSetSystemProxy", resourceCulture); + } + } + + /// + /// 查找类似 Click to import default DNS config 的本地化字符串。 + /// + public static string TbSettingDnsImportDefConfig { + get { + return ResourceManager.GetString("TbSettingDnsImportDefConfig", resourceCulture); + } + } + + /// + /// 查找类似 Advanced proxy settings, protocol selection (optional) 的本地化字符串。 + /// + public static string TbSettingsAdvancedProtocol { + get { + return ResourceManager.GetString("TbSettingsAdvancedProtocol", resourceCulture); + } + } + + /// + /// 查找类似 Allow connections from the LAN 的本地化字符串。 + /// + public static string TbSettingsAllowLAN { + get { + return ResourceManager.GetString("TbSettingsAllowLAN", resourceCulture); + } + } + + /// + /// 查找类似 Auto hide startup 的本地化字符串。 + /// + public static string TbSettingsAutoHideStartup { + get { + return ResourceManager.GetString("TbSettingsAutoHideStartup", resourceCulture); + } + } + + /// + /// 查找类似 Automatic update interval of Geo (hours) 的本地化字符串。 + /// + public static string TbSettingsAutoUpdateInterval { + get { + return ResourceManager.GetString("TbSettingsAutoUpdateInterval", resourceCulture); + } + } + + /// + /// 查找类似 Color 的本地化字符串。 + /// + public static string TbSettingsColor { + get { + return ResourceManager.GetString("TbSettingsColor", resourceCulture); + } + } + + /// + /// 查找类似 Dark Mode 的本地化字符串。 + /// + public static string TbSettingsColorMode { + get { + return ResourceManager.GetString("TbSettingsColorMode", resourceCulture); + } + } + + /// + /// 查找类似 Core: basic settings 的本地化字符串。 + /// + public static string TbSettingsCore { + get { + return ResourceManager.GetString("TbSettingsCore", resourceCulture); + } + } + + /// + /// 查找类似 V2ray DNS settings 的本地化字符串。 + /// + public static string TbSettingsCoreDns { + get { + return ResourceManager.GetString("TbSettingsCoreDns", resourceCulture); + } + } + + /// + /// 查找类似 sing-box DNS settings 的本地化字符串。 + /// + public static string TbSettingsCoreDnsSingbox { + get { + return ResourceManager.GetString("TbSettingsCoreDnsSingbox", resourceCulture); + } + } + + /// + /// 查找类似 Core: KCP settings 的本地化字符串。 + /// + public static string TbSettingsCoreKcp { + get { + return ResourceManager.GetString("TbSettingsCoreKcp", resourceCulture); + } + } + + /// + /// 查找类似 CoreType settings 的本地化字符串。 + /// + public static string TbSettingsCoreType { + get { + return ResourceManager.GetString("TbSettingsCoreType", resourceCulture); + } + } + + /// + /// 查找类似 FontFamily(Require restart) 的本地化字符串。 + /// + public static string TbSettingsCurrentFontFamily { + get { + return ResourceManager.GetString("TbSettingsCurrentFontFamily", resourceCulture); + } + } + + /// + /// 查找类似 Copy the font TTF/TTC file to the directory guiFonts, restart the settings 的本地化字符串。 + /// + public static string TbSettingsCurrentFontFamilyTip { + get { + return ResourceManager.GetString("TbSettingsCurrentFontFamilyTip", resourceCulture); + } + } + + /// + /// 查找类似 AllowInsecure 的本地化字符串。 + /// + public static string TbSettingsDefAllowInsecure { + get { + return ResourceManager.GetString("TbSettingsDefAllowInsecure", resourceCulture); + } + } + + /// + /// 查找类似 Default TLS fingerprint 的本地化字符串。 + /// + public static string TbSettingsDefFingerprint { + get { + return ResourceManager.GetString("TbSettingsDefFingerprint", resourceCulture); + } + } + + /// + /// 查找类似 User-Agent 的本地化字符串。 + /// + public static string TbSettingsDefUserAgent { + get { + return ResourceManager.GetString("TbSettingsDefUserAgent", resourceCulture); + } + } + + /// + /// 查找类似 This parameter is valid only for tcp/http and ws 的本地化字符串。 + /// + public static string TbSettingsDefUserAgentTips { + get { + return ResourceManager.GetString("TbSettingsDefUserAgentTips", resourceCulture); + } + } + + /// + /// 查找类似 Outbound Freedom domainStrategy 的本地化字符串。 + /// + public static string TbSettingsDomainStrategy4Freedom { + get { + return ResourceManager.GetString("TbSettingsDomainStrategy4Freedom", resourceCulture); + } + } + + /// + /// 查找类似 Double-click server make active 的本地化字符串。 + /// + public static string TbSettingsDoubleClick2Activate { + get { + return ResourceManager.GetString("TbSettingsDoubleClick2Activate", resourceCulture); + } + } + + /// + /// 查找类似 Automatically adjust column width after updating subscription 的本地化字符串。 + /// + public static string TbSettingsEnableAutoAdjustMainLvColWidth { + get { + return ResourceManager.GetString("TbSettingsEnableAutoAdjustMainLvColWidth", resourceCulture); + } + } + + /// + /// 查找类似 Enable cache file for sing-box (ruleset files) 的本地化字符串。 + /// + public static string TbSettingsEnableCacheFile4Sbox { + get { + return ResourceManager.GetString("TbSettingsEnableCacheFile4Sbox", resourceCulture); + } + } + + /// + /// 查找类似 Check for pre-release updates 的本地化字符串。 + /// + public static string TbSettingsEnableCheckPreReleaseUpdate { + get { + return ResourceManager.GetString("TbSettingsEnableCheckPreReleaseUpdate", resourceCulture); + } + } + + /// + /// 查找类似 Enable Server Drag Drop Sort(Require restart) 的本地化字符串。 + /// + public static string TbSettingsEnableDragDropSort { + get { + return ResourceManager.GetString("TbSettingsEnableDragDropSort", resourceCulture); + } + } + + /// + /// 查找类似 Enable additional Inbound 的本地化字符串。 + /// + public static string TbSettingsEnableExInbound { + get { + return ResourceManager.GetString("TbSettingsEnableExInbound", resourceCulture); + } + } + + /// + /// 查找类似 Enable fragment 的本地化字符串。 + /// + public static string TbSettingsEnableFragment { + get { + return ResourceManager.GetString("TbSettingsEnableFragment", resourceCulture); + } + } + + /// + /// 查找类似 Use Xray and enable non-Tun mode, which conflicts with the group previous proxy 的本地化字符串。 + /// + public static string TbSettingsEnableFragmentTips { + get { + return ResourceManager.GetString("TbSettingsEnableFragmentTips", resourceCulture); + } + } + + /// + /// 查找类似 Enable hardware acceleration(Require restart) 的本地化字符串。 + /// + public static string TbSettingsEnableHWA { + get { + return ResourceManager.GetString("TbSettingsEnableHWA", resourceCulture); + } + } + + /// + /// 查找类似 Enable IPv6 Address 的本地化字符串。 + /// + public static string TbSettingsEnableIPv6Address { + get { + return ResourceManager.GetString("TbSettingsEnableIPv6Address", resourceCulture); + } + } + + /// + /// 查找类似 Updating subscription, only determine remarks exists 的本地化字符串。 + /// + public static string TbSettingsEnableUpdateSubOnlyRemarksExist { + get { + return ResourceManager.GetString("TbSettingsEnableUpdateSubOnlyRemarksExist", resourceCulture); + } + } + + /// + /// 查找类似 Exception 的本地化字符串。 + /// + public static string TbSettingsException { + get { + return ResourceManager.GetString("TbSettingsException", resourceCulture); + } + } + + /// + /// 查找类似 Exception. Do not use proxy server for addresses beginning with,Use semicolon (;) 的本地化字符串。 + /// + public static string TbSettingsExceptionTip { + get { + return ResourceManager.GetString("TbSettingsExceptionTip", resourceCulture); + } + } + + /// + /// 查找类似 Follow System Theme 的本地化字符串。 + /// + public static string TbSettingsFollowSystemTheme { + get { + return ResourceManager.GetString("TbSettingsFollowSystemTheme", resourceCulture); + } + } + + /// + /// 查找类似 Font Size 的本地化字符串。 + /// + public static string TbSettingsFontSize { + get { + return ResourceManager.GetString("TbSettingsFontSize", resourceCulture); + } + } + + /// + /// 查找类似 HTTP Port 的本地化字符串。 + /// + public static string TbSettingsHttpPort { + get { + return ResourceManager.GetString("TbSettingsHttpPort", resourceCulture); + } + } + + /// + /// 查找类似 Hysteria Max bandwidth (Up/Dw) 的本地化字符串。 + /// + public static string TbSettingsHysteriaBandwidth { + get { + return ResourceManager.GetString("TbSettingsHysteriaBandwidth", resourceCulture); + } + } + + /// + /// 查找类似 Ignore Geo files when updating core 的本地化字符串。 + /// + public static string TbSettingsIgnoreGeoUpdateCore { + get { + return ResourceManager.GetString("TbSettingsIgnoreGeoUpdateCore", resourceCulture); + } + } + + /// + /// 查找类似 Keep older when deduplication 的本地化字符串。 + /// + public static string TbSettingsKeepOlderDedupl { + get { + return ResourceManager.GetString("TbSettingsKeepOlderDedupl", resourceCulture); + } + } + + /// + /// 查找类似 Language (Restart) 的本地化字符串。 + /// + public static string TbSettingsLanguage { + get { + return ResourceManager.GetString("TbSettingsLanguage", resourceCulture); + } + } + + /// + /// 查找类似 Enable Log 的本地化字符串。 + /// + public static string TbSettingsLogEnabled { + get { + return ResourceManager.GetString("TbSettingsLogEnabled", resourceCulture); + } + } + + /// + /// 查找类似 Enable logging to file 的本地化字符串。 + /// + public static string TbSettingsLogEnabledToFile { + get { + return ResourceManager.GetString("TbSettingsLogEnabledToFile", resourceCulture); + } + } + + /// + /// 查找类似 Log Level 的本地化字符串。 + /// + public static string TbSettingsLogLevel { + get { + return ResourceManager.GetString("TbSettingsLogLevel", resourceCulture); + } + } + + /// + /// 查找类似 sing-box Mux Protocol 的本地化字符串。 + /// + public static string TbSettingsMux4SboxProtocol { + get { + return ResourceManager.GetString("TbSettingsMux4SboxProtocol", resourceCulture); + } + } + + /// + /// 查找类似 Turn on Mux Multiplexing 的本地化字符串。 + /// + public static string TbSettingsMuxEnabled { + get { + return ResourceManager.GetString("TbSettingsMuxEnabled", resourceCulture); + } + } + + /// + /// 查找类似 v2rayN settings 的本地化字符串。 + /// + public static string TbSettingsN { + get { + return ResourceManager.GetString("TbSettingsN", resourceCulture); + } + } + + /// + /// 查找类似 New Port for LAN 的本地化字符串。 + /// + public static string TbSettingsNewPort4LAN { + get { + return ResourceManager.GetString("TbSettingsNewPort4LAN", resourceCulture); + } + } + + /// + /// 查找类似 Auth pass 的本地化字符串。 + /// + public static string TbSettingsPass { + get { + return ResourceManager.GetString("TbSettingsPass", resourceCulture); + } + } + + /// + /// 查找类似 Custom DNS (multiple, separated by commas (,)) 的本地化字符串。 + /// + public static string TbSettingsRemoteDNS { + get { + return ResourceManager.GetString("TbSettingsRemoteDNS", resourceCulture); + } + } + + /// + /// 查找类似 RouteOnly 的本地化字符串。 + /// + public static string TbSettingsRouteOnly { + get { + return ResourceManager.GetString("TbSettingsRouteOnly", resourceCulture); + } + } + + /// + /// 查找类似 Set Win10 UWP Loopback 的本地化字符串。 + /// + public static string TbSettingsSetUWP { + get { + return ResourceManager.GetString("TbSettingsSetUWP", resourceCulture); + } + } + + /// + /// 查找类似 Turn on Sniffing 的本地化字符串。 + /// + public static string TbSettingsSniffingEnabled { + get { + return ResourceManager.GetString("TbSettingsSniffingEnabled", resourceCulture); + } + } + + /// + /// 查找类似 SOCKS Port 的本地化字符串。 + /// + public static string TbSettingsSocksPort { + get { + return ResourceManager.GetString("TbSettingsSocksPort", resourceCulture); + } + } + + /// + /// 查找类似 http port = +1; Pac port = +4; *ray API port = +5; mihomo API port = +6; 的本地化字符串。 + /// + public static string TbSettingsSocksPortTip { + get { + return ResourceManager.GetString("TbSettingsSocksPortTip", resourceCulture); + } + } + + /// + /// 查找类似 Speed Ping Test URL 的本地化字符串。 + /// + public static string TbSettingsSpeedPingTestUrl { + get { + return ResourceManager.GetString("TbSettingsSpeedPingTestUrl", resourceCulture); + } + } + + /// + /// 查找类似 SpeedTest Single Timeout Value 的本地化字符串。 + /// + public static string TbSettingsSpeedTestTimeout { + get { + return ResourceManager.GetString("TbSettingsSpeedTestTimeout", resourceCulture); + } + } + + /// + /// 查找类似 SpeedTest URL 的本地化字符串。 + /// + public static string TbSettingsSpeedTestUrl { + get { + return ResourceManager.GetString("TbSettingsSpeedTestUrl", resourceCulture); + } + } + + /// + /// 查找类似 Start on boot 的本地化字符串。 + /// + public static string TbSettingsStartBoot { + get { + return ResourceManager.GetString("TbSettingsStartBoot", resourceCulture); + } + } + + /// + /// 查找类似 Set this with admin privileges, get admin privileges after startup 的本地化字符串。 + /// + public static string TbSettingsStartBootTip { + get { + return ResourceManager.GetString("TbSettingsStartBootTip", resourceCulture); + } + } + + /// + /// 查找类似 Enable Statistics (Require restart) 的本地化字符串。 + /// + public static string TbSettingsStatistics { + get { + return ResourceManager.GetString("TbSettingsStatistics", resourceCulture); + } + } + + /// + /// 查找类似 Subscription conversion URL 的本地化字符串。 + /// + public static string TbSettingsSubConvert { + get { + return ResourceManager.GetString("TbSettingsSubConvert", resourceCulture); + } + } + + /// + /// 查找类似 System proxy settings 的本地化字符串。 + /// + public static string TbSettingsSystemproxy { + get { + return ResourceManager.GetString("TbSettingsSystemproxy", resourceCulture); + } + } + + /// + /// 查找类似 Enable Security Protocol TLS v1.3 (subscription/update) 的本地化字符串。 + /// + public static string TbSettingsTLS13 { + get { + return ResourceManager.GetString("TbSettingsTLS13", resourceCulture); + } + } + + /// + /// 查找类似 Tray right-click menu servers display limit 的本地化字符串。 + /// + public static string TbSettingsTrayMenuServersLimit { + get { + return ResourceManager.GetString("TbSettingsTrayMenuServersLimit", resourceCulture); + } + } + + /// + /// 查找类似 TunMode settings 的本地化字符串。 + /// + public static string TbSettingsTunMode { + get { + return ResourceManager.GetString("TbSettingsTunMode", resourceCulture); + } + } + + /// + /// 查找类似 Bypass Mode 的本地化字符串。 + /// + public static string TbSettingsTunModeBypassMode { + get { + return ResourceManager.GetString("TbSettingsTunModeBypassMode", resourceCulture); + } + } + + /// + /// 查找类似 Custom Template 的本地化字符串。 + /// + public static string TbSettingsTunModeCustomTemplate { + get { + return ResourceManager.GetString("TbSettingsTunModeCustomTemplate", resourceCulture); + } + } + + /// + /// 查找类似 Direct IP CIDR, separated by commas (,) 的本地化字符串。 + /// + public static string TbSettingsTunModeDirectIP { + get { + return ResourceManager.GetString("TbSettingsTunModeDirectIP", resourceCulture); + } + } + + /// + /// 查找类似 Direct Process name, separated by commas (,) 的本地化字符串。 + /// + public static string TbSettingsTunModeDirectProcess { + get { + return ResourceManager.GetString("TbSettingsTunModeDirectProcess", resourceCulture); + } + } + + /// + /// 查找类似 DNS object, e.g. {"servers":[]} 的本地化字符串。 + /// + public static string TbSettingsTunModeDNS { + get { + return ResourceManager.GetString("TbSettingsTunModeDNS", resourceCulture); + } + } + + /// + /// 查找类似 Proxy IP CIDR, separated by commas (,) 的本地化字符串。 + /// + public static string TbSettingsTunModeProxyIP { + get { + return ResourceManager.GetString("TbSettingsTunModeProxyIP", resourceCulture); + } + } + + /// + /// 查找类似 Proxy Process name, separated by commas (,) 的本地化字符串。 + /// + public static string TbSettingsTunModeProxyProcess { + get { + return ResourceManager.GetString("TbSettingsTunModeProxyProcess", resourceCulture); + } + } + + /// + /// 查找类似 Show console 的本地化字符串。 + /// + public static string TbSettingsTunModeShowWindow { + get { + return ResourceManager.GetString("TbSettingsTunModeShowWindow", resourceCulture); + } + } + + /// + /// 查找类似 Enable UDP 的本地化字符串。 + /// + public static string TbSettingsUdpEnabled { + get { + return ResourceManager.GetString("TbSettingsUdpEnabled", resourceCulture); + } + } + + /// + /// 查找类似 Auth user 的本地化字符串。 + /// + public static string TbSettingsUser { + get { + return ResourceManager.GetString("TbSettingsUser", resourceCulture); + } + } + + /// + /// 查找类似 Use System Hosts 的本地化字符串。 + /// + public static string TbSettingsUseSystemHosts { + get { + return ResourceManager.GetString("TbSettingsUseSystemHosts", resourceCulture); + } + } + + /// + /// 查找类似 ShortId 的本地化字符串。 + /// + public static string TbShortId { + get { + return ResourceManager.GetString("TbShortId", resourceCulture); + } + } + + /// + /// 查找类似 SNI 的本地化字符串。 + /// + public static string TbSNI { + get { + return ResourceManager.GetString("TbSNI", resourceCulture); + } + } + + /// + /// 查找类似 Sorting 的本地化字符串。 + /// + public static string TbSorting { + get { + return ResourceManager.GetString("TbSorting", resourceCulture); + } + } + + /// + /// 查找类似 Chain 的本地化字符串。 + /// + public static string TbSortingChain { + get { + return ResourceManager.GetString("TbSortingChain", resourceCulture); + } + } + + /// + /// 查找类似 Default 的本地化字符串。 + /// + public static string TbSortingDefault { + get { + return ResourceManager.GetString("TbSortingDefault", resourceCulture); + } + } + + /// + /// 查找类似 Delay 的本地化字符串。 + /// + public static string TbSortingDelay { + get { + return ResourceManager.GetString("TbSortingDelay", resourceCulture); + } + } + + /// + /// 查找类似 Download Speed 的本地化字符串。 + /// + public static string TbSortingDownSpeed { + get { + return ResourceManager.GetString("TbSortingDownSpeed", resourceCulture); + } + } + + /// + /// 查找类似 Download Traffic 的本地化字符串。 + /// + public static string TbSortingDownTraffic { + get { + return ResourceManager.GetString("TbSortingDownTraffic", resourceCulture); + } + } + + /// + /// 查找类似 Host 的本地化字符串。 + /// + public static string TbSortingHost { + get { + return ResourceManager.GetString("TbSortingHost", resourceCulture); + } + } + + /// + /// 查找类似 Name 的本地化字符串。 + /// + public static string TbSortingName { + get { + return ResourceManager.GetString("TbSortingName", resourceCulture); + } + } + + /// + /// 查找类似 Network 的本地化字符串。 + /// + public static string TbSortingNetwork { + get { + return ResourceManager.GetString("TbSortingNetwork", resourceCulture); + } + } + + /// + /// 查找类似 Time 的本地化字符串。 + /// + public static string TbSortingTime { + get { + return ResourceManager.GetString("TbSortingTime", resourceCulture); + } + } + + /// + /// 查找类似 Type 的本地化字符串。 + /// + public static string TbSortingType { + get { + return ResourceManager.GetString("TbSortingType", resourceCulture); + } + } + + /// + /// 查找类似 Upload Speed 的本地化字符串。 + /// + public static string TbSortingUpSpeed { + get { + return ResourceManager.GetString("TbSortingUpSpeed", resourceCulture); + } + } + + /// + /// 查找类似 Upload Traffic 的本地化字符串。 + /// + public static string TbSortingUpTraffic { + get { + return ResourceManager.GetString("TbSortingUpTraffic", resourceCulture); + } + } + + /// + /// 查找类似 SpiderX 的本地化字符串。 + /// + public static string TbSpiderX { + get { + return ResourceManager.GetString("TbSpiderX", resourceCulture); + } + } + + /// + /// 查找类似 TLS 的本地化字符串。 + /// + public static string TbStreamSecurity { + get { + return ResourceManager.GetString("TbStreamSecurity", resourceCulture); + } + } + + /// + /// 查找类似 PAC mode 的本地化字符串。 + /// + public static string TbSystemProxyPac { + get { + return ResourceManager.GetString("TbSystemProxyPac", resourceCulture); + } + } + + /// + /// 查找类似 The ping of current service: {0} ms 的本地化字符串。 + /// + public static string TestMeOutput { + get { + return ResourceManager.GetString("TestMeOutput", resourceCulture); + } + } + + /// + /// 查找类似 Routing setting is changed 的本地化字符串。 + /// + public static string TipChangeRouting { + get { + return ResourceManager.GetString("TipChangeRouting", resourceCulture); + } + } + + /// + /// 查找类似 System proxy setting is changed 的本地化字符串。 + /// + public static string TipChangeSystemProxy { + get { + return ResourceManager.GetString("TipChangeSystemProxy", resourceCulture); + } + } + + /// + /// 查找类似 Please turn off when there is an abnormal disconnection 的本地化字符串。 + /// + public static string TipDisplayLog { + get { + return ResourceManager.GetString("TipDisplayLog", resourceCulture); + } + } + + /// + /// 查找类似 *Default value tcp 的本地化字符串。 + /// + public static string TipNetwork { + get { + return ResourceManager.GetString("TipNetwork", resourceCulture); + } + } + + /// + /// 查找类似 * After setting this value, an socks service will be started using Xray/sing-box(Tun) to provide functions such as speed display 的本地化字符串。 + /// + public static string TipPreSocksPort { + get { + return ResourceManager.GetString("TipPreSocksPort", resourceCulture); + } + } + + /// + /// 查找类似 Too many servers, please open the main interface 的本地化字符串。 + /// + public static string TooManyServersTip { + get { + return ResourceManager.GetString("TooManyServersTip", resourceCulture); + } + } + + /// + /// 查找类似 *tcp camouflage type 的本地化字符串。 + /// + public static string TransportHeaderTypeTip1 { + get { + return ResourceManager.GetString("TransportHeaderTypeTip1", resourceCulture); + } + } + + /// + /// 查找类似 *kcp camouflage type 的本地化字符串。 + /// + public static string TransportHeaderTypeTip2 { + get { + return ResourceManager.GetString("TransportHeaderTypeTip2", resourceCulture); + } + } + + /// + /// 查找类似 *QUIC camouflage type 的本地化字符串。 + /// + public static string TransportHeaderTypeTip3 { + get { + return ResourceManager.GetString("TransportHeaderTypeTip3", resourceCulture); + } + } + + /// + /// 查找类似 *grpc mode 的本地化字符串。 + /// + public static string TransportHeaderTypeTip4 { + get { + return ResourceManager.GetString("TransportHeaderTypeTip4", resourceCulture); + } + } + + /// + /// 查找类似 *ws/httpupgrade/splithttp path 的本地化字符串。 + /// + public static string TransportPathTip1 { + get { + return ResourceManager.GetString("TransportPathTip1", resourceCulture); + } + } + + /// + /// 查找类似 *h2 path 的本地化字符串。 + /// + public static string TransportPathTip2 { + get { + return ResourceManager.GetString("TransportPathTip2", resourceCulture); + } + } + + /// + /// 查找类似 *QUIC key/KCP seed 的本地化字符串。 + /// + public static string TransportPathTip3 { + get { + return ResourceManager.GetString("TransportPathTip3", resourceCulture); + } + } + + /// + /// 查找类似 *grpc serviceName 的本地化字符串。 + /// + public static string TransportPathTip4 { + get { + return ResourceManager.GetString("TransportPathTip4", resourceCulture); + } + } + + /// + /// 查找类似 *kcp seed 的本地化字符串。 + /// + public static string TransportPathTip5 { + get { + return ResourceManager.GetString("TransportPathTip5", resourceCulture); + } + } + + /// + /// 查找类似 *http host Separated by commas (,) 的本地化字符串。 + /// + public static string TransportRequestHostTip1 { + get { + return ResourceManager.GetString("TransportRequestHostTip1", resourceCulture); + } + } + + /// + /// 查找类似 *ws/httpupgrade/splithttp host 的本地化字符串。 + /// + public static string TransportRequestHostTip2 { + get { + return ResourceManager.GetString("TransportRequestHostTip2", resourceCulture); + } + } + + /// + /// 查找类似 *h2 host Separated by commas (,) 的本地化字符串。 + /// + public static string TransportRequestHostTip3 { + get { + return ResourceManager.GetString("TransportRequestHostTip3", resourceCulture); + } + } + + /// + /// 查找类似 *QUIC security 的本地化字符串。 + /// + public static string TransportRequestHostTip4 { + get { + return ResourceManager.GetString("TransportRequestHostTip4", resourceCulture); + } + } + + /// + /// 查找类似 *grpc Authority 的本地化字符串。 + /// + public static string TransportRequestHostTip5 { + get { + return ResourceManager.GetString("TransportRequestHostTip5", resourceCulture); + } + } + + /// + /// 查找类似 Ungrouped 的本地化字符串。 + /// + public static string UngroupedServers { + get { + return ResourceManager.GetString("UngroupedServers", resourceCulture); + } + } + } +} diff --git a/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.fa-Ir.resx b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.fa-Ir.resx new file mode 100644 index 00000000..c3441f4a --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.fa-Ir.resx @@ -0,0 +1,1081 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + صادرات دسته ای محتوای اشتراک به کلیپ بورد با موفقیت انجام شد + + + Batch export share URL to clipboard successfully + + + لطفا ابتدا تنظیمات سرور را بررسی کنید + + + فرمت پیکربندی نادرست است + + + توجه داشته باشید که پیکربندی سفارشی کاملاً به پیکربندی خود شما بستگی دارد و با همه تنظیمات کار نمی کند. اگر می خواهید از پروکسی سیستم استفاده کنید، لطفاً پورت درحال شنود را به صورت دستی تغییر دهید. + + + درحال دانلود... + + + دانلود + + + Whether to download? {0} + + + تبدیل فایل پیکربندی انجام نشد + + + فایل پیکربندی پیش فرض ایجاد نشد + + + پیکربندی پیش فرض دریافت نشد + + + سرور پیکربندی سفارشی وارد نشد + + + فایل پیکربندی خوانده نشد + + + لطفا فرمت صحیح پورت سرور را پر کنید + + + لطفاً پارامترهای KCP را به درستی پر کنید + + + لطفاً پورت گوش دادن محلی را پر کنید + + + لطفا رمز عبور را وارد کنید + + + لطفا آدرس سرور را وارد کنید + + + لطفا شناسه کاربری را وارد کنید + + + فایل پیکربندی مشتری صحیح نیست، لطفا بررسی کنید + + + پیکربندی درستی نیست، لطفا بررسی کنید + + + فایل پیکربندی سرور صحیح نیست، لطفا بررسی کنید + + + پیکربندی اولیه + + + {0} {1} در حال حاضر به روز است. + + + {0} {1} در حال حاضر به روز است. + + + آدرس + + + امنیت + + + پورت + + + نوع + + + گروه فرعی + + + ترافیک دانلود امروز + + + ترافیک اپلود امروز + + + کل ترافیک دانلود + + + کل ترافیک آپلود + + + جابجایی + + + محتوای اشتراک اصلی را پاک کنید + + + دانلود Core با موفقیت + + + محتوای اشتراک وارد نشد + + + محتوای اشتراک با موفقیت دریافت شد + + + هیچ اشتراک معتبری تنظیم نشده است + + + Resolve {0} successfully + + + شروع به دریافت اشتراک شد + + + شروع بروزرسانی {0}... + + + محتوای اشتراک نامعتبر است + + + در حال باز کردن بسته می باشد ... + + + بروزرسانی اشتراک به پایان رسید + + + بروزرسانی اشتراک شروع شد + + + هسته با موفقیت بروزرسانی شد + + + هسته با موفقیت بروزرسانی شد! راه اندازی مجدد سرویس... + + + Non-VMess or ss protocol + + + non-standard service, this feature is invalid + + + The Core file (file name: {1}) was not found under the folder ({0}), please download and put it in the folder, download address: {2} + + + Scan completed, no valid QR code found + + + عملیات انجام نشد، لطفا بررسی کنید و دوباره امتحان کنید + + + Please Fill Remarks + + + لطفاً روش رمزگذاری را انتخاب کنید + + + لطفا یک پروتکل انتخاب کنید + + + لطفا ابتدا سرور را انتخاب کنید + + + Servers deduplication completed. Old: {0}, New: {1}. + + + آیا مطمئن هستید که سرور را حذف می کنید؟ + + + The client configuration file is saved at: {0} + + + Start service ({0})... + + + پیکربندی با موفقیت انجام شد +{0} + + + سرور پیکربندی سفارشی با موفقیت وارد شد. + + + {0} سرورها از کلیپ بورد وارد شده اند. + + + اسکن URL وارد کردن با موفقیت + + + پینگ سرویس فعلی: {0} ms + + + موفقیت عملیات + + + لطفا قوانین را انتخاب کنید + + + آیا مطمئن هستید که قوانین را حذف می کنید؟ + + + {0},یکی از مورد نیاز. + + + Remarks + + + Url(Optional) + + + Count + + + Please fill in the address (Url) + + + Do you want to append rules? Choose yes to append, choose otherwise to replace + + + دانلود GeoFile: {0} با موفقیت + + + اطلاعات + + + نماد سفارشی + + + لطفاً DNS سفارشی صحیح را پر کنید + + + *ws path + + + *h2 path + + + *QUIC key/Kcp seed + + + *grpc serviceName + + + *http host Separated by commas (,) + + + *ws host + + + *h2 host Separated by commas (,) + + + *QUIC securty + + + *tcp camouflage type + + + *kcp camouflage type + + + *QUIC camouflage type + + + *grpc mode + + + TLS + + + *Kcp seed + + + Global hotkey {0} registered failed, reason {1} + + + Global hotkey {0} registered successfully + + + گروه بندی نشده + + + همه سرورها + + + Please browse to import server configuration + + + پروکسی سیستم + + + درحال تست کردن... + + + تعداد سرورها خیلی زیاد است، لطفا رابط اصلی را باز کنید + + + LAN + + + محلی + + + فیلتر سرورها + + + بررسی بروزرسانی + + + بستن + + + خروج + + + تنظیم کلید میانبر کلی + + + کمک + + + تنظیمات پارامتر + + + ترفیع + + + بارگذاری مجدد + + + تنظیم مسیریابی + + + سرورها + + + تنظیمات + + + اشتراک فعلی را بدون پروکسی به روز شود + + + اشتراک فعلی را با پروکسی به روز شود + + + گروه اشتراک + + + تنظیم گروه اشتراک + + + اشتراک را بدون پروکسی به روز شود + + + اشتراک را با پروکسی به روز شود + + + پروکسی سیستم + + + پاک کردن پروکسی سیستم + + + پروکسی سیستم تغییر نکند + + + حالت Pac + + + تنظیم پراکسی سیستم + + + رنگ + + + حالت تاریک + + + زبان + + + وارد کردن URL انبوه از کلیپ بورد (Ctrl+V) + + + اسکن کد QR روی صفحه (Ctrl+S) + + + سرور انتخاب شده را شبیه سازی کنید + + + سرورهای تکراری را حذف کنید + + + حذف سرورهای انتخابی (Delete) + + + به عنوان سرور فعال تنظیم کنید (Enter) + + + تمام آمار خدمات را پاک کنید + + + آزمایش سرورها با تاخیر واقعی (Ctrl+R) + + + مرتب سازی بر اساس نتیجه تست + + + تست سرعت دانلود سرورها (Ctrl+T) + + + تست سرورها با tcping (Ctrl+O) + + + وضعیت سرویس فعلی را تست کنید + + + سرور انتخابی را برای پیکربندی کلاینت صادر کنید + + + URL های اشتراک گذاری را به کلیپ بورد صادر کنید (Ctrl+C) + + + اشتراک (base64) را به کلیپ بورد صادر کنید + + + یک سرور پیکربندی سفارشی اضافه شود + + + سرور [شادوساکس] را اضافه کنید + + + سرور [ساکس] را اضافه کنید + + + سرور [تروجان] را اضافه کنید + + + سرور [VLESS] را اضافه کنید + + + سرور [VMess] را اضافه کنید + + + انتخاب همه (Ctrl+A) + + + همه را پاک کن + + + کپی (Ctrl+C) + + + کپی همه + + + فیلترهای پیام را تنظیم کنید + + + انتخاب همه (Ctrl+A) + + + اضافه کردن + + + حذف + + + ویرایش + + + اشتراک + + + به روز رسانی فعال شد + + + مرتب سازی + + + عامل کاربر + + + لغو + + + تایید + + + انتقال + + + آدرس + + + اجازه ناامن + + + Alpn + + + AlterId + + + Fingerprint + + + Camouflage type + + + UUID(id) + + + پروتکل جابجایی (شبکه) + + + مسیر + + + پورت + + + Alias (remarks) + + + Camouflage domain(host) + + + Encryption method (security) + + + SNI + + + TLS + + + *Default value tcp + + + نوع هسته + + + جریان + + + Generate + + + رمزعبور + + + رمز عبور (اختیاری) + + + UUID(id) + + + رمزگذاری + + + کاربر (اختیاری) + + + رمزگذاری + + + txtPreSocksPort + + + * After setting this value, an socks service will be started using Xray/sing-box(Tun) to provide functions such as speed display + + + Browse + + + ویرایش + + + تنظیمات پیشرفته پروکسی، انتخاب پروتکل (اختیاری) + + + اتصالات از LAN را مجاز کنید + + + Auto hide startup + + + فاصله به روز رسانی خودکار و Geo (ساعت) + + + هسته: تنظیمات اولیه + + + V2ray DNS settings + + + هسته: تنظیمات KCP + + + تنظیمات CoreType + + + اجازه ناامن + + + Outbound Freedom domainStrategy + + + پس از به‌روزرسانی اشتراک، عرض ستون را به صورت خودکار تنظیم شود + + + به روز رسانی های پیش از انتشار را بررسی شود + + + استثنا + + + Exception. Do not use proxy server for addresses beginning with,Use semicolon (;) + + + پورت Http + + + هنگام به‌روزرسانی هسته، فایل‌های Geo را نادیده بگیرید + + + Keep older when deduplication + + + ثبت گزارش های محلی + + + سطح ثبت رویداد + + + Turn on Mux Multiplexing + + + تنظیمات v2rayN + + + Auth pass + + + سفارشی DNS (multiple, separated by commas (,)) + + + Set Win10 UWP Loopback + + + Turn on Sniffing + + + ساکس Port + + + درهنگام راه ائدازی شروع شود + + + فعال کردن آمار (نیاز به راه اندازی مجدد) + + + Subscription conversion Url + + + تنظیمات پراکسی سیستم + + + Enable Security Protocol TLS v1.3 (subscription/update) + + + Tray right-click menu servers display limit + + + UDP را فعال شود + + + Auth user + + + پاک کردن پروکسی سیستم + + + نمایش رابط کاربری گرافیکی + + + تنظیم کلید میانبر جهانی + + + مستقیماً با فشار دادن صفحه کلید تنظیم کنید، بعد از راه اندازی مجدد اعمال شود + + + پروکسی سیستم را تغییر ندهید + + + بازنشانی + + + تنظیم پراکسی سیستم + + + حالت Pac + + + اشتراک گذاری سرور(Ctrl+F) + + + مسیریابی + + + به عنوان ادمین اجرا نمی شود + + + اجرا به عنوان ادمین + + + به پایین حرکت شود(B) + + + پایین (D) + + + Move to top (T) + + + Up (U) + + + Filter, support regular + + + {0} Website + + + عملکرد پیشرفته + + + اضافه کردن + + + وارد کردن قوانین پیشرفته + + + حذف انتخاب شده + + + Set as active rule + + + عملکرد پایه + + + واردات قوانین اساسی + + + تطبیق دامنه + + + استراتژی دامنه + + + فعال کردن عملکرد پیشرفته + + + 3.Block Domain or IP + + + 2.Direct Domain or IP + + + 1.Proxy Domain or IP + + + لیست مجموعه قوانین از پیش تعریف شده + + + *Set the rules, separated by commas (,); The comma in the regular is replaced by <COMMA> + + + وارد کردن قوانین از کلیپ بورد + + + وارد کردن قوانین از فایل + + + وارد کردن قوانین از Sub Url + + + تنظیم قانون + + + اضافه کردن قانون + + + صادر کردن قوانین انتخاب شده + + + فهرست قوانین + + + حذف قوانین + + + تنظیم جزئیات قانون مسیریابی + + + دامنه و آی پی در هنگام ذخیره به طور خودکار مرتب می شوند + + + Ruleobject Doc + + + پشتیبانی از DnsObject + + + گروه لطفا اینجا را خالی بگذارید + + + تنظیمات مسیریابی تغییر کرده است + + + تنظیمات پراکسی سیستم تغییر کرده است + + + فقط مسیر + + + One-click test Latency and speed (Ctrl+E) + + + تاخیر (میلی‌ثانیه) + + + Speed(M/s) + + + Core اجرا نشد، لطفاً گزارش را ببینید + + + Remarks regular filter + + + نمایش گزارش + + + پیکربندی قدیمی guiNConfig را وارد شود + + + Tun را فعال شود + + + پورت جدید برای LAN + + + تنظیمات TunMode + + + Direct IP CIDR, separated by commas (,) + + + Direct Process name, separated by commas (,) + + + نمایش کنسول + + + User-Agent + + + This parameter is valid only for tcp/http and ws + + + فعال‌سازی شتاب‌دهنده سخت‌افزاری (نیاز به راه‌اندازی مجدد) + + + فعال کردن کش فایل مجموعه قوانین برای sing-box + + + مرتب سازی + + + Default + + + تاخیر + + + سرعت دانلود + + + ترافیک دانلود + + + نام + + + زمان + + + سرعت اپلود + + + ترافیک آپلود + + + اتصالات + + + بستن اتصال + + + تمام اتصالات را ببندید + + + پروکسی + + + نوع قانون + + + Direct + + + Global + + + تغییر نده + + + قانون + + + All Node Latency Test + + + Part Node Latency Test + + + Refresh Proxies (F5) + + + Select active node (Enter) + + + دستورات قابل استفاده:\n\tswitch_server - تغییر سرور\n\tshow_current_subs - نمایش اشتراک‌های فعلی\n\tadd_subscription - اضافه کردن اشتراک\n\tremove_subscription - حذف اشتراک\n\tchange_proxy_mode - تغییر حالت پروکسی سیستم\n\tchange_routing - تغییر قواعد مسیریابی\n\tctrl + T - تغییر نمایش/پنهان کردن اطلاعات\n\tq - خروج از برنامه\n\nبرای مشاهده این اطلاعات کمکی هر زمان وارد 'h' کنید.\n\n + + + یک توسعه ثانویه ساده بر اساس v2rayN انجام شد تا اندازه‌گیری سرعت و تغییر گره را خودکار کند تا از مداخله دستی تا حد زیادی در محیط‌های گره ناپایدار جلوگیری شود. لطفاً برای مشاهده اطلاعات راهنما 'h' را وارد کنید. + + \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.resx b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.resx new file mode 100644 index 00000000..3d044585 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.resx @@ -0,0 +1,1306 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Batch export subscription to clipboard successfully + + + Batch export share URL to clipboard successfully + + + Please check the server settings first + + + Invalid configuration format + + + Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. + + + Downloading... + + + Download + + + Whether to download? {0} + + + Failed to convert configuration file + + + Failed to generate default configuration file + + + Failed to get the default configuration + + + Failed to import custom configuration server + + + Failed to read configuration file + + + Please fill in the correct format server port + + + Please fill in the KCP parameters correctly + + + Please fill in the local listening port + + + Please fill in the password + + + Please fill in the server address + + + Please fill in the user ID + + + Is not the correct client configuration file, please check + + + Is not the correct configuration, please check + + + Is not the correct server configuration file, please check + + + Initial Configuration + + + {0} {1} already up to date. + + + {0} {1} already up to date. + + + Address + + + Security + + + Port + + + Type + + + Subs group + + + Download traffic today + + + Upload traffic today + + + Total download traffic + + + Total upload traffic + + + Transport + + + Clear original subscription content + + + Download Core successfully + + + Failed to import subscription content + + + Get subscription content successfully + + + No valid subscriptions set + + + Resolve {0} successfully + + + Start getting subscriptions + + + Start updating {0}... + + + Invalid subscription content + + + Is unpacking...... + + + Update subscription end + + + Update subscription starts + + + Update Core successfully + + + Update Core successfully! Restarting service... + + + Non-VMess or ss protocol + + + Non-standard service, this feature is invalid + + + The Core file (file name: {1}) was not found under the folder ({0}), please download and put it in the folder, download address: {2} + + + Scan completed, no valid QR code found + + + Operation failed, please check and retry + + + Please Fill Remarks + + + Please select the encryption method + + + Please select a protocol + + + Please select the server first + + + Servers deduplication completed. Old: {0}, New: {1}. + + + Are you sure to remove the server? + + + The client configuration file is saved at: {0} + + + Start service ({0})... + + + Configuration successful +{0} + + + Custom configuration server imported successfully + + + {0} servers have been imported from clipboard + + + Scan import URL successfully + + + The ping of current service: {0} ms + + + Operation success + + + Please select rules + + + Are you sure to remove the rules? + + + {0},One of the required. + + + Remarks + + + URL(Optional) + + + Count + + + Please fill in the URL + + + Do you want to append rules? Choose yes to append, choose otherwise to replace + + + Download GeoFile: {0} successfully + + + Information + + + Custom Icon + + + Please fill in the correct custom DNS + + + *ws/httpupgrade/splithttp path + + + *h2 path + + + *QUIC key/KCP seed + + + *grpc serviceName + + + *http host Separated by commas (,) + + + *ws/httpupgrade/splithttp host + + + *h2 host Separated by commas (,) + + + *QUIC security + + + *tcp camouflage type + + + *kcp camouflage type + + + *QUIC camouflage type + + + *grpc mode + + + TLS + + + *kcp seed + + + Global hotkey {0} registered failed, reason {1} + + + Global hotkey {0} registered successfully + + + Ungrouped + + + All + + + Please browse to import server configuration + + + System proxy + + + Testing... + + + Too many servers, please open the main interface + + + LAN + + + Local + + + Servers Filter, press Enter to execute + + + Check Update + + + Close + + + Exit + + + Global Hotkey Setting + + + Help + + + Option Setting + + + Promotion + + + Reload + + + Routing Setting + + + Servers + + + Settings + + + Update current subscription without proxy + + + Update current subscription with proxy + + + Subscription group + + + Subscription group settings + + + Update subscription without proxy + + + Update subscription with proxy + + + System proxy + + + Clear system proxy + + + Do not change system proxy + + + PAC mode + + + Set system proxy + + + Color + + + Dark Mode + + + Follow System Theme + + + Language (Restart) + + + Import bulk URL from clipboard (Ctrl+V) + + + Scan QR code on the screen (Ctrl+S) + + + Clone selected server + + + Remove duplicate servers + + + Remove selected servers (Delete) + + + Set as active server (Enter) + + + Clear all service statistics + + + Test servers real delay (Ctrl+R) + + + Sort by test result + + + Test servers download speed (Ctrl+T) + + + Test servers with tcping (Ctrl+O) + + + Test current service status + + + Export selected server for client configuration + + + Export share URLs to clipboard (Ctrl+C) + + + Export subscription (base64) share to clipboard + + + Add a custom configuration server + + + Add [Shadowsocks] server + + + Add [Socks] server + + + Add [Trojan] server + + + Add [VLESS] server + + + Add [VMess] server + + + Select all (Ctrl+A) + + + Clear all + + + Copy (Ctrl+C) + + + Copy all + + + Set message filters + + + Select all (Ctrl+A) + + + Add + + + Delete + + + Edit + + + Share + + + Enabled Update + + + Sort + + + User Agent + + + Cancel + + + Confirm + + + Transport + + + Address + + + AllowInsecure + + + ALPN + + + AlterID + + + Fingerprint + + + Camouflage type + + + UUID(id) + + + Transport protocol(network) + + + Path + + + Port + + + Alias (remarks) + + + Camouflage domain(host) + + + Encryption method (security) + + + SNI + + + TLS + + + *Default value tcp + + + Core Type + + + Flow + + + Generate + + + Password + + + Password(Optional) + + + UUID(id) + + + Encryption + + + User(Optional) + + + Encryption + + + txtPreSocksPort + + + * After setting this value, an socks service will be started using Xray/sing-box(Tun) to provide functions such as speed display + + + Browse + + + Edit + + + Advanced proxy settings, protocol selection (optional) + + + Allow connections from the LAN + + + Auto hide startup + + + Automatic update interval of Geo (hours) + + + Core: basic settings + + + V2ray DNS settings + + + Core: KCP settings + + + CoreType settings + + + AllowInsecure + + + Outbound Freedom domainStrategy + + + Automatically adjust column width after updating subscription + + + Check for pre-release updates + + + Exception + + + Exception. Do not use proxy server for addresses beginning with,Use semicolon (;) + + + HTTP Port + + + Ignore Geo files when updating core + + + Keep older when deduplication + + + Enable Log + + + Log Level + + + Turn on Mux Multiplexing + + + v2rayN settings + + + Auth pass + + + Custom DNS (multiple, separated by commas (,)) + + + Set Win10 UWP Loopback + + + Turn on Sniffing + + + SOCKS Port + + + Start on boot + + + Enable Statistics (Require restart) + + + Subscription conversion URL + + + System proxy settings + + + Enable Security Protocol TLS v1.3 (subscription/update) + + + Tray right-click menu servers display limit + + + Enable UDP + + + Auth user + + + Clear system proxy + + + Display GUI + + + Global Hotkey Settings + + + Set directly by pressing the keyboard, take effect after restart + + + Do not change system proxy + + + Reset + + + Set system proxy + + + PAC mode + + + Share Server (Ctrl+F) + + + Routing + + + Not run as Admin + + + Run as Admin + + + Move to bottom (B) + + + Down (D) + + + Move to top (T) + + + Up (U) + + + Filter, support regular + + + {0} Website + + + Advanced Function + + + Add + + + Import Advanced Rules + + + Remove selected (Delete) + + + Set as active rule(Enter) + + + Basic Function + + + Import Basic Rules + + + Domain Matcher + + + Domain strategy + + + Enable advanced function + + + 3.Block Domain or IP + + + 2.Direct Domain or IP + + + 1.Proxy Domain or IP + + + Pre-defined Rule Set List + + + *Set the rules, separated by commas (,); The comma in the regular is replaced by <COMMA> + + + Import Rules From Clipboard + + + Import Rules From File + + + Import Rules From Subscription URL + + + Rule Settings + + + Add Rule + + + Export Selected Rules + + + Rule List + + + Remove Rule (Delete) + + + RoutingRuleDetailsSetting + + + Domain, ip, process are auto sorted when saving + + + Ruleobject Doc + + + Support DnsObject, Click to view the document + + + Group please leave blank here + + + Routing setting is changed + + + System proxy setting is changed + + + RouteOnly + + + One-click multi test Latency and speed (Ctrl+E) + + + Delay(ms) + + + Speed(M/s) + + + Failed to run Core, please see the log + + + Remarks regular filter + + + Display Log + + + Import old config (guiNConfig) + + + Enable Tun + + + New Port for LAN + + + TunMode settings + + + Direct IP CIDR, separated by commas (,) + + + Direct Process name, separated by commas (,) + + + Show console + + + Move to group + + + Custom Template + + + Enable Server Drag Drop Sort(Require restart) + + + AutoRefresh + + + Skip test + + + Edit Server (Ctrl+D) + + + Double-click server make active + + + Test completed + + + Default TLS fingerprint + + + User-Agent + + + This parameter is valid only for tcp/http and ws + + + FontFamily(Require restart) + + + Copy the font TTF/TTC file to the directory guiFonts, restart the settings + + + http port = +1; Pac port = +4; *ray API port = +5; mihomo API port = +6; + + + Set this with admin privileges, get admin privileges after startup + + + Font Size + + + Proxy IP CIDR, separated by commas (,) + + + Proxy Process name, separated by commas (,) + + + Bypass Mode + + + SpeedTest Single Timeout Value + + + SpeedTest URL + + + DNS object, e.g. {"servers":[]} + + + Move up and down + + + PublicKey + + + ShortId + + + SpiderX + + + Enable hardware acceleration(Require restart) + + + Waiting for testing (press ESC to terminate)... + + + Please turn off when there is an abnormal disconnection + + + Updates are not enabled, skip this subscription + + + Restart as Administrator + + + More urls, separated by commas;Subscription conversion will be invalid + + + {0} : {1}/s↑ | {2}/s↓ + + + Automatic update interval(minutes) + + + Enable logging to file + + + Convert target type + + + Please leave blank if no conversion is required + + + DNS Settings + + + sing-box DNS settings + + + Please fill in DNS Structure, Click to view the document + + + Click to import default DNS config + + + sing-box domain strategy + + + sing-box Mux Protocol + + + Full process name (Tun mode) + + + IP or IP CIDR + + + Domain + + + Add [Hysteria2] server + + + Hysteria Max bandwidth (Up/Dw) + + + Use System Hosts + + + Add [Tuic] server + + + Congestion control + + + Previous proxy remarks + + + Next proxy remarks + + + Please make sure the remarks exists and is unique + + + Enable additional Inbound + + + Enable IPv6 Address + + + Add [Wireguard] server + + + PrivateKey + + + Reserved(2,3,4) + + + Address(Ip,Ipv6) + + + obfs password + + + (Domain or IP or ProcName) and Port and Protocol and InboundTag => OutboundTag + + + Auto ScrollToEnd + + + Speed Ping Test URL + + + Updating subscription, only determine remarks exists + + + Test terminating... + + + *grpc Authority + + + Add [Http] server + + + Use Xray and enable non-Tun mode, which conflicts with the group previous proxy + + + Enable fragment + + + Enable cache file for sing-box (ruleset files) + + + Custom the rule-set of sing-box + + + Successful operation. Click the settings menu to reboot the app. + + + Open the storage location + + + Sorting + + + Chain + + + Default + + + Delay + + + Download Speed + + + Download Traffic + + + Host + + + Name + + + Network + + + Time + + + Type + + + Upload Speed + + + Upload Traffic + + + Connections + + + Close Connection + + + Close All Connection + + + Proxies + + + Rule mode + + + Direct + + + Global + + + Do not change + + + Rule + + + All Node Latency Test + + + Part Node Latency Test + + + Refresh Proxies (F5) + + + Select active node (Enter) + + + Available commands:\n\tswitch_server - Switch server\n\tshow_current_subs - Display current subscriptions\n\tadd_subscription - Add subscription\n\tremove_subscription - Remove subscription\n\tchange_proxy_mode - Change system proxy mode\n\tchange_routing - Change routing rules\n\tctrl + T - Toggle information display\n\tq - Exit the program\n\nEnter 'h' at any time to view this help information.\n\n + + + A simple secondary development was done on the basis of v2rayN to automate speed measurement and node switching to avoid manual intervention to the greatest extent in unstable node environments. Please enter 'h' to view help information. + + \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.ru.resx b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.ru.resx new file mode 100644 index 00000000..f20b58a1 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.ru.resx @@ -0,0 +1,1075 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Экспортирование подписок в буфер обмена успешно завершено + + + Экспортирование URL в буфер обмена успешно завершено + + + Пожалуйста, сначала проверьте настройки сервера + + + Недопустимый формат конфигурации + + + Обратите внимание, что пользовательская конфигурация полностью зависит от вашей собственной конфигурации и работает не со всеми настройками. Если вы хотите использовать системный прокси, пожалуйста, измените порт прослушивания вручную. + + + Загрузка... + + + Скорость загрузки + + + Хотите загрузить? {0} + + + Не удалось преобразовать файл конфигурации + + + Не удалось создать файл конфигурации по умолчаниюe + + + Не удалось получить конфигурацию по умолчанию + + + Не удалось импортировать сервер пользовательской конфигурации + + + Не удалось прочитать файл конфигурации + + + Пожалуйста, укажите порт сервера в правильном формате + + + Пожалуйста, заполните параметры KCP корректно + + + Пожалуйста, укажите локальный порт прослушивания + + + Пожалуйста, введите пароль + + + Пожалуйста, заполните адрес сервера + + + Пожалуйста, заполните идентификатор пользователя + + + Некорректный файл конфигурации клиента, пожалуйста, проверьте + + + Некорректная конфигурация, пожалуйста, проверьте + + + Некорректный файл конфигурации сервера, пожалуйста, проверьте + + + Исходная конфигурация + + + {0} {1} является последней версией. + + + {0} {1} является последней версией. + + + Адрес + + + Безопасность + + + Порт + + + Тип + + + Группа подписки + + + Загружено трафика сегодня + + + Отдано трафика сегодня + + + Всего загружено + + + Всего отдано + + + Протокол + + + Очистить контент оригинальной подписки + + + Ядро успешно загружено + + + Не удалось импортировать содержимое подписки + + + Содержимое подписки успешно импортировано + + + Нет установлены подписки + + + Парсинг {0} прошел успешно + + + Начинаю получать подписки + + + Начинаю обновление {0}... + + + Некорректное содержимое подписки + + + распаковывается... + + + Обновление подписки закончено + + + Обновление подписки начинается + + + Успешное обновление ядра + + + Успешное обновление ядра! Перезапуск службы... + + + Не является протоколом Vmess или SS + + + нестандартный сервис, эта функция недействительна + + + Файл Core (имя файла: {1}) не найден в папке ({0}), загрузите и поместите его в папку, адрес загрузки: {2} + + + Сканирование завершено, не найден корректный QR код + + + операция безуспешна, проверьте и попробуйте ещё раз + + + Пожалуйста, заполните примечания + + + Пожалуйста, выберите метод шифрования + + + Пожалуйста, выберите протокол + + + Сначала выберите сервер + + + Удаление дублей завершено. Старая: {0}, Новая: {1}. + + + Вы уверены, что хотите удалить сервер? + + + Файл конфигурации клиента сохранен по адресу: {0} + + + Запуск сервиса ({0})... + + + Конфигурация успешна +{0} + + + Пользовательский сервер конфигурации успешно импортирован. + + + {0} серверов импортировано из буфера обмена. + + + Сканирование URL-адреса импорта успешна. + + + Задержка текущего сервера: {0} мс + + + Операция успешна + + + Выберите правила + + + Вы уверены, что хотите удалить правила? + + + {0}, Один из обязательных.. + + + Примечания + + + URL (необязательно) + + + Количество + + + Пожалуйста, заполните адрес (Url) + + + Вы хотите добавить правила? Выберите «Да», чтобы добавить, выберите иное, чтобы заменить + + + Загрузка GeoFile: {0} успешна + + + Информация + + + Пользовательская иконка + + + Пожалуйста, заполните правильный пользовательский DNS + + + *ws путь + + + *h2 путь + + + *QUIC ключ/Kcp seed + + + *grpc serviceName + + + *http хосты разделенные запятыми (,) + + + *ws хост + + + *h2 хосты разделенные запятыми (,) + + + * безопасность QUIC + + + * тип TCP камуфляжа + + + * тип KCP камуфляжа + + + * тип QUIC камуфляжа + + + * режим grpc + + + TLS + + + *Kcp seed + + + Не удалось зарегистрировать глобальную горячую клавишу {0}, причина: {1} + + + Глобальная горячая клавиша {0} зарегистрирована успешно + + + Разгруппировано + + + Все сервера + + + Пожалуйста, просмотрите, чтобы импортировать конфигурацию сервера + + + Системный прокси + + + Тестирование... + + + Слишком много серверов, пожалуйста, откройте главный интерфейс + + + LAN + + + Локальный + + + Фильтр серверов + + + Проверить обновления + + + Закрыть + + + Выход + + + Глобальная настройка горячих клавиш + + + Помощь + + + Настройка параметров + + + Содействие + + + Перезагрузка + + + Настройки маршрутизации + + + Сервера + + + Настройки + + + Обновить текущую подписку без прокси + + + Обновить текущую подписку с прокси + + + Группа подписки + + + Настройки группы подписки + + + Обновить подписку без прокси + + + Обновить подписку с прокси + + + Системный прокси + + + Очистить системный прокси + + + Не менять системный прокси + + + Режим PAC + + + Установить системный прокси + + + Цвет + + + Тёмный режим + + + Следить за системной темой + + + Язык (требуется перезапуск) + + + Импорт массива URL из буфера обмена (Ctrl+V) + + + Сканировать QR-код с экрана (Ctrl+S) + + + Клонировать выбранный сервер + + + Удалить дубликаты серверов + + + Удалить выбранные серверы (Delete) + + + Установить как активный сервер (Enter) + + + Очистить всю статистику + + + Тест на реальную задержку сервера (Ctrl+R) + + + Сортировать по результату теста + + + Тест на скорость загрузки сервера (Ctrl+T) + + + Тест задержки с tcping (Ctrl+O) + + + Проверить текущий статус службы + + + Экспортировать выбранный сервер для клиента + + + Экспорт URL-адресов общего доступа в буфер обмена (Ctrl+C) + + + Экспортировать ссылку-подписку (base64) в буфер обмена + + + Добавить сервер пользовательской конфигурации + + + Добавить сервер [Shadowsocks] + + + Добавить сервер [Socks] + + + Добавить сервер [Trojan] + + + Добавить сервер [VLESS] + + + Добавить сервер [VMess] + + + Выбрать все (Ctrl+A) + + + Очистить все + + + Скопировать (Ctrl+C) + + + Скопировать все + + + Установить фильтры сообщений + + + Выбрать все (Ctrl+A) + + + Добавить + + + Удалить + + + Редактировать + + + Поделиться + + + Включены обновления + + + Сортировка + + + User Agent + + + Отмена + + + Подтвердить + + + Транспорт + + + Адрес + + + Разрешить небезопасные + + + ALPN + + + AlterId + + + Отпечаток + + + Тип камуфляжа + + + UUID (id) + + + Транспортный протокол сети + + + Путь + + + Порт + + + Примечание + + + Маскирующий домен (хост) + + + Метод шифрования + + + SNI + + + TLS + + + * По-умолчанию TCP + + + Ядро + + + Поток + + + Генерировать + + + Пароль + + + Пароль(Необязательно) + + + UUID(id) + + + Шифрование + + + Пользователь(Необязательно) + + + Шифрования + + + txtPreSocksPort + + + * После установки этого значения служба socks будет запущена с использованием Xray/sing-box(Tun) для обеспечения таких функций, как отображение скорости + + + Просмотр + + + Редактировать + + + Расширенные настройки прокси, выбор протокола (необязательно) + + + Разрешить подключения из локальной сети + + + Автоскрытие при автозапуске + + + Интервал автоматического обновления Geo в часах + + + Интервал автоматического обновления в минутах + + + Ядро: базовые настройки + + + Настройки DNS V2ray + + + Настройки DNS sing-box + + + Ядро: настройки KCP + + + Настройки типа ядра + + + Разрешить небезопасные + + + Outbound Freedom domainStrategy + + + Автоматически регулировать ширину столбца после обновления подписки + + + Проверить наличие предварительных обновлений + + + Исключение + + + Исключение. Не используйте прокси-сервер для адресов, начинающихся с (,), используйте точку с запятой (;) + + + HTTP порт + + + Игнорировать файлы Geo при обновлении ядра + + + Сохранить старые при удалении дублей + + + Записывать логи + + + Уровень логгирования + + + Включите мультиплексирование Mux + + + Настройки v2rayN + + + Пароль аутентификации + + + Пользовательский DNS (если несколько делите с запятыми (,)) + + + Set Win10 UWP Loopback + + + Включить сниффинг + + + Порт Socks + + + Автозапуск + + + Включить статистику (требуется перезагрузка) + + + URL-адрес конверсии подписки + + + Настройки системного прокси + + + Включить протокол безопасности TLS v1.3 (обновление подписки) + + + Лимит серверов в меню трея + + + Включить UDP + + + Auth user + + + Очистить системный прокси + + + Показать GUI + + + Настройка горячих клавиш + + + Установите непосредственно, нажав на клавиатуру, вступит в силу после перезапуска + + + Не изменять системный прокси + + + Обнулить + + + Установить системный прокси + + + Режим PAC + + + Поделиться сервером (Ctrl+F) + + + Маршрутизация + + + Пользователь + + + Администратор + + + Спуститься вниз (B) + + + Вниз (D) + + + Подняться наверх (T) + + + Вверх (U) + + + Фильтр, поддерживает regex + + + {0} веб-сайт + + + Расширенная функция + + + Добавить + + + Добавить расширенные правила + + + Удалить выбранные + + + Установить как активное правило + + + Основные функции + + + Импорт основных правил + + + Сопоставитель доменов + + + Доменная стратегия + + + Включить расширенные функции + + + 3.Заблокировать домен или IP + + + 2.Прямой домен или IP + + + 1.Прокси-домен или IP + + + Предустановленный список наборов правил + + + * Установите правила, разделенные запятыми (,); Запятая в регулярке заменена на <COMMA> + + + Импорт правил из буфера обмена + + + Импорт правил из файла + + + Импорт правил из URL + + + Настройка правил + + + Добавить правило + + + Экспорт выделенных правил + + + Список правил + + + Удалить правила (Delete) + + + RoutingRuleDetailsSetting + + + Домен и IP автоматически сортируются при сохранении + + + Ruleobject Doc + + + Поддержка DnsObject + + + Необязательное поле + + + Настройки маршрутизации изменены + + + Системные прокси изменены + + + Только маршрут + + + Тест задержки и скорости всех серверов (Ctrl+E) + + + Задержка (ms) + + + Скорость (M/s) + + + Не удалось запустить ядро, посмотрите логи + + + Remarks regular filter + + + Показать логи + + + Импортировать старый конфиг guiNConfig + + + Режим VPN + + + Новый порт для локальной сети + + + Настройки TunMode + + + Прямой IP CIDR, разделенный запятыми (,) + + + Имя процесса, разделенное запятыми (,) + + + Показать консоль + + + Перейти в группу + + + Пользовательский шаблон + + + Включить сортировку перетаскиванием сервера (требуется перезагрузка) + + + Автообновление + + + Пропустить тест + + + Редактировать сервер (Ctrl+D) + + + Двойной клик чтобы сделать сервер активным + + + Тест завершен + + + TLS отпечаток по умлочанию + + + User-Agent + + + Этот параметр действителен только для TCP/HTTP и WS + + + Шрифт (требуется перезагрузка) + + + Скопируйте файл шрифта TTF/TTC в каталог guiFonts, перезапустите настройки + + + Установите это с правами администратора + + + Размер шрифта + + + Включить аппаратное ускорение (требуется перезагрузка) + + + Перезагрузить как администратор + + + Настройки DNS + + + Включить логгирование в файл + + + Таймаут одиночного спидтеста + + + URL спидтеста + + + Доступные команды:\n\tswitch_server - Переключение сервера\n\tshow_current_subs - Отображение текучих подписки\n\tadd_subscription - Добавление подписки\n\tremove_subscription - Удаление подписки\n\tchange_proxy_mode - Изменение режима системного прокси\n\tchange_routing - Изменение правил маршрутизации\n\tctrl + T - Переключение видимости информации\n\tq - Выход из программы\n\nВведите 'h' в любой момент, чтобы просмотреть эту справку.\n\n + + + Простая вторичная разработка была сделана на основе v2rayN для автоматизации измерения скорости и переключения узлов, чтобы максимально избежать ручного вмешательства в нестабильных средах узлов. Пожалуйста, введите «h», чтобы просмотреть справочную информацию. + + \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.zh-Hans.resx b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.zh-Hans.resx new file mode 100644 index 00000000..537b260f --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.zh-Hans.resx @@ -0,0 +1,1303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 批量导出订阅内容至剪贴板成功 + + + 批量导出分享URL至剪贴板成功 + + + 请先检查服务器设置 + + + 配置格式不正确 + + + 注意,自定义配置完全依赖您自己的配置,不能使用所有设置功能。如需使用系统代理请手动修改监听端口。 + + + 下载开始... + + + 下载 + + + 是否下载? {0} + + + 转换配置文件失败 + + + 生成默认配置文件失败 + + + 获取默认配置失败 + + + 导入自定义配置服务器失败 + + + 读取配置文件失败 + + + 请填写正确格式服务器端口 + + + 请正确填写KCP参数 + + + 请填写本地监听端口 + + + 请填写密码 + + + 请填写服务器地址 + + + 请填写用户ID + + + 不是正确的客户端配置文件,请检查 + + + 不是正确的配置,请检查 + + + 不是正确的服务端配置文件,请检查 + + + 初始化配置 + + + {0} {1} 已是最新版本。 + + + {0} {1} 已是最新版本。 + + + 地址 + + + 加密方式 + + + 端口 + + + 类型 + + + 订阅分组 + + + 今日下载 + + + 今日上传 + + + 总下载 + + + 总上传 + + + 传输协议 + + + 清除原订阅内容 + + + 下载Core成功 + + + 导入订阅内容失败 + + + 获取订阅内容成功 + + + 未设置有效的订阅 + + + 解析{0}成功 + + + 开始获取订阅内容 + + + 开始更新 {0}... + + + 无效的订阅内容 + + + 正在解压...... + + + 更新订阅结束 + + + 更新订阅开始 + + + 更新Core成功 + + + 更新Core成功!正在重启服务... + + + 非VMess或ss协议 + + + 非标准服务,此功能无效 + + + 在文件夹 ({0}) 下未找到Core文件 (文件名:{1}),请下载后放入文件夹,下载地址: {2} + + + 扫描完成,未发现有效二维码 + + + 操作失败,请检查重试 + + + 请填写别名 + + + 请选择加密方式 + + + 请选择协议 + + + 请先选择服务器 + + + 服务器去重完成。原数量: {0},现数量: {1} + + + 是否确定移除服务器? + + + 客户端配置文件保存在:{0} + + + 启动服务({0})... + + + 配置成功 +{0} + + + 成功导入自定义配置服务器 + + + 成功从剪贴板导入 {0} 个服务器 + + + 扫描导入URL成功 + + + 当前服务的真连接延迟: {0} ms + + + 操作成功 + + + 请先选择规则 + + + 是否确定移除规则? + + + {0},必填其中一项. + + + 别名 + + + 可选地址(Url) + + + 数量 + + + 请填写Url + + + 是否追加规则?选择是则追加,选择否则替换 + + + 下载 GeoFile: {0} 成功 + + + 信息 + + + 自定义图标 + + + 请填写正确的自定义DNS + + + *ws/httpupgrade/splithttp path + + + *h2 path + + + *QUIC 加密密钥 + + + *grpc serviceName + + + *http host中间逗号(,)分隔 + + + *ws/httpupgrade/splithttp host + + + *h2 host中间逗号(,)分隔 + + + *QUIC 加密方式 + + + *tcp伪装类型 + + + *kcp伪装类型 + + + *QUIC伪装类型 + + + *grpc 模式 + + + TLS + + + *Kcp seed + + + 注册全局热键 {0} 失败,原因 {1} + + + 注册全局热键 {0} 成功 + + + 未分组服务器 + + + 所有 + + + 请浏览导入服务器配置 + + + 系统代理 + + + 测试中... + + + 服务器太多,请打开主界面操作 + + + 局域网 + + + 本地 + + + 服务器过滤器,按回车执行 + + + 检查更新 + + + 关闭 + + + 退出 + + + 全局热键设置 + + + 帮助 + + + 参数设置 + + + 推广 + + + 重启服务 + + + 路由设置 + + + 服务器 + + + 设置 + + + 更新当前订阅(不通过代理) + + + 更新当前订阅(通过代理) + + + 订阅分组 + + + 订阅分组设置 + + + 更新全部订阅(不通过代理) + + + 更新全部订阅(通过代理) + + + 系统代理 + + + 清除系统代理 + + + 不改变系统代理 + + + Pac模式 + + + 自动配置系统代理 + + + 颜色 + + + 暗黑模式 + + + 是否跟随系统主题 + + + 语言(重启) + + + 从剪贴板导入批量URL (Ctrl+V) + + + 扫描屏幕上的二维码 (Ctrl+S) + + + 克隆所选服务器 + + + 移除重复的服务器 + + + 移除所选服务器(多选) (Delete) + + + 设为活动服务器 (Enter) + + + 清除所有服务统计数据 + + + 测试服务器真连接延迟(多选) (Ctrl+R) + + + 按测试结果排序 + + + 测试服务器速度(多选) (Ctrl+T) + + + 测试服务器延迟Tcping(多选) (Ctrl+O) + + + 测试当前服务状态 + + + 导出所选服务器为客户端配置 + + + 批量导出分享URL至剪贴板(多选) (Ctrl+C) + + + 批量导出订阅内容至剪贴板(多选) + + + 添加自定义配置服务器 + + + 添加[Shadowsocks]服务器 + + + 添加[Socks]服务器 + + + 添加[Trojan]服务器 + + + 添加[VLESS]服务器 + + + 添加[VMess]服务器 + + + 全选 (Ctrl+A) + + + 清除所有 + + + 复制 (Ctrl+C) + + + 复制所有 + + + 设置信息过滤器 + + + 全选 (Ctrl+A) + + + 添加 + + + 删除 + + + 编辑 + + + 分享 + + + 启用更新 + + + 排序 + + + User Agent(可选) + + + 取消 + + + 确定 + + + 底层传输方式(transport) + + + 地址(address) + + + 跳过证书验证(allowInsecure) + + + Alpn + + + 额外ID(alterId) + + + Fingerprint + + + 伪装类型(type) + + + 用户ID(id) + + + 传输协议(network) + + + 路径(path) + + + 端口(port) + + + 别名(remarks) + + + 伪装域名(host) + + + 加密方式(security) + + + SNI + + + 传输层安全(TLS) + + + *默认tcp,选错会无法连接 + + + Core类型 + + + 流控(flow) + + + 生成 + + + 密码(password) + + + 密码(可选) + + + 用户ID(id) + + + 加密方式(encryption) + + + 用户名(可选) + + + 加密方式(encryption) + + + Socks端口 + + + * 自定义配置的Socks端口值,可不设置;当设置此值后,将使用Xray/sing-box(Tun)额外启动一个前置Socks服务,提供分流和速度显示等功能 + + + 浏览 + + + 编辑 + + + 高级代理设置, 协议选择(可选) + + + 允许来自局域网的连接 + + + 启动后隐藏窗口 + + + 自动更新Geo文件的间隔(单位小时) + + + Core: 基础设置 + + + V2ray DNS设置 + + + Core: KCP设置 + + + Core类型设置 + + + 默认跳过证书验证(allowInsecure) + + + Outbound Freedom domainStrategy + + + 自动调整服务器列宽在更新订阅后 + + + 检查Pre-Release更新(请谨慎启用) + + + 例外 + + + 例外. 对于下列字符开头的地址不使用代理配置文件:使用分号(;)分隔 + + + 本地http监听端口 + + + 更新Core时忽略Geo文件 + + + 去重时保留序号较小的项 + + + 启用日志 + + + 日志等级 + + + 开启Mux多路复用 + + + v2rayN 设置 + + + 认证密码 + + + 自定义DNS(可多个,用逗号(,)分隔) + + + 解除Win10 UWP应用回环代理限制 + + + 开启流量探测 + + + 本地socks监听端口 + + + 开机启动(可能会不成功) + + + 启用统计(实时网速显示,需重启) + + + 订阅转换网址(可选) + + + 系统代理设置 + + + 启用安全协议TLS v1.3 (订阅/检查更新) + + + 托盘右键菜单服务器展示数量限制 + + + 开启UDP + + + 认证用户名 + + + 清除系统代理 + + + 显示主界面 + + + 全局热键设置 + + + 直接按键盘进行设置, 重启后生效 + + + 不改变系统代理 + + + 重置 + + + 自动配置系统代理 + + + Pac模式 + + + 分享服务器 (Ctrl+F) + + + 路由 + + + 以非管理员身份运行 + + + 以管理员身份运行 + + + 下移至底 (B) + + + 下移 (D) + + + 上移至顶 (T) + + + 上移 (U) + + + 过滤器, 支持正则 + + + {0} 官网 + + + 高级功能 + + + 添加规则集 + + + 一键导入高级规则 + + + 移除所选规则 (Delete) + + + 设为活动规则 (Enter) + + + 基础功能 + + + 一键导入基础规则 + + + 域名匹配算法 + + + 域名解析策略 + + + 启用高级功能 + + + 3.阻止的Domain或IP + + + 2.直连的Domain或IP + + + 1.代理的Domain或IP + + + 预定义规则集列表 + + + *设置的路由规则,用逗号(,)分隔;正则中的逗号用<COMMA>替代 + + + 从剪贴板中导入规则 + + + 从文件中导入规则 + + + 从订阅Url中导入规则 + + + 规则集设置 + + + 添加规则 + + + 导出所选规则至剪贴板 + + + 规则列表 + + + 移除所选规则 (Delete) + + + 路由规则详情设置 + + + 保存时Domain, IP, 进程名 自动排序 + + + 规则详细说明文档 + + + 支持填写DnsObject,JSON格式,点击查看文档 + + + 普通分组此处请留空 + + + 路由设置改变 + + + 系统代理设置改变 + + + RouteOnly + + + 一键多线程测试延迟和速度 (Ctrl+E) + + + 延迟(ms) + + + 速度(M/s) + + + 运行Core失败,请看日志 + + + 别名正则过滤 + + + 显示日志 + + + 导入旧的配置文件guiNConfig + + + 启用Tun模式 + + + 为局域网开启新的端口 + + + Tun模式设置 + + + 直连的IP CIDR,用逗号(,)分隔 + + + 直连的进程名,用逗号(,)分隔 + + + 显示控制台 + + + 移至订阅分组 + + + 自定义配置模板 + + + 启用服务器拖放排序(需重启) + + + 自动刷新 + + + 跳过测试 + + + 编辑服务器 (Ctrl+D) + + + 主界面双击设为活动服务器 + + + 测试完成 + + + 默认TLS指纹(fingerprint) + + + 用户代理(User-Agent) + + + 仅对tcp/http、ws协议生效 + + + 当前字体(需重启) + + + 拷贝字体TTF/TTC文件到目录guiFonts,重启设置 + + + http端口= +1;Pac端口= +4;*ray API端口= +5;mihomo API端口= +6; + + + 以管理员权限设置此项,在启动后获得管理员权限 + + + 字体大小 + + + 代理的IP CIDR,用逗号(,)分隔 + + + 代理的进程名,用逗号(,)分隔 + + + 绕行模式 + + + 测速单个超时值 + + + 测速文件地址 + + + DNS对象,例如 {"servers":[]} + + + 移至上下 + + + PublicKey + + + ShortId + + + SpiderX + + + 启用硬件加速(需重启) + + + 等待测试中(按ESC终止)... + + + 当有异常断流时请关闭 + + + 未启用更新,跳过此订阅 + + + 以管理员身份重启 + + + 更多地址(url),用逗号(,)分隔;订阅转换将失效 + + + 自动更新间隔(分钟) + + + 启用日志存到文件 + + + 订阅转换目标类型 + + + 不需要转换时请留空 + + + DNS设置 + + + sing-box DNS设置 + + + 请填写 DNS JSON 结构,点击查看文档 + + + 点击导入默认DNS配置 + + + sing-box域名解析策略 + + + sing-box Mux 多路复用协议 + + + 进程名全称 (Tun模式) + + + Domain + + + IP 或 IP CIDR + + + 添加[Hysteria2]服务器 + + + Hysteria 最大带宽(Up/Dw) + + + 使用系统hosts + + + 添加[Tuic]服务器 + + + 拥塞控制算法 + + + 前置代理别名 + + + 落地代理別名 + + + 请确保别名存在并唯一 + + + 启用额外监听端口 + + + 启用IPv6 + + + 添加[Wireguard]服务器 + + + PrivateKey + + + Reserved(2,3,4) + + + Address(Ip,Ipv6) + + + 混淆密码(obfs password) + + + (Domain 或 IP 或 进程名) 与 Port 与 Protocol 与 InboundTag => OutboundTag + + + 自动滚动到末尾 + + + 真连接测试地址 + + + 更新订阅时只判断别名已存在否 + + + 测试终止中... + + + *grpc Authority + + + 添加[Http]服务器 + + + 启用分片(Fragment) + + + 使用Xray且非Tun模式启用,和分组前置代理冲突 + + + 启用sing-box(规则集文件)的缓存文件 + + + 自定义sing-box rule-set + + + 操作成功。请点击设置菜单重启应用。 + + + 打开存储所在的位置 + + + 排序 + + + 路由链 + + + 默认 + + + 延迟 + + + 下载速度 + + + 下载流量 + + + 主机 + + + 名称 + + + 网络 + + + 时间 + + + 类型 + + + 上传速度 + + + 上传流量 + + + 当前连接 + + + 关闭连接 + + + 关闭所有连接 + + + 当前代理 + + + 规则模式 + + + 直连 + + + 全局 + + + 随原配置 + + + 规则 + + + 全部节点延迟测试 + + + 当前部分节点延迟测试 + + + 刷新 (F5) + + + 设为活动节点 (Enter) + + + 可用命令:\n\tswitch_server - 切换服务器\n\tshow_current_subs - 显示当前订阅\n\tadd_subscription - 添加订阅\n\tremove_subscription - 移除订阅\n\tchange_proxy_mode - 更改系统代理模式\n\tchange_routing - 更改路由规则\n\tctrl + T - 切换信息显隐\n\tq - 退出程序\n\n输入'h'随时查看此帮助信息。\n\n + + + 在v2rayN基础上做了简单二次开发,将测速、切换节点自动化,在不稳定节点环境中最大限度避免手动干预。请输入'h'查看帮助信息。 + + \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.zh-Hant.resx b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.zh-Hant.resx new file mode 100644 index 00000000..b7c64574 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Resx/ResUI.zh-Hant.resx @@ -0,0 +1,1197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 批次匯出訂閱內容至剪貼簿成功 + + + 批次匯出分享URL至剪貼簿成功 + + + 請先檢查伺服器設定 + + + 配置格式不正確 + + + 注意,自訂配置完全依賴您自己的配置,不能使用所有設定功能。如需使用系統代理請手動修改監聽埠。 + + + 下載開始... + + + 下載 + + + 是否下載? {0} + + + 轉換配置檔案失敗 + + + 生成預設配置檔案失敗 + + + 獲取預設配置失敗 + + + 匯入自訂配置伺服器失敗 + + + 讀取配置檔案失敗 + + + 請填寫正確格式伺服器埠 + + + 請正確填寫KCP參數 + + + 請填寫本機監聽埠 + + + 請填寫密碼 + + + 請填寫伺服器位址 + + + 請填寫使用者ID + + + 不是正確的用戶端配置檔案,請檢查 + + + 不是正確的配置,請檢查 + + + 不是正確的服務端配置檔案,請檢查 + + + 初始化配置 + + + {0} {1} 已是最新版本。 + + + {0} {1} 已是最新版本。 + + + 位址 + + + 加密方式 + + + + + + 類型 + + + 訂閱分組 + + + 今日下載 + + + 今日上傳 + + + 總下載 + + + 總上傳 + + + 傳輸協定 + + + 清除原訂閱內容 + + + 下載Core成功 + + + 匯入訂閱內容失敗 + + + 獲取訂閱內容成功 + + + 未設定有效的訂閱 + + + 解析{0}成功 + + + 開始獲取訂閱內容 + + + 開始更新 {0}... + + + 無效的訂閱內容 + + + 正在解壓...... + + + 更新訂閱結束 + + + 更新訂閱開始 + + + 更新Core成功 + + + 更新Core成功!正在重啟服務... + + + 非VMess或SS協定 + + + 非標準服務,此功能無效 + + + 在資料夾 ({0}) 下未找到Core文件 (檔案名:{1}),請下載後放入資料夾、,下載網址: {2} + + + 掃描完成,未發現有效二維碼 + + + 操作失敗,請檢查重試 + + + 請填寫別名 + + + 請選擇加密方式 + + + 請選擇協定 + + + 請先選擇伺服器 + + + 伺服器去重完成。原數量: {0},現數量: {1} + + + 是否確定移除伺服器? + + + 用戶端配置檔案儲存在:{0} + + + 啟動服務({0})... + + + 配置成功{0} + + + 成功匯入自訂配置伺服器 + + + 成功從剪貼簿匯入 {0} 個伺服器 + + + 掃描匯入URL成功 + + + 目前服務的真連接延遲: {0} ms + + + 操作成功 + + + 請先選擇規則 + + + 是否確定移除規則? + + + {0},必填其中一項. + + + 別名 + + + 可選位址(URL) + + + 數量 + + + 請填寫URL + + + 是否追加規則?選擇是則追加,選擇否則取代 + + + 下載 GeoFile: {0} 成功 + + + 資訊 + + + 自訂圖示 + + + 請填寫正確的自訂DNS + + + *ws/httpupgrade/splithttp path + + + *h2 path + + + *QUIC 加密金鑰 + + + *grpc serviceName + + + *http host中間逗號(,)分隔 + + + *ws/httpupgrade/splithttp host + + + *h2 host中間逗號(,)分隔 + + + *QUIC 加密方式 + + + *TCP偽裝類型 + + + *KCP偽裝類型 + + + *QUIC偽裝類型 + + + *GRPC 模式 + + + TLS + + + *KCP seed + + + 註冊全域快速鍵 {0} 失敗,原因 {1} + + + 註冊全域快速鍵 {0} 成功 + + + 未分組伺服器 + + + 所有 + + + 請瀏覽匯入伺服器配置 + + + 系統代理 + + + 測試中... + + + 伺服器太多,請打開主介面操作 + + + 區域網路 + + + 本機 + + + 伺服器過濾器,按Enter執行 + + + 檢查更新 + + + 關閉 + + + 退出 + + + 全域快速鍵設定 + + + 說明 + + + 參數設定 + + + 推廣 + + + 重啟服務 + + + 路由設定 + + + 伺服器 + + + 設定 + + + 更新目前訂閱(不透過代理) + + + 更新目前訂閱(透過代理) + + + 訂閱分組 + + + 訂閱分組設定 + + + 更新全部訂閱(不透過代理) + + + 更新全部訂閱(透過代理) + + + 系統代理 + + + 清除系統代理 + + + 不改變系統代理 + + + PAC模式 + + + 自動配置系統代理 + + + 顏色 + + + 暗黑模式 + + + 是否跟隨系統主題 + + + 語言(重啟) + + + 從剪貼簿匯入批次URL (Ctrl+V) + + + 掃描螢幕上的二維碼 (Ctrl+S) + + + 複製所選伺服器 + + + 移除重複的伺服器 + + + 移除所選伺服器(多選) (Delete) + + + 設為活動伺服器 (Enter) + + + 清除所有服務統計資料 + + + 測試伺服器真連接延遲(多選) (Ctrl+R) + + + 按測試結果排序 + + + 測試伺服器速度(多選) (Ctrl+T) + + + 測試伺服器延遲Tcping(多選) (Ctrl+O) + + + 測試目前服務狀態 + + + 匯出所選伺服器為用戶端配置 + + + 批次匯出分享URL至剪貼簿(多選) (Ctrl+C) + + + 批次匯出訂閱內容至剪貼簿(多選) + + + 新增自訂配置伺服器 + + + 新增[Shadowsocks]伺服器 + + + 新增[Socks]伺服器 + + + 新增[Trojan]伺服器 + + + 新增[VLESS]伺服器 + + + 新增[VMess]伺服器 + + + 全選 (Ctrl+A) + + + 清除所有 + + + 複製 (Ctrl+C) + + + 複製所有 + + + 設定資訊過濾器 + + + 全選 (Ctrl+A) + + + 新增 + + + 刪除 + + + 編輯 + + + 分享 + + + 啟用更新 + + + 排序 + + + User Agent(可選) + + + 取消 + + + 確定 + + + 底層傳輸方式(transport) + + + 位址(address) + + + 跳過憑證驗證(allowinsecure) + + + ALPN + + + 額外ID(alterid) + + + Fingerprint + + + 偽裝類型(type) + + + 使用者ID(id) + + + 傳輸協定(network) + + + 路徑(path) + + + 埠(port) + + + 別名(remarks) + + + 偽裝域名(host) + + + 加密方式(security) + + + SNI + + + 傳輸層安全(TLS) + + + *預設TCP,選錯會無法連接 + + + Core類型 + + + 流控(flow) + + + 生成 + + + 密碼(password) + + + 密碼(可選) + + + 使用者ID(id) + + + 加密方式(encryption) + + + 使用者名稱(可選) + + + 加密方式(encryption) + + + SOCKS埠 + + + * 自訂配置的Socks埠值,可不設定;當設定此值後,將使用Xray/sing-box(Tun)額外啟動一個前置Socks服務,提供分流和速度顯示等功能 + + + 瀏覽 + + + 編輯 + + + 進階代理設定, 協定選擇(可選) + + + 允許來自區域網路的連接 + + + 啟動後隱藏視窗 + + + 自動更新Geo文件的間隔(單位小時) + + + Core: 基礎設定 + + + V2ray DNS設定 + + + Core: KCP設定 + + + Core類型設定 + + + 預設跳過憑證驗證(allowinsecure) + + + Outbound Freedom domainStrategy + + + 在更新訂閱後自動調整伺服器列寬 + + + 檢查Pre-Release更新(請謹慎啟用) + + + 例外 + + + 例外. 對於下列字元開頭的位址不使用代理配置檔案:使用分號(;)分隔 + + + 本機HTTP監聽埠 + + + 更新Core時忽略Geo檔案 + + + 去重時保留序號較小的項 + + + 啟用日誌 + + + 日誌等級 + + + 開啟Mux多路復用 + + + v2rayN 設定 + + + 認證密碼 + + + 自訂DNS(可多個,用逗號(,)分隔) + + + 解除Win10 UWP應用回環代理限制 + + + 開啟流量探測 + + + 本機SOCKS監聽埠 + + + 開機啟動(可能會不成功) + + + 啟用統計(即時網速顯示,需重啟) + + + 訂閱轉換網址(可選) + + + 系統代理設定 + + + 啟用安全協定TLS v1.3 (訂閱/檢查更新) + + + 工具列右鍵選單伺服器展示數量限制 + + + 開啟UDP + + + 認證使用者名稱 + + + 清除系統代理 + + + 顯示主介面 + + + 全域快速鍵設定 + + + 直接按鍵盤進行設定, 重啟後生效 + + + 不改變系統代理 + + + 重設 + + + 自動配置系統代理 + + + PAC模式 + + + 分享伺服器 (Ctrl+F) + + + 路由 + + + 以非管理員身份執行 + + + 以管理員身份執行 + + + 下移至底部 (B) + + + 下移 (D) + + + 上移至頂部 (T) + + + 上移 (U) + + + 過濾器, 支援正則 + + + {0} 官網 + + + 進階功能 + + + 新增規則集 + + + 一鍵匯入進階規則 + + + 移除所選規則 (Delete) + + + 設為活動規則 (Enter) + + + 基礎功能 + + + 一鍵匯入基礎規則 + + + 域名匹配演算法 + + + 域名解析策略 + + + 啟動進階功能 + + + 3.阻止的Domain或IP + + + 2.直連的Domain或IP + + + 1.代理的Domain或IP + + + 預定義規則集列表 + + + *設定的路由規則,用逗號(,)分隔;正則中的逗號用<COMMA>替代 + + + 從剪貼簿中匯入規則 + + + 從文件中匯入規則 + + + 從訂閱URL中匯入規則 + + + 規則集設定 + + + 新增規則 + + + 匯出所選規則至剪貼簿 + + + 規則列表 + + + 移除所選規則 (Delete) + + + 路由規則詳情設定 + + + 儲存時Domain, IP, 行程名 自動排序 + + + 規則詳細說明文件 + + + 支援填寫DnsObject,JSON格式,點擊查看文件 + + + 普通分組此處請留空 + + + 路由設定已改變 + + + 系統代理設定已改變 + + + RouteOnly + + + 一鍵多執行緒測試延遲和速度 (Ctrl+E) + + + 延遲(ms) + + + 速度(M/s) + + + 執行Core失敗,請看日誌 + + + 別名正則過濾 + + + 顯示日誌 + + + 匯入舊的配置檔案guiNConfig + + + 啟用TUN模式 + + + 為區域網路開啟新的埠 + + + TUN模式設定 + + + 直連的IP CIDR,用逗號(,)分隔 + + + 直連的行程名,用逗號(,)分隔 + + + 顯示控制台 + + + 移至訂閱分組 + + + 自訂配置模板 + + + 啟動伺服器拖放排序(需重啟) + + + 自動重新整理 + + + 跳過測試 + + + 編輯伺服器 (Ctrl+D) + + + 主介面輕按兩下設為活動伺服器 + + + 測試完成 + + + 預設TLS指紋(fingerprint) + + + 使用者代理(User-Agent) + + + 僅對TCP/HTTP、WS協定生效 + + + 目前字型(需重啟) + + + 複製字型TTF/TTC文件到目錄guiFonts,重啟設定 + + + http端口= +1;Pac端口= +4;*ray API端口= +5;mihomo API端口= +6; + + + 以管理員權限設定此項,在啟動後獲得管理員權限 + + + 字型大小 + + + 代理的IP CIDR,用逗號(,)分隔 + + + 代理的行程名,用逗號(,)分隔 + + + 繞行模式 + + + 測速單個超時值 + + + 測速檔案位址 + + + DNS物件,例如 {"servers":[]} + + + 移至上下 + + + PublicKey + + + ShortId + + + SpiderX + + + 啟用硬體加速(需重啟) + + + 等待测试中(按ESC终止)... + + + 當有異常斷流時請關閉 + + + 未啟動更新,跳過此訂閱 + + + 以管理員身份重啟 + + + 更多位址(url),用逗號(,)分隔;訂閱轉換將失效 + + + 自動更新間隔(分鐘) + + + 啟動日誌存到文件 + + + 訂閱轉換目標類型 + + + 不需要轉換時請留空 + + + DNS設定 + + + sing-box DNS設定 + + + 請填寫 DNS JSON 結構,點擊查看文件 + + + 點擊匯入預設DNS配置 + + + sing-box域名解析策略 + + + sing-box Mux 多路復用協定 + + + 行程名全稱 (TUN模式) + + + Domain + + + IP 或 IP CIDR + + + 新增[Tuic]伺服器 + + + 前置代理別名 + + + 落地代理別名 + + + 請確保別名存在並且唯一 + + + 啟用額外監聽端口 + + + 啟用IPv6 + + + (Domain 或 IP 或 行程名) 与 Port 与 Protocol 与 InboundTag => OutboundTag + + + 自动滚动到末尾 + + + 真連接測試地址 + + + 更新訂閱時只判斷別名是否存在 + + + 測試終止中... + + + *grpc Authority + + + 新增[Http]伺服器 + + + 啟用分片(Fragment) + + + 使用Xray且非Tun模式啟用,和分組前置代理衝突 + + + 啟用sing-box(規則集文件)的緩存文件 + + + 自訂sing-box rule-set + + + 操作成功。 請點選設定選單重啟應用程式。 + + + 打開儲存所在的位置 + + + 可用命令:\n\tswitch_server - 切換伺服器\n\tshow_current_subs - 顯示當前訂閱\n\tadd_subscription - 添加訂閱\n\tremove_subscription - 移除訂閱\n\tchange_proxy_mode - 更改系統代理模式\n\tchange_routing - 更改路由規則\n\tctrl + T - 切換資訊顯示/隱藏\n\tq - 退出程式\n\n輸入'h'隨時查看此幫助資訊。\n + + + 在v2rayN基礎上做了簡單二次開發,將測速、切換節點自動化,在不穩定節點環境中最大限度避免手動幹預。請輸入'h'查看幫助資訊。 + + \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/RunningObjects.cs b/v2rayMiniConsole/v2rayMiniConsole/RunningObjects.cs new file mode 100644 index 00000000..55700c35 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/RunningObjects.cs @@ -0,0 +1,82 @@ +using System.Collections.Concurrent; +using System.Resources; +using v2rayN; +using v2rayN.Handler; +using v2rayN.Models; + +namespace v2rayMiniConsole +{ + public sealed class RunningObjects + { + private static readonly Lazy _instance = new(() => new()); + public static RunningObjects Instance => _instance.Value; + public ConcurrentBag ProfileItems = new ConcurrentBag(); + + //public ResourceManager rm = new ResourceManager("", typeof(Program).Assembly); + + private readonly object lockObj = new object(); + private bool _messageOn = false; + private string _statusStr = string.Empty; + + #region message_management + public bool IsMessageOn() + { + return _messageOn; + } + + public void TurnOnMessage() + { + _messageOn = true; + Console.WriteLine("\nmessage turned on"); + } + public void TurnOffMessage() + { + _messageOn = false; + Console.WriteLine("\nmessage turned off"); + } + public void ToggleMessage() + { + _messageOn = !_messageOn; + string status = _messageOn ? "on" : "off"; + Console.WriteLine($"message turned {status}"); + } + #endregion message_management + + #region status_management + public void SetStatus() + { + var config = LazyConfig.Instance.GetConfig(); + var currentServer = LazyConfig.Instance.GetProfileItem(config.indexId); + var currentRouting = LazyConfig.Instance.GetRoutingItem(config.routingBasicItem.routingIndexId); + lock (lockObj) + { + _statusStr = Utils.GetVersion(false) + " / " + string.Format(Global.StatusTemplate, + currentServer?.address + ":" + currentServer?.port + "-> " + MainTask.Instance.CurrentServerStats?.delay + "ms", + config.sysProxyType.ToString(), currentRouting.remarks); + Console.Title = _statusStr; + } + + } + + public void SetStatus(string testItem,int totalTaskCount, int completedTaskCount) + { + string status = $"{_statusStr} | {testItem}: {completedTaskCount}/{totalTaskCount}"; + lock (lockObj) + { + Console.Title = status; + } + + } + + public void SetStatus(string message) + { + string status = Utils.GetVersion(false) + " / " + message; + lock (lockObj) + { + Console.Title = status; + }; + } + + #endregion status_management + } +} diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleClientConfig b/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleClientConfig new file mode 100644 index 00000000..98c58e0e --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleClientConfig @@ -0,0 +1,61 @@ +{ + "log": { + "access": "Vaccess.log", + "error": "Verror.log", + "loglevel": "warning" + }, + "inbounds": [], + "outbounds": [{ + "tag": "proxy", + "protocol": "vmess", + "settings": { + "vnext": [{ + "address": "v2ray.cool", + "port": 10086, + "users": [{ + "id": "a3482e88-686a-4a58-8126-99c9df64b7bf", + "security": "auto" + }] + }], + "servers": [{ + "address": "v2ray.cool", + "method": "chacha20", + "ota": false, + "password": "123456", + "port": 10086, + "level": 1 + }] + }, + "streamSettings": { + "network": "tcp" + }, + "mux": { + "enabled": false + } + }, + { + "protocol": "freedom", + "settings": {}, + "tag": "direct" + }, + { + "protocol": "blackhole", + "tag": "block", + "settings": { + "response": { + "type": "http" + } + } + } + ], + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "inboundTag": ["api"], + "outboundTag": "api", + "type": "field" + } + ] + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleHttpRequest b/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleHttpRequest new file mode 100644 index 00000000..583b89eb --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleHttpRequest @@ -0,0 +1 @@ +{"version":"1.1","method":"GET","path":[$requestPath$],"headers":{"Host":[$requestHost$],"User-Agent":[$requestUserAgent$],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleHttpResponse b/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleHttpResponse new file mode 100644 index 00000000..257eaf66 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleHttpResponse @@ -0,0 +1 @@ +{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleInbound b/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleInbound new file mode 100644 index 00000000..a21452dd --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleInbound @@ -0,0 +1,18 @@ +{ + "tag": "tag1", + "port": 10808, + "protocol": "socks", + "listen": "127.0.0.1", + "settings": { + "auth": "noauth", + "udp": true, + "allowTransparent": false + }, + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls" + ] + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleOutbound b/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleOutbound new file mode 100644 index 00000000..54885375 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/SampleOutbound @@ -0,0 +1,34 @@ +{ + "tag": "proxy", + "protocol": "vmess", + "settings": { + "vnext": [ + { + "address": "v2ray.cool", + "port": 10086, + "users": [ + { + "id": "a3482e88-686a-4a58-8126-99c9df64b7bf", + "security": "auto" + } + ] + } + ], + "servers": [ + { + "address": "v2ray.cool", + "method": "chacha20", + "ota": false, + "password": "123456", + "port": 10086, + "level": 1 + } + ] + }, + "streamSettings": { + "network": "tcp" + }, + "mux": { + "enabled": false + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/SingboxSampleClientConfig b/v2rayMiniConsole/v2rayMiniConsole/Sample/SingboxSampleClientConfig new file mode 100644 index 00000000..f88422a1 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/SingboxSampleClientConfig @@ -0,0 +1,35 @@ +{ + "log": { + "level": "debug", + "timestamp": true + }, + "inbounds": [], + "outbounds": [ + { + "type": "vless", + "tag": "proxy", + "server": "", + "server_port": 443 + }, + { + "type": "direct", + "tag": "direct" + }, + { + "type": "block", + "tag": "block" + }, + { + "tag": "dns_out", + "type": "dns" + } + ], + "route": { + "rules": [ + { + "protocol": [ "dns" ], + "outbound": "dns_out" + } + ] + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/SingboxSampleOutbound b/v2rayMiniConsole/v2rayMiniConsole/Sample/SingboxSampleOutbound new file mode 100644 index 00000000..9c87194f --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/SingboxSampleOutbound @@ -0,0 +1,6 @@ +{ + "type": "vless", + "tag": "proxy", + "server": "", + "server_port": 443 +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/clash_mixin_yaml b/v2rayMiniConsole/v2rayMiniConsole/Sample/clash_mixin_yaml new file mode 100644 index 00000000..940144ee --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/clash_mixin_yaml @@ -0,0 +1,39 @@ +# +# 配置文件内容不会被修改,混合行为只会发生在内存中 +# +# 注意下面缩进,请用支持yaml显示的编辑器打开 +# +# 使用clash配置文件关键字则覆盖原配置 +# +# removed-rules 循环匹配rules数组每行,符合则移除当前行 (此规则请放最前面) +# +# append-rules 数组合并至原配置rules数组后 +# prepend-rules 数组合并至原配置rules数组前 +# append-proxies 数组合并至原配置proxies数组后 +# prepend-proxies 数组合并至原配置proxies数组前 +# append-proxy-groups 数组合并至原配置proxy-groups数组后 +# prepend-proxy-groups 数组合并至原配置proxy-groups数组前 +# append-rule-providers 数组合并至原配置rule-providers数组后 +# prepend-rule-providers 数组合并至原配置rule-providers数组前 +# + +dns: + enable: true + enhanced-mode: fake-ip + nameserver: + - 114.114.114.114 + - 223.5.5.5 + - 8.8.8.8 + fallback: [] + fake-ip-filter: + - +.stun.*.* + - +.stun.*.*.* + - +.stun.*.*.*.* + - +.stun.*.*.*.*.* + - "*.n.n.srv.nintendo.net" + - +.stun.playstation.net + - xbox.*.*.microsoft.com + - "*.*.xboxlive.com" + - "*.msftncsi.com" + - "*.msftconnecttest.com" + - WORKGROUP \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/clash_tun_yaml b/v2rayMiniConsole/v2rayMiniConsole/Sample/clash_tun_yaml new file mode 100644 index 00000000..d8427f33 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/clash_tun_yaml @@ -0,0 +1,7 @@ +tun: + enable: true + stack: gvisor + dns-hijack: + - 0.0.0.0:53 + auto-route: true + auto-detect-interface: true diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_black b/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_black new file mode 100644 index 00000000..f56cfa2c --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_black @@ -0,0 +1,41 @@ +[ + { + "outboundTag": "direct", + "protocol": [ + "bittorrent" + ] + }, + { + "outboundTag": "block", + "domain": [ + "geosite:category-ads-all" + ] + }, + { + "outboundTag": "proxy", + + "domain": [ + "geosite:geolocation-!cn", + "geosite:greatfire" + ] + }, + { + "outboundTag": "proxy", + "ip": [ + "1.0.0.1", + "1.1.1.1", + "8.8.8.8", + "8.8.4.4", + "geoip:facebook", + "geoip:fastly", + "geoip:google", + "geoip:netflix", + "geoip:telegram", + "geoip:twitter" + ] + }, + { + "port": "0-65535", + "outboundTag": "direct" + } +] \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_global b/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_global new file mode 100644 index 00000000..169d8156 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_global @@ -0,0 +1,6 @@ +[ + { + "port": "0-65535", + "outboundTag": "proxy" + } +] \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_locked b/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_locked new file mode 100644 index 00000000..018ee608 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_locked @@ -0,0 +1,21 @@ +[ + { + "domain": [ + "geosite:google" + ], + "outboundTag": "proxy" + }, + { + "outboundTag": "direct", + "domain": [ + "domain:example-example.com", + "domain:example-example2.com" + ] + }, + { + "outboundTag": "block", + "domain": [ + "geosite:category-ads-all" + ] + } +] \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_rules b/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_rules new file mode 100644 index 00000000..94b28108 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_rules @@ -0,0 +1,28 @@ +[{ + "remarks": "block", + "outboundTag": "block", + "domain": [ + "geosite:category-ads-all" + ] + }, + { + "remarks": "direct", + "outboundTag": "direct", + "domain": [ + "geosite:cn" + ] + }, + { + "remarks": "direct", + "outboundTag": "direct", + "ip": [ + "geoip:private", + "geoip:cn" + ] + }, + { + "remarks": "proxy", + "port": "0-65535", + "outboundTag": "proxy" + } +] \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_white b/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_white new file mode 100644 index 00000000..7d34872f --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/custom_routing_white @@ -0,0 +1,37 @@ +[ + { + "outboundTag": "direct", + "domain": [ + "domain:example-example.com", + "domain:example-example2.com" + ] + }, + { + "outboundTag": "block", + "domain": [ + "geosite:category-ads-all" + ] + }, + { + "outboundTag": "direct", + "domain": [ + "geosite:cn", + "geosite:geolocation-cn" + ] + }, + { + "outboundTag": "direct", + "ip": [ + "223.5.5.5/32", + "119.29.29.29/32", + "180.76.76.76/32", + "114.114.114.114/32", + "geoip:private", + "geoip:cn" + ] + }, + { + "port": "0-65535", + "outboundTag": "proxy" + } +] \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/dns_singbox_normal b/v2rayMiniConsole/v2rayMiniConsole/Sample/dns_singbox_normal new file mode 100644 index 00000000..b323520c --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/dns_singbox_normal @@ -0,0 +1,35 @@ +{ + "servers": [ + { + "tag": "remote", + "address": "tcp://8.8.8.8", + "strategy": "ipv4_only", + "detour": "proxy" + }, + { + "tag": "local", + "address": "223.5.5.5", + "strategy": "ipv4_only", + "detour": "direct" + }, + { + "tag": "block", + "address": "rcode://success" + } + ], + "rules": [ + { + "rule_set": [ + "geosite-geolocation-!cn" + ], + "server": "remote" + }, + { + "rule_set": [ + "geosite-category-ads-all" + ], + "server": "block" + } + ], + "final": "local" +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/dns_v2ray_normal b/v2rayMiniConsole/v2rayMiniConsole/Sample/dns_v2ray_normal new file mode 100644 index 00000000..c4b9ba02 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/dns_v2ray_normal @@ -0,0 +1,21 @@ +{ + "hosts": { + "dns.google": "8.8.8.8", + "proxy.example.com": "127.0.0.1" + }, + "servers": [ + { + "address": "223.5.5.5", + "domains": [ + "geosite:cn", + "geosite:geolocation-cn" + ], + "expectIPs": [ + "geoip:cn" + ] + }, + "1.1.1.1", + "8.8.8.8", + "https://dns.google/dns-query" + ] +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/tun_singbox_dns b/v2rayMiniConsole/v2rayMiniConsole/Sample/tun_singbox_dns new file mode 100644 index 00000000..b323520c --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/tun_singbox_dns @@ -0,0 +1,35 @@ +{ + "servers": [ + { + "tag": "remote", + "address": "tcp://8.8.8.8", + "strategy": "ipv4_only", + "detour": "proxy" + }, + { + "tag": "local", + "address": "223.5.5.5", + "strategy": "ipv4_only", + "detour": "direct" + }, + { + "tag": "block", + "address": "rcode://success" + } + ], + "rules": [ + { + "rule_set": [ + "geosite-geolocation-!cn" + ], + "server": "remote" + }, + { + "rule_set": [ + "geosite-category-ads-all" + ], + "server": "block" + } + ], + "final": "local" +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/tun_singbox_inbound b/v2rayMiniConsole/v2rayMiniConsole/Sample/tun_singbox_inbound new file mode 100644 index 00000000..b4856dd3 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/tun_singbox_inbound @@ -0,0 +1,12 @@ +{ + "type": "tun", + "tag": "tun-in", + "interface_name": "singbox_tun", + "inet4_address": "172.19.0.1/30", + "inet6_address": "fdfe:dcba:9876::1/126", + "mtu": 9000, + "auto_route": true, + "strict_route": false, + "stack": "system", + "sniff": true +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/Sample/tun_singbox_rules b/v2rayMiniConsole/v2rayMiniConsole/Sample/tun_singbox_rules new file mode 100644 index 00000000..df1dc4ec --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/Sample/tun_singbox_rules @@ -0,0 +1,20 @@ +[ + { + "network": "udp", + "port": [ + 135, + 137, + 138, + 139, + 5353 + ], + "outbound": "block" + }, + { + "ip_cidr": [ + "224.0.0.0/3", + "ff00::/8" + ], + "outbound": "block" + } +] \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayMiniConsole/v2rayMiniConsole.csproj b/v2rayMiniConsole/v2rayMiniConsole/v2rayMiniConsole.csproj new file mode 100644 index 00000000..c9d249a9 --- /dev/null +++ b/v2rayMiniConsole/v2rayMiniConsole/v2rayMiniConsole.csproj @@ -0,0 +1,105 @@ + + + + Exe + net8.0-windows + enable + enable + true + 1.1 + Copyright © 2024 (GPLv3) + + + + + + + + + + + + + + + + + + + + + Never + + + Never + + + Never + + + Never + + + Never + + + Never + + + Never + + + Never + + + Never + + + Never + + + Never + + + + Never + + + Never + + + Never + + + Never + + + + + + True + True + ResUI.resx + + + + + + PublicResXFileCodeGenerator + ResUI.Designer.cs + + + PublicResXFileCodeGenerator + + + PublicResXFileCodeGenerator + + + PublicResXFileCodeGenerator + + + PublicResXFileCodeGenerator + + + + diff --git a/v2rayMiniConsole/v2rayUpgrade/MainForm.Designer.cs b/v2rayMiniConsole/v2rayUpgrade/MainForm.Designer.cs new file mode 100644 index 00000000..bdfa4379 --- /dev/null +++ b/v2rayMiniConsole/v2rayUpgrade/MainForm.Designer.cs @@ -0,0 +1,107 @@ +namespace v2rayUpgrade +{ + partial class MainForm + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows 窗体设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要修改 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + this.btnClose = new System.Windows.Forms.Button(); + this.btnOK = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // btnClose + // + this.btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnClose.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.btnClose.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.btnClose.Location = new System.Drawing.Point(367, 118); + this.btnClose.Name = "btnClose"; + this.btnClose.Size = new System.Drawing.Size(184, 89); + this.btnClose.TabIndex = 1; + this.btnClose.Text = "&Exit(退出)"; + this.btnClose.UseVisualStyleBackColor = true; + this.btnClose.Click += new System.EventHandler(this.btnClose_Click); + // + // btnOK + // + this.btnOK.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.btnOK.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.btnOK.Location = new System.Drawing.Point(81, 118); + this.btnOK.Name = "btnOK"; + this.btnOK.Size = new System.Drawing.Size(184, 89); + this.btnOK.TabIndex = 0; + this.btnOK.Text = "&Upgrade(升级)"; + this.btnOK.UseVisualStyleBackColor = true; + this.btnOK.Click += new System.EventHandler(this.btnOK_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.label1.Location = new System.Drawing.Point(79, 64); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(205, 15); + this.label1.TabIndex = 8; + this.label1.Text = "升级成功后将自动重启v2rayN"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.label2.Location = new System.Drawing.Point(79, 37); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(471, 15); + this.label2.TabIndex = 9; + this.label2.Text = "v2rayN will restart automatically after successful upgrade"; + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(616, 284); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Controls.Add(this.btnClose); + this.Controls.Add(this.btnOK); + this.Name = "MainForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "v2rayUpgrade"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button btnClose; + private System.Windows.Forms.Button btnOK; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + } +} + diff --git a/v2rayMiniConsole/v2rayUpgrade/MainForm.cs b/v2rayMiniConsole/v2rayUpgrade/MainForm.cs new file mode 100644 index 00000000..39f8196c --- /dev/null +++ b/v2rayMiniConsole/v2rayUpgrade/MainForm.cs @@ -0,0 +1,147 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Text; +using System.Windows.Forms; + +namespace v2rayUpgrade +{ + public partial class MainForm : Form + { + private readonly string defaultFilename = "v2ray-windows.zip"; + private string? fileName; + + public MainForm(string[] args) + { + InitializeComponent(); + if (args.Length > 0) + { + fileName = Uri.UnescapeDataString(string.Join(" ", args)); + } + else + { + fileName = defaultFilename; + } + } + + private void ShowWarn(string message) + { + MessageBox.Show(message, "", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + + private void btnOK_Click(object sender, EventArgs e) + { + try + { + Process[] existing = Process.GetProcessesByName("v2rayN"); + foreach (Process p in existing) + { + string? path = p.MainModule?.FileName; + if (path == GetPath("v2rayN.exe")) + { + p.Kill(); + p.WaitForExit(100); + } + } + } + catch (Exception ex) + { + // Access may be denied without admin right. The user may not be an administrator. + ShowWarn("Failed to close v2rayN(关闭v2rayN失败).\n" + + "Close it manually, or the upgrade may fail.(请手动关闭正在运行的v2rayN,否则可能升级失败。\n\n" + ex.StackTrace); + } + + if (!File.Exists(fileName)) + { + if (File.Exists(defaultFilename)) + { + fileName = defaultFilename; + } + else + { + ShowWarn("Upgrade Failed, File Not Exist(升级失败,文件不存在)."); + return; + } + } + + StringBuilder sb = new(); + try + { + string thisAppOldFile = $"{Application.ExecutablePath}.tmp"; + File.Delete(thisAppOldFile); + string startKey = "v2rayN/"; + + using ZipArchive archive = ZipFile.OpenRead(fileName); + foreach (ZipArchiveEntry entry in archive.Entries) + { + try + { + if (entry.Length == 0) + { + continue; + } + string fullName = entry.FullName; + if (fullName.StartsWith(startKey)) + { + fullName = fullName[startKey.Length..]; + } + if (string.Equals(Application.ExecutablePath, GetPath(fullName), StringComparison.OrdinalIgnoreCase)) + { + File.Move(Application.ExecutablePath, thisAppOldFile); + } + + string entryOutputPath = GetPath(fullName); + Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!); + entry.ExtractToFile(entryOutputPath, true); + } + catch (Exception ex) + { + sb.Append(ex.StackTrace); + } + } + } + catch (Exception ex) + { + ShowWarn("Upgrade Failed(升级失败)." + ex.StackTrace); + return; + } + if (sb.Length > 0) + { + ShowWarn("Upgrade Failed,Hold ctrl + c to copy to clipboard.\n" + + "(升级失败,按住ctrl+c可以复制到剪贴板)." + sb.ToString()); + return; + } + + Process.Start("v2rayN.exe"); + MessageBox.Show("Upgrade successed(升级成功)", "", MessageBoxButtons.OK, MessageBoxIcon.Information); + + Close(); + } + + private void btnClose_Click(object sender, EventArgs e) + { + Close(); + } + + public static string GetExePath() + { + return Application.ExecutablePath; + } + + public static string StartupPath() + { + return Application.StartupPath; + } + + public static string GetPath(string fileName) + { + string startupPath = StartupPath(); + if (string.IsNullOrEmpty(fileName)) + { + return startupPath; + } + return Path.Combine(startupPath, fileName); + } + } +} diff --git a/v2rayMiniConsole/v2rayUpgrade/MainForm.resx b/v2rayMiniConsole/v2rayUpgrade/MainForm.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/v2rayMiniConsole/v2rayUpgrade/MainForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayUpgrade/Program.cs b/v2rayMiniConsole/v2rayUpgrade/Program.cs new file mode 100644 index 00000000..bc4303de --- /dev/null +++ b/v2rayMiniConsole/v2rayUpgrade/Program.cs @@ -0,0 +1,21 @@ +using System; +using System.Windows.Forms; + +namespace v2rayUpgrade +{ + internal static class Program + { + /// + /// 应用程序的主入口点。 + /// + [STAThread] + private static void Main(string[] args) + { + Application.EnableVisualStyles(); + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm(args)); + } + } +} \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayUpgrade/Properties/Resources.Designer.cs b/v2rayMiniConsole/v2rayUpgrade/Properties/Resources.Designer.cs new file mode 100644 index 00000000..f766ed10 --- /dev/null +++ b/v2rayMiniConsole/v2rayUpgrade/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace v2rayUpgrade.Properties { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("v2rayUpgrade.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/v2rayMiniConsole/v2rayUpgrade/Properties/Resources.resx b/v2rayMiniConsole/v2rayUpgrade/Properties/Resources.resx new file mode 100644 index 00000000..af7dbebb --- /dev/null +++ b/v2rayMiniConsole/v2rayUpgrade/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayUpgrade/Properties/Settings.Designer.cs b/v2rayMiniConsole/v2rayUpgrade/Properties/Settings.Designer.cs new file mode 100644 index 00000000..ecf1927f --- /dev/null +++ b/v2rayMiniConsole/v2rayUpgrade/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace v2rayUpgrade.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.3.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/v2rayMiniConsole/v2rayUpgrade/Properties/Settings.settings b/v2rayMiniConsole/v2rayUpgrade/Properties/Settings.settings new file mode 100644 index 00000000..39645652 --- /dev/null +++ b/v2rayMiniConsole/v2rayUpgrade/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/v2rayMiniConsole/v2rayUpgrade/app.manifest b/v2rayMiniConsole/v2rayUpgrade/app.manifest new file mode 100644 index 00000000..3081161b --- /dev/null +++ b/v2rayMiniConsole/v2rayUpgrade/app.manifest @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/v2rayMiniConsole/v2rayUpgrade/v2rayUpgrade.csproj b/v2rayMiniConsole/v2rayUpgrade/v2rayUpgrade.csproj new file mode 100644 index 00000000..73349b29 --- /dev/null +++ b/v2rayMiniConsole/v2rayUpgrade/v2rayUpgrade.csproj @@ -0,0 +1,11 @@ + + + net8.0-windows + WinExe + true + Copyright © 2019-2024 (GPLv3) + 1.1.0.0 + app.manifest + enable + + \ No newline at end of file