mirror of
https://github.com/netchx/netch.git
synced 2026-05-11 23:45:06 +08:00
Compare commits
41 Commits
1.8.2
...
1.8.3-Beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a3a1e3664 | ||
|
|
3f4a31dac8 | ||
|
|
7c0088cc7f | ||
|
|
1a28d791b7 | ||
|
|
05df786ab6 | ||
|
|
1ffbae6135 | ||
|
|
8ca9d6d9be | ||
|
|
4f1ae20b9b | ||
|
|
15a1db3b21 | ||
|
|
54243a80e7 | ||
|
|
947bf2b3ca | ||
|
|
2a165c79df | ||
|
|
55280df299 | ||
|
|
af48e7119e | ||
|
|
a1b978a22c | ||
|
|
d08a9d5bfd | ||
|
|
c69c40750a | ||
|
|
77376502b7 | ||
|
|
fdfc3f11eb | ||
|
|
0d956efac6 | ||
|
|
3f9709167d | ||
|
|
425e468f78 | ||
|
|
a485a4647c | ||
|
|
0b484face4 | ||
|
|
e0b5b0e49c | ||
|
|
f519850ffc | ||
|
|
4513a68e73 | ||
|
|
95de42e778 | ||
|
|
18168c3a4e | ||
|
|
77f2b761fc | ||
|
|
9bd02ec122 | ||
|
|
cfb4a5b3f6 | ||
|
|
6178045f15 | ||
|
|
4773de99e5 | ||
|
|
eb713db867 | ||
|
|
15f4895c0f | ||
|
|
afbda60dfb | ||
|
|
f51229f2c8 | ||
|
|
26f9ae3958 | ||
|
|
dfc680f0b7 | ||
|
|
5e56556534 |
30
.github/workflows/ci.yml
vendored
Normal file
30
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Netch CI
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
dependabot/**
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Build Solution
|
||||
shell: pwsh
|
||||
run: .\BUILD.ps1
|
||||
|
||||
- name: Upload Artifact
|
||||
continue-on-error: true
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Netch
|
||||
path: Netch\bin\x64\Release
|
||||
@@ -1,5 +1,8 @@
|
||||
name: Netch CI
|
||||
on: [push, pull_request]
|
||||
name: Netch Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*.*'
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
@@ -17,14 +20,6 @@ jobs:
|
||||
shell: pwsh
|
||||
run: .\BUILD.ps1
|
||||
|
||||
- name: Upload Artifact
|
||||
continue-on-error: true
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Netch
|
||||
path: Netch\bin\x64\Release
|
||||
|
||||
- name: Package
|
||||
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}
|
||||
shell: pwsh
|
||||
@@ -39,10 +34,8 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
name: ${{ env.GITHUB_TAG_NAME }}
|
||||
prerelease: true
|
||||
prerelease: ${{ contains(github.ref, '-') }}
|
||||
draft: false
|
||||
files: |
|
||||
C:\builtfiles\Netch.7z
|
||||
@@ -55,4 +48,4 @@ jobs:
|
||||
## 校验和
|
||||
| 文件名 | SHA256 |
|
||||
| :- | :- |
|
||||
| Netch.7z | ${{ env.Netch_SHA256 }} |
|
||||
| Netch.7z | ${{ env.Netch_SHA256 }} |
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
/.vs
|
||||
/packages
|
||||
.vs/
|
||||
.idea/
|
||||
/*.user
|
||||
*/bin/
|
||||
*/obj/
|
||||
*.csproj.user
|
||||
3
Netch/.gitignore
vendored
3
Netch/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
/bin
|
||||
/obj
|
||||
/Netch.csproj.user
|
||||
@@ -24,7 +24,6 @@ namespace Netch.Controllers
|
||||
public void Start(in Mode mode)
|
||||
{
|
||||
PrivoxyController.Start(MainController.Server!);
|
||||
Global.Job.AddProcess(PrivoxyController.Instance!);
|
||||
string? pacUrl = null;
|
||||
|
||||
if (MainController.Server is Socks5 or Trojan && mode.BypassChina || (Global.Settings.AlwaysStartPACServer ?? false))
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Netch.Models;
|
||||
using Netch.Servers.Socks5;
|
||||
using Netch.Utils;
|
||||
using static Netch.Utils.PortHelper;
|
||||
|
||||
namespace Netch.Controllers
|
||||
{
|
||||
@@ -105,20 +104,11 @@ namespace Netch.Controllers
|
||||
{
|
||||
controller = ServerHelper.GetUtilByTypeName(server.Type).GetController();
|
||||
|
||||
if (controller is Guard instanceController)
|
||||
Utils.Utils.KillProcessByName(instanceController.MainFile);
|
||||
|
||||
PortCheck(controller.Socks5LocalPort(), "Socks5");
|
||||
TryReleaseTcpPort(controller.Socks5LocalPort(), "Socks5");
|
||||
|
||||
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", controller.Name));
|
||||
|
||||
controller.Start(in server, mode);
|
||||
if (controller is Guard {Instance: { }} guard)
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
Global.Job.AddProcess(guard.Instance!);
|
||||
});
|
||||
|
||||
if (server is Socks5 socks5)
|
||||
{
|
||||
@@ -133,19 +123,17 @@ namespace Netch.Controllers
|
||||
|
||||
private static void StartMode(Mode mode)
|
||||
{
|
||||
ModeController = ModeHelper.GetModeControllerByType(mode.Type, out var port, out var portName, out var portType);
|
||||
ModeController = ModeHelper.GetModeControllerByType(mode.Type, out var port, out var portName);
|
||||
|
||||
if (ModeController == null)
|
||||
return;
|
||||
|
||||
if (port != null)
|
||||
PortCheck((ushort) port, portName, portType);
|
||||
TryReleaseTcpPort((ushort) port, portName);
|
||||
|
||||
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ModeController.Name));
|
||||
|
||||
ModeController.Start(mode);
|
||||
if (ModeController is Guard {Instance: { }} guard)
|
||||
Global.Job.AddProcess(guard.Instance!);
|
||||
}
|
||||
|
||||
public static async Task StopAsync()
|
||||
@@ -189,7 +177,7 @@ namespace Netch.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
CheckPort(port, portType);
|
||||
PortHelper.CheckPort(port, portType);
|
||||
}
|
||||
catch (PortInUseException)
|
||||
{
|
||||
@@ -200,6 +188,26 @@ namespace Netch.Controllers
|
||||
throw new MessageException(i18N.TranslateFormat("The {0} port is reserved by system.", $"{portName} ({port})"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void TryReleaseTcpPort(ushort port, string portName)
|
||||
{
|
||||
foreach (var p in PortHelper.GetProcessByUsedTcpPort(port))
|
||||
{
|
||||
if (p.MainModule!.FileName.StartsWith(Global.NetchDir))
|
||||
{
|
||||
p.Kill();
|
||||
p.WaitForExit();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new MessageException(i18N.TranslateFormat("The {0} port is used by {1}.",
|
||||
$"{portName} ({port})",
|
||||
$"({p.Id}){p.MainModule.FileName}"));
|
||||
}
|
||||
}
|
||||
|
||||
PortCheck(port, portName, PortType.TCP);
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageException : Exception
|
||||
|
||||
@@ -17,57 +17,30 @@ namespace Netch.Controllers
|
||||
{
|
||||
private static readonly ServiceController NFService = new("netfilter2");
|
||||
|
||||
private static readonly string BinDriver;
|
||||
private const string BinDriver = "bin\\nfdriver.sys";
|
||||
private static readonly string SystemDriver = $"{Environment.SystemDirectory}\\drivers\\netfilter2.sys";
|
||||
|
||||
static NFController()
|
||||
{
|
||||
string fileName;
|
||||
switch ($"{Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}")
|
||||
{
|
||||
case "10.0":
|
||||
case "6.3":
|
||||
case "6.2":
|
||||
case "6.1":
|
||||
case "6.0":
|
||||
fileName = "nfdriver.sys";
|
||||
break;
|
||||
default:
|
||||
throw new MessageException($"不支持的系统版本:{Environment.OSVersion.Version}");
|
||||
}
|
||||
|
||||
BinDriver = "bin\\" + fileName;
|
||||
}
|
||||
|
||||
public string Name { get; } = "Redirector";
|
||||
|
||||
public void Start(in Mode mode)
|
||||
{
|
||||
CheckDriver();
|
||||
|
||||
#region aio_dial
|
||||
aio_dial((int) NameList.TYPE_FILTERLOOPBACK, "false");
|
||||
aio_dial((int) NameList.TYPE_TCPLISN, Global.Settings.RedirectorTCPPort.ToString());
|
||||
|
||||
aio_dial((int)NameList.TYPE_FILTERLOOPBACK, "false");
|
||||
aio_dial((int)NameList.TYPE_TCPLISN, Global.Settings.RedirectorTCPPort.ToString());
|
||||
// Server
|
||||
aio_dial((int) NameList.TYPE_FILTERUDP, (Global.Settings.ProcessProxyProtocol != PortType.TCP).ToString().ToLower());
|
||||
aio_dial((int) NameList.TYPE_FILTERTCP, (Global.Settings.ProcessProxyProtocol != PortType.UDP).ToString().ToLower());
|
||||
dial_Server(Global.Settings.ProcessProxyProtocol);
|
||||
|
||||
aio_dial((int)NameList.TYPE_FILTERUDP, (Global.Settings.ProcessProxyProtocol != PortType.TCP).ToString().ToLower());
|
||||
aio_dial((int)NameList.TYPE_FILTERTCP, (Global.Settings.ProcessProxyProtocol != PortType.UDP).ToString().ToLower());
|
||||
SetServer(Global.Settings.ProcessProxyProtocol);
|
||||
// Mode Rule
|
||||
dial_Name(mode);
|
||||
|
||||
if (!CheckRule(mode.FullRule, out var list))
|
||||
throw new MessageException($"\"{string.Join("", list.Select(s => s + "\n"))}\" does not conform to C++ regular expression syntax");
|
||||
|
||||
SetName(mode);
|
||||
|
||||
#endregion
|
||||
|
||||
if (Global.Settings.RedirectDNS)
|
||||
aio_dial((int)NameList.TYPE_REDIRCTOR_DNS, Global.Settings.RedirectDNSAddr.ToString());
|
||||
|
||||
if (Global.Settings.RedirectICMP)
|
||||
aio_dial((int)NameList.TYPE_REDIRCTOR_ICMP, Global.Settings.RedirectICMPAddr.ToString());
|
||||
|
||||
aio_dial((int)NameList.TYPE_FILTERCHILDPROC, Global.Settings.ChildProcessHandle.ToString());
|
||||
// Features
|
||||
aio_dial((int) NameList.TYPE_REDIRCTOR_DNS, Global.Settings.RedirectDNS ? Global.Settings.RedirectDNSAddr : "");
|
||||
aio_dial((int) NameList.TYPE_REDIRCTOR_ICMP, Global.Settings.RedirectICMP ? Global.Settings.RedirectICMPAddr : "");
|
||||
aio_dial((int) NameList.TYPE_FILTERCHILDPROC, Global.Settings.ChildProcessHandle.ToString().ToLower());
|
||||
|
||||
if (!aio_init())
|
||||
throw new MessageException("Redirector Start failed, run Netch with \"-console\" argument");
|
||||
@@ -78,83 +51,54 @@ namespace Netch.Controllers
|
||||
aio_free();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="rules"></param>
|
||||
/// <param name="incompatibleRule"></param>
|
||||
/// <returns>No Problem true</returns>
|
||||
public static bool CheckRule(IEnumerable<string> rules, out IEnumerable<string> incompatibleRule)
|
||||
{
|
||||
incompatibleRule = rules.Where(r => !CheckCppRegex(r, false));
|
||||
aio_dial((int)NameList.TYPE_CLRNAME, "");
|
||||
return !incompatibleRule.Any();
|
||||
}
|
||||
#region CheckRule
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="r"></param>
|
||||
/// <param name="clear"></param>
|
||||
/// <returns>No Problem true</returns>
|
||||
public static bool CheckCppRegex(string r, bool clear = true)
|
||||
private static bool CheckCppRegex(string r, bool clear = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (r.StartsWith("!"))
|
||||
return aio_dial((int)NameList.TYPE_ADDNAME, r.Substring(1));
|
||||
return aio_dial((int) NameList.TYPE_ADDNAME, r.Substring(1));
|
||||
|
||||
return aio_dial((int)NameList.TYPE_ADDNAME, r);
|
||||
return aio_dial((int) NameList.TYPE_ADDNAME, r);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (clear)
|
||||
aio_dial((int)NameList.TYPE_CLRNAME, "");
|
||||
aio_dial((int) NameList.TYPE_CLRNAME, "");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckDriver()
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="rules"></param>
|
||||
/// <param name="results"></param>
|
||||
/// <returns>No Problem true</returns>
|
||||
public static bool CheckRules(IEnumerable<string> rules, out IEnumerable<string> results)
|
||||
{
|
||||
var binFileVersion = Utils.Utils.GetFileVersion(BinDriver);
|
||||
var systemFileVersion = Utils.Utils.GetFileVersion(SystemDriver);
|
||||
|
||||
Logging.Info("内置驱动版本: " + binFileVersion);
|
||||
Logging.Info("系统驱动版本: " + systemFileVersion);
|
||||
|
||||
if (!File.Exists(SystemDriver))
|
||||
{
|
||||
InstallDriver();
|
||||
return;
|
||||
}
|
||||
|
||||
var reinstallFlag = false;
|
||||
if (Version.TryParse(binFileVersion, out var binResult) && Version.TryParse(systemFileVersion, out var systemResult))
|
||||
{
|
||||
if (binResult.CompareTo(systemResult) > 0)
|
||||
// Bin greater than Installed
|
||||
reinstallFlag = true;
|
||||
else if (systemResult.Major != binResult.Major)
|
||||
// Installed greater than Bin but Major Version Difference (has breaking changes), do downgrade
|
||||
reinstallFlag = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!systemFileVersion.Equals(binFileVersion))
|
||||
reinstallFlag = true;
|
||||
}
|
||||
|
||||
if (!reinstallFlag)
|
||||
return;
|
||||
|
||||
Logging.Info("更新驱动");
|
||||
UninstallDriver();
|
||||
InstallDriver();
|
||||
results = rules.Where(r => !CheckCppRegex(r, false));
|
||||
aio_dial((int) NameList.TYPE_CLRNAME, "");
|
||||
return !results.Any();
|
||||
}
|
||||
|
||||
private void SetServer(in PortType portType)
|
||||
public static string GenerateInvalidRulesMessage(IEnumerable<string> rules)
|
||||
{
|
||||
return $"{string.Join("\n", rules)}\nAbove rules does not conform to C++ regular expression syntax";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void dial_Server(in PortType portType)
|
||||
{
|
||||
if (portType == PortType.Both)
|
||||
{
|
||||
SetServer(PortType.TCP);
|
||||
SetServer(PortType.UDP);
|
||||
dial_Server(PortType.TCP);
|
||||
dial_Server(PortType.UDP);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -177,105 +121,97 @@ namespace Netch.Controllers
|
||||
|
||||
if (server is Socks5 socks5)
|
||||
{
|
||||
aio_dial((int)NameList.TYPE_TCPTYPE + offset, "Socks5");
|
||||
aio_dial((int)NameList.TYPE_TCPHOST + offset, $"{socks5.AutoResolveHostname()}:{socks5.Port}");
|
||||
aio_dial((int)NameList.TYPE_TCPUSER + offset, socks5.Username ?? string.Empty);
|
||||
aio_dial((int)NameList.TYPE_TCPPASS + offset, socks5.Password ?? string.Empty);
|
||||
aio_dial((int)NameList.TYPE_TCPMETH + offset, string.Empty);
|
||||
aio_dial((int) NameList.TYPE_TCPTYPE + offset, "Socks5");
|
||||
aio_dial((int) NameList.TYPE_TCPHOST + offset, $"{socks5.AutoResolveHostname()}:{socks5.Port}");
|
||||
aio_dial((int) NameList.TYPE_TCPUSER + offset, socks5.Username ?? string.Empty);
|
||||
aio_dial((int) NameList.TYPE_TCPPASS + offset, socks5.Password ?? string.Empty);
|
||||
aio_dial((int) NameList.TYPE_TCPMETH + offset, string.Empty);
|
||||
}
|
||||
else if (server is Shadowsocks shadowsocks && !shadowsocks.HasPlugin() && Global.Settings.RedirectorSS)
|
||||
{
|
||||
aio_dial((int)NameList.TYPE_TCPTYPE + offset, "Shadowsocks");
|
||||
aio_dial((int)NameList.TYPE_TCPHOST + offset, $"{shadowsocks.AutoResolveHostname()}:{shadowsocks.Port}");
|
||||
aio_dial((int)NameList.TYPE_TCPMETH + offset, shadowsocks.EncryptMethod ?? string.Empty);
|
||||
aio_dial((int)NameList.TYPE_TCPPASS + offset, shadowsocks.Password ?? string.Empty);
|
||||
aio_dial((int) NameList.TYPE_TCPTYPE + offset, "Shadowsocks");
|
||||
aio_dial((int) NameList.TYPE_TCPHOST + offset, $"{shadowsocks.AutoResolveHostname()}:{shadowsocks.Port}");
|
||||
aio_dial((int) NameList.TYPE_TCPMETH + offset, shadowsocks.EncryptMethod);
|
||||
aio_dial((int) NameList.TYPE_TCPPASS + offset, shadowsocks.Password);
|
||||
}
|
||||
else
|
||||
{
|
||||
aio_dial((int)NameList.TYPE_TCPTYPE + offset, "Socks5");
|
||||
aio_dial((int)NameList.TYPE_TCPHOST + offset, $"127.0.0.1:{controller.Socks5LocalPort()}");
|
||||
aio_dial((int)NameList.TYPE_TCPUSER + offset, string.Empty);
|
||||
aio_dial((int)NameList.TYPE_TCPPASS + offset, string.Empty);
|
||||
aio_dial((int)NameList.TYPE_TCPMETH + offset, string.Empty);
|
||||
aio_dial((int) NameList.TYPE_TCPTYPE + offset, "Socks5");
|
||||
aio_dial((int) NameList.TYPE_TCPHOST + offset, $"127.0.0.1:{controller.Socks5LocalPort()}");
|
||||
aio_dial((int) NameList.TYPE_TCPUSER + offset, string.Empty);
|
||||
aio_dial((int) NameList.TYPE_TCPPASS + offset, string.Empty);
|
||||
aio_dial((int) NameList.TYPE_TCPMETH + offset, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetName(Mode mode)
|
||||
private void dial_Name(Mode mode)
|
||||
{
|
||||
aio_dial((int)NameList.TYPE_CLRNAME, "");
|
||||
foreach (var rule in mode.FullRule)
|
||||
aio_dial((int) NameList.TYPE_CLRNAME, "");
|
||||
var list = new List<string>();
|
||||
foreach (var s in mode.FullRule)
|
||||
{
|
||||
if (rule.StartsWith("!"))
|
||||
if (s.StartsWith("!"))
|
||||
{
|
||||
aio_dial((int)NameList.TYPE_BYPNAME, rule.Substring(1));
|
||||
if (!aio_dial((int) NameList.TYPE_BYPNAME, s.Substring(1)))
|
||||
list.Add(s);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
aio_dial((int)NameList.TYPE_ADDNAME, rule);
|
||||
if (!aio_dial((int) NameList.TYPE_ADDNAME, s))
|
||||
list.Add(s);
|
||||
}
|
||||
|
||||
aio_dial((int)NameList.TYPE_ADDNAME, @"NTT\.exe");
|
||||
aio_dial((int)NameList.TYPE_BYPNAME, "^" + Global.NetchDir.ToRegexString() + @"((?!NTT\.exe).)*$");
|
||||
if (list.Any())
|
||||
throw new MessageException(GenerateInvalidRulesMessage(list));
|
||||
|
||||
aio_dial((int) NameList.TYPE_ADDNAME, @"NTT\.exe");
|
||||
aio_dial((int) NameList.TYPE_BYPNAME, "^" + Global.NetchDir.ToRegexString() + @"((?!NTT\.exe).)*$");
|
||||
}
|
||||
|
||||
#region NativeMethods
|
||||
#region DriverUtil
|
||||
|
||||
private const int UdpNameListOffset = (int)NameList.TYPE_UDPTYPE - (int)NameList.TYPE_TCPTYPE;
|
||||
|
||||
[DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool aio_dial(int name, [MarshalAs(UnmanagedType.LPWStr)] string value);
|
||||
|
||||
[DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool aio_init();
|
||||
|
||||
[DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool aio_free();
|
||||
|
||||
[DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern ulong aio_getUP();
|
||||
|
||||
[DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern ulong aio_getDL();
|
||||
|
||||
public enum NameList
|
||||
private static void CheckDriver()
|
||||
{
|
||||
//bool
|
||||
TYPE_FILTERLOOPBACK,
|
||||
TYPE_FILTERTCP,
|
||||
TYPE_FILTERUDP,
|
||||
TYPE_FILTERIP,
|
||||
TYPE_FILTERCHILDPROC,//子进程捕获
|
||||
var binFileVersion = Utils.Utils.GetFileVersion(BinDriver);
|
||||
var systemFileVersion = Utils.Utils.GetFileVersion(SystemDriver);
|
||||
|
||||
TYPE_TCPLISN,
|
||||
TYPE_TCPTYPE,
|
||||
TYPE_TCPHOST,
|
||||
TYPE_TCPUSER,
|
||||
TYPE_TCPPASS,
|
||||
TYPE_TCPMETH,
|
||||
Logging.Info("内置驱动版本: " + binFileVersion);
|
||||
Logging.Info("系统驱动版本: " + systemFileVersion);
|
||||
|
||||
TYPE_UDPTYPE,
|
||||
TYPE_UDPHOST,
|
||||
TYPE_UDPUSER,
|
||||
TYPE_UDPPASS,
|
||||
TYPE_UDPMETH,
|
||||
if (!File.Exists(SystemDriver))
|
||||
{
|
||||
// Install
|
||||
InstallDriver();
|
||||
return;
|
||||
}
|
||||
|
||||
TYPE_ADDNAME,
|
||||
TYPE_ADDFIP,
|
||||
var reinstall = false;
|
||||
if (Version.TryParse(binFileVersion, out var binResult) && Version.TryParse(systemFileVersion, out var systemResult))
|
||||
{
|
||||
if (binResult.CompareTo(systemResult) > 0)
|
||||
// Update
|
||||
reinstall = true;
|
||||
else if (systemResult.Major != binResult.Major)
|
||||
// Downgrade when Major version different (may have breaking changes)
|
||||
reinstall = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse File versionName to Version failed
|
||||
if (!systemFileVersion.Equals(binFileVersion))
|
||||
// versionNames are different, Reinstall
|
||||
reinstall = true;
|
||||
}
|
||||
|
||||
TYPE_BYPNAME,
|
||||
if (!reinstall)
|
||||
return;
|
||||
|
||||
TYPE_CLRNAME,
|
||||
TYPE_CLRFIP,
|
||||
|
||||
//str addr x.x.x.x only ipv4
|
||||
TYPE_REDIRCTOR_DNS,
|
||||
TYPE_REDIRCTOR_ICMP
|
||||
Logging.Info("更新驱动");
|
||||
UninstallDriver();
|
||||
InstallDriver();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utils
|
||||
|
||||
/// <summary>
|
||||
/// 安装 NF 驱动
|
||||
/// </summary>
|
||||
@@ -341,5 +277,61 @@ namespace Netch.Controllers
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region NativeMethods
|
||||
|
||||
private const int UdpNameListOffset = (int) NameList.TYPE_UDPTYPE - (int) NameList.TYPE_TCPTYPE;
|
||||
|
||||
[DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool aio_dial(int name, [MarshalAs(UnmanagedType.LPWStr)] string value);
|
||||
|
||||
[DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool aio_init();
|
||||
|
||||
[DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool aio_free();
|
||||
|
||||
[DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern ulong aio_getUP();
|
||||
|
||||
[DllImport("Redirector.bin", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern ulong aio_getDL();
|
||||
|
||||
public enum NameList
|
||||
{
|
||||
//bool
|
||||
TYPE_FILTERLOOPBACK,
|
||||
TYPE_FILTERTCP,
|
||||
TYPE_FILTERUDP,
|
||||
TYPE_FILTERIP,
|
||||
TYPE_FILTERCHILDPROC, //子进程捕获
|
||||
|
||||
TYPE_TCPLISN,
|
||||
TYPE_TCPTYPE,
|
||||
TYPE_TCPHOST,
|
||||
TYPE_TCPUSER,
|
||||
TYPE_TCPPASS,
|
||||
TYPE_TCPMETH,
|
||||
|
||||
TYPE_UDPTYPE,
|
||||
TYPE_UDPHOST,
|
||||
TYPE_UDPUSER,
|
||||
TYPE_UDPPASS,
|
||||
TYPE_UDPMETH,
|
||||
|
||||
TYPE_ADDNAME,
|
||||
TYPE_ADDFIP,
|
||||
|
||||
TYPE_BYPNAME,
|
||||
|
||||
TYPE_CLRNAME,
|
||||
TYPE_CLRFIP,
|
||||
|
||||
//str addr x.x.x.x only ipv4
|
||||
TYPE_REDIRCTOR_DNS,
|
||||
TYPE_REDIRCTOR_ICMP
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace Netch.Controllers
|
||||
Global.MainForm.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (!_form!.IsDisposed)
|
||||
_form!.richTextBox1.AppendText(line + "\n");
|
||||
_form.richTextBox1.AppendText(line + "\n");
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
@@ -18,8 +19,8 @@ namespace Netch.Controllers
|
||||
public const string Name = @"Netch";
|
||||
public const string Copyright = @"Copyright © 2019 - 2021";
|
||||
|
||||
public const string AssemblyVersion = @"1.8.2";
|
||||
private const string Suffix = @"";
|
||||
public const string AssemblyVersion = @"1.8.3";
|
||||
private const string Suffix = @"Beta2";
|
||||
|
||||
public static readonly string Version = $"{AssemblyVersion}{(string.IsNullOrEmpty(Suffix) ? "" : $"-{Suffix}")}";
|
||||
|
||||
@@ -89,5 +90,19 @@ namespace Netch.Controllers
|
||||
fileName = match.Groups["filename"].Value;
|
||||
sha256 = match.Groups["sha256"].Value;
|
||||
}
|
||||
|
||||
public static string GetLatestReleaseContent()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (string l in LatestRelease.body.GetLines(false).SkipWhile(l => l.FirstOrDefault() != '#'))
|
||||
{
|
||||
if (l.Contains("校验和"))
|
||||
break;
|
||||
|
||||
sb.AppendLine(l);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
Netch/Forms/MainForm.Designer.cs
generated
4
Netch/Forms/MainForm.Designer.cs
generated
@@ -423,7 +423,7 @@
|
||||
this.ModeComboBox.Size = new System.Drawing.Size(546, 24);
|
||||
this.ModeComboBox.TabIndex = 2;
|
||||
this.ModeComboBox.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.ComboBox_DrawItem);
|
||||
this.ModeComboBox.SelectedIndexChanged += new System.EventHandler(this.ModeComboBox_SelectedIndexChanged);
|
||||
this.ModeComboBox.SelectionChangeCommitted += new System.EventHandler(this.ModeComboBox_SelectionChangeCommitted);
|
||||
//
|
||||
// ServerComboBox
|
||||
//
|
||||
@@ -438,7 +438,7 @@
|
||||
this.ServerComboBox.Size = new System.Drawing.Size(546, 24);
|
||||
this.ServerComboBox.TabIndex = 1;
|
||||
this.ServerComboBox.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.ComboBox_DrawItem);
|
||||
this.ServerComboBox.SelectedIndexChanged += new System.EventHandler(this.ServerComboBox_SelectedIndexChanged);
|
||||
this.ServerComboBox.SelectionChangeCommitted += new System.EventHandler(this.ServerComboBox_SelectionChangeCommitted);
|
||||
//
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace Netch.Forms
|
||||
|
||||
private readonly Dictionary<string, object> _mainFormText = new();
|
||||
|
||||
private bool _comboBoxInitialized;
|
||||
private bool _textRecorded;
|
||||
|
||||
public MainForm()
|
||||
@@ -85,6 +84,8 @@ namespace Netch.Forms
|
||||
|
||||
private void MainForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
Netch.TimePoint("MainForm ctor (Pre MainForm Load)");
|
||||
|
||||
// 计算 ComboBox绘制 目标宽度
|
||||
RecordSize();
|
||||
|
||||
@@ -93,7 +94,6 @@ namespace Netch.Forms
|
||||
|
||||
ModeHelper.Load();
|
||||
LoadModes();
|
||||
_comboBoxInitialized = true;
|
||||
|
||||
// 加载翻译
|
||||
TranslateControls();
|
||||
@@ -121,6 +121,8 @@ namespace Netch.Forms
|
||||
if (Global.Settings.StartWhenOpened)
|
||||
ControlButton_Click(null, null);
|
||||
});
|
||||
|
||||
Netch.TimePoint("Post Form Load", false);
|
||||
}
|
||||
|
||||
private void RecordSize()
|
||||
@@ -577,7 +579,8 @@ namespace Netch.Forms
|
||||
return;
|
||||
}
|
||||
|
||||
if (MessageBoxX.Show(i18N.Translate("Download and install now?"), confirm: true) != DialogResult.OK)
|
||||
if (MessageBoxX.Show(i18N.Translate($"Download and install now?\n\n{UpdateChecker.GetLatestReleaseContent()}"), confirm: true) !=
|
||||
DialogResult.OK)
|
||||
return;
|
||||
|
||||
NotifyTip(i18N.Translate("Start downloading new version"));
|
||||
@@ -735,13 +738,9 @@ namespace Netch.Forms
|
||||
|
||||
private void LoadServers()
|
||||
{
|
||||
var comboBoxInitialized = _comboBoxInitialized;
|
||||
_comboBoxInitialized = false;
|
||||
|
||||
ServerComboBox.Items.Clear();
|
||||
ServerComboBox.Items.AddRange(Global.Settings.Server.ToArray());
|
||||
SelectLastServer();
|
||||
_comboBoxInitialized = comboBoxInitialized;
|
||||
}
|
||||
|
||||
public void SelectLastServer()
|
||||
@@ -756,11 +755,8 @@ namespace Netch.Forms
|
||||
// 如果当前 ServerComboBox 中没元素,不做处理
|
||||
}
|
||||
|
||||
private void ServerComboBox_SelectedIndexChanged(object sender, EventArgs o)
|
||||
private void ServerComboBox_SelectionChangeCommitted(object sender, EventArgs o)
|
||||
{
|
||||
if (!_comboBoxInitialized)
|
||||
return;
|
||||
|
||||
Global.Settings.ServerComboBoxSelectedIndex = ServerComboBox.SelectedIndex;
|
||||
}
|
||||
|
||||
@@ -859,14 +855,10 @@ namespace Netch.Forms
|
||||
|
||||
public void LoadModes()
|
||||
{
|
||||
var comboBoxInitialized = _comboBoxInitialized;
|
||||
_comboBoxInitialized = false;
|
||||
|
||||
ModeComboBox.Items.Clear();
|
||||
ModeComboBox.Items.AddRange(Global.Modes.ToArray());
|
||||
ModeComboBox.Items.AddRange(Global.Modes.Cast<object>().ToArray());
|
||||
ModeComboBox.Tag = null;
|
||||
SelectLastMode();
|
||||
_comboBoxInitialized = comboBoxInitialized;
|
||||
}
|
||||
|
||||
public void SelectLastMode()
|
||||
@@ -881,11 +873,8 @@ namespace Netch.Forms
|
||||
// 如果当前 ModeComboBox 中没元素,不做处理
|
||||
}
|
||||
|
||||
private void ModeComboBox_SelectedIndexChanged(object sender, EventArgs o)
|
||||
private void ModeComboBox_SelectionChangeCommitted(object sender, EventArgs o)
|
||||
{
|
||||
if (!_comboBoxInitialized)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Global.Settings.ModeComboBoxSelectedIndex = Global.Modes.IndexOf((Models.Mode) ModeComboBox.SelectedItem);
|
||||
|
||||
123
Netch/Forms/Mode/Process.Designer.cs
generated
123
Netch/Forms/Mode/Process.Designer.cs
generated
@@ -31,25 +31,21 @@ namespace Netch.Forms.Mode
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.ConfigurationGroupBox = new System.Windows.Forms.GroupBox();
|
||||
this.RemarkLabel = new System.Windows.Forms.Label();
|
||||
this.RemarkTextBox = new System.Windows.Forms.TextBox();
|
||||
this.FilenameLabel = new System.Windows.Forms.Label();
|
||||
this.FilenameTextBox = new System.Windows.Forms.TextBox();
|
||||
this.containerControl1 = new System.Windows.Forms.ContainerControl();
|
||||
this.RuleListBox = new System.Windows.Forms.ListBox();
|
||||
this.RuleRichTextBox = new System.Windows.Forms.RichTextBox();
|
||||
this.ProcessGroupBox = new System.Windows.Forms.GroupBox();
|
||||
this.ProcessNameTextBox = new System.Windows.Forms.TextBox();
|
||||
this.AddButton = new System.Windows.Forms.Button();
|
||||
this.SelectButton = new System.Windows.Forms.Button();
|
||||
this.contextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components);
|
||||
this.DeleteToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.ScanButton = new System.Windows.Forms.Button();
|
||||
this.ValidationButton = new System.Windows.Forms.Button();
|
||||
this.ControlButton = new System.Windows.Forms.Button();
|
||||
this.ConfigurationGroupBox.SuspendLayout();
|
||||
this.containerControl1.SuspendLayout();
|
||||
this.ProcessGroupBox.SuspendLayout();
|
||||
this.contextMenuStrip.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// ConfigurationGroupBox
|
||||
@@ -60,11 +56,12 @@ namespace Netch.Forms.Mode
|
||||
this.ConfigurationGroupBox.Controls.Add(this.FilenameTextBox);
|
||||
this.ConfigurationGroupBox.Controls.Add(this.containerControl1);
|
||||
this.ConfigurationGroupBox.Controls.Add(this.ProcessGroupBox);
|
||||
this.ConfigurationGroupBox.Controls.Add(this.SelectButton);
|
||||
this.ConfigurationGroupBox.Location = new System.Drawing.Point(12, 12);
|
||||
this.ConfigurationGroupBox.Controls.Add(this.ControlButton);
|
||||
this.ConfigurationGroupBox.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.ConfigurationGroupBox.Location = new System.Drawing.Point(12, 5);
|
||||
this.ConfigurationGroupBox.Name = "ConfigurationGroupBox";
|
||||
this.ConfigurationGroupBox.Size = new System.Drawing.Size(340, 344);
|
||||
this.ConfigurationGroupBox.TabIndex = 1;
|
||||
this.ConfigurationGroupBox.Size = new System.Drawing.Size(431, 378);
|
||||
this.ConfigurationGroupBox.TabIndex = 0;
|
||||
this.ConfigurationGroupBox.TabStop = false;
|
||||
this.ConfigurationGroupBox.Text = "Configuration";
|
||||
//
|
||||
@@ -81,7 +78,7 @@ namespace Netch.Forms.Mode
|
||||
//
|
||||
this.RemarkTextBox.Location = new System.Drawing.Point(84, 22);
|
||||
this.RemarkTextBox.Name = "RemarkTextBox";
|
||||
this.RemarkTextBox.Size = new System.Drawing.Size(250, 23);
|
||||
this.RemarkTextBox.Size = new System.Drawing.Size(341, 23);
|
||||
this.RemarkTextBox.TabIndex = 1;
|
||||
this.RemarkTextBox.TextChanged += new System.EventHandler(this.RemarkTextBox_TextChanged);
|
||||
//
|
||||
@@ -99,86 +96,76 @@ namespace Netch.Forms.Mode
|
||||
this.FilenameTextBox.Location = new System.Drawing.Point(84, 52);
|
||||
this.FilenameTextBox.Name = "FilenameTextBox";
|
||||
this.FilenameTextBox.ReadOnly = true;
|
||||
this.FilenameTextBox.Size = new System.Drawing.Size(250, 23);
|
||||
this.FilenameTextBox.Size = new System.Drawing.Size(341, 23);
|
||||
this.FilenameTextBox.TabIndex = 3;
|
||||
//
|
||||
// containerControl1
|
||||
//
|
||||
this.containerControl1.Controls.Add(this.RuleListBox);
|
||||
this.containerControl1.Controls.Add(this.RuleRichTextBox);
|
||||
this.containerControl1.Location = new System.Drawing.Point(6, 81);
|
||||
this.containerControl1.Name = "containerControl1";
|
||||
this.containerControl1.Size = new System.Drawing.Size(328, 176);
|
||||
this.containerControl1.TabIndex = 5;
|
||||
this.containerControl1.Size = new System.Drawing.Size(419, 221);
|
||||
this.containerControl1.TabIndex = 4;
|
||||
this.containerControl1.Text = "containerControl1";
|
||||
//
|
||||
// RuleListBox
|
||||
// RuleRichTextBox
|
||||
//
|
||||
this.RuleListBox.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.RuleListBox.FormattingEnabled = true;
|
||||
this.RuleListBox.ItemHeight = 17;
|
||||
this.RuleListBox.Location = new System.Drawing.Point(0, 0);
|
||||
this.RuleListBox.Name = "RuleListBox";
|
||||
this.RuleListBox.Size = new System.Drawing.Size(328, 176);
|
||||
this.RuleListBox.TabIndex = 0;
|
||||
this.RuleListBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.RuleListBox_MouseUp);
|
||||
this.RuleRichTextBox.DetectUrls = false;
|
||||
this.RuleRichTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.RuleRichTextBox.Location = new System.Drawing.Point(0, 0);
|
||||
this.RuleRichTextBox.Name = "RuleRichTextBox";
|
||||
this.RuleRichTextBox.Size = new System.Drawing.Size(419, 221);
|
||||
this.RuleRichTextBox.TabIndex = 0;
|
||||
this.RuleRichTextBox.Text = "";
|
||||
this.RuleRichTextBox.WordWrap = false;
|
||||
//
|
||||
// ProcessGroupBox
|
||||
//
|
||||
this.ProcessGroupBox.Controls.Add(this.ProcessNameTextBox);
|
||||
this.ProcessGroupBox.Controls.Add(this.AddButton);
|
||||
this.ProcessGroupBox.Location = new System.Drawing.Point(6, 263);
|
||||
this.ProcessGroupBox.Controls.Add(this.SelectButton);
|
||||
this.ProcessGroupBox.Controls.Add(this.ScanButton);
|
||||
this.ProcessGroupBox.Controls.Add(this.ValidationButton);
|
||||
this.ProcessGroupBox.Location = new System.Drawing.Point(6, 295);
|
||||
this.ProcessGroupBox.Name = "ProcessGroupBox";
|
||||
this.ProcessGroupBox.Size = new System.Drawing.Size(328, 46);
|
||||
this.ProcessGroupBox.TabIndex = 6;
|
||||
this.ProcessGroupBox.Size = new System.Drawing.Size(419, 44);
|
||||
this.ProcessGroupBox.TabIndex = 5;
|
||||
this.ProcessGroupBox.TabStop = false;
|
||||
//
|
||||
// ProcessNameTextBox
|
||||
//
|
||||
this.ProcessNameTextBox.Location = new System.Drawing.Point(6, 15);
|
||||
this.ProcessNameTextBox.Name = "ProcessNameTextBox";
|
||||
this.ProcessNameTextBox.Size = new System.Drawing.Size(222, 23);
|
||||
this.ProcessNameTextBox.TabIndex = 0;
|
||||
//
|
||||
// AddButton
|
||||
//
|
||||
this.AddButton.Location = new System.Drawing.Point(247, 15);
|
||||
this.AddButton.Name = "AddButton";
|
||||
this.AddButton.Size = new System.Drawing.Size(75, 23);
|
||||
this.AddButton.TabIndex = 1;
|
||||
this.AddButton.Text = "Add";
|
||||
this.AddButton.UseVisualStyleBackColor = true;
|
||||
this.AddButton.Click += new System.EventHandler(this.AddButton_Click);
|
||||
//
|
||||
// SelectButton
|
||||
//
|
||||
this.SelectButton.Location = new System.Drawing.Point(6, 315);
|
||||
this.SelectButton.Location = new System.Drawing.Point(6, 13);
|
||||
this.SelectButton.Name = "SelectButton";
|
||||
this.SelectButton.Size = new System.Drawing.Size(75, 23);
|
||||
this.SelectButton.TabIndex = 7;
|
||||
this.SelectButton.TabIndex = 0;
|
||||
this.SelectButton.Text = "Select";
|
||||
this.SelectButton.UseVisualStyleBackColor = true;
|
||||
this.SelectButton.Click += new System.EventHandler(this.SelectButton_Click);
|
||||
//
|
||||
// contextMenuStrip
|
||||
// ScanButton
|
||||
//
|
||||
this.contextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.DeleteToolStripMenuItem});
|
||||
this.contextMenuStrip.Name = "contextMenuStrip";
|
||||
this.contextMenuStrip.Size = new System.Drawing.Size(114, 26);
|
||||
this.ScanButton.Location = new System.Drawing.Point(87, 13);
|
||||
this.ScanButton.Name = "ScanButton";
|
||||
this.ScanButton.Size = new System.Drawing.Size(75, 23);
|
||||
this.ScanButton.TabIndex = 1;
|
||||
this.ScanButton.Text = "Scan";
|
||||
this.ScanButton.UseVisualStyleBackColor = true;
|
||||
this.ScanButton.Click += new System.EventHandler(this.ScanButton_Click);
|
||||
//
|
||||
// DeleteToolStripMenuItem
|
||||
// ValidationButton
|
||||
//
|
||||
this.DeleteToolStripMenuItem.Name = "DeleteToolStripMenuItem";
|
||||
this.DeleteToolStripMenuItem.Size = new System.Drawing.Size(113, 22);
|
||||
this.DeleteToolStripMenuItem.Text = "Delete";
|
||||
this.DeleteToolStripMenuItem.Click += new System.EventHandler(this.deleteRule_Click);
|
||||
this.ValidationButton.Location = new System.Drawing.Point(338, 13);
|
||||
this.ValidationButton.Name = "ValidationButton";
|
||||
this.ValidationButton.Size = new System.Drawing.Size(75, 23);
|
||||
this.ValidationButton.TabIndex = 2;
|
||||
this.ValidationButton.Text = "Validation";
|
||||
this.ValidationButton.UseVisualStyleBackColor = true;
|
||||
this.ValidationButton.Click += new System.EventHandler(this.ValidationButton_Click);
|
||||
//
|
||||
// ControlButton
|
||||
//
|
||||
this.ControlButton.Location = new System.Drawing.Point(277, 362);
|
||||
this.ControlButton.Location = new System.Drawing.Point(344, 345);
|
||||
this.ControlButton.Name = "ControlButton";
|
||||
this.ControlButton.Size = new System.Drawing.Size(75, 23);
|
||||
this.ControlButton.TabIndex = 2;
|
||||
this.ControlButton.TabIndex = 6;
|
||||
this.ControlButton.Text = "Save";
|
||||
this.ControlButton.UseVisualStyleBackColor = true;
|
||||
this.ControlButton.Click += new System.EventHandler(this.ControlButton_Click);
|
||||
@@ -187,14 +174,14 @@ namespace Netch.Forms.Mode
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
this.ClientSize = new System.Drawing.Size(364, 397);
|
||||
this.ClientSize = new System.Drawing.Size(455, 388);
|
||||
this.Controls.Add(this.ConfigurationGroupBox);
|
||||
this.Controls.Add(this.ControlButton);
|
||||
this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||
this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.MaximizeBox = false;
|
||||
this.Name = "Process";
|
||||
this.Padding = new System.Windows.Forms.Padding(12, 5, 12, 5);
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Create Process Mode";
|
||||
this.Load += new System.EventHandler(this.ModeForm_Load);
|
||||
@@ -202,27 +189,23 @@ namespace Netch.Forms.Mode
|
||||
this.ConfigurationGroupBox.PerformLayout();
|
||||
this.containerControl1.ResumeLayout(false);
|
||||
this.ProcessGroupBox.ResumeLayout(false);
|
||||
this.ProcessGroupBox.PerformLayout();
|
||||
this.contextMenuStrip.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Button ScanButton;
|
||||
private System.Windows.Forms.ContainerControl containerControl1;
|
||||
private System.Windows.Forms.ContextMenuStrip contextMenuStrip;
|
||||
private System.Windows.Forms.ToolStripMenuItem DeleteToolStripMenuItem;
|
||||
public System.Windows.Forms.GroupBox ConfigurationGroupBox;
|
||||
private System.Windows.Forms.Label RemarkLabel;
|
||||
private System.Windows.Forms.GroupBox ProcessGroupBox;
|
||||
private System.Windows.Forms.ListBox RuleListBox;
|
||||
private System.Windows.Forms.TextBox RemarkTextBox;
|
||||
private System.Windows.Forms.TextBox ProcessNameTextBox;
|
||||
private System.Windows.Forms.Button AddButton;
|
||||
private System.Windows.Forms.Button SelectButton;
|
||||
public System.Windows.Forms.Button ControlButton;
|
||||
private System.Windows.Forms.Label FilenameLabel;
|
||||
private System.Windows.Forms.TextBox FilenameTextBox;
|
||||
private RichTextBox RuleRichTextBox;
|
||||
private Button ValidationButton;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||
using Netch.Controllers;
|
||||
using Netch.Models;
|
||||
using Netch.Properties;
|
||||
using Netch.Utils;
|
||||
|
||||
@@ -33,10 +35,24 @@ namespace Netch.Forms.Mode
|
||||
_mode = mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否被编辑过
|
||||
/// </summary>
|
||||
public bool Edited { get; private set; }
|
||||
#region Model
|
||||
|
||||
public IEnumerable<string> Rules => RuleRichTextBox.Lines;
|
||||
|
||||
private void RuleAdd(string value)
|
||||
{
|
||||
RuleRichTextBox.AppendText($"{value}\n");
|
||||
}
|
||||
|
||||
private void RuleAddRange(IEnumerable<string> value)
|
||||
{
|
||||
foreach (string s in value)
|
||||
{
|
||||
RuleAdd(s);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ModeForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
@@ -47,60 +63,10 @@ namespace Netch.Forms.Mode
|
||||
RemarkTextBox.TextChanged -= RemarkTextBox_TextChanged;
|
||||
RemarkTextBox.Text = _mode.Remark;
|
||||
FilenameTextBox.Text = _mode.RelativePath;
|
||||
RuleListBox.Items.AddRange(_mode.Rule.Cast<object>().ToArray());
|
||||
RuleAddRange(_mode.Rule);
|
||||
}
|
||||
|
||||
i18N.TranslateForm(this);
|
||||
i18N.Translate(contextMenuStrip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// listBox右键菜单
|
||||
/// </summary>
|
||||
private void RuleListBox_MouseUp(object sender, MouseEventArgs e)
|
||||
{
|
||||
RuleListBox.SelectedIndex = RuleListBox.IndexFromPoint(e.X, e.Y);
|
||||
if (RuleListBox.SelectedIndex == -1)
|
||||
return;
|
||||
|
||||
if (e.Button == MouseButtons.Right)
|
||||
contextMenuStrip.Show(RuleListBox, e.Location);
|
||||
}
|
||||
|
||||
private void deleteRule_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (RuleListBox.SelectedIndex == -1)
|
||||
return;
|
||||
|
||||
RuleListBox.Items.RemoveAt(RuleListBox.SelectedIndex);
|
||||
Edited = true;
|
||||
}
|
||||
|
||||
private async void AddButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ProcessNameTextBox.Text))
|
||||
{
|
||||
MessageBoxX.Show(i18N.Translate("rule can not be empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NFController.CheckCppRegex(ProcessNameTextBox.Text))
|
||||
{
|
||||
MessageBoxX.Show("Rule does not conform to C++ regular expression syntax");
|
||||
return;
|
||||
}
|
||||
|
||||
var process = ProcessNameTextBox.Text;
|
||||
|
||||
if (!RuleListBox.Items.Contains(process))
|
||||
RuleListBox.Items.Add(process);
|
||||
|
||||
Edited = true;
|
||||
RuleListBox.SelectedIndex = RuleListBox.Items.IndexOf(process);
|
||||
ProcessNameTextBox.Text = string.Empty;
|
||||
});
|
||||
}
|
||||
|
||||
private void SelectButton_Click(object sender, EventArgs e)
|
||||
@@ -108,7 +74,7 @@ namespace Netch.Forms.Mode
|
||||
var dialog = new CommonOpenFileDialog
|
||||
{
|
||||
IsFolderPicker = true,
|
||||
Multiselect = false,
|
||||
Multiselect = true,
|
||||
Title = i18N.Translate("Select a folder"),
|
||||
AddToMostRecentlyUsedList = false,
|
||||
EnsurePathExists = true,
|
||||
@@ -117,17 +83,20 @@ namespace Netch.Forms.Mode
|
||||
|
||||
if (dialog.ShowDialog(Handle) == CommonFileDialogResult.Ok)
|
||||
{
|
||||
var path = dialog.FileName;
|
||||
if (!path.EndsWith(@"\"))
|
||||
path += @"\";
|
||||
foreach (string p in dialog.FileNames)
|
||||
{
|
||||
string path = p;
|
||||
if (!path.EndsWith(@"\"))
|
||||
path += @"\";
|
||||
|
||||
RuleListBox.Items.Add(path.ToRegexString());
|
||||
RuleAdd($"^{path.ToRegexString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ControlButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (RuleListBox.Items.Count == 0)
|
||||
if (!RuleRichTextBox.Lines.Any())
|
||||
{
|
||||
MessageBoxX.Show(i18N.Translate("Unable to add empty rule"));
|
||||
return;
|
||||
@@ -149,11 +118,9 @@ namespace Netch.Forms.Mode
|
||||
{
|
||||
_mode.Remark = RemarkTextBox.Text;
|
||||
_mode.Rule.Clear();
|
||||
_mode.Rule.AddRange(RuleListBox.Items.Cast<string>());
|
||||
_mode.Rule.AddRange(RuleRichTextBox.Lines);
|
||||
|
||||
_mode.WriteFile();
|
||||
Global.MainForm.LoadModes();
|
||||
Edited = false;
|
||||
MessageBoxX.Show(i18N.Translate("Mode updated successfully"));
|
||||
}
|
||||
else
|
||||
@@ -173,10 +140,9 @@ namespace Netch.Forms.Mode
|
||||
Remark = RemarkTextBox.Text
|
||||
};
|
||||
|
||||
mode.Rule.AddRange(RuleListBox.Items.Cast<string>());
|
||||
mode.Rule.AddRange(RuleRichTextBox.Lines);
|
||||
|
||||
mode.WriteFile();
|
||||
ModeHelper.Add(mode);
|
||||
MessageBoxX.Show(i18N.Translate("Mode added successfully"));
|
||||
}
|
||||
|
||||
@@ -190,5 +156,57 @@ namespace Netch.Forms.Mode
|
||||
FilenameTextBox.Text = FilenameTextBox.Text = ModeEditorUtils.GetCustomModeRelativePath(RemarkTextBox.Text);
|
||||
}));
|
||||
}
|
||||
|
||||
private void ScanButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new CommonOpenFileDialog
|
||||
{
|
||||
IsFolderPicker = true,
|
||||
Multiselect = false,
|
||||
Title = i18N.Translate("Select a folder"),
|
||||
AddToMostRecentlyUsedList = false,
|
||||
EnsurePathExists = true,
|
||||
NavigateToShortcut = true
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog(Handle) == CommonFileDialogResult.Ok)
|
||||
{
|
||||
var path = dialog.FileName;
|
||||
var list = new List<string>();
|
||||
const uint maxCount = 50;
|
||||
try
|
||||
{
|
||||
ScanDirectory(path, list);
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBoxX.Show(i18N.Translate($"The number of executable files in the \"{path}\" directory is greater than {maxCount}"),
|
||||
LogLevel.WARNING);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RuleAddRange(list);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScanDirectory(string directory, List<string> list, uint maxCount = 30)
|
||||
{
|
||||
foreach (string dir in Directory.GetDirectories(directory))
|
||||
ScanDirectory(dir, list, maxCount);
|
||||
|
||||
list.AddRange(Directory.GetFiles(directory).Select(Path.GetFileName).Where(s => s.EndsWith(".exe")).Select(s => s.ToRegexString()));
|
||||
|
||||
if (maxCount != 0 && list.Count > maxCount)
|
||||
throw new Exception("The number of results is greater than maxCount");
|
||||
}
|
||||
|
||||
private void ValidationButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (NFController.CheckRules(Rules, out var results))
|
||||
MessageBoxX.Show(NFController.GenerateInvalidRulesMessage(results), LogLevel.WARNING);
|
||||
else
|
||||
MessageBoxX.Show("Fine");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,6 @@ namespace Netch.Forms.Mode
|
||||
_mode.Type = (int) comboBox1.SelectedValue;
|
||||
|
||||
_mode.WriteFile();
|
||||
Global.MainForm.LoadModes();
|
||||
MessageBoxX.Show(i18N.Translate("Mode updated successfully"));
|
||||
}
|
||||
else
|
||||
@@ -105,7 +104,6 @@ namespace Netch.Forms.Mode
|
||||
mode.Rule.AddRange(richTextBox1.Lines);
|
||||
|
||||
mode.WriteFile();
|
||||
ModeHelper.Add(mode);
|
||||
MessageBoxX.Show(i18N.Translate("Mode added successfully"));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using WindowsJobAPI;
|
||||
using Netch.Controllers;
|
||||
using Netch.Forms;
|
||||
using Netch.Models;
|
||||
@@ -34,12 +33,6 @@ namespace Netch
|
||||
|
||||
public static Mutex Mutex => LazyMutex.Value;
|
||||
|
||||
#if DEBUG
|
||||
public static bool Testing = false;
|
||||
#else
|
||||
public const bool Testing = false;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 用于读取和写入的配置
|
||||
/// </summary>
|
||||
@@ -50,11 +43,6 @@ namespace Netch
|
||||
/// </summary>
|
||||
public static readonly List<Mode> Modes = new();
|
||||
|
||||
/// <summary>
|
||||
/// Windows Job API
|
||||
/// </summary>
|
||||
public static readonly JobObject Job = new();
|
||||
|
||||
public static class Flags
|
||||
{
|
||||
public static readonly bool IsWindows10Upper = Environment.OSVersion.Version.Major >= 10;
|
||||
|
||||
@@ -1,54 +1,28 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
|
||||
namespace Netch.Models.GitHubRelease
|
||||
{
|
||||
[Serializable]
|
||||
public struct SuffixVersion : ICloneable, IComparable, IComparable<SuffixVersion>, IEquatable<SuffixVersion>
|
||||
public struct SuffixVersion : IComparable, IComparable<SuffixVersion>
|
||||
{
|
||||
public int Major { get; }
|
||||
public Version Version { get; }
|
||||
|
||||
public int Minor { get; }
|
||||
public string Suffix { get; }
|
||||
|
||||
public int Patch { get; }
|
||||
|
||||
public string PreRelease { get; }
|
||||
|
||||
public int Build { get; }
|
||||
|
||||
public SuffixVersion(int major, int minor, int patch, string preRelease, int build)
|
||||
public SuffixVersion(Version version, string suffix)
|
||||
{
|
||||
Major = major;
|
||||
Minor = minor;
|
||||
Patch = patch;
|
||||
PreRelease = preRelease;
|
||||
Build = build;
|
||||
}
|
||||
|
||||
public SuffixVersion(Version version, string preRelease, int build)
|
||||
{
|
||||
Major = version.Major;
|
||||
Minor = version.Minor;
|
||||
Patch = version.Build;
|
||||
PreRelease = preRelease;
|
||||
Build = build;
|
||||
Version = version;
|
||||
Suffix = suffix;
|
||||
}
|
||||
|
||||
public static SuffixVersion Parse(string input)
|
||||
{
|
||||
var splitStr = input.Split('-');
|
||||
var dotNetVersion = Version.Parse(splitStr[0]);
|
||||
var preRelease = new StringBuilder();
|
||||
var build = 0;
|
||||
var split = input.Split('-');
|
||||
var dotNetVersion = Version.Parse(split[0]);
|
||||
var preRelease = split.ElementAtOrDefault(1) ?? string.Empty;
|
||||
|
||||
if (splitStr.Length > 1)
|
||||
foreach (var c in splitStr[1])
|
||||
if (int.TryParse(c.ToString(), out var n))
|
||||
build = build * 10 + n;
|
||||
else
|
||||
preRelease.Append(c);
|
||||
|
||||
return new SuffixVersion(dotNetVersion, preRelease.ToString(), build);
|
||||
return new SuffixVersion(dotNetVersion, preRelease);
|
||||
}
|
||||
|
||||
public static bool TryParse(string input, out SuffixVersion result)
|
||||
@@ -65,17 +39,12 @@ namespace Netch.Models.GitHubRelease
|
||||
}
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new SuffixVersion(Major, Major, Patch, PreRelease, Build);
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is SuffixVersion version)
|
||||
return CompareTo(version);
|
||||
if (obj is not SuffixVersion version)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
return -1;
|
||||
return CompareTo(version);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,57 +55,27 @@ namespace Netch.Models.GitHubRelease
|
||||
/// </returns>
|
||||
public int CompareTo(SuffixVersion other)
|
||||
{
|
||||
var majorComparison = Major.CompareTo(other.Major);
|
||||
if (majorComparison != 0)
|
||||
return majorComparison;
|
||||
var versionComparison = Version.CompareTo(other.Version);
|
||||
if (versionComparison != 0)
|
||||
return versionComparison;
|
||||
|
||||
var minorComparison = Minor.CompareTo(other.Minor);
|
||||
if (minorComparison != 0)
|
||||
return minorComparison;
|
||||
if (Suffix == string.Empty)
|
||||
return other.Suffix == string.Empty ? 0 : 1;
|
||||
|
||||
var patchComparison = Patch.CompareTo(other.Patch);
|
||||
if (patchComparison != 0)
|
||||
return patchComparison;
|
||||
|
||||
if (PreRelease == string.Empty)
|
||||
return other.PreRelease == string.Empty ? 0 : 1;
|
||||
|
||||
if (other.PreRelease == string.Empty)
|
||||
if (other.Suffix == string.Empty)
|
||||
return -1;
|
||||
|
||||
var suffixComparison = string.Compare(PreRelease, other.PreRelease, StringComparison.Ordinal);
|
||||
if (suffixComparison != 0)
|
||||
return suffixComparison;
|
||||
|
||||
return Build.CompareTo(other.Build);
|
||||
}
|
||||
|
||||
public bool Equals(SuffixVersion other)
|
||||
{
|
||||
return Major == other.Major && Minor == other.Minor && Patch == other.Patch && PreRelease == other.PreRelease && Build == other.Build;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is SuffixVersion other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = Major;
|
||||
hashCode = (hashCode * 397) ^ Minor;
|
||||
hashCode = (hashCode * 397) ^ Patch;
|
||||
hashCode = (hashCode * 397) ^ (PreRelease != null ? PreRelease.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ Build;
|
||||
return hashCode;
|
||||
}
|
||||
var suffixComparison = string.Compare(Suffix, other.Suffix, StringComparison.OrdinalIgnoreCase);
|
||||
return suffixComparison;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Major}.{Minor}.{Patch}{(string.IsNullOrEmpty(PreRelease) ? "" : "-")}{PreRelease}{(Build == 0 ? "" : Build.ToString())}";
|
||||
var s = Version.ToString();
|
||||
if (Suffix != string.Empty)
|
||||
s += $"-{Suffix}";
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,41 @@ namespace Netch.Models
|
||||
{
|
||||
public class Mode
|
||||
{
|
||||
public readonly string? FullName;
|
||||
private readonly Lazy<List<string>> _lazyRule;
|
||||
|
||||
public string? FullName { get; private set; }
|
||||
|
||||
public Mode(string? fullName = default)
|
||||
{
|
||||
_lazyRule = new Lazy<List<string>>(ReadRules);
|
||||
if (fullName == null)
|
||||
return;
|
||||
|
||||
FullName = fullName;
|
||||
if (!File.Exists(FullName))
|
||||
return;
|
||||
|
||||
var text = File.ReadLines(FullName).First();
|
||||
|
||||
// load head
|
||||
if (text.First() != '#')
|
||||
throw new Exception($"mode {FullName} head not found at Line 0");
|
||||
|
||||
var split = text.Substring(1).SplitTrimEntries(',');
|
||||
Remark = split[0];
|
||||
|
||||
var typeResult = int.TryParse(split.ElementAtOrDefault(1), out var type);
|
||||
Type = typeResult ? type : 0;
|
||||
// TODO throw NotSupportedModeTypeException
|
||||
|
||||
var bypassChinaResult = int.TryParse(split.ElementAtOrDefault(2), out var bypassChina);
|
||||
BypassChina = this.ClientRouting() && bypassChinaResult && bypassChina == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规则
|
||||
/// </summary>
|
||||
public readonly List<string> Rule = new();
|
||||
public List<string> Rule => _lazyRule.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 绕过中国(0. 不绕过 1. 绕过)
|
||||
@@ -45,15 +74,6 @@ namespace Netch.Models
|
||||
/// </summary>
|
||||
public int Type { get; set; } = 0;
|
||||
|
||||
public Mode(string fullName)
|
||||
{
|
||||
FullName = fullName;
|
||||
}
|
||||
|
||||
public Mode()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件相对路径(必须是存在的文件)
|
||||
/// </summary>
|
||||
@@ -79,7 +99,7 @@ namespace Netch.Models
|
||||
relativePath.Replace(">", "");
|
||||
relativePath.Replace(".h", ".txt");
|
||||
|
||||
var mode = Global.Modes.FirstOrDefault(m => m!.FullName != null && m.RelativePath!.Equals(relativePath.ToString()));
|
||||
var mode = Global.Modes.FirstOrDefault(m => m.FullName != null && m.RelativePath!.Equals(relativePath.ToString()));
|
||||
|
||||
if (mode == null)
|
||||
throw new MessageException($"{relativePath} file included in {Remark} not found");
|
||||
@@ -105,6 +125,14 @@ namespace Netch.Models
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> ReadRules()
|
||||
{
|
||||
if (FullName == null || !File.Exists(FullName))
|
||||
return new List<string>();
|
||||
|
||||
return File.ReadLines(FullName!).Skip(1).ToList();
|
||||
}
|
||||
|
||||
public void WriteFile(string? fullName = null)
|
||||
{
|
||||
if (fullName != null)
|
||||
|
||||
121
Netch/Models/ParameterBase.cs
Normal file
121
Netch/Models/ParameterBase.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Netch.Utils;
|
||||
|
||||
namespace Netch.Models
|
||||
{
|
||||
public abstract class ParameterBase
|
||||
{
|
||||
// null value par
|
||||
|
||||
private readonly bool _full;
|
||||
|
||||
protected readonly string ParametersSeparate = " ";
|
||||
protected readonly string Separate = " ";
|
||||
protected readonly string VerbPrefix = "-";
|
||||
protected readonly string FullPrefix = "--";
|
||||
|
||||
protected ParameterBase()
|
||||
{
|
||||
_full = !GetType().IsDefined(typeof(VerbAttribute));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var parameters = GetType().GetProperties().Select(PropToParameter).Where(s => s != null).Cast<string>();
|
||||
return string.Join(ParametersSeparate, parameters).Trim();
|
||||
}
|
||||
|
||||
private string? PropToParameter(PropertyInfo p)
|
||||
{
|
||||
// prefix
|
||||
bool full;
|
||||
if (p.IsDefined(typeof(VerbAttribute)))
|
||||
full = false;
|
||||
else if (p.IsDefined(typeof(FullAttribute)))
|
||||
full = true;
|
||||
else
|
||||
full = _full;
|
||||
|
||||
var prefix = full ? FullPrefix : VerbPrefix;
|
||||
// key
|
||||
var key = p.GetCustomAttribute<RealNameAttribute>()?.Name ?? p.Name;
|
||||
|
||||
// build
|
||||
var value = p.GetValue(this);
|
||||
switch (value)
|
||||
{
|
||||
case bool b:
|
||||
return b ? $"{prefix}{key}" : null;
|
||||
default:
|
||||
if ((value?.ToString() ?? null).IsNullOrWhiteSpace())
|
||||
return p.IsDefined(typeof(OptionalAttribute)) ? null : throw new RequiredArgumentValueInvalidException(p.Name, this, null);
|
||||
|
||||
if (p.IsDefined(typeof(QuoteAttribute)))
|
||||
value = $"\"{value}\"";
|
||||
|
||||
return $"{prefix}{key}{Separate}{value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
|
||||
public class VerbAttribute : Attribute
|
||||
{
|
||||
// Don't use verb and full both on one class or property
|
||||
// if you did, will take verb
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
|
||||
public class FullAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class OptionalAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class QuoteAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class RealNameAttribute : Attribute
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public RealNameAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RequiredArgumentValueInvalidException : Exception
|
||||
{
|
||||
public string? ArgumentName { get; }
|
||||
|
||||
public object? ArgumentObject { get; }
|
||||
|
||||
private readonly string? _message;
|
||||
|
||||
private const string DefaultMessage = "{0}'s Argument \"{1}\" value invalid. A required argument's value can't be null or empty.";
|
||||
|
||||
public override string Message => _message ?? string.Format(DefaultMessage, ArgumentObject!.GetType(), ArgumentName);
|
||||
|
||||
public RequiredArgumentValueInvalidException()
|
||||
{
|
||||
_message = "Some Argument value invalid. A required argument value's can't be null or empty.";
|
||||
}
|
||||
|
||||
public RequiredArgumentValueInvalidException(string argumentName, object argumentObject, string? message)
|
||||
{
|
||||
ArgumentName = argumentName;
|
||||
ArgumentObject = argumentObject;
|
||||
_message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,41 @@ namespace Netch
|
||||
{
|
||||
public static class Netch
|
||||
{
|
||||
private static readonly Stopwatch Stopwatch = new();
|
||||
|
||||
public static void StartStopwatch(string name)
|
||||
{
|
||||
if (Stopwatch.IsRunning)
|
||||
throw new Exception();
|
||||
|
||||
Stopwatch.Start();
|
||||
Console.WriteLine($"Start {name} Stopwatch");
|
||||
}
|
||||
|
||||
public static void TimePoint(string name, bool restart = true)
|
||||
{
|
||||
if (!Stopwatch.IsRunning)
|
||||
throw new Exception();
|
||||
|
||||
Stopwatch.Stop();
|
||||
Console.WriteLine($"{name} Stopwatch: {Stopwatch.ElapsedMilliseconds}");
|
||||
if (restart)
|
||||
Stopwatch.Restart();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序的主入口点
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
#if DEBUG
|
||||
AttachConsole();
|
||||
#else
|
||||
if (args.Contains("-console"))
|
||||
if (!NativeMethods.AttachConsole(-1))
|
||||
NativeMethods.AllocConsole();
|
||||
AttachConsole();
|
||||
#endif
|
||||
StartStopwatch("Netch");
|
||||
|
||||
// 设置当前目录
|
||||
Directory.SetCurrentDirectory(Global.NetchDir);
|
||||
@@ -40,9 +66,11 @@ namespace Netch
|
||||
if (!Directory.Exists(item))
|
||||
Directory.CreateDirectory(item);
|
||||
|
||||
TimePoint("Clean Old, Create Directory");
|
||||
// 加载配置
|
||||
Configuration.Load();
|
||||
|
||||
TimePoint("Load Configuration");
|
||||
// 检查是否已经运行
|
||||
if (!Global.Mutex.WaitOne(0, false))
|
||||
{
|
||||
@@ -76,6 +104,8 @@ namespace Netch
|
||||
Logging.Info($"版本: {UpdateChecker.Owner}/{UpdateChecker.Repo}@{UpdateChecker.Version}");
|
||||
Task.Run(() => { Logging.Info($"主程序 SHA256: {Utils.Utils.SHA256CheckSum(Global.NetchExecutable)}"); });
|
||||
|
||||
TimePoint("Get Info, Pre-Form");
|
||||
|
||||
// 绑定错误捕获
|
||||
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
|
||||
Application.ThreadException += Application_OnException;
|
||||
@@ -85,6 +115,12 @@ namespace Netch
|
||||
Application.Run(Global.MainForm);
|
||||
}
|
||||
|
||||
private static void AttachConsole()
|
||||
{
|
||||
if (!NativeMethods.AttachConsole(-1))
|
||||
NativeMethods.AllocConsole();
|
||||
}
|
||||
|
||||
public static void Application_OnException(object sender, ThreadExceptionEventArgs e)
|
||||
{
|
||||
Logging.Error(e.Exception.ToString());
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- <EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>-->
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
@@ -43,15 +46,18 @@
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.66" GeneratePathProperty="true" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Nullable.Extended.Analyzer" Version="1.2.4089">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Collections.Immutable" Version="5.0.0" />
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="5.0.1" />
|
||||
<PackageReference Include="TaskScheduler" Version="2.9.1" />
|
||||
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="3.3.5" />
|
||||
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="3.3.6" />
|
||||
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="3.3.5" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="3.3.6" />
|
||||
<PackageReference Include="WindowsFirewallHelper" Version="2.0.4.70-beta2" />
|
||||
<PackageReference Include="WindowsJobAPI" Version="5.0.1" />
|
||||
<PackageReference Include="WindowsProxy" Version="5.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
64
Netch/Properties/Resources.Designer.cs
generated
64
Netch/Properties/Resources.Designer.cs
generated
@@ -1,10 +1,10 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
// 此代码由工具生成。
|
||||
// 运行时版本:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果
|
||||
// 重新生成代码,这些更改将会丢失。
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -13,13 +13,13 @@ namespace Netch.Properties {
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// 一个强类型的资源类,用于查找本地化的字符串等。
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
// 此类是由 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 {
|
||||
@@ -33,7 +33,7 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// 返回此类使用的缓存的 ResourceManager 实例。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
@@ -47,8 +47,8 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// 重写当前线程的 CurrentUICulture 属性,对
|
||||
/// 使用此强类型资源类的所有资源查找执行重写。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
@@ -61,7 +61,7 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// 查找 System.Byte[] 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static byte[] _7za {
|
||||
get {
|
||||
@@ -71,17 +71,7 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
internal static byte[] abp_js {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("abp_js", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap CopyLink {
|
||||
get {
|
||||
@@ -91,17 +81,7 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
internal static byte[] defaultTUNTAP {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("defaultTUNTAP", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap delete {
|
||||
get {
|
||||
@@ -111,7 +91,7 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap edit {
|
||||
get {
|
||||
@@ -121,7 +101,7 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
||||
/// 查找类似于 (图标) 的 System.Drawing.Icon 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static System.Drawing.Icon icon {
|
||||
get {
|
||||
@@ -131,7 +111,7 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap Netch {
|
||||
get {
|
||||
@@ -141,7 +121,7 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap speed {
|
||||
get {
|
||||
@@ -151,7 +131,7 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// 查找 System.Drawing.Bitmap 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap Sponsor {
|
||||
get {
|
||||
@@ -161,7 +141,7 @@ namespace Netch.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// 查找 System.Byte[] 类型的本地化资源。
|
||||
/// </summary>
|
||||
internal static byte[] zh_CN {
|
||||
get {
|
||||
|
||||
@@ -118,10 +118,6 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="defaultTUNTAP" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\defaultTUNTAP;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="zh_CN" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\zh-CN;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089</value>
|
||||
@@ -150,10 +146,6 @@
|
||||
<value>..\Resources\CopyLink.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="abp_js" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\abp.js.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="7za" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\7za.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089</value>
|
||||
|
||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
[Generic]
|
||||
Address = 10.0.236.10
|
||||
Netmask = 255.255.255.0
|
||||
Gateway = 10.0.236.1
|
||||
DNS = 1.1.1.1
|
||||
UseCustomDNS = False
|
||||
@@ -1,4 +1,5 @@
|
||||
using Netch.Forms;
|
||||
using Netch.Utils;
|
||||
|
||||
namespace Netch.Servers.Shadowsocks.Form
|
||||
{
|
||||
@@ -8,7 +9,7 @@ namespace Netch.Servers.Shadowsocks.Form
|
||||
{
|
||||
server ??= new Shadowsocks();
|
||||
Server = server;
|
||||
CreateTextBox("Password", "Password", s => true, s => server.Password = s, server.Password);
|
||||
CreateTextBox("Password", "Password", s => !s.IsNullOrWhiteSpace(), s => server.Password = s, server.Password);
|
||||
CreateComboBox("EncryptMethod", "Encrypt Method", SSGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod);
|
||||
CreateTextBox("Plugin", "Plugin", s => true, s => server.Plugin = s, server.Plugin);
|
||||
CreateTextBox("PluginsOption", "Plugin Options", s => true, s => server.PluginOption = s, server.PluginOption);
|
||||
|
||||
@@ -24,26 +24,60 @@ namespace Netch.Servers.Shadowsocks
|
||||
{
|
||||
var server = (Shadowsocks) s;
|
||||
|
||||
#region Argument
|
||||
|
||||
var argument = new StringBuilder();
|
||||
argument.Append($"-s {server.AutoResolveHostname()} " + $"-p {server.Port} " + $"-b {this.LocalAddress()} " +
|
||||
$"-l {this.Socks5LocalPort()} " + $"-m {server.EncryptMethod} " + $"-k \"{server.Password}\" " + "-u");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(server.Plugin) && !string.IsNullOrWhiteSpace(server.PluginOption))
|
||||
argument.Append($" --plugin {server.Plugin}" + $" --plugin-opts \"{server.PluginOption}\"");
|
||||
var command = new SSParameter
|
||||
{
|
||||
s = server.AutoResolveHostname(),
|
||||
p = server.Port,
|
||||
b = this.LocalAddress(),
|
||||
l = this.Socks5LocalPort(),
|
||||
m = server.EncryptMethod,
|
||||
k = server.Password,
|
||||
u = true,
|
||||
plugin = server.Plugin,
|
||||
plugin_opts = server.PluginOption
|
||||
};
|
||||
|
||||
if (mode.BypassChina)
|
||||
argument.Append($" --acl \"{Path.GetFullPath(File.Exists(Global.UserACL) ? Global.UserACL : Global.BuiltinACL)}\"");
|
||||
command.acl = $"{Path.GetFullPath(File.Exists(Global.UserACL) ? Global.UserACL : Global.BuiltinACL)}";
|
||||
|
||||
#endregion
|
||||
StartInstanceAuto(command.ToString());
|
||||
}
|
||||
|
||||
StartInstanceAuto(argument.ToString());
|
||||
[Verb]
|
||||
private class SSParameter : ParameterBase
|
||||
{
|
||||
public string? s { get; set; }
|
||||
|
||||
public ushort? p { get; set; }
|
||||
|
||||
public string? b { get; set; }
|
||||
|
||||
public ushort? l { get; set; }
|
||||
|
||||
public string? m { get; set; }
|
||||
|
||||
public string? k { get; set; }
|
||||
|
||||
public bool u { get; set; }
|
||||
|
||||
[Full]
|
||||
[Optional]
|
||||
public string? plugin { get; set; }
|
||||
|
||||
[Full]
|
||||
[Optional]
|
||||
[RealName("plugin-opts")]
|
||||
public string? plugin_opts { get; set; }
|
||||
|
||||
[Full]
|
||||
[Quote]
|
||||
[Optional]
|
||||
public string? acl { get; set; }
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
StopInstance();
|
||||
StopInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Netch.Servers.Shadowsocks
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string? Password { get; set; }
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 插件
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Netch.Forms;
|
||||
using Netch.Utils;
|
||||
|
||||
namespace Netch.Servers.ShadowsocksR.Form
|
||||
{
|
||||
@@ -8,7 +9,7 @@ namespace Netch.Servers.ShadowsocksR.Form
|
||||
{
|
||||
server ??= new ShadowsocksR();
|
||||
Server = server;
|
||||
CreateTextBox("Password", "Password", s => true, s => server.Password = s, server.Password);
|
||||
CreateTextBox("Password", "Password", s => !s.IsNullOrWhiteSpace(), s => server.Password = s, server.Password);
|
||||
CreateComboBox("EncryptMethod", "Encrypt Method", SSRGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod);
|
||||
CreateComboBox("Protocol", "Protocol", SSRGlobal.Protocols, s => server.Protocol = s, server.Protocol);
|
||||
CreateTextBox("ProtocolParam", "Protocol Param", s => true, s => server.ProtocolParam = s, server.ProtocolParam);
|
||||
|
||||
@@ -24,31 +24,64 @@ namespace Netch.Servers.ShadowsocksR
|
||||
{
|
||||
var server = (ShadowsocksR) s;
|
||||
|
||||
#region Argument
|
||||
|
||||
var argument = new StringBuilder();
|
||||
argument.Append($"-s {server.AutoResolveHostname()} -p {server.Port} -k \"{server.Password}\" -m {server.EncryptMethod} -t 120");
|
||||
if (!string.IsNullOrEmpty(server.Protocol))
|
||||
var command = new SSRParameter
|
||||
{
|
||||
argument.Append($" -O {server.Protocol}");
|
||||
if (!string.IsNullOrEmpty(server.ProtocolParam))
|
||||
argument.Append($" -G \"{server.ProtocolParam}\"");
|
||||
}
|
||||
s = server.AutoResolveHostname(),
|
||||
p = server.Port,
|
||||
k = server.Password,
|
||||
m = server.EncryptMethod,
|
||||
t = "120",
|
||||
O = server.Protocol,
|
||||
G = server.ProtocolParam,
|
||||
o = server.OBFS,
|
||||
g = server.OBFSParam,
|
||||
b = this.LocalAddress(),
|
||||
l = this.Socks5LocalPort(),
|
||||
u = true
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(server.OBFS))
|
||||
{
|
||||
argument.Append($" -o {server.OBFS}");
|
||||
if (!string.IsNullOrEmpty(server.OBFSParam))
|
||||
argument.Append($" -g \"{server.OBFSParam}\"");
|
||||
}
|
||||
|
||||
argument.Append($" -b {this.LocalAddress()} -l {this.Socks5LocalPort()} -u");
|
||||
if (mode.BypassChina)
|
||||
argument.Append($" --acl \"{Path.GetFullPath(File.Exists(Global.UserACL) ? Global.UserACL : Global.BuiltinACL)}\"");
|
||||
command.acl = $"{Path.GetFullPath(File.Exists(Global.UserACL) ? Global.UserACL : Global.BuiltinACL)}";
|
||||
|
||||
#endregion
|
||||
StartInstanceAuto(command.ToString());
|
||||
}
|
||||
|
||||
StartInstanceAuto(argument.ToString());
|
||||
[Verb]
|
||||
class SSRParameter : ParameterBase
|
||||
{
|
||||
public string? s { get; set; }
|
||||
|
||||
public ushort? p { get; set; }
|
||||
|
||||
[Quote]
|
||||
public string? k { get; set; }
|
||||
|
||||
public string? m { get; set; }
|
||||
|
||||
public string? t { get; set; }
|
||||
|
||||
[Optional]
|
||||
public string? O { get; set; }
|
||||
|
||||
[Optional]
|
||||
public string? G { get; set; }
|
||||
|
||||
[Optional]
|
||||
public string? o { get; set; }
|
||||
|
||||
[Optional]
|
||||
public string? g { get; set; }
|
||||
|
||||
public string? b { get; set; }
|
||||
|
||||
public ushort? l { get; set; }
|
||||
|
||||
public bool u { get; set; }
|
||||
|
||||
[Full]
|
||||
[Quote]
|
||||
[Optional]
|
||||
public string? acl { get; set; }
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
|
||||
@@ -7,26 +7,16 @@ namespace Netch.Servers.ShadowsocksR
|
||||
{
|
||||
public override string Type { get; } = "SSR";
|
||||
|
||||
/// <summary>
|
||||
/// 加密方式
|
||||
/// </summary>
|
||||
public string EncryptMethod { get; set; } = SSRGlobal.EncryptMethods[0];
|
||||
|
||||
/// <summary>
|
||||
/// 混淆
|
||||
/// </summary>
|
||||
public string OBFS { get; set; } = SSRGlobal.OBFSs[0];
|
||||
|
||||
/// <summary>
|
||||
/// 混淆参数
|
||||
/// </summary>
|
||||
public string? OBFSParam { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 加密方式
|
||||
/// </summary>
|
||||
public string EncryptMethod { get; set; } = SSRGlobal.EncryptMethods[0];
|
||||
|
||||
/// <summary>
|
||||
/// 协议
|
||||
/// </summary>
|
||||
@@ -36,6 +26,16 @@ namespace Netch.Servers.ShadowsocksR
|
||||
/// 协议参数
|
||||
/// </summary>
|
||||
public string? ProtocolParam { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 混淆
|
||||
/// </summary>
|
||||
public string OBFS { get; set; } = SSRGlobal.OBFSs[0];
|
||||
|
||||
/// <summary>
|
||||
/// 混淆参数
|
||||
/// </summary>
|
||||
public string? OBFSParam { get; set; }
|
||||
}
|
||||
|
||||
public class SSRGlobal
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
/// <summary>
|
||||
/// 额外 ID
|
||||
/// </summary>
|
||||
public string aid { get; set; } = string.Empty;
|
||||
public int aid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 伪装域名(HTTP,WS)
|
||||
@@ -38,7 +38,7 @@
|
||||
/// <summary>
|
||||
/// 端口
|
||||
/// </summary>
|
||||
public string port { get; set; } = string.Empty;
|
||||
public ushort port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
@@ -58,6 +58,6 @@
|
||||
/// <summary>
|
||||
/// 链接版本
|
||||
/// </summary>
|
||||
public string v { get; set; } = string.Empty;
|
||||
public int v { get; set; } = 2;
|
||||
}
|
||||
}
|
||||
@@ -77,9 +77,7 @@ namespace Netch.Servers.V2ray
|
||||
var parameter = new Dictionary<string, string>();
|
||||
// protocol-specific fields
|
||||
parameter.Add("type", server.TransferProtocol);
|
||||
if (server.EncryptMethod == "none")
|
||||
// VLESS outbounds[].settings.encryption,当前可选值只有 none
|
||||
parameter.Add("encryption", server.EncryptMethod);
|
||||
parameter.Add("encryption", server.EncryptMethod);
|
||||
|
||||
// transport-specific fields
|
||||
switch (server.TransferProtocol)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Netch.Controllers;
|
||||
using Netch.Models;
|
||||
using Netch.Servers.V2ray;
|
||||
@@ -43,12 +44,12 @@ namespace Netch.Servers.VMess
|
||||
|
||||
var vmessJson = JsonSerializer.Serialize(new V2rayNSharing
|
||||
{
|
||||
v = "2",
|
||||
v = 2,
|
||||
ps = server.Remark,
|
||||
add = server.Hostname,
|
||||
port = server.Port.ToString(),
|
||||
port = server.Port,
|
||||
id = server.UserID,
|
||||
aid = server.AlterID.ToString(),
|
||||
aid = server.AlterID,
|
||||
net = server.TransferProtocol,
|
||||
type = server.FakeType,
|
||||
host = server.Host,
|
||||
@@ -85,13 +86,14 @@ namespace Netch.Servers.VMess
|
||||
return V2rayUtils.ParseVUri(text);
|
||||
}
|
||||
|
||||
V2rayNSharing vmess = JsonSerializer.Deserialize<V2rayNSharing>(s)!;
|
||||
V2rayNSharing vmess = JsonSerializer.Deserialize<V2rayNSharing>(s,
|
||||
new JsonSerializerOptions {NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString})!;
|
||||
|
||||
data.Remark = vmess.ps;
|
||||
data.Hostname = vmess.add;
|
||||
data.Port = ushort.Parse(vmess.port);
|
||||
data.Port = vmess.port;
|
||||
data.UserID = vmess.id;
|
||||
data.AlterID = int.Parse(vmess.aid);
|
||||
data.AlterID = vmess.aid;
|
||||
data.TransferProtocol = vmess.net;
|
||||
data.FakeType = vmess.type;
|
||||
|
||||
|
||||
@@ -95,6 +95,17 @@ namespace Netch.Updater
|
||||
|
||||
private void ApplyUpdate()
|
||||
{
|
||||
// Pre Update
|
||||
try
|
||||
{
|
||||
// Backup Configuration file
|
||||
File.Copy(Configuration.SettingFileFullName, Configuration.SettingFileFullName + ".bak", true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
// extract Update file to {tempDirectory}\extract
|
||||
var extractPath = Path.Combine(_tempDirectory, "extract");
|
||||
int exitCode;
|
||||
@@ -126,7 +137,7 @@ namespace Netch.Updater
|
||||
if (extendedKeepDirectories.Any(p => file.StartsWith(p)))
|
||||
continue;
|
||||
|
||||
if (Path.GetFileName(file) is ModeHelper.DISABLE_MODE_DIRECTORY_FILENAME)
|
||||
if (Path.GetFileName(file) is ModeHelper.DisableModeDirectoryFileName)
|
||||
continue;
|
||||
|
||||
filesToDelete.Add(file);
|
||||
|
||||
@@ -12,12 +12,10 @@ namespace Netch.Utils
|
||||
/// <summary>
|
||||
/// 数据目录
|
||||
/// </summary>
|
||||
public const string DATA_DIR = "data";
|
||||
public static string DataDirectoryFullName => Path.Combine(Global.NetchDir, "data");
|
||||
|
||||
public static string SettingFileFullName => $"{DataDirectoryFullName}\\settings.json";
|
||||
|
||||
/// <summary>
|
||||
/// 设置
|
||||
/// </summary>
|
||||
public static readonly string SETTINGS_JSON = $"{DATA_DIR}\\settings.json";
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = Global.NewDefaultJsonSerializerOptions;
|
||||
|
||||
static Configuration()
|
||||
@@ -31,45 +29,39 @@ namespace Netch.Utils
|
||||
/// </summary>
|
||||
public static void Load()
|
||||
{
|
||||
if (File.Exists(SETTINGS_JSON))
|
||||
if (File.Exists(SettingFileFullName))
|
||||
{
|
||||
Global.Settings = ParseSetting(File.ReadAllText(SETTINGS_JSON));
|
||||
try
|
||||
{
|
||||
using var fileStream = File.OpenRead(SettingFileFullName);
|
||||
var settings = JsonSerializer.DeserializeAsync<Setting>(fileStream, JsonSerializerOptions).Result!;
|
||||
|
||||
CheckSetting(settings);
|
||||
|
||||
Global.Settings = settings;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Error(e.ToString());
|
||||
Utils.Open(Logging.LogFile);
|
||||
Environment.Exit(-1);
|
||||
Global.Settings = null!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 弹出提示
|
||||
i18N.Load("System");
|
||||
|
||||
// 创建 data 文件夹并保存默认设置
|
||||
// 保存默认设置
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public static Setting ParseSetting(string text)
|
||||
private static void CheckSetting(Setting settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = JsonSerializer.Deserialize<Setting>(text, JsonSerializerOptions)!;
|
||||
settings.Profiles.RemoveAll(p => p.ServerRemark == string.Empty || p.ModeRemark == string.Empty);
|
||||
|
||||
#region Check Profile
|
||||
|
||||
settings.Profiles.RemoveAll(p => p.ServerRemark == string.Empty || p.ModeRemark == string.Empty);
|
||||
|
||||
if (settings.Profiles.Any(p => settings.Profiles.Any(p1 => p1 != p && p1.Index == p.Index)))
|
||||
for (var i = 0; i < settings.Profiles.Count; i++)
|
||||
settings.Profiles[i].Index = i;
|
||||
|
||||
#endregion
|
||||
|
||||
return settings;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Error(e.ToString());
|
||||
Utils.Open(Logging.LogFile);
|
||||
Environment.Exit(-1);
|
||||
return null!;
|
||||
}
|
||||
if (settings.Profiles.Any(p => settings.Profiles.Any(p1 => p1 != p && p1.Index == p.Index)))
|
||||
for (var i = 0; i < settings.Profiles.Count; i++)
|
||||
settings.Profiles[i].Index = i;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,10 +69,10 @@ namespace Netch.Utils
|
||||
/// </summary>
|
||||
public static void Save()
|
||||
{
|
||||
if (!Directory.Exists(DATA_DIR))
|
||||
Directory.CreateDirectory(DATA_DIR);
|
||||
if (!Directory.Exists(DataDirectoryFullName))
|
||||
Directory.CreateDirectory(DataDirectoryFullName);
|
||||
|
||||
File.WriteAllBytes(SETTINGS_JSON, JsonSerializer.SerializeToUtf8Bytes(Global.Settings, JsonSerializerOptions));
|
||||
File.WriteAllBytes(SettingFileFullName, JsonSerializer.SerializeToUtf8Bytes(Global.Settings, JsonSerializerOptions));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,14 +40,23 @@ namespace Netch.Utils
|
||||
private static void Write(string text, LogLevel logLevel)
|
||||
{
|
||||
var contents = $@"[{DateTime.Now}][{logLevel.ToString()}] {text}{Global.EOF}";
|
||||
if (Global.Testing)
|
||||
#if DEBUG
|
||||
switch (logLevel)
|
||||
{
|
||||
Console.WriteLine(contents);
|
||||
return;
|
||||
case LogLevel.INFO:
|
||||
case LogLevel.WARNING:
|
||||
Console.Write(contents);
|
||||
break;
|
||||
case LogLevel.ERROR:
|
||||
Console.Error.Write(contents);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null);
|
||||
}
|
||||
|
||||
#else
|
||||
lock (FileLock)
|
||||
File.AppendAllText(LogFile, contents);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,45 @@ namespace Netch.Utils
|
||||
{
|
||||
public static class ModeHelper
|
||||
{
|
||||
private const string MODE_DIR = "mode";
|
||||
public const string DISABLE_MODE_DIRECTORY_FILENAME = "disabled";
|
||||
public const string DisableModeDirectoryFileName = "disabled";
|
||||
|
||||
public static readonly string ModeDirectory = Path.Combine(Global.NetchDir, $"{MODE_DIR}\\");
|
||||
public static string ModeDirectoryFullName => Path.Combine(Global.NetchDir, "mode");
|
||||
|
||||
private static readonly FileSystemWatcher FileSystemWatcher;
|
||||
|
||||
static ModeHelper()
|
||||
{
|
||||
FileSystemWatcher = new FileSystemWatcher(ModeDirectoryFullName)
|
||||
{
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.FileName,
|
||||
IncludeSubdirectories = true,
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
|
||||
FileSystemWatcher.Changed += OnModeChanged;
|
||||
FileSystemWatcher.Created += OnModeChanged;
|
||||
FileSystemWatcher.Deleted += OnModeChanged;
|
||||
FileSystemWatcher.Renamed += OnModeChanged;
|
||||
}
|
||||
|
||||
private static void OnModeChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
Load();
|
||||
Global.MainForm.LoadModes();
|
||||
}
|
||||
|
||||
public static string GetRelativePath(string fullName)
|
||||
{
|
||||
return fullName.Substring(ModeDirectory.Length);
|
||||
var length = ModeDirectoryFullName.Length;
|
||||
if (!ModeDirectoryFullName.EndsWith("\\"))
|
||||
length++;
|
||||
|
||||
return fullName.Substring(length);
|
||||
}
|
||||
|
||||
public static string GetFullPath(string relativeName)
|
||||
{
|
||||
return Path.Combine(ModeDirectory, relativeName);
|
||||
return Path.Combine(ModeDirectoryFullName, relativeName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -31,7 +57,7 @@ namespace Netch.Utils
|
||||
public static void Load()
|
||||
{
|
||||
Global.Modes.Clear();
|
||||
LoadModeDirectory(ModeDirectory);
|
||||
LoadModeDirectory(ModeDirectoryFullName);
|
||||
|
||||
Sort();
|
||||
}
|
||||
@@ -44,11 +70,18 @@ namespace Netch.Utils
|
||||
LoadModeDirectory(directory);
|
||||
|
||||
// skip Directory with a disabled file in
|
||||
if (File.Exists(Path.Combine(modeDirectory, DISABLE_MODE_DIRECTORY_FILENAME)))
|
||||
if (File.Exists(Path.Combine(modeDirectory, DisableModeDirectoryFileName)))
|
||||
return;
|
||||
|
||||
foreach (var file in Directory.GetFiles(modeDirectory).Where(f => f.EndsWith(".txt")))
|
||||
LoadModeFile(file);
|
||||
try
|
||||
{
|
||||
Global.Modes.Add(new Mode(file));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -56,71 +89,17 @@ namespace Netch.Utils
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadModeFile(string fullName)
|
||||
{
|
||||
var mode = new Mode(fullName);
|
||||
|
||||
var content = File.ReadAllLines(fullName);
|
||||
if (content.Length == 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < content.Length; i++)
|
||||
{
|
||||
var text = content[i].Trim();
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
if (text.First() != '#')
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var splited = text.Substring(1).SplitTrimEntries(',');
|
||||
|
||||
mode.Remark = splited[0];
|
||||
|
||||
var typeResult = int.TryParse(splited.ElementAtOrDefault(1), out var type);
|
||||
mode.Type = typeResult ? type : 0;
|
||||
|
||||
var bypassChinaResult = int.TryParse(splited.ElementAtOrDefault(2), out var bypassChina);
|
||||
mode.BypassChina = mode.ClientRouting() && bypassChinaResult && bypassChina == 1;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mode.Rule.Add(text);
|
||||
}
|
||||
}
|
||||
|
||||
Global.Modes.Add(mode);
|
||||
}
|
||||
|
||||
private static void Sort()
|
||||
{
|
||||
Global.Modes.Sort((a, b) => string.Compare(a.Remark, b.Remark, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
public static void Add(Mode mode)
|
||||
{
|
||||
Global.Modes.Add(mode);
|
||||
Sort();
|
||||
Global.MainForm.LoadModes();
|
||||
}
|
||||
|
||||
public static void Delete(Mode mode)
|
||||
{
|
||||
if (mode.FullName == null)
|
||||
throw new ArgumentException("FullName");
|
||||
throw new ArgumentException(nameof(mode.FullName));
|
||||
|
||||
if (File.Exists(mode.FullName))
|
||||
File.Delete(mode.FullName);
|
||||
|
||||
Global.Modes.Remove(mode);
|
||||
Global.MainForm.LoadModes();
|
||||
File.Delete(mode.FullName);
|
||||
}
|
||||
|
||||
public static bool SkipServerController(Server server, Mode mode)
|
||||
@@ -137,17 +116,15 @@ namespace Netch.Utils
|
||||
};
|
||||
}
|
||||
|
||||
public static IModeController? GetModeControllerByType(int type, out ushort? port, out string portName, out PortType portType)
|
||||
public static IModeController? GetModeControllerByType(int type, out ushort? port, out string portName)
|
||||
{
|
||||
port = null;
|
||||
portName = string.Empty;
|
||||
portType = PortType.Both;
|
||||
switch (type)
|
||||
{
|
||||
case 0:
|
||||
port = Global.Settings.RedirectorTCPPort;
|
||||
portName = "Redirector TCP";
|
||||
portType = PortType.TCP;
|
||||
return new NFController();
|
||||
case 1:
|
||||
case 2:
|
||||
@@ -156,7 +133,6 @@ namespace Netch.Utils
|
||||
case 5:
|
||||
port = Global.Settings.HTTPLocalPort;
|
||||
portName = "HTTP";
|
||||
portType = PortType.TCP;
|
||||
StatusPortInfoText.HttpPort = (ushort) port;
|
||||
return new HTTPController();
|
||||
case 4:
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using Netch.Models;
|
||||
using static Vanara.PInvoke.IpHlpApi;
|
||||
using static Vanara.PInvoke.Ws2_32;
|
||||
|
||||
namespace Netch.Utils
|
||||
{
|
||||
@@ -26,6 +28,16 @@ namespace Netch.Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Process> GetProcessByUsedTcpPort(ushort port)
|
||||
{
|
||||
if (port == 0)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
var row = GetTcpTable2().Where(r => ntohs((ushort) r.dwLocalPort) == port);
|
||||
|
||||
return row.Select(r => Process.GetProcessById((int) r.dwOwningPid));
|
||||
}
|
||||
|
||||
private static void GetReservedPortRange(PortType portType, ref List<Range> targetList)
|
||||
{
|
||||
var process = new Process
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Netch.Utils
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Unsupported Server Type
|
||||
return JsonSerializer.Deserialize<Server>(jsonElement.GetRawText(), new JsonSerializerOptions())!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ namespace Netch.Utils
|
||||
{
|
||||
public static bool Open(string path)
|
||||
{
|
||||
if (Global.Testing)
|
||||
return true;
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
@@ -116,30 +114,11 @@ namespace Netch.Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static void KillProcessByName(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var p in Process.GetProcessesByName(name))
|
||||
if (p.MainModule != null && p.MainModule.FileName.StartsWith(Global.NetchDir))
|
||||
p.Kill();
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
Logging.Error($"结束进程 {name} 错误:" + e.Message);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFileVersion(string file)
|
||||
{
|
||||
return File.Exists(file) ? FileVersionInfo.GetVersionInfo(file).FileVersion : string.Empty;
|
||||
}
|
||||
|
||||
|
||||
public static void DrawCenterComboBox(object sender, DrawItemEventArgs e)
|
||||
{
|
||||
if (sender is ComboBox cbx)
|
||||
|
||||
3
SearchComboBox/.gitignore
vendored
3
SearchComboBox/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
/bin
|
||||
/obj
|
||||
/SearchComboBox.csproj.user
|
||||
3
UnitTest/.gitignore
vendored
3
UnitTest/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
/bin
|
||||
/obj
|
||||
/Netch.csproj.user
|
||||
@@ -6,10 +6,10 @@ using Netch.Utils;
|
||||
namespace UnitTest
|
||||
{
|
||||
[TestClass]
|
||||
public class FunctionTest : TestBase
|
||||
public class Function : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestLoadI18N()
|
||||
public void LoadLanguage()
|
||||
{
|
||||
void TestLoad(string t)
|
||||
{
|
||||
98
UnitTest/ParameterTest.cs
Normal file
98
UnitTest/ParameterTest.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Netch.Models;
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
[TestClass]
|
||||
public class ParameterTest
|
||||
{
|
||||
[Verb]
|
||||
private class VerbAndRealName : ParameterBase
|
||||
{
|
||||
[RealName("v")]
|
||||
public string v1 { get; } = "a";
|
||||
|
||||
[RealName("v")]
|
||||
public string v2 { get; } = "b";
|
||||
}
|
||||
|
||||
private class Full : ParameterBase
|
||||
{
|
||||
[RealName("f")]
|
||||
public string f1 { get; } = "a";
|
||||
|
||||
[RealName("f")]
|
||||
public string f2 { get; } = "b";
|
||||
}
|
||||
|
||||
private class FullWithVerb : ParameterBase
|
||||
{
|
||||
public string f { get; } = "a";
|
||||
|
||||
[Verb]
|
||||
public string v { get; } = "b";
|
||||
}
|
||||
|
||||
[Verb]
|
||||
private class VerbWithFull : ParameterBase
|
||||
{
|
||||
public string v { get; } = "a";
|
||||
|
||||
[Full]
|
||||
public string f { get; } = "b";
|
||||
}
|
||||
|
||||
private class QuoteValue : ParameterBase
|
||||
{
|
||||
public static string pathValue = @"C:\Programe Files\Damn thats space";
|
||||
|
||||
[Quote]
|
||||
public string path { get; set; } = pathValue;
|
||||
}
|
||||
|
||||
private class FlagAndOptional : ParameterBase
|
||||
{
|
||||
public bool a { get; set; } = true;
|
||||
|
||||
public bool b { get; set; } = false;
|
||||
|
||||
[Optional]
|
||||
public string c { get; set; } = string.Empty;
|
||||
|
||||
[Optional]
|
||||
public string? d { get; set; } = null;
|
||||
}
|
||||
|
||||
private class RequiredEmpty : ParameterBase
|
||||
{
|
||||
public string? udp { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private class RequiredNull : ParameterBase
|
||||
{
|
||||
public string? udp { get; set; } = null;
|
||||
}
|
||||
|
||||
private class Number : ParameterBase
|
||||
{
|
||||
public ushort a { get; set; } = 1;
|
||||
|
||||
public int b { get; set; } = 1;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Test()
|
||||
{
|
||||
Assert.AreEqual(new VerbAndRealName().ToString(), "-v a -v b");
|
||||
Assert.AreEqual(new Full().ToString(), "--f a --f b");
|
||||
Assert.AreEqual(new FullWithVerb().ToString(), "--f a -v b");
|
||||
Assert.AreEqual(new VerbWithFull().ToString(), "-v a --f b");
|
||||
Assert.AreEqual(new QuoteValue().ToString(), $"--path \"{QuoteValue.pathValue}\"");
|
||||
Assert.AreEqual(new FlagAndOptional().ToString(), "--a");
|
||||
Assert.ThrowsException<RequiredArgumentValueInvalidException>(() => { _ = new RequiredEmpty().ToString(); });
|
||||
Assert.ThrowsException<RequiredArgumentValueInvalidException>(() => { _ = new RequiredNull().ToString(); });
|
||||
Assert.AreEqual(new Number().ToString(), "--a 1 --b 1");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,10 @@ using Netch.Utils;
|
||||
namespace UnitTest
|
||||
{
|
||||
[TestClass]
|
||||
public class TestParseShareLink : TestBase
|
||||
public class ParseShareLink : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestServerFromSSR()
|
||||
public void ParseSSR()
|
||||
{
|
||||
const string normalCase =
|
||||
"ssr://MTI3LjAuMC4xOjEyMzQ6YXV0aF9hZXMxMjhfbWQ1OmFlcy0xMjgtY2ZiOnRsczEuMl90aWNrZXRfYXV0aDpZV0ZoWW1KaS8_b2Jmc3BhcmFtPVluSmxZV3QzWVRFeExtMXZaUQ";
|
||||
|
||||
26
UnitTest/SuffixVersionTest.cs
Normal file
26
UnitTest/SuffixVersionTest.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Netch.Models.GitHubRelease;
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
[TestClass]
|
||||
public class SuffixVersionTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void Test()
|
||||
{
|
||||
var rel = SuffixVersion.Parse("1.0.0");
|
||||
var a1 = SuffixVersion.Parse("1.0.0-Alpha1");
|
||||
var a3 = SuffixVersion.Parse("1.0.0-aLpHa3");
|
||||
var b2 = SuffixVersion.Parse("1.0.0-betA2");
|
||||
|
||||
Assert.AreEqual(rel.ToString(), "1.0.0");
|
||||
Assert.AreEqual(a1.ToString(), "1.0.0-Alpha1");
|
||||
Assert.IsTrue(rel.CompareTo(a1) > 0);
|
||||
Assert.IsTrue(rel.CompareTo(b2) > 0);
|
||||
|
||||
Assert.IsTrue(b2.CompareTo(a1) > 0);
|
||||
Assert.IsTrue(b2.CompareTo(a3) > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,6 @@
|
||||
using Netch;
|
||||
|
||||
namespace UnitTest
|
||||
namespace UnitTest
|
||||
{
|
||||
public class TestBase
|
||||
{
|
||||
protected TestBase()
|
||||
{
|
||||
#if DEBUG
|
||||
Global.Testing = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -23,8 +24,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
|
||||
<PackageReference Include="System.Text.Json" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
2
binaries
2
binaries
Submodule binaries updated: 61b435e28c...c64a784152
2
modes
2
modes
Submodule modes updated: 42375ef724...72ad96d018
Reference in New Issue
Block a user