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";