Create glass launcher prototype

This commit is contained in:
nikit3s
2026-09-26 14:42:06 +03:00
commit 1975c1ceff
22 changed files with 5581 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Text;
using SbLauncher;
public static class ImportSmoke
{
static int failures;
static void Check(bool ok, string name)
{
Console.WriteLine((ok ? "ok " : "FAIL ") + name);
if (!ok) failures++;
}
[STAThread]
public static int Main()
{
string root = AppDomain.CurrentDomain.BaseDirectory;
Paths paths = new Paths(root);
Profiles profiles = Profiles.Load(paths.Profiles);
// Проверяем импорт на искусственных данных: тест не зависит от
// настоящего приватного ключа и сетевого адаптера пользователя.
byte[] privateBytes = new byte[32], publicBytes = new byte[32];
for (int n = 0; n < 32; n++) { privateBytes[n] = (byte)(n + 1); publicBytes[n] = (byte)(n + 33); }
LocalCfg local = new LocalCfg {
PrivateKey = Convert.ToBase64String(privateBytes),
LocalAddress = "10.10.0.2/24", Adapter = "Ethernet"
};
profiles.Wg.Server = "198.51.100.10";
profiles.Wg.PeerPublicKey = Convert.ToBase64String(publicBytes);
string id = "58863334-3b8a-4c81-9145-a18311621a7f";
string pbk = "Ot1KGILofMNTQa5Ewu5j3EoO1Ap0LEHOhYzzfANiB14";
string vmessJson = "{\"v\":\"2\",\"ps\":\"VMess WS\",\"add\":\"example.com\",\"port\":\"443\",\"id\":\"" + id + "\",\"aid\":\"0\",\"scy\":\"auto\",\"net\":\"ws\",\"host\":\"cdn.example.com\",\"path\":\"/ws\",\"tls\":\"tls\",\"sni\":\"example.com\"}";
string[] keys = {
"vless://" + id + "@example.com:443?security=reality&pbk=" + pbk + "&sid=ab&flow=xtls-rprx-vision#Reality",
"vless://" + id + "@example.com:443?security=tls&type=ws&host=cdn.example.com&path=%2Fws&alpn=h2%2Chttp%2F1.1#WebSocket",
"vless://" + id + "@example.com:443?security=tls&type=grpc&serviceName=service#GRPC",
"vless://" + id + "@example.com:443?security=tls&type=httpupgrade&host=cdn.example.com&path=%2Fup#Upgrade",
"vless://" + id + "@example.com:80?security=none#Plain",
"vmess://" + Convert.ToBase64String(Encoding.UTF8.GetBytes(vmessJson)),
"trojan://test-password@example.com:443?type=grpc&serviceName=tun&sni=example.com#Trojan",
"ss://" + Convert.ToBase64String(Encoding.UTF8.GetBytes("aes-256-gcm:password")) + "@example.com:8388#SIP002",
"ss://" + Convert.ToBase64String(Encoding.UTF8.GetBytes("aes-256-gcm:password@example.com:8388")) + "#LegacySS",
"hysteria2://password@example.com:443?sni=example.com&insecure=1&obfs=salamander&obfs-password=secret#Hy2",
"tuic://" + id + ":password@example.com:443?sni=example.com&alpn=h3#TUIC",
"anytls://password@example.com:443?sni=example.com#AnyTLS"
};
for (int i = 0; i < keys.Length; i++)
{
ServerCfg server;
string error;
bool parsed = KeyImporter.TryParseOne(keys[i], out server, out error);
Check(parsed, "parse " + i + (parsed ? " " + server.Protocol : " " + error));
if (!parsed) continue;
bool valid = KeyImporter.CheckWithSingBox(paths, server, out error);
Check(valid, "outbound " + i + (valid ? "" : " " + error));
if (!valid) continue;
foreach (string mode in new string[] { ConfigBuilder.ModeWg, ConfigBuilder.ModeDirect })
{
string config = Path.Combine(paths.RunDir, "import-smoke-" + i + "-" + mode + ".json");
try
{
BuildResult built = ConfigBuilder.Build(paths, profiles, local, server, mode, config);
if (!built.Ok) { Check(false, "build " + i + " " + mode + " " + built.Error); continue; }
string output;
bool checkedOk = SingBoxRunner.Check(paths.SbExe, config, paths.Root, out output);
Check(checkedOk, "config " + i + " " + mode + (checkedOk ? "" : " " + output));
}
finally { if (File.Exists(config)) File.Delete(config); }
}
}
ImportResult batch = KeyImporter.Parse(keys[0] + "\n" + keys[6]);
Check(batch.Servers.Count == 2 && batch.Errors.Count == 0, "multiline import");
string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(keys[0] + "\n" + keys[6]));
ImportResult subscription = KeyImporter.Parse(encoded);
Check(subscription.Servers.Count == 2 && subscription.Errors.Count == 0, "base64 subscription");
TcpListener listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
bool accepted = false;
Task served = Task.Run(delegate
{
using (TcpClient client = listener.AcceptTcpClient())
using (NetworkStream stream = client.GetStream())
{
accepted = true;
byte[] endOfHeaders = { 13, 10, 13, 10 };
int matched = 0;
while (matched < 4)
{
int b = stream.ReadByte();
if (b < 0) break;
matched = b == endOfHeaders[matched] ? matched + 1 : 0;
}
byte[] body = Encoding.UTF8.GetBytes(encoded);
byte[] head = Encoding.ASCII.GetBytes("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: " + body.Length + "\r\nConnection: close\r\n\r\n");
stream.Write(head, 0, head.Length);
stream.Write(body, 0, body.Length);
}
});
try
{
ImportResult remote = KeyImporter.Parse("http://127.0.0.1:" + port + "/subscription");
Check(remote.Servers.Count == 2 && remote.Errors.Count == 0, "HTTP subscription " + remote.Servers.Count + " accepted=" + accepted + " served=" + served.IsCompleted + " / " + string.Join("; ", remote.Errors.ToArray()));
served.Wait(3000);
}
finally { listener.Stop(); }
ImportResult json = KeyImporter.Parse("{\"outbounds\":[{\"type\":\"trojan\",\"tag\":\"from-json\",\"server\":\"example.com\",\"server_port\":443,\"password\":\"test\",\"tls\":{\"enabled\":true}}]}");
Check(json.Servers.Count == 1 && json.Servers[0].Protocol == "trojan", "sing-box JSON");
ImportResult sameJson = KeyImporter.Parse("{\"type\":\"trojan\",\"tls\":{\"enabled\":true},\"password\":\"test\",\"server_port\":443,\"server\":\"example.com\"}");
Check(sameJson.Servers.Count == 1 && KeyImporter.Same(json.Servers[0], sameJson.Servers[0]), "dedupe reordered JSON");
ServerCfg bad; string reason;
Check(!KeyImporter.TryParseOne("vless://" + id + "@example.com:443?security=tls&type=xhttp", out bad, out reason) && reason.Contains("транспорт"), "unsupported transport rejected");
Check(!KeyImporter.TryParseOne("vless://" + id + "@example.com:443?security=reality", out bad, out reason) && reason.Contains("public key"), "incomplete Reality rejected");
Console.WriteLine("failures: " + failures);
return failures == 0 ? 0 : 1;
}
}
+53
View File
@@ -0,0 +1,53 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using SbLauncher;
public static class ProfileSmoke
{
[STAThread]
public static int Main()
{
string root = Path.Combine(Path.GetTempPath(), "SbLauncherRev2-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(root);
try
{
var app = new Application();
app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
var vm = new MainVm();
vm.Load(new Paths(root));
string vless = "vless://58863334-3b8a-4c81-9145-a18311621a7f@localhost:443?security=reality&pbk=test&sni=example.com#Smoke";
string trojan = "trojan://password@example.com:443?sni=example.com#Second";
Task<bool> added = vm.AddFromLinkAsync(vless + "\n" + trojan + "\n" + vless);
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(20) };
var started = DateTime.UtcNow;
timer.Tick += delegate
{
if (added.IsCompleted || (DateTime.UtcNow - started).TotalSeconds > 5)
{
timer.Stop();
app.Shutdown();
}
};
timer.Start();
app.Run();
vm.DisposeTimers();
Profiles stored = Profiles.Load(Path.Combine(root, "profiles.json"));
bool ok = added.IsCompleted && added.Result && stored.Servers.Count == 2 &&
vm.Servers.Count == 3 && stored.Servers[0].Outbound != null &&
stored.Servers[1].Outbound != null &&
stored.Servers[0].Protocol == "vless" && stored.Servers[1].Protocol == "trojan" &&
stored.Servers[0].Code != stored.Servers[1].Code;
Console.WriteLine(ok ? "batch import, deduplication and roundtrip: ok" :
"batch import, deduplication and roundtrip: failed");
return ok ? 0 : 1;
}
finally
{
if (root.StartsWith(Path.GetTempPath(), StringComparison.OrdinalIgnoreCase))
Directory.Delete(root, true);
}
}
}
+54
View File
@@ -0,0 +1,54 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Threading;
using SbLauncher;
public static class UiStress
{
[STAThread]
public static int Main()
{
var app = new Application();
app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
var root = AppDomain.CurrentDomain.BaseDirectory;
var window = (Window)XamlReader.Parse(File.ReadAllText(Path.Combine(root, "Ui.xaml")));
var vm = new MainVm();
window.DataContext = vm;
vm.ThemeApplier = delegate(string theme) { Theme.Apply(window, theme); };
vm.Load(new Paths(root));
vm.SetTheme(Theme.Dark);
var stopwatch = Stopwatch.StartNew();
long previous = 0, longest = 0;
int ticks = 0;
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) };
timer.Tick += delegate
{
long now = stopwatch.ElapsedMilliseconds;
if (previous != 0) longest = Math.Max(longest, now - previous);
previous = now;
ticks++;
vm.ShowSettings = !vm.ShowSettings;
vm.SetTheme(ticks % 2 == 0 ? Theme.Dark : Theme.Light);
// Оставляем каждую фазу на несколько кадров, чтобы реально
// проверить запуск и остановку Storyboard при нагрузке.
if (ticks % 10 == 0)
vm.ConnectionPhase = new[] { "connecting", "connected", "disconnecting", "idle" }[(ticks / 10) % 4];
if (now >= 4000) { timer.Stop(); app.Shutdown(); }
};
window.Show();
timer.Start();
Task.Run(delegate
{
for (int i = 0; i < 20000; i++) vm.AppendLog("stress line " + i);
});
app.Run();
vm.DisposeTimers();
Console.WriteLine("UI ticks: " + ticks + ", max interval: " + longest + " ms, log length: " + vm.LogText.Length);
return ticks >= 40 && longest < 500 && vm.LogText.Length <= 50000 ? 0 : 1;
}
}