diff --git a/InvisibleMan-XRay/Handlers/TemplateHandler.cs b/InvisibleMan-XRay/Handlers/TemplateHandler.cs index 29f8071..1e53225 100644 --- a/InvisibleMan-XRay/Handlers/TemplateHandler.cs +++ b/InvisibleMan-XRay/Handlers/TemplateHandler.cs @@ -22,6 +22,8 @@ void RegisterTemplates() { templates.Add("vmess", typeof(Vmess)); templates.Add("vless", typeof(Vless)); + templates.Add("trojan", typeof(Trojan)); + templates.Add("ss", typeof(Shadowsocks)); } } diff --git a/InvisibleMan-XRay/InvisibleMan-XRay.csproj b/InvisibleMan-XRay/InvisibleMan-XRay.csproj index 50d970e..7eeede0 100644 --- a/InvisibleMan-XRay/InvisibleMan-XRay.csproj +++ b/InvisibleMan-XRay/InvisibleMan-XRay.csproj @@ -9,8 +9,8 @@ InvisibleMan XRay InvisibleMan Copyright (C) 2023 Invisible Man - 0.1.0.0 - 0.1.0.0 + 0.3.0.0 + 0.3.0.0 enable 0108;8600;8601;8602;8603;8604;8618;8625;8762 true diff --git a/InvisibleMan-XRay/Models/Templates/Shadowsocks.cs b/InvisibleMan-XRay/Models/Templates/Shadowsocks.cs new file mode 100644 index 0000000..3611433 --- /dev/null +++ b/InvisibleMan-XRay/Models/Templates/Shadowsocks.cs @@ -0,0 +1,102 @@ +using System; +using System.Linq; +using System.Text; + +namespace InvisibleManXRay.Models.Templates +{ + using Values; + + public class Shadowsocks : Template + { + public class Data + { + public Uri uri; + public string security; + public string id; + public string remark; + + public Data(string url) + { + this.uri = new Uri(url); + this.remark = url.Split("#")[1]; + } + } + + private Data data; + private readonly string[] validSecurity = new[] { + "aes-256-gcm", + "aes-128-gcm", + "chacha20-poly1305", + "chacha20-ietf-poly1305", + "xchacha20-poly1305", + "xchacha20-ietf-poly1305", + "none", + "plain", + "2022-blake3-aes-128-gcm", + "2022-blake3-aes-256-gcm", + "2022-blake3-chacha20-poly1305" + }; + + public override Status FetchDataFromLink(string link) + { + try + { + data = new Data(link); + FetchRemark(); + MapDecodedLinkToData(decodedString: DecodeBase64Link()); + + return new Status(Code.SUCCESS, SubCode.SUCCESS, null); + } + catch(Exception) + { + return new Status( + code: Code.ERROR, + subCode: SubCode.INVALID_CONFIG, + content: Message.INVALID_CONFIG + ); + } + + string DecodeBase64Link() + { + string base64String = link.Split("@").FirstOrDefault().Replace("ss://", ""); + byte[] dataBytes = Convert.FromBase64String(base64String); + + return Encoding.UTF8.GetString(dataBytes); + } + + void MapDecodedLinkToData(string decodedString) + { + string[] stringArray = decodedString.Split(":"); + data.security = stringArray[0]; + data.id = stringArray[1]; + } + + void FetchRemark() + { + data.remark = Uri.UnescapeDataString(data.uri.ToString().Split("#")[1]); + } + } + + protected override Adapter Adapter => new Adapter() { + type = "shadowsocks", + remark = data.remark, + address = data.uri.IdnHost, + port = data.uri.Port, + id = data.id, + security = data.security + }; + + protected override V2Ray.Outbound.Settings OutboundSettings => new V2Ray.Outbound.Settings() { + servers = new V2Ray.Outbound.Settings.Server[] { + new V2Ray.Outbound.Settings.Server() { + address = Adapter.address, + port = Adapter.port, + password = Adapter.id, + ota = false, + level = 1, + method = validSecurity.Contains(Adapter.security) ? Adapter.security : "none" + } + } + }; + } +} \ No newline at end of file diff --git a/InvisibleMan-XRay/Models/Templates/Trojan.cs b/InvisibleMan-XRay/Models/Templates/Trojan.cs new file mode 100644 index 0000000..3dab92a --- /dev/null +++ b/InvisibleMan-XRay/Models/Templates/Trojan.cs @@ -0,0 +1,135 @@ +using System; +using System.Web; +using System.Collections.Specialized; + +namespace InvisibleManXRay.Models.Templates +{ + using Values; + + public class Trojan : Template + { + public class Data + { + public Uri uri; + public NameValueCollection query; + + public Data(string url) + { + this.uri = new Uri(url); + this.query = HttpUtility.ParseQueryString(uri.Query); + } + } + + private Data data; + + public override Status FetchDataFromLink(string link) + { + data = new Data(link); + + if (IsInvalidLink()) + return new Status( + code: Code.ERROR, + subCode: SubCode.INVALID_CONFIG, + content: Message.INVALID_CONFIG + ); + + return new Status(Code.SUCCESS, SubCode.SUCCESS, null); + + bool IsInvalidLink() => data.query.Count == 0; + } + + protected override Adapter Adapter + { + get + { + Adapter adapter = new Adapter() { + type = "trojan", + remark = data.uri.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + address = data.uri.IdnHost, + port = data.uri.Port, + id = data.uri.UserInfo, + security = data.query["encryption"] ?? "none", + streamNetwork = data.query["type"] ?? "tcp", + streamSecurity = data.query["security"] ?? "", + flow = data.query["flow"] ?? "", + sni = data.query["sni"] ?? "", + alpn = HttpUtility.UrlDecode(data.query["alpn"] ?? ""), + fingerprint = HttpUtility.UrlDecode(data.query["fp"] ?? "") + }; + + switch (adapter.streamNetwork) + { + case "tcp": + adapter.headerType = data.query["headerType"] ?? "none"; + adapter.requestHost = HttpUtility.UrlDecode(data.query["host"] ?? ""); + break; + case "kcp": + adapter.headerType = data.query["headerType"] ?? "none"; + adapter.path = HttpUtility.UrlDecode(data.query["seed"] ?? ""); + break; + case "ws": + adapter.requestHost = HttpUtility.UrlDecode(data.query["host"] ?? ""); + adapter.path = HttpUtility.UrlDecode(data.query["path"] ?? "/"); + break; + case "http": + case "h2": + adapter.streamNetwork = "h2"; + adapter.requestHost = HttpUtility.UrlDecode(data.query["host"] ?? ""); + adapter.path = HttpUtility.UrlDecode(data.query["path"] ?? "/"); + break; + case "quic": + adapter.headerType = data.query["headerType"] ?? "none"; + adapter.requestHost = data.query["quicSecurity"] ?? "none"; + adapter.path = HttpUtility.UrlDecode(data.query["key"] ?? ""); + break; + case "grpc": + adapter.path = HttpUtility.UrlDecode(data.query["serviceName"] ?? ""); + adapter.headerType = HttpUtility.UrlDecode(data.query["mode"] ?? "gun"); + break; + default: + break; + } + + return adapter; + } + } + + protected override V2Ray.Outbound.Settings OutboundSettings + { + get + { + if (Adapter.streamSecurity == Global.StreamSecurity.XTLS) + { + if (string.IsNullOrEmpty(Adapter.flow)) + Adapter.flow = "xtls-rprx-origin"; + else + Adapter.flow = Adapter.flow.Replace("splice", "direct"); + } + + return new V2Ray.Outbound.Settings() { + servers = new V2Ray.Outbound.Settings.Server[] { + new V2Ray.Outbound.Settings.Server() { + address = Adapter.address, + port = Adapter.port, + password = Adapter.id, + ota = false, + level = 1, + flow = SetServerFlow() + } + } + }; + + string SetServerFlow() + { + if (Adapter.streamSecurity != "xtls") + return string.Empty; + + if (string.IsNullOrEmpty(Adapter.flow)) + return "xtls-rprx-origin"; + + return Adapter.flow.Replace("splice", "direct"); + } + } + } + } +} \ No newline at end of file