This commit is contained in:
snltty
2025-04-29 16:22:40 +08:00
parent 16961395eb
commit dcef59708c
26 changed files with 273 additions and 289 deletions

View File

@@ -255,8 +255,8 @@ namespace linker.app
Modes = new string[] { "client" },
};
return new Dictionary<string, string> {
{"Client",Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(client)))},
{"Common", Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(common)))}
{"Client",client.ToJson()},
{"Common", common.ToJson()}
};
}
private void TuntapSetup()

View File

@@ -10,7 +10,8 @@ sidebar_position: 11
:::tip[2、Android]
没做保活没做Activity恢复请自行设置锁定和允许后台行为按住下拉慢一点拉满100%松开可以刷新整个页面
1. 没做保活没做Activity恢复请自行设置锁定和允许后台行为按住下拉慢一点拉满100%松开可以刷新整个页面
2. Android 使用VpnService服务创建网卡无法在创建网卡后动态添加路由在点对网时如果无法使用请手动重启一两次网卡
![Docusaurus Plushie](./img/app.jpg)

View File

@@ -1,14 +0,0 @@
---
sidebar_position: 0
---
# 2.0、重要说明
:::tip[说明]
1. linker.exe 一体包含`客户端``服务端(信标服务+中继服务)`,区别只在于 `configs/common.json` 里的 Modes 配置,是 client 还是 server
2. 服务端模式 监听端口 `1802(TCP+UDP)`
3. 客户端模式 监听端口 `1803(TCP)`websocket管理接口`1804(TCP)`管理UI端口
4. 服务端只要运行起来就可以了,没有任何管理,最多手动修改一下`configs/server.json`里的监听端口和密钥
:::

View File

@@ -0,0 +1,19 @@
---
sidebar_position: 0
---
# 2.0、重要说明(必看)
:::tip[公共配置]
1. linker.exe 一体包含`客户端``服务端(信标服务+中继服务)`,区别只在于 `configs/common.json` 里的 Modes 配置,是 client 还是 server
:::
:::tip[客户端]
1. 客户端模式 监听端口 `1803(TCP)`websocket管理接口`1804(TCP)`管理UI端口
2. 运行起来后可以使用`http://127.0.0.1:1804`打开管理页面如果是windows也可以在`linker.tray.win.exe`托盘程序里打开管理页面
:::
:::tip[服务端]
1. 服务端模式 监听端口 `1802(TCP+UDP)`
2. 服务端只要运行起来就可以了,没有任何管理,最多手动修改一下`configs/server.json`里的监听端口和密钥
:::

View File

@@ -1,8 +1,8 @@
---
sidebar_position: 9
sidebar_position: 10
---
# 2.9、信标密钥
# 2.10、信标密钥
:::tip[说明]

View File

@@ -54,64 +54,3 @@ systemctl restart linker
systemctl enable linker
```
:::
## 3、linux 手动 docker
:::tip[说明]
1. 服务端端口 `1802` TCP+UDP
2. 客户端端口 `1804``1803` TCP
3. 配置文件夹 `./configs`
4. 日志文件夹 `./logs`
5. 镜像清单 `snltty/linker-debian``snltty/linker-musl`
6. 特定平台 `snltty/linker-debian-{arch}``snltty/linker-musl-{arch}``{arch}`可以是`x64``arm64``arm`
7. `bridge`模式,需要映射一些端口比如`-p 18000-18010:18000-18010`,用于端口转发
8. `host`模式或者直接使用host`--network host`**建议使用host点对网网对网端口转发都方便**
#### 客户端-bridge
```
docker run -it -d --name linker \
-p 1804:1804/tcp -p 1803:1803/tcp \
-p 18000-18010:18000-18010/tcp \
-p 18000-18010:18000-18010/udp \
-v /usr/local/linker-docker/configs:/app/configs \
-v /usr/local/linker-docker/logs:/app/logs \
--device /dev/net/tun \
--restart=always \
--privileged=true \
snltty/linker-musl
```
#### 客户端-host
```
docker run -it -d --name linker \
-v /usr/local/linker-docker/configs:/app/configs \
-v /usr/local/linker-docker/logs:/app/logs \
--device /dev/net/tun \
--restart=always \
--privileged=true \
--network host \
snltty/linker-musl
```
#### 服务端-bridge
```
docker run -it -d --name linker \
-p 1802:1802/tcp -p 1802:1802/udp \
-v /usr/local/linker-docker/configs:/app/configs \
-v /usr/local/linker-docker/logs:/app/logs \
--restart=always \
--privileged=true \
snltty/linker-musl
```
#### 服务端-host
```
docker run -it -d --name linker \
-v /usr/local/linker-docker/configs:/app/configs \
-v /usr/local/linker-docker/logs:/app/logs \
--restart=always \
--privileged=true \
--network host \
snltty/linker-musl
```
:::

View File

@@ -0,0 +1,83 @@
---
sidebar_position: 3
---
# 2.3、安装方法docker
## 1、linux 一键安装
:::tip[说明]
下载安装脚本
```
curl -fsSL https://linker-doc.snltty.com/linker-install.sh -o linker-install.sh
chmod +x linker-install.sh
```
默认安装位置
```
./linker-install.sh
```
指定安装位置
```
./linker-install.sh /usr/local/bin
```
:::
## 2、linux 手动 docker
:::tip[说明]
1. 服务端端口 `1802` TCP+UDP
2. 客户端端口 `1804``1803` TCP
3. 配置文件夹 `./configs`
4. 日志文件夹 `./logs`
5. 镜像清单 `snltty/linker-debian``snltty/linker-musl`
6. 特定平台 `snltty/linker-debian-{arch}``snltty/linker-musl-{arch}``{arch}`可以是`x64``arm64``arm`
7. `bridge`模式,需要映射一些端口比如`-p 18000-18010:18000-18010`,用于端口转发
8. `host`模式或者直接使用host`--network host`**建议使用host点对网网对网端口转发都方便**
#### 客户端-bridge
```
docker run -it -d --name linker \
-p 1804:1804/tcp -p 1803:1803/tcp \
-p 18000-18010:18000-18010/tcp \
-p 18000-18010:18000-18010/udp \
-v /usr/local/linker-docker/configs:/app/configs \
-v /usr/local/linker-docker/logs:/app/logs \
--device /dev/net/tun \
--restart=always \
--privileged=true \
snltty/linker-musl
```
#### 客户端-host
```
docker run -it -d --name linker \
-v /usr/local/linker-docker/configs:/app/configs \
-v /usr/local/linker-docker/logs:/app/logs \
--device /dev/net/tun \
--restart=always \
--privileged=true \
--network host \
snltty/linker-musl
```
#### 服务端-bridge
```
docker run -it -d --name linker \
-p 1802:1802/tcp -p 1802:1802/udp \
-v /usr/local/linker-docker/configs:/app/configs \
-v /usr/local/linker-docker/logs:/app/logs \
--restart=always \
--privileged=true \
snltty/linker-musl
```
#### 服务端-host
```
docker run -it -d --name linker \
-v /usr/local/linker-docker/configs:/app/configs \
-v /usr/local/linker-docker/logs:/app/logs \
--restart=always \
--privileged=true \
--network host \
snltty/linker-musl
```
:::

View File

@@ -1,8 +1,8 @@
---
sidebar_position: 4
sidebar_position: 5
---
# 2.4、安装方法NAS
# 2.5、安装方法NAS
:::tip[各个nas都差不多主要看配置信息]

View File

@@ -1,8 +1,8 @@
---
sidebar_position: 5
sidebar_position: 6
---
# 2.5、初始化配置1公共
# 2.6、初始化配置1公共
## 1、公共配置文件客户端服务端都有的

View File

@@ -1,12 +1,12 @@
---
sidebar_position: 6
sidebar_position: 7
---
# 2.6、初始化配置2服务端
# 2.7、初始化配置2服务端
## 1、服务端手动修改配置文件
这是自建服务器需要配置的,没有服务器则跳过
这是自建服务器需要配置的,没有服务器则跳过**`运行起来就可以了没有配置只有一个server.json只需要把对应的密钥填到客户端即可`**
:::tip[服务端运行流程]
1. 运行程序在configs目录下会生成 common.json server.json

View File

@@ -1,8 +1,8 @@
---
sidebar_position: 7
sidebar_position: 8
---
# 2.7、初始化配置3客户端
# 2.8、初始化配置3客户端
## 1、客户端使用web初始化

View File

@@ -1,17 +0,0 @@
---
sidebar_position: 8
---
# 2.8、初始化配置4三方入参调用
## 1、使用参数初始化
:::tip[v1.7.0+]
1. 服务端,客户端,都可以使用这种方式覆盖配置
2. 如果你使用第三方程序启动linker可以传入参数进行初始化`client.json``server.json``action.json``common.json`
3. 像这样不填写的字段将以默认值生成将json转为base64
```
linker.exe --config-client base641 --config-server base642 --config-action base643 --config-common base644
```
:::

View File

@@ -0,0 +1,54 @@
---
sidebar_position: 9
---
# 2.9、初始化配置4三方入参调用
## 1、使用参数初始化
:::tip[v1.7.0+]
1. 如果你使用第三方程序启动linker可以传入参数进行初始化
2. 填写了哪些字段,哪些字段就会强制覆盖原有配置
3. 将json转为base64后传入 `linker.exe base64`
```
{
"Client":"{}", //对应client.json
"Server":"{}", //对应server.json
"Common":"{}", //对应common.json
"Action":"{}", //对应action.json
"Tuntap":"{
"IP":"10.18.18.2", 网卡IP
"PrefixLength":24, 网卡掩码
局域网IP列表
"Lans":[{
IP:"10.18.18.1", 局域网IP
PrefixLength:24, 掩码
Disabled:false, 禁用
MapIP:"10.18.18.1", //路由IP
MapPrefixLength:24, //路由掩码
}],
"Name":"linker", 网卡名
"Running":true, 是否运行true则启动后自动运行网卡
"Switch":2, 2显示延迟4使用高级功能8自动连接16禁用广播
32禁用nat64开启TCP包合并128调整网卡顺序
端口转发列表
"Forwards":[
{
ListenAddr:"0.0.0.0"
ListenPort:33890
ConnectAddr:"192.168.1.1"
ConnectPort:3389,
Remark:"33890 转发到 3389",
}
],
//本组网络配置
"Lease":{
IP : "10.18.18.0"
PrefixLength:24,
Name:"linker"
}
}"
}
```
:::

View File

@@ -123,8 +123,8 @@ namespace linker.messenger.store.file
var (client, clientObject, common, commonObject) = await GetConfig(configExportInfo).ConfigureAwait(false);
Dictionary<string, object> dic = new Dictionary<string, object>
{
{"Client",Convert.ToBase64String(Encoding.UTF8.GetBytes(clientObject.ToJson()))},
{"Common",Convert.ToBase64String(Encoding.UTF8.GetBytes(commonObject.ToJson()))},
{"Client",clientObject.ToJson()},
{"Common",commonObject.ToJson()},
};
return Convert.ToBase64String(Encoding.UTF8.GetBytes(dic.ToJson()));
@@ -145,8 +145,8 @@ namespace linker.messenger.store.file
var (client, clientObject, common, commonObject) = await GetConfig(configExportInfo).ConfigureAwait(false);
Dictionary<string, object> dic = new Dictionary<string, object>
{
{"Client",Convert.ToBase64String(Encoding.UTF8.GetBytes(clientObject.ToJson()))},
{"Common",Convert.ToBase64String(Encoding.UTF8.GetBytes(commonObject.ToJson()))},
{"Client",clientObject.ToJson()},
{"Common",commonObject.ToJson()},
};
string value = Convert.ToBase64String(Encoding.UTF8.GetBytes(dic.ToJson()));
return await exportResolver.Save(signInClientState.Connection.Address, value);

View File

@@ -109,10 +109,8 @@ namespace linker.messenger.store.file
continue;
}
string text = item.Value.PropertyMethod.Serialize(item.Value.Property.GetValue(Data));
if (dic != null && dic.TryGetValue(item.Value.Property.Name, out string base64))
if (dic != null && dic.TryGetValue(item.Value.Property.Name, out string text2))
{
string text2 = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
text = item.Value.PropertyMethod.Deserialize(text).ToJson();
text = MergeJson(text, text2);

View File

@@ -260,6 +260,7 @@ namespace linker.messenger.tuntap
}
return tuntapForwardTestWrapInfo;
}
}
public sealed class TuntabListInfo
{

View File

@@ -5,7 +5,6 @@ using linker.messenger.signin;
using linker.messenger.exroute;
using linker.tun;
using System.Net;
using linker.libs.extends;
namespace linker.messenger.tuntap
{

View File

@@ -84,6 +84,11 @@ namespace linker.messenger.updater
private readonly LastTicksManager lastTicksManager = new LastTicksManager();
/// <summary>
/// 订阅更新信息
/// </summary>
/// <param name="machineId"></param>
public void Subscribe(string machineId)
{
if (subscribes.TryGetValue(machineId, out LastTicksManager _lastTicksManager) == false)
@@ -146,7 +151,6 @@ namespace linker.messenger.updater
}, () => lastTicksManager.DiffLessEqual(5000) ? 3000 : 15000);
}
public void Check()
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<project ver="10" name="linker.route.win" libEmbed="true" icon="res\favicon.ico" ui="win" output="linker.route.win.exe" CompanyName="snltty" FileDescription="linker.route.win" LegalCopyright="Copyright (C) snltty 2024" ProductName="linker.route.win" InternalName="linker.route.win" FileVersion="0.0.0.20" ProductVersion="0.0.0.20" publishDir="/dist/" dstrip="false">
<project ver="10" name="linker.route.win" libEmbed="true" icon="res\favicon.ico" ui="win" output="linker.route.win.exe" CompanyName="snltty" FileDescription="linker.route.win" LegalCopyright="Copyright (C) snltty 2024" ProductName="linker.route.win" InternalName="linker.route.win" FileVersion="0.0.0.25" ProductVersion="0.0.0.25" publishDir="/dist/" dstrip="false">
<file name="main.aardio" path="main.aardio" comment="main.aardio"/>
<folder name="资源文件" path="res" embed="true" local="false" ignored="false">
<file name="Program.cs" path="res\Program.cs" comment="res\Program.cs"/>

Binary file not shown.

View File

@@ -36,7 +36,6 @@ mainForm.compiler = dotNet.createCompiler("C#");
mainForm.compiler.Source = string.load("/res/Program.cs");
mainForm.compiler.import("LinkerRoute");
mainForm.netObj = LinkerRoute.LinkerRoute();
mainForm.ipv4 = mainForm.netObj.GetIPV4("linker.snltty.com");
mainForm.serviceName = "linker.route.service";
mainForm.exeName = "linker.route.win.exe";
@@ -112,13 +111,21 @@ import win.reg;
mainForm.reg = win.reg("HKEY_CURRENT_USER\Software\linker.route.win")
mainForm.ws = web.socket.client();
mainForm.ws.protocol = mainForm.reg.queryValue("password") || mainForm.tbPassword.text;
mainForm.wsUrl = mainForm.reg.queryValue("url") || mainForm.tbUrl.text;
if(mainForm.reg.queryValue("password"))
{
mainForm.tbPassword.text = mainForm.reg.queryValue("password");
}
mainForm.ws.protocol = mainForm.tbPassword.text;
if(mainForm.reg.queryValue("url"))
{
mainForm.tbUrl.text = mainForm.reg.queryValue("url");
}
mainForm.wsUrl = mainForm.tbUrl.text;
mainForm.wsConnected = false;
mainForm.running = true;
mainForm.hashCode = 0;
mainForm.routeContent = {List={}};
mainForm.routeList = {};
mainForm.IP = mainForm.netObj.GetWsIP(mainForm.wsUrl);
mainForm.ws.onOpen = function(){
mainForm.wsConnected = true;
@@ -138,42 +145,29 @@ mainForm.ws.onError = function(err){
mainForm.textStatus.text = "未连接服务";
}
mainForm.alreadyExists = function(ip,prefixLength){
for(i=1;table.len(mainForm.ipv4);1){
if(mainForm.netObj.GetIsSameNetwork(mainForm.ipv4[i],ip,prefixLength)){
return true;
}
}
return false;
}
mainForm.addRoute = function(){
for(i=1;table.len(mainForm.routeContent.List) ;1){
var item = mainForm.routeContent.List[i];
if(mainForm.alreadyExists(item.IP,item.PrefixLength) == false){
process.popen.cmd("route add "+item.Network+" mask "+item.PrefixIP+" "+mainForm.routeContent.IP);
//console.log("route add "+item.Network+" mask "+item.PrefixIP+" "+mainForm.routeContent.IP);
}
for(i=1;table.len(mainForm.routeList) ;1){
item = mainForm.routeList[i];
process.popen.cmd("route add "+item.Network+" mask "+item.PrefixIP+" "+mainForm.IP);
}
}
mainForm.delRoute = function(){
for(i=1;table.len(mainForm.routeContent.List) ;1){
var item = mainForm.routeContent.List[i];
//console.log("route delete "+item.Network+" mask "+item.PrefixIP+" "+mainForm.routeContent.IP);
process.popen.cmd("route delete "+item.Network+" mask "+item.PrefixIP+" "+mainForm.routeContent.IP);
for(i=1;table.len(mainForm.routeList) ;1){
var item = mainForm.routeList[i];
process.popen.cmd("route delete "+item.Network+" mask "+item.PrefixIP+" "+mainForm.IP);
}
}
mainForm.sameRoute = function(content){
mainForm.sameRoute = function(list){
var same = true;
if(content.List && table.len(content.List) > 0)
if(list && table.len(list) > 0)
{
same = table.len(content.List) == table.len(mainForm.routeContent.List);
same = table.len(list) == table.len(mainForm.routeList);
if(same)
{
for(i=1;table.len(content.List) ;1)
for(i=1;table.len(list) ;1)
{
same = content.List[i].IP == mainForm.routeContent.List[i].IP
&& content.List[i].PrefixLength == mainForm.routeContent.List[i].PrefixLength;
same = list[i].IP == mainForm.routeList[i].IP
&& list[i].PrefixLength == mainForm.routeList[i].PrefixLength;
if(!same)
{
break;
@@ -185,12 +179,51 @@ mainForm.sameRoute = function(content){
}
mainForm.ws.onMessage = function(msg){
import console;
try{
var content = web.json.parse(msg.data).Content;
var same = mainForm.sameRoute(content);
content = web.json.parse(msg.data).Content;
result = {};
added = false;
for(k,v in content.List){
if(!added)
{
added = true;
table.push(result,{
IP:v.IP,
PrefixLength = v.PrefixLength,
Network:mainForm.netObj.ToNetwork(v.IP,v.PrefixLength),
PrefixIP:mainForm.netObj.ToPrefixIP(v.PrefixLength)
});
}
for(k1,v1 in v.Lans){
if(!v1.Disabled && !v1.Exists)
{
if(v1.MapIP && v1.MapIP != "0.0.0.0")
{
table.push(result,{
IP:v1.MapIP,
PrefixLength = v1.MapPrefixLength,
Network:mainForm.netObj.ToNetwork(v1.MapIP,v1.MapPrefixLength),
PrefixIP:mainForm.netObj.ToPrefixIP(v1.MapPrefixLength)
});
}else
{
table.push(result,{
IP:v1.IP,
PrefixLength = v1.PrefixLength,
Network:mainForm.netObj.ToNetwork(v1.IP,v1.PrefixLength),
PrefixIP:mainForm.netObj.ToPrefixIP(v1.PrefixLength)
});
}
}
}
}
var same = mainForm.sameRoute(result);
if(!same){
mainForm.delRoute();
mainForm.routeContent = content;
mainForm.routeList = result;
mainForm.IP = mainForm.netObj.GetWsIP(mainForm.wsUrl);
mainForm.addRoute();
}
}
@@ -198,7 +231,6 @@ mainForm.ws.onMessage = function(msg){
}
}
mainForm.failureService = function()
{
import process.popen;
@@ -277,7 +309,7 @@ mainForm.setInterval(
}else
{
mainForm.ws.send(web.json.stringify({
Path: "tuntapclient/routeitems",
Path: "tuntap/get",
RequestId: 0,
Content:mainForm.hashCode+""
}));

View File

@@ -8,102 +8,30 @@ namespace LinkerRoute
{
public class LinkerRoute
{
public string[] GetIPV4(string server)
public string GetWsIP(string wsServer)
{
List<string> result = new List<string>();
var ips1 = GetRouteLevel(server);
var ips2 = GetIPV4();
foreach (var ip in ips1)
{
if (result.Contains(ip) == false)
{
result.Add(ip);
}
}
foreach (var ip in ips2)
{
if (result.Contains(ip) == false)
{
result.Add(ip);
}
}
return result.ToArray();
return new Uri(wsServer).Host;
}
private string[] starts = new string[] { "10.", "100.", "192.168.", "172.", "127." };
private string[] GetRouteLevel(string server)
public string ToNetwork(string ip, byte prefixLength)
{
if (string.IsNullOrWhiteSpace(server) == false)
{
server = server.Split(':')[0];
}
var ips = GetRouteLevelWindows(server);
string[] result = new string[ips.Length];
for (int i = 0; i < ips.Length; i++)
{
result[i] = ips[i].ToString();
}
return result;
}
private IPAddress[] GetRouteLevelWindows(string server)
{
List<IPAddress> result = new List<IPAddress>();
try
{
IPAddress target = Dns.GetHostEntry(server).AddressList[0];
for (ushort i = 1; i <= 5; i++)
{
using (Ping pinger = new Ping())
{
PingReply reply = pinger.Send(target, 100, Encoding.ASCII.GetBytes("snltty"), new PingOptions { Ttl = i, DontFragment = true });
bool any = false;
string ip = reply.Address.ToString();
for (int k = 0; k < starts.Length; k++)
{
if (ip.IndexOf(starts[k]) == 0)
{
any = true;
break;
}
}
if (any)
{
result.Add(reply.Address);
}
else
{
return result.ToArray();
}
}
}
}
catch (Exception)
{
}
return result.ToArray();
}
public bool GetIsSameNetwork(string sourceIP, string distIP, byte prefixLength)
{
byte[] sourceBytes = IPAddress.Parse(sourceIP).GetAddressBytes();
byte[] sourceBytes = IPAddress.Parse(ip).GetAddressBytes();
Array.Reverse(sourceBytes);
byte[] distBytes = IPAddress.Parse(distIP).GetAddressBytes();
Array.Reverse(distBytes);
uint sourceIPInt = BitConverter.ToUInt32(sourceBytes, 0);
uint distIPInt = BitConverter.ToUInt32(distBytes, 0);
uint prefixIP = GetPrefixIP(prefixLength);
return (sourceIPInt & prefixIP) == (distIPInt & prefixIP);
sourceBytes = new IPAddress((sourceIPInt & prefixIP)).GetAddressBytes();
Array.Reverse(sourceBytes);
return string.Join(".",sourceBytes);
}
public string ToPrefixIP(byte prefixLength)
{
uint prefixIP = GetPrefixIP(prefixLength);
byte[] sourceBytes = new IPAddress(prefixIP).GetAddressBytes();
Array.Reverse(sourceBytes);
return string.Join(".",sourceBytes);
}
private uint GetPrefixIP(byte prefixLength)
{
@@ -111,28 +39,5 @@ namespace LinkerRoute
if (prefixLength < 1) return 0;
return 0xffffffff << (32 - prefixLength);
}
private string[] GetIPV4()
{
try
{
List<string> result = new List<string>();
foreach (var item in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
if (item.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && item.IsIPv4MappedToIPv6 == false && item.Equals(IPAddress.Any) == false)
{
if (result.Contains(item.ToString()) == false)
{
result.Add(item.ToString());
}
}
}
return result.ToArray();
}
catch (Exception)
{
}
return Array.Empty<string>();
}
}
}

View File

@@ -211,7 +211,7 @@ namespace linker.tun
{
return;
}
error = $"NetNat and ICS not supported,{error}";
error = $"NetNat not supported,{error}";
}
catch (Exception ex)
{

View File

@@ -2,6 +2,7 @@
using System.ServiceProcess;
using System.Diagnostics;
using linker.messenger.entry;
using linker.libs.extends;
namespace linker
{
@@ -66,33 +67,12 @@ namespace linker
private static Dictionary<string, string> ParseArgs(string[] args)
{
Dictionary<string, string> configDic = new Dictionary<string, string>();
for (int i = 0; i < args.Length; i++)
try
{
configDic = args[0].DeJson<Dictionary<string, string>>();
}
catch (Exception)
{
if (args[i] == "--config-client")
{
configDic.Add("Client", args[i + 1]);
i++;
}
else if (args[i] == "--config-server")
{
configDic.Add("Server", args[i + 1]);
i++;
}
else if (args[i] == "--config-action")
{
configDic.Add("Action", args[i + 1]);
i++;
}
else if (args[i] == "--config-common")
{
configDic.Add("Common", args[i + 1]);
i++;
}
else if (args[i] == "--config-tuntap")
{
configDic.Add("Tuntap", args[i + 1]);
i++;
}
}
return configDic;
}

View File

@@ -1,5 +1,5 @@
v1.7.6
2025-04-28 16:23:59
2025-04-29 16:22:40
1. 一些优化
2. 安卓APP勉强能用支持分身下拉刷新在线升级
5. 如果你设备很多,请尝试升级其中一个成功重启后再升级其它