Compare commits

...

45 Commits

Author SHA1 Message Date
AmazingDM
9d2fd2cce3 Update UpdateChecker.cs 2021-03-24 10:30:10 +08:00
AmazingDM
5a3295d10c RDR Memory optimization 2021-03-23 22:28:23 +08:00
ChsBuffer
8269948288 Update GetProcessByUsedTcpPort (Fix #591) 2021-03-23 16:14:34 +08:00
ChsBuffer
8e545fc05f Remove Reload Mode MenuToolStrip 2021-03-23 13:41:43 +08:00
ChsBuffer
9a3a1e3664 Fix #589 GetProcessByUsedTcpPort multiple process 2021-03-22 22:01:03 +08:00
AmazingDM
3f4a31dac8 Update UpdateChecker.cs 2021-03-22 21:40:26 +08:00
AmazingDM
7c0088cc7f optimization 2021-03-22 21:23:11 +08:00
ChsBuffer
1a28d791b7 Merge pull request #585 from NetchX/dependabot/nuget/Vanara.PInvoke.IpHlpApi-3.3.6
Bump Vanara.PInvoke.IpHlpApi from 3.3.5 to 3.3.6
2021-03-22 11:18:42 +08:00
ChsBuffer
05df786ab6 Merge pull request #586 from NetchX/dependabot/nuget/Vanara.PInvoke.User32-3.3.6
Bump Vanara.PInvoke.User32 from 3.3.5 to 3.3.6
2021-03-22 11:18:33 +08:00
ChsBuffer
1ffbae6135 Feat: Show Release Note when confirm 2021-03-22 11:09:16 +08:00
dependabot[bot]
8ca9d6d9be Bump Vanara.PInvoke.User32 from 3.3.5 to 3.3.6
Bumps [Vanara.PInvoke.User32](https://github.com/dahall/vanara) from 3.3.5 to 3.3.6.
- [Release notes](https://github.com/dahall/vanara/releases)
- [Commits](https://github.com/dahall/vanara/compare/v3.3.5...v3.3.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-21 23:10:21 +00:00
dependabot[bot]
4f1ae20b9b Bump Vanara.PInvoke.IpHlpApi from 3.3.5 to 3.3.6
Bumps [Vanara.PInvoke.IpHlpApi](https://github.com/dahall/vanara) from 3.3.5 to 3.3.6.
- [Release notes](https://github.com/dahall/vanara/releases)
- [Commits](https://github.com/dahall/vanara/compare/v3.3.5...v3.3.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-21 23:10:16 +00:00
ChsBuffer
15a1db3b21 Feat: Backup configuration file before update 2021-03-22 01:59:49 +08:00
ChsBuffer
54243a80e7 Update SuffixVersion 2021-03-22 01:43:06 +08:00
ChsBuffer
947bf2b3ca Bump version to 1.8.3-Beta1 2021-03-21 22:53:00 +08:00
ChsBuffer
2a165c79df Start Profile, Refactor Save LastSelectedServer/Mode 2021-03-21 22:44:15 +08:00
ChsBuffer
55280df299 Update Load configuration 2021-03-21 22:00:16 +08:00
ChsBuffer
af48e7119e Auto reload modes, Lazy load mode rules 2021-03-21 04:54:33 +08:00
ChsBuffer
a1b978a22c Remove WindowsJobAPI 2021-03-21 03:38:34 +08:00
ChsBuffer
d08a9d5bfd Refactor Start Port Check Kill Process 2021-03-21 03:38:25 +08:00
ChsBuffer
c69c40750a Fix a typo 2021-03-21 01:48:44 +08:00
ChsBuffer
77376502b7 Update CI 2021-03-21 00:05:20 +08:00
ChsBuffer
fdfc3f11eb Update NFController 2021-03-20 21:38:24 +08:00
AmazingDM
0d956efac6 update binaries 2021-03-20 20:36:37 +08:00
AmazingDM
3f9709167d update NFController
optimization rdr
2021-03-20 20:35:56 +08:00
ChsBuffer
425e468f78 Revert "Update Netch.csproj to be compatible with dotnet cli"
This reverts commit 77f2b761fc.
2021-03-20 18:33:04 +08:00
AmazingDM
a485a4647c fix error set TYPE_FILTERCHILDPROC 2021-03-20 17:56:45 +08:00
ChsBuffer
0b484face4 Update Edit Process Mode Form 2021-03-20 04:33:35 +08:00
ChsBuffer
e0b5b0e49c Restore Edit Process Scan Button And Update Select Button 2021-03-20 03:41:37 +08:00
ChsBuffer
f519850ffc Trim 2021-03-20 00:50:06 +08:00
ChsBuffer
4513a68e73 Update Nuget packages
Bump MSTest.TestAdapter from 2.2.1 to 2.2.3
Bump MSTest.TestFramework from 2.2.1 to 2.2.3
2021-03-19 03:58:57 +08:00
ChsBuffer
95de42e778 Update SS/SSR Parameter 2021-03-19 03:56:54 +08:00
ChsBuffer
18168c3a4e Add Nullable.Extended.Analyzer 2021-03-19 03:50:08 +08:00
ChsBuffer
77f2b761fc Update Netch.csproj to be compatible with dotnet cli 2021-03-19 03:07:07 +08:00
ChsBuffer
9bd02ec122 Update Debug Logging 2021-03-19 03:05:57 +08:00
ChsBuffer
cfb4a5b3f6 The Debug configuration will make the build attach to the console and write the application log to standard output 2021-03-19 00:58:34 +08:00
ChsBuffer
6178045f15 Fix Import v2rayN format sharelink got exception when property type Number 2021-03-16 16:43:46 +08:00
ChsBuffer
4773de99e5 Fix typo 2021-03-16 16:41:35 +08:00
ChsBuffer
eb713db867 Refactor: generate SS/SSR start arguments 2021-03-16 15:55:46 +08:00
ChsBuffer
15f4895c0f Update UnitTest 2021-03-16 15:24:30 +08:00
ChsBuffer
afbda60dfb format NFController 2021-03-16 15:19:25 +08:00
ChsBuffer
f51229f2c8 Fix: SS/SSR password not allow empty and Update Model 2021-03-16 15:19:12 +08:00
ChsBuffer
26f9ae3958 Update Server Model value type 2021-03-15 22:38:33 +08:00
ChsBuffer
dfc680f0b7 Update .gitignore 2021-03-15 22:10:46 +08:00
ChsBuffer
5e56556534 Remove unused resources 2021-03-15 22:03:47 +08:00
51 changed files with 1223 additions and 830 deletions

30
.github/workflows/ci.yml vendored Normal file
View 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

View File

@@ -1,5 +1,8 @@
name: Netch CI name: Netch Release
on: [push, pull_request] on:
push:
tags:
- '*.*'
jobs: jobs:
build: build:
name: Build name: Build
@@ -17,14 +20,6 @@ jobs:
shell: pwsh shell: pwsh
run: .\BUILD.ps1 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 - name: Package
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }} if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}
shell: pwsh shell: pwsh
@@ -39,10 +34,8 @@ jobs:
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
env: env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}
with: with:
name: ${{ env.GITHUB_TAG_NAME }} prerelease: ${{ contains(github.ref, '-') }}
prerelease: true
draft: false draft: false
files: | files: |
C:\builtfiles\Netch.7z C:\builtfiles\Netch.7z
@@ -55,4 +48,4 @@ jobs:
## 校验和 ## 校验和
| 文件名 | SHA256 | | 文件名 | SHA256 |
| :- | :- | | :- | :- |
| Netch.7z | ${{ env.Netch_SHA256 }} | | Netch.7z | ${{ env.Netch_SHA256 }} |

7
.gitignore vendored
View File

@@ -1,4 +1,5 @@
/.vs .vs/
/packages
.idea/ .idea/
/*.user */bin/
*/obj/
*.csproj.user

3
Netch/.gitignore vendored
View File

@@ -1,3 +0,0 @@
/bin
/obj
/Netch.csproj.user

View File

@@ -24,7 +24,6 @@ namespace Netch.Controllers
public void Start(in Mode mode) public void Start(in Mode mode)
{ {
PrivoxyController.Start(MainController.Server!); PrivoxyController.Start(MainController.Server!);
Global.Job.AddProcess(PrivoxyController.Instance!);
string? pacUrl = null; string? pacUrl = null;
if (MainController.Server is Socks5 or Trojan && mode.BypassChina || (Global.Settings.AlwaysStartPACServer ?? false)) if (MainController.Server is Socks5 or Trojan && mode.BypassChina || (Global.Settings.AlwaysStartPACServer ?? false))

View File

@@ -1,11 +1,10 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Netch.Models; using Netch.Models;
using Netch.Servers.Socks5; using Netch.Servers.Socks5;
using Netch.Utils; using Netch.Utils;
using static Netch.Utils.PortHelper;
namespace Netch.Controllers namespace Netch.Controllers
{ {
@@ -105,20 +104,11 @@ namespace Netch.Controllers
{ {
controller = ServerHelper.GetUtilByTypeName(server.Type).GetController(); controller = ServerHelper.GetUtilByTypeName(server.Type).GetController();
if (controller is Guard instanceController) TryReleaseTcpPort(controller.Socks5LocalPort(), "Socks5");
Utils.Utils.KillProcessByName(instanceController.MainFile);
PortCheck(controller.Socks5LocalPort(), "Socks5");
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", controller.Name)); Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", controller.Name));
controller.Start(in server, mode); 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) if (server is Socks5 socks5)
{ {
@@ -133,19 +123,17 @@ namespace Netch.Controllers
private static void StartMode(Mode mode) 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) if (ModeController == null)
return; return;
if (port != null) if (port != null)
PortCheck((ushort) port, portName, portType); TryReleaseTcpPort((ushort) port, portName);
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ModeController.Name)); Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ModeController.Name));
ModeController.Start(mode); ModeController.Start(mode);
if (ModeController is Guard {Instance: { }} guard)
Global.Job.AddProcess(guard.Instance!);
} }
public static async Task StopAsync() public static async Task StopAsync()
@@ -189,7 +177,7 @@ namespace Netch.Controllers
{ {
try try
{ {
CheckPort(port, portType); PortHelper.CheckPort(port, portType);
} }
catch (PortInUseException) catch (PortInUseException)
{ {
@@ -200,6 +188,36 @@ namespace Netch.Controllers
throw new MessageException(i18N.TranslateFormat("The {0} port is reserved by system.", $"{portName} ({port})")); 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))
{
try
{
_ = p.MainModule!.FileName;
}
catch (Exception e)
{
Logging.Warning(e.ToString());
continue;
}
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 public class MessageException : Exception

View File

@@ -17,57 +17,30 @@ namespace Netch.Controllers
{ {
private static readonly ServiceController NFService = new("netfilter2"); 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"; 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 string Name { get; } = "Redirector";
public void Start(in Mode mode) public void Start(in Mode mode)
{ {
CheckDriver(); 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"); // Server
aio_dial((int)NameList.TYPE_TCPLISN, Global.Settings.RedirectorTCPPort.ToString()); 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()); // Mode Rule
aio_dial((int)NameList.TYPE_FILTERTCP, (Global.Settings.ProcessProxyProtocol != PortType.UDP).ToString().ToLower()); dial_Name(mode);
SetServer(Global.Settings.ProcessProxyProtocol);
if (!CheckRule(mode.FullRule, out var list)) // Features
throw new MessageException($"\"{string.Join("", list.Select(s => s + "\n"))}\" does not conform to C++ regular expression syntax"); 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 : "");
SetName(mode); aio_dial((int) NameList.TYPE_FILTERCHILDPROC, Global.Settings.ChildProcessHandle.ToString().ToLower());
#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());
if (!aio_init()) if (!aio_init())
throw new MessageException("Redirector Start failed, run Netch with \"-console\" argument"); throw new MessageException("Redirector Start failed, run Netch with \"-console\" argument");
@@ -78,83 +51,54 @@ namespace Netch.Controllers
aio_free(); aio_free();
} }
/// <summary> #region CheckRule
/// </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();
}
/// <summary> /// <summary>
/// </summary> /// </summary>
/// <param name="r"></param> /// <param name="r"></param>
/// <param name="clear"></param> /// <param name="clear"></param>
/// <returns>No Problem true</returns> /// <returns>No Problem true</returns>
public static bool CheckCppRegex(string r, bool clear = true) private static bool CheckCppRegex(string r, bool clear = true)
{ {
try try
{ {
if (r.StartsWith("!")) 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 finally
{ {
if (clear) 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); results = rules.Where(r => !CheckCppRegex(r, false));
var systemFileVersion = Utils.Utils.GetFileVersion(SystemDriver); aio_dial((int) NameList.TYPE_CLRNAME, "");
return !results.Any();
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();
} }
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) if (portType == PortType.Both)
{ {
SetServer(PortType.TCP); dial_Server(PortType.TCP);
SetServer(PortType.UDP); dial_Server(PortType.UDP);
return; return;
} }
@@ -177,105 +121,97 @@ namespace Netch.Controllers
if (server is Socks5 socks5) if (server is Socks5 socks5)
{ {
aio_dial((int)NameList.TYPE_TCPTYPE + offset, "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_TCPHOST + offset, $"{socks5.AutoResolveHostname()}:{socks5.Port}");
aio_dial((int)NameList.TYPE_TCPUSER + offset, socks5.Username ?? string.Empty); 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_TCPPASS + offset, socks5.Password ?? string.Empty);
aio_dial((int)NameList.TYPE_TCPMETH + offset, string.Empty); aio_dial((int) NameList.TYPE_TCPMETH + offset, string.Empty);
} }
else if (server is Shadowsocks shadowsocks && !shadowsocks.HasPlugin() && Global.Settings.RedirectorSS) else if (server is Shadowsocks shadowsocks && !shadowsocks.HasPlugin() && Global.Settings.RedirectorSS)
{ {
aio_dial((int)NameList.TYPE_TCPTYPE + offset, "Shadowsocks"); aio_dial((int) NameList.TYPE_TCPTYPE + offset, "Shadowsocks");
aio_dial((int)NameList.TYPE_TCPHOST + offset, $"{shadowsocks.AutoResolveHostname()}:{shadowsocks.Port}"); 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_TCPMETH + offset, shadowsocks.EncryptMethod);
aio_dial((int)NameList.TYPE_TCPPASS + offset, shadowsocks.Password ?? string.Empty); aio_dial((int) NameList.TYPE_TCPPASS + offset, shadowsocks.Password);
} }
else else
{ {
aio_dial((int)NameList.TYPE_TCPTYPE + offset, "Socks5"); 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_TCPHOST + offset, $"127.0.0.1:{controller.Socks5LocalPort()}");
aio_dial((int)NameList.TYPE_TCPUSER + offset, string.Empty); aio_dial((int) NameList.TYPE_TCPUSER + offset, string.Empty);
aio_dial((int)NameList.TYPE_TCPPASS + 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_TCPMETH + offset, string.Empty);
} }
} }
private void SetName(Mode mode) private void dial_Name(Mode mode)
{ {
aio_dial((int)NameList.TYPE_CLRNAME, ""); aio_dial((int) NameList.TYPE_CLRNAME, "");
foreach (var rule in mode.FullRule) 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; 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"); if (list.Any())
aio_dial((int)NameList.TYPE_BYPNAME, "^" + Global.NetchDir.ToRegexString() + @"((?!NTT\.exe).)*$"); 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; private static void CheckDriver()
[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 var binFileVersion = Utils.Utils.GetFileVersion(BinDriver);
TYPE_FILTERLOOPBACK, var systemFileVersion = Utils.Utils.GetFileVersion(SystemDriver);
TYPE_FILTERTCP,
TYPE_FILTERUDP,
TYPE_FILTERIP,
TYPE_FILTERCHILDPROC,//子进程捕获
TYPE_TCPLISN, Logging.Info("内置驱动版本: " + binFileVersion);
TYPE_TCPTYPE, Logging.Info("系统驱动版本: " + systemFileVersion);
TYPE_TCPHOST,
TYPE_TCPUSER,
TYPE_TCPPASS,
TYPE_TCPMETH,
TYPE_UDPTYPE, if (!File.Exists(SystemDriver))
TYPE_UDPHOST, {
TYPE_UDPUSER, // Install
TYPE_UDPPASS, InstallDriver();
TYPE_UDPMETH, return;
}
TYPE_ADDNAME, var reinstall = false;
TYPE_ADDFIP, 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, Logging.Info("更新驱动");
TYPE_CLRFIP, UninstallDriver();
InstallDriver();
//str addr x.x.x.x only ipv4
TYPE_REDIRCTOR_DNS,
TYPE_REDIRCTOR_ICMP
} }
#endregion
#region Utils
/// <summary> /// <summary>
/// 安装 NF 驱动 /// 安装 NF 驱动
/// </summary> /// </summary>
@@ -341,5 +277,61 @@ namespace Netch.Controllers
} }
#endregion #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
} }
} }

View File

@@ -47,7 +47,7 @@ namespace Netch.Controllers
Global.MainForm.BeginInvoke(new Action(() => Global.MainForm.BeginInvoke(new Action(() =>
{ {
if (!_form!.IsDisposed) if (!_form!.IsDisposed)
_form!.richTextBox1.AppendText(line + "\n"); _form.richTextBox1.AppendText(line + "\n");
})); }));
} }

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -18,8 +19,8 @@ namespace Netch.Controllers
public const string Name = @"Netch"; public const string Name = @"Netch";
public const string Copyright = @"Copyright © 2019 - 2021"; public const string Copyright = @"Copyright © 2019 - 2021";
public const string AssemblyVersion = @"1.8.2"; public const string AssemblyVersion = @"1.8.3";
private const string Suffix = @""; private const string Suffix = @"Beta3";
public static readonly string Version = $"{AssemblyVersion}{(string.IsNullOrEmpty(Suffix) ? "" : $"-{Suffix}")}"; public static readonly string Version = $"{AssemblyVersion}{(string.IsNullOrEmpty(Suffix) ? "" : $"-{Suffix}")}";
@@ -89,5 +90,19 @@ namespace Netch.Controllers
fileName = match.Groups["filename"].Value; fileName = match.Groups["filename"].Value;
sha256 = match.Groups["sha256"].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();
}
} }
} }

View File

@@ -29,14 +29,12 @@
private void InitializeComponent() private void InitializeComponent()
{ {
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.MenuStrip = new System.Windows.Forms.MenuStrip(); this.MenuStrip = new System.Windows.Forms.MenuStrip();
this.ServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ImportServersFromClipboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ImportServersFromClipboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ModeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ModeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.CreateProcessModeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.CreateProcessModeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.CreateRouteTableRuleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.CreateRouteTableRuleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ReloadModesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SubscribeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SubscribeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ManageSubscribeLinksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ManageSubscribeLinksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.UpdateServersFromSubscribeLinksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.UpdateServersFromSubscribeLinksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -95,13 +93,13 @@
this.ConfigurationGroupBox.SuspendLayout(); this.ConfigurationGroupBox.SuspendLayout();
this.configLayoutPanel.SuspendLayout(); this.configLayoutPanel.SuspendLayout();
this.tableLayoutPanel2.SuspendLayout(); this.tableLayoutPanel2.SuspendLayout();
((System.ComponentModel.ISupportInitialize) (this.EditServerPictureBox)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.EditServerPictureBox)).BeginInit();
((System.ComponentModel.ISupportInitialize) (this.CopyLinkPictureBox)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.CopyLinkPictureBox)).BeginInit();
((System.ComponentModel.ISupportInitialize) (this.DeleteServerPictureBox)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.DeleteServerPictureBox)).BeginInit();
((System.ComponentModel.ISupportInitialize) (this.SpeedPictureBox)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.SpeedPictureBox)).BeginInit();
this.tableLayoutPanel3.SuspendLayout(); this.tableLayoutPanel3.SuspendLayout();
((System.ComponentModel.ISupportInitialize) (this.EditModePictureBox)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.EditModePictureBox)).BeginInit();
((System.ComponentModel.ISupportInitialize) (this.DeleteModePictureBox)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.DeleteModePictureBox)).BeginInit();
this.StatusStrip.SuspendLayout(); this.StatusStrip.SuspendLayout();
this.NotifyMenu.SuspendLayout(); this.NotifyMenu.SuspendLayout();
this.ProfileGroupBox.SuspendLayout(); this.ProfileGroupBox.SuspendLayout();
@@ -113,10 +111,16 @@
// //
this.MenuStrip.BackColor = System.Drawing.SystemColors.Control; this.MenuStrip.BackColor = System.Drawing.SystemColors.Control;
this.MenuStrip.ImageScalingSize = new System.Drawing.Size(20, 20); this.MenuStrip.ImageScalingSize = new System.Drawing.Size(20, 20);
this.MenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] this.MenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
{ this.ServerToolStripMenuItem,
this.ServerToolStripMenuItem, this.ModeToolStripMenuItem, this.SubscribeToolStripMenuItem, this.OptionsToolStripMenuItem, this.HelpToolStripMenuItem, this.exitToolStripMenuItem, this.AboutToolStripButton, this.NewVersionLabel, this.VersionLabel this.ModeToolStripMenuItem,
}); this.SubscribeToolStripMenuItem,
this.OptionsToolStripMenuItem,
this.HelpToolStripMenuItem,
this.exitToolStripMenuItem,
this.AboutToolStripButton,
this.NewVersionLabel,
this.VersionLabel});
this.MenuStrip.Location = new System.Drawing.Point(0, 0); this.MenuStrip.Location = new System.Drawing.Point(0, 0);
this.MenuStrip.Name = "MenuStrip"; this.MenuStrip.Name = "MenuStrip";
this.MenuStrip.RenderMode = System.Windows.Forms.ToolStripRenderMode.Professional; this.MenuStrip.RenderMode = System.Windows.Forms.ToolStripRenderMode.Professional;
@@ -125,10 +129,8 @@
// //
// ServerToolStripMenuItem // ServerToolStripMenuItem
// //
this.ServerToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] this.ServerToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
{ this.ImportServersFromClipboardToolStripMenuItem});
this.ImportServersFromClipboardToolStripMenuItem
});
this.ServerToolStripMenuItem.Margin = new System.Windows.Forms.Padding(3, 0, 0, 1); this.ServerToolStripMenuItem.Margin = new System.Windows.Forms.Padding(3, 0, 0, 1);
this.ServerToolStripMenuItem.Name = "ServerToolStripMenuItem"; this.ServerToolStripMenuItem.Name = "ServerToolStripMenuItem";
this.ServerToolStripMenuItem.Size = new System.Drawing.Size(57, 21); this.ServerToolStripMenuItem.Size = new System.Drawing.Size(57, 21);
@@ -143,7 +145,9 @@
// //
// ModeToolStripMenuItem // ModeToolStripMenuItem
// //
this.ModeToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {this.CreateProcessModeToolStripMenuItem, this.CreateRouteTableRuleToolStripMenuItem, this.ReloadModesToolStripMenuItem}); this.ModeToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.CreateProcessModeToolStripMenuItem,
this.CreateRouteTableRuleToolStripMenuItem});
this.ModeToolStripMenuItem.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1); this.ModeToolStripMenuItem.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.ModeToolStripMenuItem.Name = "ModeToolStripMenuItem"; this.ModeToolStripMenuItem.Name = "ModeToolStripMenuItem";
this.ModeToolStripMenuItem.Size = new System.Drawing.Size(55, 21); this.ModeToolStripMenuItem.Size = new System.Drawing.Size(55, 21);
@@ -163,19 +167,12 @@
this.CreateRouteTableRuleToolStripMenuItem.Text = "Create Route Table Rule"; this.CreateRouteTableRuleToolStripMenuItem.Text = "Create Route Table Rule";
this.CreateRouteTableRuleToolStripMenuItem.Click += new System.EventHandler(this.createRouteTableModeToolStripMenuItem_Click); this.CreateRouteTableRuleToolStripMenuItem.Click += new System.EventHandler(this.createRouteTableModeToolStripMenuItem_Click);
// //
// ReloadModesToolStripMenuItem
//
this.ReloadModesToolStripMenuItem.Name = "ReloadModesToolStripMenuItem";
this.ReloadModesToolStripMenuItem.Size = new System.Drawing.Size(217, 22);
this.ReloadModesToolStripMenuItem.Text = "Reload Modes";
this.ReloadModesToolStripMenuItem.Click += new System.EventHandler(this.ReloadModesToolStripMenuItem_Click);
//
// SubscribeToolStripMenuItem // SubscribeToolStripMenuItem
// //
this.SubscribeToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] this.SubscribeToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
{ this.ManageSubscribeLinksToolStripMenuItem,
this.ManageSubscribeLinksToolStripMenuItem, this.UpdateServersFromSubscribeLinksToolStripMenuItem, this.UpdateServersFromSubscribeLinksWithProxyToolStripMenuItem this.UpdateServersFromSubscribeLinksToolStripMenuItem,
}); this.UpdateServersFromSubscribeLinksWithProxyToolStripMenuItem});
this.SubscribeToolStripMenuItem.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1); this.SubscribeToolStripMenuItem.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.SubscribeToolStripMenuItem.Name = "SubscribeToolStripMenuItem"; this.SubscribeToolStripMenuItem.Name = "SubscribeToolStripMenuItem";
this.SubscribeToolStripMenuItem.Size = new System.Drawing.Size(77, 21); this.SubscribeToolStripMenuItem.Size = new System.Drawing.Size(77, 21);
@@ -204,10 +201,15 @@
// //
// OptionsToolStripMenuItem // OptionsToolStripMenuItem
// //
this.OptionsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] this.OptionsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
{ this.OpenDirectoryToolStripMenuItem,
this.OpenDirectoryToolStripMenuItem, this.CleanDNSCacheToolStripMenuItem, this.UpdateACLToolStripMenuItem, this.updateACLWithProxyToolStripMenuItem, this.updatePACToolStripMenuItem, this.UninstallServiceToolStripMenuItem, this.UninstallTapDriverToolStripMenuItem, this.removeNetchFirewallRulesToolStripMenuItem this.CleanDNSCacheToolStripMenuItem,
}); this.UpdateACLToolStripMenuItem,
this.updateACLWithProxyToolStripMenuItem,
this.updatePACToolStripMenuItem,
this.UninstallServiceToolStripMenuItem,
this.UninstallTapDriverToolStripMenuItem,
this.removeNetchFirewallRulesToolStripMenuItem});
this.OptionsToolStripMenuItem.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1); this.OptionsToolStripMenuItem.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.OptionsToolStripMenuItem.Name = "OptionsToolStripMenuItem"; this.OptionsToolStripMenuItem.Name = "OptionsToolStripMenuItem";
this.OptionsToolStripMenuItem.Size = new System.Drawing.Size(66, 21); this.OptionsToolStripMenuItem.Size = new System.Drawing.Size(66, 21);
@@ -271,10 +273,9 @@
// //
// HelpToolStripMenuItem // HelpToolStripMenuItem
// //
this.HelpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] this.HelpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
{ this.CheckForUpdatesToolStripMenuItem,
this.CheckForUpdatesToolStripMenuItem, this.fAQToolStripMenuItem this.fAQToolStripMenuItem});
});
this.HelpToolStripMenuItem.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1); this.HelpToolStripMenuItem.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.HelpToolStripMenuItem.Name = "HelpToolStripMenuItem"; this.HelpToolStripMenuItem.Name = "HelpToolStripMenuItem";
this.HelpToolStripMenuItem.Size = new System.Drawing.Size(47, 21); this.HelpToolStripMenuItem.Size = new System.Drawing.Size(47, 21);
@@ -423,7 +424,7 @@
this.ModeComboBox.Size = new System.Drawing.Size(546, 24); this.ModeComboBox.Size = new System.Drawing.Size(546, 24);
this.ModeComboBox.TabIndex = 2; this.ModeComboBox.TabIndex = 2;
this.ModeComboBox.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.ComboBox_DrawItem); 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 // ServerComboBox
// //
@@ -438,7 +439,7 @@
this.ServerComboBox.Size = new System.Drawing.Size(546, 24); this.ServerComboBox.Size = new System.Drawing.Size(546, 24);
this.ServerComboBox.TabIndex = 1; this.ServerComboBox.TabIndex = 1;
this.ServerComboBox.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.ComboBox_DrawItem); 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 // tableLayoutPanel2
// //
@@ -550,10 +551,14 @@
// StatusStrip // StatusStrip
// //
this.StatusStrip.ImageScalingSize = new System.Drawing.Size(20, 20); this.StatusStrip.ImageScalingSize = new System.Drawing.Size(20, 20);
this.StatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] this.StatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
{ this.StatusLabel,
this.StatusLabel, this.UsedBandwidthLabel, this.DownloadSpeedLabel, this.UploadSpeedLabel, this.blankToolStripStatusLabel, this.NatTypeStatusLabel, this.NatTypeStatusLightLabel this.UsedBandwidthLabel,
}); this.DownloadSpeedLabel,
this.UploadSpeedLabel,
this.blankToolStripStatusLabel,
this.NatTypeStatusLabel,
this.NatTypeStatusLightLabel});
this.StatusStrip.Location = new System.Drawing.Point(0, 272); this.StatusStrip.Location = new System.Drawing.Point(0, 272);
this.StatusStrip.Name = "StatusStrip"; this.StatusStrip.Name = "StatusStrip";
this.StatusStrip.Size = new System.Drawing.Size(740, 22); this.StatusStrip.Size = new System.Drawing.Size(740, 22);
@@ -616,7 +621,7 @@
// //
// ControlButton // ControlButton
// //
this.ControlButton.Anchor = ((System.Windows.Forms.AnchorStyles) ((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.ControlButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.ControlButton.Location = new System.Drawing.Point(631, 3); this.ControlButton.Location = new System.Drawing.Point(631, 3);
this.ControlButton.Name = "ControlButton"; this.ControlButton.Name = "ControlButton";
this.ControlButton.Size = new System.Drawing.Size(75, 27); this.ControlButton.Size = new System.Drawing.Size(75, 27);
@@ -635,10 +640,9 @@
// NotifyMenu // NotifyMenu
// //
this.NotifyMenu.ImageScalingSize = new System.Drawing.Size(20, 20); this.NotifyMenu.ImageScalingSize = new System.Drawing.Size(20, 20);
this.NotifyMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] this.NotifyMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
{ this.ShowMainFormToolStripButton,
this.ShowMainFormToolStripButton, this.ExitToolStripButton this.ExitToolStripButton});
});
this.NotifyMenu.Name = "NotifyMenu"; this.NotifyMenu.Name = "NotifyMenu";
this.NotifyMenu.ShowItemToolTips = false; this.NotifyMenu.ShowItemToolTips = false;
this.NotifyMenu.Size = new System.Drawing.Size(108, 48); this.NotifyMenu.Size = new System.Drawing.Size(108, 48);
@@ -659,7 +663,7 @@
// //
// SettingsButton // SettingsButton
// //
this.SettingsButton.Anchor = ((System.Windows.Forms.AnchorStyles) ((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.SettingsButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.SettingsButton.Location = new System.Drawing.Point(1, 3); this.SettingsButton.Location = new System.Drawing.Point(1, 3);
this.SettingsButton.Name = "SettingsButton"; this.SettingsButton.Name = "SettingsButton";
this.SettingsButton.Size = new System.Drawing.Size(72, 27); this.SettingsButton.Size = new System.Drawing.Size(72, 27);
@@ -728,7 +732,7 @@
this.Controls.Add(this.MenuStrip); this.Controls.Add(this.MenuStrip);
this.Controls.Add(this.StatusStrip); this.Controls.Add(this.StatusStrip);
this.Controls.Add(this.flowLayoutPanel1); this.Controls.Add(this.flowLayoutPanel1);
this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) (134))); 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.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.MaximizeBox = false; this.MaximizeBox = false;
@@ -744,13 +748,13 @@
this.configLayoutPanel.ResumeLayout(false); this.configLayoutPanel.ResumeLayout(false);
this.configLayoutPanel.PerformLayout(); this.configLayoutPanel.PerformLayout();
this.tableLayoutPanel2.ResumeLayout(false); this.tableLayoutPanel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize) (this.EditServerPictureBox)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.EditServerPictureBox)).EndInit();
((System.ComponentModel.ISupportInitialize) (this.CopyLinkPictureBox)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.CopyLinkPictureBox)).EndInit();
((System.ComponentModel.ISupportInitialize) (this.DeleteServerPictureBox)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.DeleteServerPictureBox)).EndInit();
((System.ComponentModel.ISupportInitialize) (this.SpeedPictureBox)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.SpeedPictureBox)).EndInit();
this.tableLayoutPanel3.ResumeLayout(false); this.tableLayoutPanel3.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize) (this.EditModePictureBox)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.EditModePictureBox)).EndInit();
((System.ComponentModel.ISupportInitialize) (this.DeleteModePictureBox)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.DeleteModePictureBox)).EndInit();
this.StatusStrip.ResumeLayout(false); this.StatusStrip.ResumeLayout(false);
this.StatusStrip.PerformLayout(); this.StatusStrip.PerformLayout();
this.NotifyMenu.ResumeLayout(false); this.NotifyMenu.ResumeLayout(false);
@@ -760,6 +764,7 @@
this.ButtomControlContainerControl.ResumeLayout(false); this.ButtomControlContainerControl.ResumeLayout(false);
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout(); this.PerformLayout();
} }
private System.Windows.Forms.ToolStripMenuItem CreateRouteTableRuleToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem CreateRouteTableRuleToolStripMenuItem;
@@ -798,7 +803,6 @@
private System.Windows.Forms.TableLayoutPanel ProfileTable; private System.Windows.Forms.TableLayoutPanel ProfileTable;
private System.Windows.Forms.ToolStripMenuItem UninstallTapDriverToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem UninstallTapDriverToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem CheckForUpdatesToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem CheckForUpdatesToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem ReloadModesToolStripMenuItem;
private System.Windows.Forms.ComboBox ServerComboBox; private System.Windows.Forms.ComboBox ServerComboBox;
private System.Windows.Forms.Label ServerLabel; private System.Windows.Forms.Label ServerLabel;
private System.Windows.Forms.ToolStripMenuItem ServerToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem ServerToolStripMenuItem;

View File

@@ -30,7 +30,6 @@ namespace Netch.Forms
private readonly Dictionary<string, object> _mainFormText = new(); private readonly Dictionary<string, object> _mainFormText = new();
private bool _comboBoxInitialized;
private bool _textRecorded; private bool _textRecorded;
public MainForm() public MainForm()
@@ -85,6 +84,8 @@ namespace Netch.Forms
private void MainForm_Load(object sender, EventArgs e) private void MainForm_Load(object sender, EventArgs e)
{ {
Netch.TimePoint("MainForm ctor (Pre MainForm Load)");
// 计算 ComboBox绘制 目标宽度 // 计算 ComboBox绘制 目标宽度
RecordSize(); RecordSize();
@@ -93,7 +94,6 @@ namespace Netch.Forms
ModeHelper.Load(); ModeHelper.Load();
LoadModes(); LoadModes();
_comboBoxInitialized = true;
// 加载翻译 // 加载翻译
TranslateControls(); TranslateControls();
@@ -121,6 +121,8 @@ namespace Netch.Forms
if (Global.Settings.StartWhenOpened) if (Global.Settings.StartWhenOpened)
ControlButton_Click(null, null); ControlButton_Click(null, null);
}); });
Netch.TimePoint("Post Form Load", false);
} }
private void RecordSize() private void RecordSize()
@@ -264,25 +266,6 @@ namespace Netch.Forms
Show(); Show();
} }
private void ReloadModesToolStripMenuItem_Click(object sender, EventArgs e)
{
Enabled = false;
try
{
ModeHelper.Load();
LoadModes();
NotifyTip(i18N.Translate("Modes have been reload"));
}
catch (Exception)
{
// ignored
}
finally
{
Enabled = true;
}
}
#endregion #endregion
#region Subscription #region Subscription
@@ -577,7 +560,8 @@ namespace Netch.Forms
return; 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; return;
NotifyTip(i18N.Translate("Start downloading new version")); NotifyTip(i18N.Translate("Start downloading new version"));
@@ -735,13 +719,9 @@ namespace Netch.Forms
private void LoadServers() private void LoadServers()
{ {
var comboBoxInitialized = _comboBoxInitialized;
_comboBoxInitialized = false;
ServerComboBox.Items.Clear(); ServerComboBox.Items.Clear();
ServerComboBox.Items.AddRange(Global.Settings.Server.ToArray()); ServerComboBox.Items.AddRange(Global.Settings.Server.ToArray());
SelectLastServer(); SelectLastServer();
_comboBoxInitialized = comboBoxInitialized;
} }
public void SelectLastServer() public void SelectLastServer()
@@ -756,11 +736,8 @@ namespace Netch.Forms
// 如果当前 ServerComboBox 中没元素,不做处理 // 如果当前 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; Global.Settings.ServerComboBoxSelectedIndex = ServerComboBox.SelectedIndex;
} }
@@ -859,14 +836,10 @@ namespace Netch.Forms
public void LoadModes() public void LoadModes()
{ {
var comboBoxInitialized = _comboBoxInitialized;
_comboBoxInitialized = false;
ModeComboBox.Items.Clear(); ModeComboBox.Items.Clear();
ModeComboBox.Items.AddRange(Global.Modes.ToArray()); ModeComboBox.Items.AddRange(Global.Modes.Cast<object>().ToArray());
ModeComboBox.Tag = null; ModeComboBox.Tag = null;
SelectLastMode(); SelectLastMode();
_comboBoxInitialized = comboBoxInitialized;
} }
public void SelectLastMode() public void SelectLastMode()
@@ -881,11 +854,8 @@ namespace Netch.Forms
// 如果当前 ModeComboBox 中没元素,不做处理 // 如果当前 ModeComboBox 中没元素,不做处理
} }
private void ModeComboBox_SelectedIndexChanged(object sender, EventArgs o) private void ModeComboBox_SelectionChangeCommitted(object sender, EventArgs o)
{ {
if (!_comboBoxInitialized)
return;
try try
{ {
Global.Settings.ModeComboBoxSelectedIndex = Global.Modes.IndexOf((Models.Mode) ModeComboBox.SelectedItem); Global.Settings.ModeComboBoxSelectedIndex = Global.Modes.IndexOf((Models.Mode) ModeComboBox.SelectedItem);
@@ -1137,8 +1107,7 @@ namespace Netch.Forms
// 启动需要禁用的控件 // 启动需要禁用的控件
UninstallServiceToolStripMenuItem.Enabled = UpdateACLToolStripMenuItem.Enabled = updateACLWithProxyToolStripMenuItem.Enabled = UninstallServiceToolStripMenuItem.Enabled = UpdateACLToolStripMenuItem.Enabled = updateACLWithProxyToolStripMenuItem.Enabled =
updatePACToolStripMenuItem.Enabled = UpdateServersFromSubscribeLinksToolStripMenuItem.Enabled = updatePACToolStripMenuItem.Enabled = UpdateServersFromSubscribeLinksToolStripMenuItem.Enabled =
UpdateServersFromSubscribeLinksWithProxyToolStripMenuItem.Enabled = UninstallTapDriverToolStripMenuItem.Enabled = UpdateServersFromSubscribeLinksWithProxyToolStripMenuItem.Enabled = UninstallTapDriverToolStripMenuItem.Enabled = enabled;
ReloadModesToolStripMenuItem.Enabled = enabled;
} }
_state = value; _state = value;

132
Netch/Forms/MainForm.resx Normal file
View File

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

View File

@@ -31,25 +31,21 @@ namespace Netch.Forms.Mode
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
this.components = new System.ComponentModel.Container();
this.ConfigurationGroupBox = new System.Windows.Forms.GroupBox(); this.ConfigurationGroupBox = new System.Windows.Forms.GroupBox();
this.RemarkLabel = new System.Windows.Forms.Label(); this.RemarkLabel = new System.Windows.Forms.Label();
this.RemarkTextBox = new System.Windows.Forms.TextBox(); this.RemarkTextBox = new System.Windows.Forms.TextBox();
this.FilenameLabel = new System.Windows.Forms.Label(); this.FilenameLabel = new System.Windows.Forms.Label();
this.FilenameTextBox = new System.Windows.Forms.TextBox(); this.FilenameTextBox = new System.Windows.Forms.TextBox();
this.containerControl1 = new System.Windows.Forms.ContainerControl(); 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.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.SelectButton = new System.Windows.Forms.Button();
this.contextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components); this.ScanButton = new System.Windows.Forms.Button();
this.DeleteToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ValidationButton = new System.Windows.Forms.Button();
this.ControlButton = new System.Windows.Forms.Button(); this.ControlButton = new System.Windows.Forms.Button();
this.ConfigurationGroupBox.SuspendLayout(); this.ConfigurationGroupBox.SuspendLayout();
this.containerControl1.SuspendLayout(); this.containerControl1.SuspendLayout();
this.ProcessGroupBox.SuspendLayout(); this.ProcessGroupBox.SuspendLayout();
this.contextMenuStrip.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// ConfigurationGroupBox // ConfigurationGroupBox
@@ -60,11 +56,12 @@ namespace Netch.Forms.Mode
this.ConfigurationGroupBox.Controls.Add(this.FilenameTextBox); this.ConfigurationGroupBox.Controls.Add(this.FilenameTextBox);
this.ConfigurationGroupBox.Controls.Add(this.containerControl1); this.ConfigurationGroupBox.Controls.Add(this.containerControl1);
this.ConfigurationGroupBox.Controls.Add(this.ProcessGroupBox); this.ConfigurationGroupBox.Controls.Add(this.ProcessGroupBox);
this.ConfigurationGroupBox.Controls.Add(this.SelectButton); this.ConfigurationGroupBox.Controls.Add(this.ControlButton);
this.ConfigurationGroupBox.Location = new System.Drawing.Point(12, 12); this.ConfigurationGroupBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.ConfigurationGroupBox.Location = new System.Drawing.Point(12, 5);
this.ConfigurationGroupBox.Name = "ConfigurationGroupBox"; this.ConfigurationGroupBox.Name = "ConfigurationGroupBox";
this.ConfigurationGroupBox.Size = new System.Drawing.Size(340, 344); this.ConfigurationGroupBox.Size = new System.Drawing.Size(431, 378);
this.ConfigurationGroupBox.TabIndex = 1; this.ConfigurationGroupBox.TabIndex = 0;
this.ConfigurationGroupBox.TabStop = false; this.ConfigurationGroupBox.TabStop = false;
this.ConfigurationGroupBox.Text = "Configuration"; this.ConfigurationGroupBox.Text = "Configuration";
// //
@@ -81,7 +78,7 @@ namespace Netch.Forms.Mode
// //
this.RemarkTextBox.Location = new System.Drawing.Point(84, 22); this.RemarkTextBox.Location = new System.Drawing.Point(84, 22);
this.RemarkTextBox.Name = "RemarkTextBox"; 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.TabIndex = 1;
this.RemarkTextBox.TextChanged += new System.EventHandler(this.RemarkTextBox_TextChanged); 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.Location = new System.Drawing.Point(84, 52);
this.FilenameTextBox.Name = "FilenameTextBox"; this.FilenameTextBox.Name = "FilenameTextBox";
this.FilenameTextBox.ReadOnly = true; 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; this.FilenameTextBox.TabIndex = 3;
// //
// containerControl1 // containerControl1
// //
this.containerControl1.Controls.Add(this.RuleListBox); this.containerControl1.Controls.Add(this.RuleRichTextBox);
this.containerControl1.Location = new System.Drawing.Point(6, 81); this.containerControl1.Location = new System.Drawing.Point(6, 81);
this.containerControl1.Name = "containerControl1"; this.containerControl1.Name = "containerControl1";
this.containerControl1.Size = new System.Drawing.Size(328, 176); this.containerControl1.Size = new System.Drawing.Size(419, 221);
this.containerControl1.TabIndex = 5; this.containerControl1.TabIndex = 4;
this.containerControl1.Text = "containerControl1"; this.containerControl1.Text = "containerControl1";
// //
// RuleListBox // RuleRichTextBox
// //
this.RuleListBox.Dock = System.Windows.Forms.DockStyle.Fill; this.RuleRichTextBox.DetectUrls = false;
this.RuleListBox.FormattingEnabled = true; this.RuleRichTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.RuleListBox.ItemHeight = 17; this.RuleRichTextBox.Location = new System.Drawing.Point(0, 0);
this.RuleListBox.Location = new System.Drawing.Point(0, 0); this.RuleRichTextBox.Name = "RuleRichTextBox";
this.RuleListBox.Name = "RuleListBox"; this.RuleRichTextBox.Size = new System.Drawing.Size(419, 221);
this.RuleListBox.Size = new System.Drawing.Size(328, 176); this.RuleRichTextBox.TabIndex = 0;
this.RuleListBox.TabIndex = 0; this.RuleRichTextBox.Text = "";
this.RuleListBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.RuleListBox_MouseUp); this.RuleRichTextBox.WordWrap = false;
// //
// ProcessGroupBox // ProcessGroupBox
// //
this.ProcessGroupBox.Controls.Add(this.ProcessNameTextBox); this.ProcessGroupBox.Controls.Add(this.SelectButton);
this.ProcessGroupBox.Controls.Add(this.AddButton); this.ProcessGroupBox.Controls.Add(this.ScanButton);
this.ProcessGroupBox.Location = new System.Drawing.Point(6, 263); this.ProcessGroupBox.Controls.Add(this.ValidationButton);
this.ProcessGroupBox.Location = new System.Drawing.Point(6, 295);
this.ProcessGroupBox.Name = "ProcessGroupBox"; this.ProcessGroupBox.Name = "ProcessGroupBox";
this.ProcessGroupBox.Size = new System.Drawing.Size(328, 46); this.ProcessGroupBox.Size = new System.Drawing.Size(419, 44);
this.ProcessGroupBox.TabIndex = 6; this.ProcessGroupBox.TabIndex = 5;
this.ProcessGroupBox.TabStop = false; 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 // 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.Name = "SelectButton";
this.SelectButton.Size = new System.Drawing.Size(75, 23); this.SelectButton.Size = new System.Drawing.Size(75, 23);
this.SelectButton.TabIndex = 7; this.SelectButton.TabIndex = 0;
this.SelectButton.Text = "Select"; this.SelectButton.Text = "Select";
this.SelectButton.UseVisualStyleBackColor = true; this.SelectButton.UseVisualStyleBackColor = true;
this.SelectButton.Click += new System.EventHandler(this.SelectButton_Click); this.SelectButton.Click += new System.EventHandler(this.SelectButton_Click);
// //
// contextMenuStrip // ScanButton
// //
this.contextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.ScanButton.Location = new System.Drawing.Point(87, 13);
this.DeleteToolStripMenuItem}); this.ScanButton.Name = "ScanButton";
this.contextMenuStrip.Name = "contextMenuStrip"; this.ScanButton.Size = new System.Drawing.Size(75, 23);
this.contextMenuStrip.Size = new System.Drawing.Size(114, 26); 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.ValidationButton.Location = new System.Drawing.Point(338, 13);
this.DeleteToolStripMenuItem.Size = new System.Drawing.Size(113, 22); this.ValidationButton.Name = "ValidationButton";
this.DeleteToolStripMenuItem.Text = "Delete"; this.ValidationButton.Size = new System.Drawing.Size(75, 23);
this.DeleteToolStripMenuItem.Click += new System.EventHandler(this.deleteRule_Click); this.ValidationButton.TabIndex = 2;
this.ValidationButton.Text = "Validation";
this.ValidationButton.UseVisualStyleBackColor = true;
this.ValidationButton.Click += new System.EventHandler(this.ValidationButton_Click);
// //
// ControlButton // 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.Name = "ControlButton";
this.ControlButton.Size = new System.Drawing.Size(75, 23); this.ControlButton.Size = new System.Drawing.Size(75, 23);
this.ControlButton.TabIndex = 2; this.ControlButton.TabIndex = 6;
this.ControlButton.Text = "Save"; this.ControlButton.Text = "Save";
this.ControlButton.UseVisualStyleBackColor = true; this.ControlButton.UseVisualStyleBackColor = true;
this.ControlButton.Click += new System.EventHandler(this.ControlButton_Click); 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.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; 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.ConfigurationGroupBox);
this.Controls.Add(this.ControlButton);
this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); 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.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.MaximizeBox = false; this.MaximizeBox = false;
this.Name = "Process"; this.Name = "Process";
this.Padding = new System.Windows.Forms.Padding(12, 5, 12, 5);
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Create Process Mode"; this.Text = "Create Process Mode";
this.Load += new System.EventHandler(this.ModeForm_Load); this.Load += new System.EventHandler(this.ModeForm_Load);
@@ -202,27 +189,23 @@ namespace Netch.Forms.Mode
this.ConfigurationGroupBox.PerformLayout(); this.ConfigurationGroupBox.PerformLayout();
this.containerControl1.ResumeLayout(false); this.containerControl1.ResumeLayout(false);
this.ProcessGroupBox.ResumeLayout(false); this.ProcessGroupBox.ResumeLayout(false);
this.ProcessGroupBox.PerformLayout();
this.contextMenuStrip.ResumeLayout(false);
this.ResumeLayout(false); this.ResumeLayout(false);
} }
#endregion #endregion
private System.Windows.Forms.Button ScanButton;
private System.Windows.Forms.ContainerControl containerControl1; private System.Windows.Forms.ContainerControl containerControl1;
private System.Windows.Forms.ContextMenuStrip contextMenuStrip;
private System.Windows.Forms.ToolStripMenuItem DeleteToolStripMenuItem;
public System.Windows.Forms.GroupBox ConfigurationGroupBox; public System.Windows.Forms.GroupBox ConfigurationGroupBox;
private System.Windows.Forms.Label RemarkLabel; private System.Windows.Forms.Label RemarkLabel;
private System.Windows.Forms.GroupBox ProcessGroupBox; private System.Windows.Forms.GroupBox ProcessGroupBox;
private System.Windows.Forms.ListBox RuleListBox;
private System.Windows.Forms.TextBox RemarkTextBox; private System.Windows.Forms.TextBox RemarkTextBox;
private System.Windows.Forms.TextBox ProcessNameTextBox;
private System.Windows.Forms.Button AddButton;
private System.Windows.Forms.Button SelectButton; private System.Windows.Forms.Button SelectButton;
public System.Windows.Forms.Button ControlButton; public System.Windows.Forms.Button ControlButton;
private System.Windows.Forms.Label FilenameLabel; private System.Windows.Forms.Label FilenameLabel;
private System.Windows.Forms.TextBox FilenameTextBox; private System.Windows.Forms.TextBox FilenameTextBox;
private RichTextBox RuleRichTextBox;
private Button ValidationButton;
} }
} }

View File

@@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using Microsoft.WindowsAPICodePack.Dialogs; using Microsoft.WindowsAPICodePack.Dialogs;
using Netch.Controllers; using Netch.Controllers;
using Netch.Models;
using Netch.Properties; using Netch.Properties;
using Netch.Utils; using Netch.Utils;
@@ -33,10 +35,24 @@ namespace Netch.Forms.Mode
_mode = mode; _mode = mode;
} }
/// <summary> #region Model
/// 是否被编辑过
/// </summary> public IEnumerable<string> Rules => RuleRichTextBox.Lines;
public bool Edited { get; private set; }
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) public void ModeForm_Load(object sender, EventArgs e)
{ {
@@ -47,60 +63,10 @@ namespace Netch.Forms.Mode
RemarkTextBox.TextChanged -= RemarkTextBox_TextChanged; RemarkTextBox.TextChanged -= RemarkTextBox_TextChanged;
RemarkTextBox.Text = _mode.Remark; RemarkTextBox.Text = _mode.Remark;
FilenameTextBox.Text = _mode.RelativePath; FilenameTextBox.Text = _mode.RelativePath;
RuleListBox.Items.AddRange(_mode.Rule.Cast<object>().ToArray()); RuleAddRange(_mode.Rule);
} }
i18N.TranslateForm(this); 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) private void SelectButton_Click(object sender, EventArgs e)
@@ -108,7 +74,7 @@ namespace Netch.Forms.Mode
var dialog = new CommonOpenFileDialog var dialog = new CommonOpenFileDialog
{ {
IsFolderPicker = true, IsFolderPicker = true,
Multiselect = false, Multiselect = true,
Title = i18N.Translate("Select a folder"), Title = i18N.Translate("Select a folder"),
AddToMostRecentlyUsedList = false, AddToMostRecentlyUsedList = false,
EnsurePathExists = true, EnsurePathExists = true,
@@ -117,17 +83,20 @@ namespace Netch.Forms.Mode
if (dialog.ShowDialog(Handle) == CommonFileDialogResult.Ok) if (dialog.ShowDialog(Handle) == CommonFileDialogResult.Ok)
{ {
var path = dialog.FileName; foreach (string p in dialog.FileNames)
if (!path.EndsWith(@"\")) {
path += @"\"; string path = p;
if (!path.EndsWith(@"\"))
path += @"\";
RuleListBox.Items.Add(path.ToRegexString()); RuleAdd($"^{path.ToRegexString()}");
}
} }
} }
public void ControlButton_Click(object sender, EventArgs e) 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")); MessageBoxX.Show(i18N.Translate("Unable to add empty rule"));
return; return;
@@ -149,11 +118,9 @@ namespace Netch.Forms.Mode
{ {
_mode.Remark = RemarkTextBox.Text; _mode.Remark = RemarkTextBox.Text;
_mode.Rule.Clear(); _mode.Rule.Clear();
_mode.Rule.AddRange(RuleListBox.Items.Cast<string>()); _mode.Rule.AddRange(RuleRichTextBox.Lines);
_mode.WriteFile(); _mode.WriteFile();
Global.MainForm.LoadModes();
Edited = false;
MessageBoxX.Show(i18N.Translate("Mode updated successfully")); MessageBoxX.Show(i18N.Translate("Mode updated successfully"));
} }
else else
@@ -173,10 +140,9 @@ namespace Netch.Forms.Mode
Remark = RemarkTextBox.Text Remark = RemarkTextBox.Text
}; };
mode.Rule.AddRange(RuleListBox.Items.Cast<string>()); mode.Rule.AddRange(RuleRichTextBox.Lines);
mode.WriteFile(); mode.WriteFile();
ModeHelper.Add(mode);
MessageBoxX.Show(i18N.Translate("Mode added successfully")); MessageBoxX.Show(i18N.Translate("Mode added successfully"));
} }
@@ -190,5 +156,57 @@ namespace Netch.Forms.Mode
FilenameTextBox.Text = FilenameTextBox.Text = ModeEditorUtils.GetCustomModeRelativePath(RemarkTextBox.Text); 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");
}
} }
} }

View File

@@ -83,7 +83,6 @@ namespace Netch.Forms.Mode
_mode.Type = (int) comboBox1.SelectedValue; _mode.Type = (int) comboBox1.SelectedValue;
_mode.WriteFile(); _mode.WriteFile();
Global.MainForm.LoadModes();
MessageBoxX.Show(i18N.Translate("Mode updated successfully")); MessageBoxX.Show(i18N.Translate("Mode updated successfully"));
} }
else else
@@ -105,7 +104,6 @@ namespace Netch.Forms.Mode
mode.Rule.AddRange(richTextBox1.Lines); mode.Rule.AddRange(richTextBox1.Lines);
mode.WriteFile(); mode.WriteFile();
ModeHelper.Add(mode);
MessageBoxX.Show(i18N.Translate("Mode added successfully")); MessageBoxX.Show(i18N.Translate("Mode added successfully"));
} }

View File

@@ -4,7 +4,6 @@ using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using WindowsJobAPI;
using Netch.Controllers; using Netch.Controllers;
using Netch.Forms; using Netch.Forms;
using Netch.Models; using Netch.Models;
@@ -34,12 +33,6 @@ namespace Netch
public static Mutex Mutex => LazyMutex.Value; public static Mutex Mutex => LazyMutex.Value;
#if DEBUG
public static bool Testing = false;
#else
public const bool Testing = false;
#endif
/// <summary> /// <summary>
/// 用于读取和写入的配置 /// 用于读取和写入的配置
/// </summary> /// </summary>
@@ -50,11 +43,6 @@ namespace Netch
/// </summary> /// </summary>
public static readonly List<Mode> Modes = new(); public static readonly List<Mode> Modes = new();
/// <summary>
/// Windows Job API
/// </summary>
public static readonly JobObject Job = new();
public static class Flags public static class Flags
{ {
public static readonly bool IsWindows10Upper = Environment.OSVersion.Version.Major >= 10; public static readonly bool IsWindows10Upper = Environment.OSVersion.Version.Major >= 10;

View File

@@ -1,54 +1,28 @@
using System; using System;
using System.Text; using System.Linq;
namespace Netch.Models.GitHubRelease namespace Netch.Models.GitHubRelease
{ {
[Serializable] [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 SuffixVersion(Version version, string suffix)
public string PreRelease { get; }
public int Build { get; }
public SuffixVersion(int major, int minor, int patch, string preRelease, int build)
{ {
Major = major; Version = version;
Minor = minor; Suffix = suffix;
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;
} }
public static SuffixVersion Parse(string input) public static SuffixVersion Parse(string input)
{ {
var splitStr = input.Split('-'); var split = input.Split('-');
var dotNetVersion = Version.Parse(splitStr[0]); var dotNetVersion = Version.Parse(split[0]);
var preRelease = new StringBuilder(); var preRelease = split.ElementAtOrDefault(1) ?? string.Empty;
var build = 0;
if (splitStr.Length > 1) return new SuffixVersion(dotNetVersion, preRelease);
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);
} }
public static bool TryParse(string input, out SuffixVersion result) 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) public int CompareTo(object obj)
{ {
if (obj is SuffixVersion version) if (obj is not SuffixVersion version)
return CompareTo(version); throw new ArgumentOutOfRangeException();
return -1; return CompareTo(version);
} }
/// <summary> /// <summary>
@@ -86,57 +55,27 @@ namespace Netch.Models.GitHubRelease
/// </returns> /// </returns>
public int CompareTo(SuffixVersion other) public int CompareTo(SuffixVersion other)
{ {
var majorComparison = Major.CompareTo(other.Major); var versionComparison = Version.CompareTo(other.Version);
if (majorComparison != 0) if (versionComparison != 0)
return majorComparison; return versionComparison;
var minorComparison = Minor.CompareTo(other.Minor); if (Suffix == string.Empty)
if (minorComparison != 0) return other.Suffix == string.Empty ? 0 : 1;
return minorComparison;
var patchComparison = Patch.CompareTo(other.Patch); if (other.Suffix == string.Empty)
if (patchComparison != 0)
return patchComparison;
if (PreRelease == string.Empty)
return other.PreRelease == string.Empty ? 0 : 1;
if (other.PreRelease == string.Empty)
return -1; return -1;
var suffixComparison = string.Compare(PreRelease, other.PreRelease, StringComparison.Ordinal); var suffixComparison = string.Compare(Suffix, other.Suffix, StringComparison.OrdinalIgnoreCase);
if (suffixComparison != 0) return suffixComparison;
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;
}
} }
public override string ToString() 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;
} }
} }
} }

View File

@@ -10,12 +10,41 @@ namespace Netch.Models
{ {
public class Mode 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>
/// 规则 /// 规则
/// </summary> /// </summary>
public readonly List<string> Rule = new(); public List<string> Rule => _lazyRule.Value;
/// <summary> /// <summary>
/// 绕过中国0. 不绕过 1. 绕过) /// 绕过中国0. 不绕过 1. 绕过)
@@ -45,15 +74,6 @@ namespace Netch.Models
/// </summary> /// </summary>
public int Type { get; set; } = 0; public int Type { get; set; } = 0;
public Mode(string fullName)
{
FullName = fullName;
}
public Mode()
{
}
/// <summary> /// <summary>
/// 文件相对路径(必须是存在的文件) /// 文件相对路径(必须是存在的文件)
/// </summary> /// </summary>
@@ -79,7 +99,7 @@ namespace Netch.Models
relativePath.Replace(">", ""); relativePath.Replace(">", "");
relativePath.Replace(".h", ".txt"); 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) if (mode == null)
throw new MessageException($"{relativePath} file included in {Remark} not found"); 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) public void WriteFile(string? fullName = null)
{ {
if (fullName != null) if (fullName != null)

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

View File

@@ -16,15 +16,41 @@ namespace Netch
{ {
public static class 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>
/// 应用程序的主入口点 /// 应用程序的主入口点
/// </summary> /// </summary>
[STAThread] [STAThread]
public static void Main(string[] args) public static void Main(string[] args)
{ {
#if DEBUG
AttachConsole();
#else
if (args.Contains("-console")) if (args.Contains("-console"))
if (!NativeMethods.AttachConsole(-1)) AttachConsole();
NativeMethods.AllocConsole(); #endif
StartStopwatch("Netch");
// 设置当前目录 // 设置当前目录
Directory.SetCurrentDirectory(Global.NetchDir); Directory.SetCurrentDirectory(Global.NetchDir);
@@ -40,9 +66,11 @@ namespace Netch
if (!Directory.Exists(item)) if (!Directory.Exists(item))
Directory.CreateDirectory(item); Directory.CreateDirectory(item);
TimePoint("Clean Old, Create Directory");
// 加载配置 // 加载配置
Configuration.Load(); Configuration.Load();
TimePoint("Load Configuration");
// 检查是否已经运行 // 检查是否已经运行
if (!Global.Mutex.WaitOne(0, false)) if (!Global.Mutex.WaitOne(0, false))
{ {
@@ -76,6 +104,8 @@ namespace Netch
Logging.Info($"版本: {UpdateChecker.Owner}/{UpdateChecker.Repo}@{UpdateChecker.Version}"); Logging.Info($"版本: {UpdateChecker.Owner}/{UpdateChecker.Repo}@{UpdateChecker.Version}");
Task.Run(() => { Logging.Info($"主程序 SHA256: {Utils.Utils.SHA256CheckSum(Global.NetchExecutable)}"); }); Task.Run(() => { Logging.Info($"主程序 SHA256: {Utils.Utils.SHA256CheckSum(Global.NetchExecutable)}"); });
TimePoint("Get Info, Pre-Form");
// 绑定错误捕获 // 绑定错误捕获
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
Application.ThreadException += Application_OnException; Application.ThreadException += Application_OnException;
@@ -85,6 +115,12 @@ namespace Netch
Application.Run(Global.MainForm); Application.Run(Global.MainForm);
} }
private static void AttachConsole()
{
if (!NativeMethods.AttachConsole(-1))
NativeMethods.AllocConsole();
}
public static void Application_OnException(object sender, ThreadExceptionEventArgs e) public static void Application_OnException(object sender, ThreadExceptionEventArgs e)
{ {
Logging.Error(e.Exception.ToString()); Logging.Error(e.Exception.ToString());

View File

@@ -14,6 +14,9 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<!-- <EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>-->
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -43,15 +46,18 @@
<PackageReference Include="MaxMind.GeoIP2" Version="4.0.1" /> <PackageReference Include="MaxMind.GeoIP2" Version="4.0.1" />
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.66" GeneratePathProperty="true" /> <PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.66" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <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.Collections.Immutable" Version="5.0.0" />
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" /> <PackageReference Include="System.Reflection.Metadata" Version="5.0.0" />
<PackageReference Include="System.Text.Json" Version="5.0.1" /> <PackageReference Include="System.Text.Json" Version="5.0.1" />
<PackageReference Include="TaskScheduler" Version="2.9.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="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="WindowsFirewallHelper" Version="2.0.4.70-beta2" />
<PackageReference Include="WindowsJobAPI" Version="5.0.1" />
<PackageReference Include="WindowsProxy" Version="5.0.3" /> <PackageReference Include="WindowsProxy" Version="5.0.3" />
</ItemGroup> </ItemGroup>

View File

@@ -1,10 +1,10 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <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> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -13,13 +13,13 @@ namespace Netch.Properties {
/// <summary> /// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc. /// 一个强类型的资源类,用于查找本地化的字符串等。
/// </summary> /// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder // 此类是由 StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio. // 类通过类似于 ResGen Visual Studio 的工具自动生成的。
// To add or remove a member, edit your .ResX file then rerun ResGen // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// with the /str option, or rebuild your VS project. // (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { internal class Resources {
@@ -33,7 +33,7 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Returns the cached ResourceManager instance used by this class. /// 返回此类使用的缓存的 ResourceManager 实例。
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager { internal static global::System.Resources.ResourceManager ResourceManager {
@@ -47,8 +47,8 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Overrides the current thread's CurrentUICulture property for all /// 重写当前线程的 CurrentUICulture 属性,对
/// resource lookups using this strongly typed resource class. /// 使用此强类型资源类的所有资源查找执行重写。
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture { internal static global::System.Globalization.CultureInfo Culture {
@@ -61,7 +61,7 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Byte[]. /// 查找 System.Byte[] 类型的本地化资源。
/// </summary> /// </summary>
internal static byte[] _7za { internal static byte[] _7za {
get { get {
@@ -71,17 +71,7 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Byte[]. /// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// </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.
/// </summary> /// </summary>
internal static System.Drawing.Bitmap CopyLink { internal static System.Drawing.Bitmap CopyLink {
get { get {
@@ -91,17 +81,7 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Byte[]. /// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// </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.
/// </summary> /// </summary>
internal static System.Drawing.Bitmap delete { internal static System.Drawing.Bitmap delete {
get { get {
@@ -111,7 +91,7 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap. /// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// </summary> /// </summary>
internal static System.Drawing.Bitmap edit { internal static System.Drawing.Bitmap edit {
get { get {
@@ -121,7 +101,7 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// 查找类似于 (图标) 的 System.Drawing.Icon 类型的本地化资源。
/// </summary> /// </summary>
internal static System.Drawing.Icon icon { internal static System.Drawing.Icon icon {
get { get {
@@ -131,7 +111,7 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap. /// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// </summary> /// </summary>
internal static System.Drawing.Bitmap Netch { internal static System.Drawing.Bitmap Netch {
get { get {
@@ -141,7 +121,7 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap. /// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// </summary> /// </summary>
internal static System.Drawing.Bitmap speed { internal static System.Drawing.Bitmap speed {
get { get {
@@ -151,7 +131,7 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap. /// 查找 System.Drawing.Bitmap 类型的本地化资源。
/// </summary> /// </summary>
internal static System.Drawing.Bitmap Sponsor { internal static System.Drawing.Bitmap Sponsor {
get { get {
@@ -161,7 +141,7 @@ namespace Netch.Properties {
} }
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Byte[]. /// 查找 System.Byte[] 类型的本地化资源。
/// </summary> /// </summary>
internal static byte[] zh_CN { internal static byte[] zh_CN {
get { get {

View File

@@ -118,10 +118,6 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <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"> <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, <value>..\Resources\zh-CN;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089</value> PublicKeyToken=b77a5c561934e089</value>
@@ -150,10 +146,6 @@
<value>..\Resources\CopyLink.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, <value>..\Resources\CopyLink.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a</value> PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </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"> <data name="7za" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\7za.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, <value>..\Resources\7za.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089</value> PublicKeyToken=b77a5c561934e089</value>

Binary file not shown.

View File

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

View File

@@ -1,4 +1,5 @@
using Netch.Forms; using Netch.Forms;
using Netch.Utils;
namespace Netch.Servers.Shadowsocks.Form namespace Netch.Servers.Shadowsocks.Form
{ {
@@ -8,7 +9,7 @@ namespace Netch.Servers.Shadowsocks.Form
{ {
server ??= new Shadowsocks(); server ??= new Shadowsocks();
Server = server; 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); CreateComboBox("EncryptMethod", "Encrypt Method", SSGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod);
CreateTextBox("Plugin", "Plugin", s => true, s => server.Plugin = s, server.Plugin); CreateTextBox("Plugin", "Plugin", s => true, s => server.Plugin = s, server.Plugin);
CreateTextBox("PluginsOption", "Plugin Options", s => true, s => server.PluginOption = s, server.PluginOption); CreateTextBox("PluginsOption", "Plugin Options", s => true, s => server.PluginOption = s, server.PluginOption);

View File

@@ -24,26 +24,60 @@ namespace Netch.Servers.Shadowsocks
{ {
var server = (Shadowsocks) s; var server = (Shadowsocks) s;
#region Argument var command = new SSParameter
{
var argument = new StringBuilder(); s = server.AutoResolveHostname(),
argument.Append($"-s {server.AutoResolveHostname()} " + $"-p {server.Port} " + $"-b {this.LocalAddress()} " + p = server.Port,
$"-l {this.Socks5LocalPort()} " + $"-m {server.EncryptMethod} " + $"-k \"{server.Password}\" " + "-u"); b = this.LocalAddress(),
l = this.Socks5LocalPort(),
if (!string.IsNullOrWhiteSpace(server.Plugin) && !string.IsNullOrWhiteSpace(server.PluginOption)) m = server.EncryptMethod,
argument.Append($" --plugin {server.Plugin}" + $" --plugin-opts \"{server.PluginOption}\""); k = server.Password,
u = true,
plugin = server.Plugin,
plugin_opts = server.PluginOption
};
if (mode.BypassChina) 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() public override void Stop()
{ {
StopInstance(); StopInstance();
} }
} }
} }

View File

@@ -15,7 +15,7 @@ namespace Netch.Servers.Shadowsocks
/// <summary> /// <summary>
/// 密码 /// 密码
/// </summary> /// </summary>
public string? Password { get; set; } public string Password { get; set; } = string.Empty;
/// <summary> /// <summary>
/// 插件 /// 插件

View File

@@ -1,4 +1,5 @@
using Netch.Forms; using Netch.Forms;
using Netch.Utils;
namespace Netch.Servers.ShadowsocksR.Form namespace Netch.Servers.ShadowsocksR.Form
{ {
@@ -8,7 +9,7 @@ namespace Netch.Servers.ShadowsocksR.Form
{ {
server ??= new ShadowsocksR(); server ??= new ShadowsocksR();
Server = server; 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("EncryptMethod", "Encrypt Method", SSRGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod);
CreateComboBox("Protocol", "Protocol", SSRGlobal.Protocols, s => server.Protocol = s, server.Protocol); CreateComboBox("Protocol", "Protocol", SSRGlobal.Protocols, s => server.Protocol = s, server.Protocol);
CreateTextBox("ProtocolParam", "Protocol Param", s => true, s => server.ProtocolParam = s, server.ProtocolParam); CreateTextBox("ProtocolParam", "Protocol Param", s => true, s => server.ProtocolParam = s, server.ProtocolParam);

View File

@@ -24,31 +24,64 @@ namespace Netch.Servers.ShadowsocksR
{ {
var server = (ShadowsocksR) s; var server = (ShadowsocksR) s;
#region Argument var command = new SSRParameter
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))
{ {
argument.Append($" -O {server.Protocol}"); s = server.AutoResolveHostname(),
if (!string.IsNullOrEmpty(server.ProtocolParam)) p = server.Port,
argument.Append($" -G \"{server.ProtocolParam}\""); 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) 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() public override void Stop()

View File

@@ -7,26 +7,16 @@ namespace Netch.Servers.ShadowsocksR
{ {
public override string Type { get; } = "SSR"; 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>
/// 密码 /// 密码
/// </summary> /// </summary>
public string Password { get; set; } = string.Empty; public string Password { get; set; } = string.Empty;
/// <summary>
/// 加密方式
/// </summary>
public string EncryptMethod { get; set; } = SSRGlobal.EncryptMethods[0];
/// <summary> /// <summary>
/// 协议 /// 协议
/// </summary> /// </summary>
@@ -36,6 +26,16 @@ namespace Netch.Servers.ShadowsocksR
/// 协议参数 /// 协议参数
/// </summary> /// </summary>
public string? ProtocolParam { get; set; } 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 public class SSRGlobal

View File

@@ -13,7 +13,7 @@
/// <summary> /// <summary>
/// 额外 ID /// 额外 ID
/// </summary> /// </summary>
public string aid { get; set; } = string.Empty; public int aid { get; set; }
/// <summary> /// <summary>
/// 伪装域名HTTPWS /// 伪装域名HTTPWS
@@ -38,7 +38,7 @@
/// <summary> /// <summary>
/// 端口 /// 端口
/// </summary> /// </summary>
public string port { get; set; } = string.Empty; public ushort port { get; set; }
/// <summary> /// <summary>
/// 备注 /// 备注
@@ -58,6 +58,6 @@
/// <summary> /// <summary>
/// 链接版本 /// 链接版本
/// </summary> /// </summary>
public string v { get; set; } = string.Empty; public int v { get; set; } = 2;
} }
} }

View File

@@ -77,9 +77,7 @@ namespace Netch.Servers.V2ray
var parameter = new Dictionary<string, string>(); var parameter = new Dictionary<string, string>();
// protocol-specific fields // protocol-specific fields
parameter.Add("type", server.TransferProtocol); parameter.Add("type", server.TransferProtocol);
if (server.EncryptMethod == "none") parameter.Add("encryption", server.EncryptMethod);
// VLESS outbounds[].settings.encryption当前可选值只有 none
parameter.Add("encryption", server.EncryptMethod);
// transport-specific fields // transport-specific fields
switch (server.TransferProtocol) switch (server.TransferProtocol)

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using Netch.Controllers; using Netch.Controllers;
using Netch.Models; using Netch.Models;
using Netch.Servers.V2ray; using Netch.Servers.V2ray;
@@ -43,12 +44,12 @@ namespace Netch.Servers.VMess
var vmessJson = JsonSerializer.Serialize(new V2rayNSharing var vmessJson = JsonSerializer.Serialize(new V2rayNSharing
{ {
v = "2", v = 2,
ps = server.Remark, ps = server.Remark,
add = server.Hostname, add = server.Hostname,
port = server.Port.ToString(), port = server.Port,
id = server.UserID, id = server.UserID,
aid = server.AlterID.ToString(), aid = server.AlterID,
net = server.TransferProtocol, net = server.TransferProtocol,
type = server.FakeType, type = server.FakeType,
host = server.Host, host = server.Host,
@@ -85,13 +86,14 @@ namespace Netch.Servers.VMess
return V2rayUtils.ParseVUri(text); 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.Remark = vmess.ps;
data.Hostname = vmess.add; data.Hostname = vmess.add;
data.Port = ushort.Parse(vmess.port); data.Port = vmess.port;
data.UserID = vmess.id; data.UserID = vmess.id;
data.AlterID = int.Parse(vmess.aid); data.AlterID = vmess.aid;
data.TransferProtocol = vmess.net; data.TransferProtocol = vmess.net;
data.FakeType = vmess.type; data.FakeType = vmess.type;

View File

@@ -95,6 +95,17 @@ namespace Netch.Updater
private void ApplyUpdate() 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 // extract Update file to {tempDirectory}\extract
var extractPath = Path.Combine(_tempDirectory, "extract"); var extractPath = Path.Combine(_tempDirectory, "extract");
int exitCode; int exitCode;
@@ -126,7 +137,7 @@ namespace Netch.Updater
if (extendedKeepDirectories.Any(p => file.StartsWith(p))) if (extendedKeepDirectories.Any(p => file.StartsWith(p)))
continue; continue;
if (Path.GetFileName(file) is ModeHelper.DISABLE_MODE_DIRECTORY_FILENAME) if (Path.GetFileName(file) is ModeHelper.DisableModeDirectoryFileName)
continue; continue;
filesToDelete.Add(file); filesToDelete.Add(file);

View File

@@ -12,12 +12,10 @@ namespace Netch.Utils
/// <summary> /// <summary>
/// 数据目录 /// 数据目录
/// </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; private static readonly JsonSerializerOptions JsonSerializerOptions = Global.NewDefaultJsonSerializerOptions;
static Configuration() static Configuration()
@@ -31,45 +29,39 @@ namespace Netch.Utils
/// </summary> /// </summary>
public static void Load() 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 else
{ {
// 弹出提示 // 保存默认设置
i18N.Load("System");
// 创建 data 文件夹并保存默认设置
Save(); Save();
} }
} }
public static Setting ParseSetting(string text) private static void CheckSetting(Setting settings)
{ {
try settings.Profiles.RemoveAll(p => p.ServerRemark == string.Empty || p.ModeRemark == string.Empty);
{
var settings = JsonSerializer.Deserialize<Setting>(text, JsonSerializerOptions)!;
#region Check Profile 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.RemoveAll(p => p.ServerRemark == string.Empty || p.ModeRemark == string.Empty); settings.Profiles[i].Index = i;
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!;
}
} }
/// <summary> /// <summary>
@@ -77,10 +69,10 @@ namespace Netch.Utils
/// </summary> /// </summary>
public static void Save() public static void Save()
{ {
if (!Directory.Exists(DATA_DIR)) if (!Directory.Exists(DataDirectoryFullName))
Directory.CreateDirectory(DATA_DIR); Directory.CreateDirectory(DataDirectoryFullName);
File.WriteAllBytes(SETTINGS_JSON, JsonSerializer.SerializeToUtf8Bytes(Global.Settings, JsonSerializerOptions)); File.WriteAllBytes(SettingFileFullName, JsonSerializer.SerializeToUtf8Bytes(Global.Settings, JsonSerializerOptions));
} }
} }
} }

View File

@@ -40,14 +40,23 @@ namespace Netch.Utils
private static void Write(string text, LogLevel logLevel) private static void Write(string text, LogLevel logLevel)
{ {
var contents = $@"[{DateTime.Now}][{logLevel.ToString()}] {text}{Global.EOF}"; var contents = $@"[{DateTime.Now}][{logLevel.ToString()}] {text}{Global.EOF}";
if (Global.Testing) #if DEBUG
switch (logLevel)
{ {
Console.WriteLine(contents); case LogLevel.INFO:
return; 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) lock (FileLock)
File.AppendAllText(LogFile, contents); File.AppendAllText(LogFile, contents);
#endif
} }
} }
} }

View File

@@ -10,19 +10,45 @@ namespace Netch.Utils
{ {
public static class ModeHelper public static class ModeHelper
{ {
private const string MODE_DIR = "mode"; public const string DisableModeDirectoryFileName = "disabled";
public const string DISABLE_MODE_DIRECTORY_FILENAME = "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) 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) public static string GetFullPath(string relativeName)
{ {
return Path.Combine(ModeDirectory, relativeName); return Path.Combine(ModeDirectoryFullName, relativeName);
} }
/// <summary> /// <summary>
@@ -31,7 +57,7 @@ namespace Netch.Utils
public static void Load() public static void Load()
{ {
Global.Modes.Clear(); Global.Modes.Clear();
LoadModeDirectory(ModeDirectory); LoadModeDirectory(ModeDirectoryFullName);
Sort(); Sort();
} }
@@ -44,11 +70,18 @@ namespace Netch.Utils
LoadModeDirectory(directory); LoadModeDirectory(directory);
// skip Directory with a disabled file in // 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; return;
foreach (var file in Directory.GetFiles(modeDirectory).Where(f => f.EndsWith(".txt"))) 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 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() private static void Sort()
{ {
Global.Modes.Sort((a, b) => string.Compare(a.Remark, b.Remark, StringComparison.Ordinal)); 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) public static void Delete(Mode mode)
{ {
if (mode.FullName == null) if (mode.FullName == null)
throw new ArgumentException("FullName"); throw new ArgumentException(nameof(mode.FullName));
if (File.Exists(mode.FullName)) File.Delete(mode.FullName);
File.Delete(mode.FullName);
Global.Modes.Remove(mode);
Global.MainForm.LoadModes();
} }
public static bool SkipServerController(Server server, Mode mode) 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; port = null;
portName = string.Empty; portName = string.Empty;
portType = PortType.Both;
switch (type) switch (type)
{ {
case 0: case 0:
port = Global.Settings.RedirectorTCPPort; port = Global.Settings.RedirectorTCPPort;
portName = "Redirector TCP"; portName = "Redirector TCP";
portType = PortType.TCP;
return new NFController(); return new NFController();
case 1: case 1:
case 2: case 2:
@@ -156,7 +133,6 @@ namespace Netch.Utils
case 5: case 5:
port = Global.Settings.HTTPLocalPort; port = Global.Settings.HTTPLocalPort;
portName = "HTTP"; portName = "HTTP";
portType = PortType.TCP;
StatusPortInfoText.HttpPort = (ushort) port; StatusPortInfoText.HttpPort = (ushort) port;
return new HTTPController(); return new HTTPController();
case 4: case 4:

View File

@@ -4,6 +4,8 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using Netch.Models; using Netch.Models;
using static Vanara.PInvoke.IpHlpApi;
using static Vanara.PInvoke.Ws2_32;
namespace Netch.Utils 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).Where(r => r.dwOwningPid is not (0 or 4));
return row.Select(r => Process.GetProcessById((int) r.dwOwningPid));
}
private static void GetReservedPortRange(PortType portType, ref List<Range> targetList) private static void GetReservedPortRange(PortType portType, ref List<Range> targetList)
{ {
var process = new Process var process = new Process

View File

@@ -20,6 +20,7 @@ namespace Netch.Utils
} }
catch catch
{ {
// Unsupported Server Type
return JsonSerializer.Deserialize<Server>(jsonElement.GetRawText(), new JsonSerializerOptions())!; return JsonSerializer.Deserialize<Server>(jsonElement.GetRawText(), new JsonSerializerOptions())!;
} }
} }

View File

@@ -21,8 +21,6 @@ namespace Netch.Utils
{ {
public static bool Open(string path) public static bool Open(string path)
{ {
if (Global.Testing)
return true;
try try
{ {
Process.Start(new ProcessStartInfo 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) public static string GetFileVersion(string file)
{ {
return File.Exists(file) ? FileVersionInfo.GetVersionInfo(file).FileVersion : string.Empty; return File.Exists(file) ? FileVersionInfo.GetVersionInfo(file).FileVersion : string.Empty;
} }
public static void DrawCenterComboBox(object sender, DrawItemEventArgs e) public static void DrawCenterComboBox(object sender, DrawItemEventArgs e)
{ {
if (sender is ComboBox cbx) if (sender is ComboBox cbx)

View File

@@ -1,3 +0,0 @@
/bin
/obj
/SearchComboBox.csproj.user

3
UnitTest/.gitignore vendored
View File

@@ -1,3 +0,0 @@
/bin
/obj
/Netch.csproj.user

View File

@@ -6,10 +6,10 @@ using Netch.Utils;
namespace UnitTest namespace UnitTest
{ {
[TestClass] [TestClass]
public class FunctionTest : TestBase public class Function : TestBase
{ {
[TestMethod] [TestMethod]
public void TestLoadI18N() public void LoadLanguage()
{ {
void TestLoad(string t) void TestLoad(string t)
{ {

98
UnitTest/ParameterTest.cs Normal file
View 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");
}
}
}

View File

@@ -10,10 +10,10 @@ using Netch.Utils;
namespace UnitTest namespace UnitTest
{ {
[TestClass] [TestClass]
public class TestParseShareLink : TestBase public class ParseShareLink : TestBase
{ {
[TestMethod] [TestMethod]
public void TestServerFromSSR() public void ParseSSR()
{ {
const string normalCase = const string normalCase =
"ssr://MTI3LjAuMC4xOjEyMzQ6YXV0aF9hZXMxMjhfbWQ1OmFlcy0xMjgtY2ZiOnRsczEuMl90aWNrZXRfYXV0aDpZV0ZoWW1KaS8_b2Jmc3BhcmFtPVluSmxZV3QzWVRFeExtMXZaUQ"; "ssr://MTI3LjAuMC4xOjEyMzQ6YXV0aF9hZXMxMjhfbWQ1OmFlcy0xMjgtY2ZiOnRsczEuMl90aWNrZXRfYXV0aDpZV0ZoWW1KaS8_b2Jmc3BhcmFtPVluSmxZV3QzWVRFeExtMXZaUQ";

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

View File

@@ -1,14 +1,6 @@
using Netch; namespace UnitTest
namespace UnitTest
{ {
public class TestBase public class TestBase
{ {
protected TestBase()
{
#if DEBUG
Global.Testing = true;
#endif
}
} }
} }

View File

@@ -6,6 +6,7 @@
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -23,8 +24,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.1" /> <PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
<PackageReference Include="System.Text.Json" Version="5.0.1" /> <PackageReference Include="System.Text.Json" Version="5.0.1" />
</ItemGroup> </ItemGroup>

2
modes

Submodule modes updated: 42375ef724...72ad96d018