diff --git a/references/Android Native Library/shadowsocks-android/arm64-v8a/libtun2socks.so b/references/Android Native Library/shadowsocks-android/arm64-v8a/libtun2socks.so
new file mode 100644
index 000000000..1977c1d1c
Binary files /dev/null and b/references/Android Native Library/shadowsocks-android/arm64-v8a/libtun2socks.so differ
diff --git a/references/Android Native Library/shadowsocks-android/armeabi-v7a/libtun2socks.so b/references/Android Native Library/shadowsocks-android/armeabi-v7a/libtun2socks.so
new file mode 100644
index 000000000..97b820f27
Binary files /dev/null and b/references/Android Native Library/shadowsocks-android/armeabi-v7a/libtun2socks.so differ
diff --git a/references/Android Native Library/shadowsocks-android/x86_64/libtun2socks.so b/references/Android Native Library/shadowsocks-android/x86_64/libtun2socks.so
new file mode 100644
index 000000000..16b3decd4
Binary files /dev/null and b/references/Android Native Library/shadowsocks-android/x86_64/libtun2socks.so differ
diff --git a/src/AboutAppInfoPopup.cs b/src/AboutAppInfoPopup.cs
index e74f8a5a3..57370ff27 100644
--- a/src/AboutAppInfoPopup.cs
+++ b/src/AboutAppInfoPopup.cs
@@ -197,9 +197,11 @@ namespace System.Application.UI
#if __ANDROID__
+#if V2RAY
b.Append("[v2ray.ver] ");
b.Append(Libv2ray.Libv2ray.CheckVersionX());
b.AppendLine();
+#endif
//if (_ThisAssembly.Debuggable)
//{
diff --git a/src/Common.CoreLib/Properties/ThisAssembly.DisplayTrademark.cs b/src/Common.CoreLib/Properties/ThisAssembly.DisplayTrademark.cs
new file mode 100644
index 000000000..23c8f7ba2
--- /dev/null
+++ b/src/Common.CoreLib/Properties/ThisAssembly.DisplayTrademark.cs
@@ -0,0 +1,7 @@
+namespace System.Properties
+{
+ partial class ThisAssembly
+ {
+ public static string DisplayTrademark => DesktopBridge.IsRunningAsUwp ? $"蒸汽++" : AssemblyTrademark;
+ }
+}
diff --git a/src/Common.CoreLib/Properties/ThisAssembly.cs b/src/Common.CoreLib/Properties/ThisAssembly.cs
index a76cf30cf..40555f1d0 100644
--- a/src/Common.CoreLib/Properties/ThisAssembly.cs
+++ b/src/Common.CoreLib/Properties/ThisAssembly.cs
@@ -15,7 +15,7 @@ namespace System.Properties
///
/// 提供程序集的文本说明
///
- public const string AssemblyDescription = "「Steam++」是一个包含多种Steam工具功能的工具箱。";
+ public const string AssemblyDescription = "「Steam++ 工具箱」是一个开源跨平台的多功能游戏工具箱。";
///
/// 定义程序集清单的版权自定义属性
diff --git a/src/ST.Client.Android.Shadowsocks/Jars/shadowsocks-release.aar b/src/ST.Client.Android.Shadowsocks/Jars/shadowsocks-release.aar
index ac5dc14da..b5f2f6044 100644
Binary files a/src/ST.Client.Android.Shadowsocks/Jars/shadowsocks-release.aar and b/src/ST.Client.Android.Shadowsocks/Jars/shadowsocks-release.aar differ
diff --git a/src/ST.Client.Android/ST.Client.Android.csproj b/src/ST.Client.Android/ST.Client.Android.csproj
index 9a67e6c67..7e06f9b97 100644
--- a/src/ST.Client.Android/ST.Client.Android.csproj
+++ b/src/ST.Client.Android/ST.Client.Android.csproj
@@ -4,7 +4,8 @@
MonoAndroid12.0
System.Application
Xamarin.Forms.Platform.Android.UnitTests
- __HAVE_ANDROIDX__;$(DefineConstants)
+ SHADOWSOCKS;__HAVE_ANDROIDX__;$(DefineConstants)
+
21.0
@@ -161,7 +162,8 @@
-->
-
+
+
diff --git a/src/ST.Client.Android/Services/Shadowsocks/VpnService.cs b/src/ST.Client.Android/Services/Shadowsocks/VpnService.cs
new file mode 100644
index 000000000..835958d95
--- /dev/null
+++ b/src/ST.Client.Android/Services/Shadowsocks/VpnService.cs
@@ -0,0 +1,129 @@
+#if SHADOWSOCKS
+using Android.App;
+using Android.Content;
+using Android.Runtime;
+using System.Application.Services.Implementation;
+using static System.Application.Services.Native.IServiceBase;
+using AndroidX.Core.App;
+using static System.Properties.ThisAssembly;
+using System.Collections.Generic;
+
+namespace System.Application.Services.Native
+{
+ partial class ProxyForegroundService
+ {
+ partial class VpnService : Shadowsocks.VpnService
+ {
+ bool isStart;
+ int notifyId;
+ NotificationCompat.Builder? builder;
+ NotificationManagerCompat? mNotificationManager;
+ NotificationManagerCompat NotificationManager
+ {
+ get
+ {
+ if (mNotificationManager == null)
+ {
+ mNotificationManager = NotificationManagerCompat.From(this);
+ }
+ return mNotificationManager;
+ }
+ }
+
+ public override void OnStart()
+ {
+ if (isStart) return;
+ isStart = true;
+
+ // 先显示通知 UI
+ IForegroundService f = this;
+ (builder, mNotificationManager, notifyId) = AndroidNotificationServiceImpl.Instance.StartForeground(this, f.NotificationType, f.NotificationText, f.NotificationEntranceAction);
+
+ // 启动本地代理服务
+ ProxyService.Current.ProxyStatus = true;
+
+ // 启动 VPN tun2socks
+ base.OnStart();
+ }
+
+ public override void OnStop()
+ {
+ if (!isStart) return;
+ isStart = false;
+
+ // 停止通知栏 UI
+ StopForeground(true);
+
+ // 停止 VPN tun2socks
+ base.OnStop();
+
+ // 停止本地代理服务
+ ProxyService.Current.ProxyStatus = false;
+ }
+
+ [return: GeneratedEnum]
+ public override StartCommandResult OnStartCommand(Intent? intent, [GeneratedEnum] StartCommandFlags flags, int startId)
+ {
+ if (intent != null)
+ {
+ var action = intent.Action;
+ switch (action)
+ {
+ case START:
+ OnStart();
+ return StartCommandResult.RedeliverIntent;
+ case STOP:
+ default:
+ OnStop();
+ StopSelf();
+ break;
+ }
+ }
+ return StartCommandResult.NotSticky;
+ }
+
+ public override string Session => AssemblyTrademark;
+
+ public override string SocksServerAddress
+ {
+ get
+ {
+ var s = ProxyService.Current;
+ return $"{s.ProxyIp}:{s.Socks5ProxyPortId}";
+ }
+ }
+
+ public override void OnConfigure(Builder builder)
+ {
+ builder.AddDnsServer(IDnsAnalysisService.PrimaryDNS_Ali);
+ builder.AddDisallowedApplication(PackageName!);
+ }
+
+#if DEBUG
+ public override void OnError(string msg, Java.Lang.Throwable tr)
+ {
+ base.OnError(msg, tr);
+ }
+
+ public override IList GetTun2socksArgs(int mtu, bool isSupportIpv6, string PRIVATE_VLAN4_ROUTER, string PRIVATE_VLAN6_ROUTER)
+ {
+ var args = base.GetTun2socksArgs(mtu, isSupportIpv6, PRIVATE_VLAN4_ROUTER, PRIVATE_VLAN6_ROUTER);
+ var value = string.Join(' ', args);
+ Log.Info(nameof(VpnService), $"Tun2socksArgs: {value}");
+ return args;
+ }
+
+ public override string SockPath
+ {
+ get
+ {
+ var value = base.SockPath;
+ Log.Info(nameof(VpnService), $"SockPath: {value}");
+ return value;
+ }
+ }
+#endif
+ }
+ }
+}
+#endif
diff --git a/src/ST.Client.Android/Services/V2Ray.Binding/V2RayVpnService.cs b/src/ST.Client.Android/Services/V2Ray.Binding/V2RayVpnService.cs
index d01cd9386..8eaf901ba 100644
--- a/src/ST.Client.Android/Services/V2Ray.Binding/V2RayVpnService.cs
+++ b/src/ST.Client.Android/Services/V2Ray.Binding/V2RayVpnService.cs
@@ -1,3 +1,4 @@
+#if V2RAY
using V2RayNG;
using Android.App;
using Android.Content;
@@ -132,3 +133,4 @@ namespace System.Application.Services.Native
}
}
}
+#endif
\ No newline at end of file
diff --git a/src/ST.Client.Android/UI/MainApplication.cs b/src/ST.Client.Android/UI/MainApplication.cs
index 9a6c67b96..ded743063 100644
--- a/src/ST.Client.Android/UI/MainApplication.cs
+++ b/src/ST.Client.Android/UI/MainApplication.cs
@@ -70,6 +70,9 @@ namespace System.Application.UI
public override void OnCreate()
{
base.OnCreate();
+#if SHADOWSOCKS
+ Shadowsocks.Core.Instance.Init(this);
+#endif
//#if DEBUG
// //SetupLeakCanary();
//#endif
diff --git a/src/ST.Client.Desktop.Avalonia.App.Bridge.Package/Package.appxmanifest b/src/ST.Client.Desktop.Avalonia.App.Bridge.Package/Package.appxmanifest
index 59f3fa0d9..c599bc154 100644
--- a/src/ST.Client.Desktop.Avalonia.App.Bridge.Package/Package.appxmanifest
+++ b/src/ST.Client.Desktop.Avalonia.App.Bridge.Package/Package.appxmanifest
@@ -13,7 +13,7 @@
Version="2.6.9.0" />
- Steam++ for Windows
+ 蒸汽++
长沙次元超越科技有限公司
Images\StoreLogo.png
@@ -32,8 +32,8 @@
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_SoftwarePage.axaml b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_SoftwarePage.axaml
index bfb828035..c17c8e768 100644
--- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_SoftwarePage.axaml
+++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_SoftwarePage.axaml
@@ -28,7 +28,7 @@
Height="78"/>
-
+
Debug
@@ -83,9 +83,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/ST.Client.Mobile.Droid.Design/shadowsocks/src/main/java/net/steampp/app/shadowsocks/bg/VpnService.kt b/src/ST.Client.Mobile.Droid.Design/shadowsocks/src/main/java/net/steampp/app/shadowsocks/bg/VpnService.kt
index 4e033f5a5..59612d9fd 100644
--- a/src/ST.Client.Mobile.Droid.Design/shadowsocks/src/main/java/net/steampp/app/shadowsocks/bg/VpnService.kt
+++ b/src/ST.Client.Mobile.Droid.Design/shadowsocks/src/main/java/net/steampp/app/shadowsocks/bg/VpnService.kt
@@ -56,6 +56,7 @@ abstract class VpnService : BaseVpnService() {
private var processes: GuardedProcessPool? = null
private var conn: ParcelFileDescriptor? = null
+ private var connectingJob: Job? = null
open fun getMtu(): Int {
return VPN_MTU
@@ -85,16 +86,68 @@ abstract class VpnService : BaseVpnService() {
abstract fun getSocksServerAddress(): String
- abstract fun getPortLocalDns(): String
+// abstract fun getPortLocalDns(): String
- open suspend fun onStart() {
- startProcesses()
+ open fun onError(msg: String, tr: Throwable) {
+ Log.e(TAG, msg, tr)
+ }
+
+ open fun onStart() {
+ connectingJob = GlobalScope.launch(Dispatchers.Main) {
+ try {
+ Executable.killAll()
+ processes = GuardedProcessPool {
+ onError("new GuardedProcessPool onFatal", it)
+ stopRunner()
+ }
+ startProcesses()
+ } catch (_: CancellationException) {
+ // if the job was cancelled, it is canceller's responsibility to call stopRunner
+ } catch (exc: Throwable) {
+ onError("startProcesses", exc)
+ stopRunner()
+ } finally {
+ connectingJob = null
+ }
+ }
}
private suspend fun startProcesses() {
sendFd(startVpn())
}
+ fun getTun2socksExecutablePath(): String {
+ return File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath
+ }
+
+ open fun getTun2socksLogLevel(): String {
+ return "warning"
+ }
+
+ open fun getTun2socksArgs(
+ mtu: Int,
+ isSupportIpv6: Boolean,
+ PRIVATE_VLAN4_ROUTER: String,
+ PRIVATE_VLAN6_ROUTER: String
+ ): ArrayList {
+ val cmd = arrayListOf(
+ getTun2socksExecutablePath(),
+ "--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
+ "--socks-server-addr", getSocksServerAddress(),
+ "--tunmtu", mtu.toString(),
+ "--sock-path", "sock_path",
+// "--dnsgw", "127.0.0.1:${getPortLocalDns()}",
+ "--loglevel", getTun2socksLogLevel()
+ )
+ if (isSupportIpv6) {
+ cmd += "--netif-ip6addr"
+ cmd += PRIVATE_VLAN6_ROUTER
+ }
+ cmd += "--enable-udprelay"
+
+ return cmd
+ }
+
private suspend fun startVpn(): FileDescriptor {
val mtu = getMtu();
val PRIVATE_VLAN4_CLIENT = getPrivateVLAN(KEY_PRIVATE_VLAN4_CLIENT)
@@ -105,7 +158,7 @@ abstract class VpnService : BaseVpnService() {
.setSession(getSession())
.setMtu(mtu)
.addAddress(PRIVATE_VLAN4_CLIENT, 30)
- .addDnsServer(PRIVATE_VLAN4_ROUTER)
+// .addDnsServer(PRIVATE_VLAN4_ROUTER)
val isSupportIpv6 = isSupportIpv6();
if (isSupportIpv6) builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
@@ -115,32 +168,13 @@ abstract class VpnService : BaseVpnService() {
val conn = builder.establish() ?: throw NullConnectionException()
this.conn = conn
- val cmd = arrayListOf(
- File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath,
- "--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
- "--socks-server-addr", getSocksServerAddress(),
- "--tunmtu", mtu.toString(),
- "--sock-path", "sock_path",
- "--dnsgw", "127.0.0.1:${getPortLocalDns()}",
- "--loglevel", "warning"
- )
- if (isSupportIpv6) {
- cmd += "--netif-ip6addr"
- cmd += PRIVATE_VLAN6_ROUTER
- }
- cmd += "--enable-udprelay"
-
- if (processes == null) {
- processes = GuardedProcessPool {
- Log.w(TAG, it)
- onStop()
- }
- }
+ val cmd = getTun2socksArgs(mtu, isSupportIpv6, PRIVATE_VLAN4_ROUTER, PRIVATE_VLAN6_ROUTER)
processes!!.start(cmd, onRestartCallback = {
try {
sendFd(conn.fileDescriptor)
} catch (e: ErrnoException) {
+ onError("processes.start catch", e)
stopRunner()
}
})
@@ -148,9 +182,13 @@ abstract class VpnService : BaseVpnService() {
return conn.fileDescriptor
}
+ open fun getSockPath(): String {
+ return File(Core.deviceStorage.noBackupFilesDir, "sock_path").absolutePath
+ }
+
private suspend fun sendFd(fd: FileDescriptor) {
var tries = 0
- val path = File(Core.deviceStorage.noBackupFilesDir, "sock_path").absolutePath
+ val path = getSockPath()
while (true) try {
delay(50L shl tries)
LocalSocket().use { localSocket ->
@@ -165,6 +203,7 @@ abstract class VpnService : BaseVpnService() {
}
return
} catch (e: IOException) {
+ onError("sendFd tries: $tries", e)
if (tries > 5) throw e
tries += 1
}
@@ -176,6 +215,7 @@ abstract class VpnService : BaseVpnService() {
private fun stopRunner() {
GlobalScope.launch(Dispatchers.Main.immediate) {
+ connectingJob?.cancelAndJoin() // ensure stop connecting first
// we use a coroutineScope here to allow clean-up in parallel
coroutineScope {
killProcesses(this)
diff --git a/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs b/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs
index 0a6621a67..655b2d8b7 100644
--- a/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/ST.Client/UI/ViewModels/MainWindowViewModel.cs
@@ -67,7 +67,7 @@ namespace System.Application.UI.ViewModels
if (IApplication.IsDesktopPlatform)
{
var adminTag = platformService.IsAdministrator ? (OperatingSystem2.IsWindows ? " (Administrator)" : " (Root)") : string.Empty;
- var title = $"{ThisAssembly.AssemblyTrademark} {RuntimeInformation.ProcessArchitecture.ToString().ToLower()} v{ThisAssembly.VersionDisplay} for {DeviceInfo2.OSName}{adminTag}";
+ var title = $"{ThisAssembly.DisplayTrademark} {RuntimeInformation.ProcessArchitecture.ToString().ToLower()} v{ThisAssembly.VersionDisplay} for {DeviceInfo2.OSName}{adminTag}";
#if DEBUG
title = $"[Debug] {title}";
#endif
diff --git a/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.cs b/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.cs
index e81cfc575..ded8a6957 100644
--- a/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.cs
+++ b/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.cs
@@ -317,5 +317,7 @@ namespace System.Application.UI.ViewModels
public static string MicrosoftStoreReviewLink => UrlConstants.MicrosoftStoreReviewLink;
#endregion
+
+ public string AppName => ThisAssembly.DisplayTrademark;
}
}
diff --git a/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.TrayIcon.cs b/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.TrayIcon.cs
index 01c841480..17000192f 100644
--- a/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.TrayIcon.cs
+++ b/src/ST.Client/UI/ViewModels/Windows/TaskBarWindowViewModel.TrayIcon.cs
@@ -5,7 +5,7 @@ namespace System.Application.UI.ViewModels
{
partial class TaskBarWindowViewModel
{
- public static string TitleString => ThisAssembly.AssemblyTrademark;
+ public static string TitleString => ThisAssembly.DisplayTrademark;
public const string CommandExit = "Exit";