1
0
bdbai 4 жил өмнө
parent
commit
3a97fe98d0
100 өөрчлөгдсөн 2762 нэмэгдсэн , 0 устгасан
  1. 4 0
      .gitmodules
  2. 134 0
      Maple.App/App.cpp
  3. 14 0
      Maple.App/App.h
  4. 3 0
      Maple.App/App.idl
  5. 12 0
      Maple.App/App.xaml
  6. BIN
      Maple.App/Assets/LargeTile.scale-100.png
  7. BIN
      Maple.App/Assets/LargeTile.scale-125.png
  8. BIN
      Maple.App/Assets/LargeTile.scale-150.png
  9. BIN
      Maple.App/Assets/LargeTile.scale-200.png
  10. BIN
      Maple.App/Assets/LargeTile.scale-400.png
  11. BIN
      Maple.App/Assets/LockScreenLogo.scale-200.png
  12. BIN
      Maple.App/Assets/SmallTile.scale-100.png
  13. BIN
      Maple.App/Assets/SmallTile.scale-125.png
  14. BIN
      Maple.App/Assets/SmallTile.scale-150.png
  15. BIN
      Maple.App/Assets/SmallTile.scale-200.png
  16. BIN
      Maple.App/Assets/SmallTile.scale-400.png
  17. BIN
      Maple.App/Assets/SplashScreen.scale-100.png
  18. BIN
      Maple.App/Assets/SplashScreen.scale-125.png
  19. BIN
      Maple.App/Assets/SplashScreen.scale-150.png
  20. BIN
      Maple.App/Assets/SplashScreen.scale-200.png
  21. BIN
      Maple.App/Assets/SplashScreen.scale-400.png
  22. BIN
      Maple.App/Assets/Square150x150Logo.scale-100.png
  23. BIN
      Maple.App/Assets/Square150x150Logo.scale-125.png
  24. BIN
      Maple.App/Assets/Square150x150Logo.scale-150.png
  25. BIN
      Maple.App/Assets/Square150x150Logo.scale-200.png
  26. BIN
      Maple.App/Assets/Square150x150Logo.scale-400.png
  27. BIN
      Maple.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png
  28. BIN
      Maple.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png
  29. BIN
      Maple.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png
  30. BIN
      Maple.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png
  31. BIN
      Maple.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png
  32. BIN
      Maple.App/Assets/Square44x44Logo.altform-unplated_targetsize-16.png
  33. BIN
      Maple.App/Assets/Square44x44Logo.altform-unplated_targetsize-256.png
  34. BIN
      Maple.App/Assets/Square44x44Logo.altform-unplated_targetsize-32.png
  35. BIN
      Maple.App/Assets/Square44x44Logo.altform-unplated_targetsize-48.png
  36. BIN
      Maple.App/Assets/Square44x44Logo.scale-100.png
  37. BIN
      Maple.App/Assets/Square44x44Logo.scale-125.png
  38. BIN
      Maple.App/Assets/Square44x44Logo.scale-150.png
  39. BIN
      Maple.App/Assets/Square44x44Logo.scale-200.png
  40. BIN
      Maple.App/Assets/Square44x44Logo.scale-400.png
  41. BIN
      Maple.App/Assets/Square44x44Logo.targetsize-16.png
  42. BIN
      Maple.App/Assets/Square44x44Logo.targetsize-24.png
  43. BIN
      Maple.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
  44. BIN
      Maple.App/Assets/Square44x44Logo.targetsize-256.png
  45. BIN
      Maple.App/Assets/Square44x44Logo.targetsize-32.png
  46. BIN
      Maple.App/Assets/Square44x44Logo.targetsize-48.png
  47. BIN
      Maple.App/Assets/StoreLogo.backup.png
  48. BIN
      Maple.App/Assets/StoreLogo.scale-100.png
  49. BIN
      Maple.App/Assets/StoreLogo.scale-125.png
  50. BIN
      Maple.App/Assets/StoreLogo.scale-150.png
  51. BIN
      Maple.App/Assets/StoreLogo.scale-200.png
  52. BIN
      Maple.App/Assets/StoreLogo.scale-400.png
  53. BIN
      Maple.App/Assets/Wide310x150Logo.scale-100.png
  54. BIN
      Maple.App/Assets/Wide310x150Logo.scale-125.png
  55. BIN
      Maple.App/Assets/Wide310x150Logo.scale-150.png
  56. BIN
      Maple.App/Assets/Wide310x150Logo.scale-200.png
  57. BIN
      Maple.App/Assets/Wide310x150Logo.scale-400.png
  58. BIN
      Maple.App/Assets/logo.psd
  59. 71 0
      Maple.App/Config/default.conf
  60. 165 0
      Maple.App/Config/default.json
  61. 20 0
      Maple.App/Converter/DateTimeConverter.cpp
  62. 28 0
      Maple.App/Converter/DateTimeConverter.h
  63. 9 0
      Maple.App/Converter/DateTimeConverter.idl
  64. 73 0
      Maple.App/EditPage.cpp
  65. 44 0
      Maple.App/EditPage.h
  66. 8 0
      Maple.App/EditPage.idl
  67. 39 0
      Maple.App/EditPage.xaml
  68. 317 0
      Maple.App/MainPage.cpp
  69. 79 0
      Maple.App/MainPage.h
  70. 15 0
      Maple.App/MainPage.idl
  71. 195 0
      Maple.App/MainPage.xaml
  72. 324 0
      Maple.App/Maple.App.vcxproj
  73. 181 0
      Maple.App/Maple.App.vcxproj.filters
  74. 52 0
      Maple.App/Model/ConfigViewModel.cpp
  75. 33 0
      Maple.App/Model/ConfigViewModel.h
  76. 15 0
      Maple.App/Model/ConfigViewModel.idl
  77. 164 0
      Maple.App/Model/Netif.cpp
  78. 29 0
      Maple.App/Model/Netif.h
  79. 11 0
      Maple.App/Model/Netif.idl
  80. 52 0
      Maple.App/Package.appxmanifest
  81. 16 0
      Maple.App/PropertySheet.props
  82. 4 0
      Maple.App/packages.config
  83. 1 0
      Maple.App/pch.cpp
  84. 22 0
      Maple.App/pch.h
  85. 46 0
      Maple.Task/CustomBuffer.h
  86. 198 0
      Maple.Task/Maple.Task.vcxproj
  87. 3 0
      Maple.Task/Maple_Task.def
  88. 16 0
      Maple.Task/PropertySheet.props
  89. 161 0
      Maple.Task/VpnPlugin.cpp
  90. 35 0
      Maple.Task/VpnPlugin.h
  91. 7 0
      Maple.Task/VpnPlugin.idl
  92. 12 0
      Maple.Task/VpnTask.cpp
  93. 18 0
      Maple.Task/VpnTask.h
  94. 8 0
      Maple.Task/VpnTask.idl
  95. 14 0
      Maple.Task/leaf.h
  96. 4 0
      Maple.Task/packages.config
  97. 1 0
      Maple.Task/pch.cpp
  98. 4 0
      Maple.Task/pch.h
  99. 69 0
      Maple.sln
  100. 32 0
      README.md

+ 4 - 0
.gitmodules

@@ -0,0 +1,4 @@
+[submodule "leaf"]
+	path = leaf
+	url = https://github.com/YtFlow/leaf.git
+	branch = feat/uwp

+ 134 - 0
Maple.App/App.cpp

@@ -0,0 +1,134 @@
+#include "pch.h"
+#include <winsock2.h>
+
+#include "App.h"
+#include "MainPage.h"
+#include "EditPage.h"
+
+using namespace winrt;
+using namespace Windows::ApplicationModel;
+using namespace Windows::ApplicationModel::Activation;
+using namespace Windows::Foundation;
+using namespace Windows::UI::Xaml;
+using namespace Windows::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Navigation;
+using namespace Maple_App;
+using namespace Maple_App::implementation;
+
+/// <summary>
+/// Initializes the singleton application object.  This is the first line of authored code
+/// executed, and as such is the logical equivalent of main() or WinMain().
+/// </summary>
+App::App()
+{
+    WSADATA wsaData;
+
+    if (FAILED(WSAStartup(0x202, &wsaData))) {
+        // ???
+    }
+    InitializeComponent();
+    Suspending({ this, &App::OnSuspending });
+
+#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
+    UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e)
+        {
+            if (IsDebuggerPresent())
+            {
+                auto errorMessage = e.Message();
+                __debugbreak();
+            }
+        });
+#endif
+}
+
+/// <summary>
+/// Invoked when the application is launched normally by the end user.  Other entry points
+/// will be used such as when the application is launched to open a specific file.
+/// </summary>
+/// <param name="e">Details about the launch request and process.</param>
+void App::OnLaunched(LaunchActivatedEventArgs const& e)
+{
+    Frame rootFrame{ nullptr };
+    auto content = Window::Current().Content();
+    if (content)
+    {
+        rootFrame = content.try_as<Frame>();
+    }
+
+    // Do not repeat app initialization when the Window already has content,
+    // just ensure that the window is active
+    if (rootFrame == nullptr)
+    {
+        // Create a Frame to act as the navigation context and associate it with
+        // a SuspensionManager key
+        rootFrame = Frame();
+
+        rootFrame.NavigationFailed({ this, &App::OnNavigationFailed });
+
+        if (e.PreviousExecutionState() == ApplicationExecutionState::Terminated)
+        {
+            // Restore the saved session state only when appropriate, scheduling the
+            // final launch steps after the restore is complete
+        }
+
+        if (e.PrelaunchActivated() == false)
+        {
+            if (rootFrame.Content() == nullptr)
+            {
+                // When the navigation stack isn't restored navigate to the first page,
+                // configuring the new page by passing required information as a navigation
+                // parameter
+                rootFrame.Navigate(xaml_typename<Maple_App::MainPage>(), box_value(e.Arguments()));
+            }
+            // Place the frame in the current Window
+            Window::Current().Content(rootFrame);
+            // Ensure the current window is active
+            Window::Current().Activate();
+        }
+    }
+    else
+    {
+        if (e.PrelaunchActivated() == false)
+        {
+            if (rootFrame.Content() == nullptr)
+            {
+                // When the navigation stack isn't restored navigate to the first page,
+                // configuring the new page by passing required information as a navigation
+                // parameter
+                rootFrame.Navigate(xaml_typename<Maple_App::MainPage>(), box_value(e.Arguments()));
+            }
+            // Ensure the current window is active
+            Window::Current().Activate();
+        }
+    }
+}
+
+/// <summary>
+/// Invoked when application execution is being suspended.  Application state is saved
+/// without knowing whether the application will be terminated or resumed with the contents
+/// of memory still intact.
+/// </summary>
+/// <param name="sender">The source of the suspend request.</param>
+/// <param name="e">Details about the suspend request.</param>
+fire_and_forget App::OnSuspending([[maybe_unused]] IInspectable const& sender, [[maybe_unused]] SuspendingEventArgs const& e)
+{
+    // Save application state and stop any background activity
+    const auto& saveModifiedContent = EditPage::SaveModifiedContent;
+    if (saveModifiedContent == nullptr) {
+        co_return;
+    }
+
+    const auto& def = e.SuspendingOperation().GetDeferral();
+    co_await saveModifiedContent();
+    def.Complete();
+}
+
+/// <summary>
+/// Invoked when Navigation to a certain page fails
+/// </summary>
+/// <param name="sender">The Frame which failed navigation</param>
+/// <param name="e">Details about the navigation failure</param>
+void App::OnNavigationFailed(IInspectable const&, NavigationFailedEventArgs const& e)
+{
+    throw hresult_error(E_FAIL, hstring(L"Failed to load Page ") + e.SourcePageType().Name);
+}

+ 14 - 0
Maple.App/App.h

@@ -0,0 +1,14 @@
+#pragma once
+#include "App.xaml.g.h"
+
+namespace winrt::Maple_App::implementation
+{
+    struct App : AppT<App>
+    {
+        App();
+
+        void OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs const&);
+        fire_and_forget OnSuspending(IInspectable const&, Windows::ApplicationModel::SuspendingEventArgs const&);
+        void OnNavigationFailed(IInspectable const&, Windows::UI::Xaml::Navigation::NavigationFailedEventArgs const&);
+    };
+}

+ 3 - 0
Maple.App/App.idl

@@ -0,0 +1,3 @@
+namespace Maple_App
+{
+}

+ 12 - 0
Maple.App/App.xaml

@@ -0,0 +1,12 @@
+<Application
+    x:Class="Maple_App.App"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:Maple_App"
+    RequestedTheme="Light">
+    <Application.Resources>
+        <ResourceDictionary>
+            <Color x:Key="SystemAccentColor">#2ebe5a</Color>
+        </ResourceDictionary>
+    </Application.Resources>
+</Application>

BIN
Maple.App/Assets/LargeTile.scale-100.png


BIN
Maple.App/Assets/LargeTile.scale-125.png


BIN
Maple.App/Assets/LargeTile.scale-150.png


BIN
Maple.App/Assets/LargeTile.scale-200.png


BIN
Maple.App/Assets/LargeTile.scale-400.png


BIN
Maple.App/Assets/LockScreenLogo.scale-200.png


BIN
Maple.App/Assets/SmallTile.scale-100.png


BIN
Maple.App/Assets/SmallTile.scale-125.png


BIN
Maple.App/Assets/SmallTile.scale-150.png


BIN
Maple.App/Assets/SmallTile.scale-200.png


BIN
Maple.App/Assets/SmallTile.scale-400.png


BIN
Maple.App/Assets/SplashScreen.scale-100.png


BIN
Maple.App/Assets/SplashScreen.scale-125.png


BIN
Maple.App/Assets/SplashScreen.scale-150.png


BIN
Maple.App/Assets/SplashScreen.scale-200.png


BIN
Maple.App/Assets/SplashScreen.scale-400.png


BIN
Maple.App/Assets/Square150x150Logo.scale-100.png


BIN
Maple.App/Assets/Square150x150Logo.scale-125.png


BIN
Maple.App/Assets/Square150x150Logo.scale-150.png


BIN
Maple.App/Assets/Square150x150Logo.scale-200.png


BIN
Maple.App/Assets/Square150x150Logo.scale-400.png


BIN
Maple.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png


BIN
Maple.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png


BIN
Maple.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png


BIN
Maple.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png


BIN
Maple.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png


BIN
Maple.App/Assets/Square44x44Logo.altform-unplated_targetsize-16.png


BIN
Maple.App/Assets/Square44x44Logo.altform-unplated_targetsize-256.png


BIN
Maple.App/Assets/Square44x44Logo.altform-unplated_targetsize-32.png


BIN
Maple.App/Assets/Square44x44Logo.altform-unplated_targetsize-48.png


BIN
Maple.App/Assets/Square44x44Logo.scale-100.png


BIN
Maple.App/Assets/Square44x44Logo.scale-125.png


BIN
Maple.App/Assets/Square44x44Logo.scale-150.png


BIN
Maple.App/Assets/Square44x44Logo.scale-200.png


BIN
Maple.App/Assets/Square44x44Logo.scale-400.png


BIN
Maple.App/Assets/Square44x44Logo.targetsize-16.png


BIN
Maple.App/Assets/Square44x44Logo.targetsize-24.png


BIN
Maple.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png


BIN
Maple.App/Assets/Square44x44Logo.targetsize-256.png


BIN
Maple.App/Assets/Square44x44Logo.targetsize-32.png


BIN
Maple.App/Assets/Square44x44Logo.targetsize-48.png


BIN
Maple.App/Assets/StoreLogo.backup.png


BIN
Maple.App/Assets/StoreLogo.scale-100.png


BIN
Maple.App/Assets/StoreLogo.scale-125.png


BIN
Maple.App/Assets/StoreLogo.scale-150.png


BIN
Maple.App/Assets/StoreLogo.scale-200.png


BIN
Maple.App/Assets/StoreLogo.scale-400.png


BIN
Maple.App/Assets/Wide310x150Logo.scale-100.png


BIN
Maple.App/Assets/Wide310x150Logo.scale-125.png


BIN
Maple.App/Assets/Wide310x150Logo.scale-150.png


BIN
Maple.App/Assets/Wide310x150Logo.scale-200.png


BIN
Maple.App/Assets/Wide310x150Logo.scale-400.png


BIN
Maple.App/Assets/logo.psd


+ 71 - 0
Maple.App/Config/default.conf

@@ -0,0 +1,71 @@
+[General]
+# Log are for debug only
+loglevel = error
+# Do not remove tun-fd option
+tun-fd = 233
+dns-server = 223.5.5.5, 114.114.114.114
+
+[Proxy]
+Direct = direct
+Reject = reject
+
+# Shadowsocks
+SS = ss, 1.2.3.4, 8485, encrypt-method=chacha20-ietf-poly1305, password=123456
+
+# VMess
+VMess = vmess, my.domain.com, 8001, username=0eb5486e-e1b5-49c5-aa75-d15e54dfac9d
+
+# VMess over WebSocket over TLS (TLS + WebSocket + VMess)
+VMessWSS = vmess, my.domain.com, 443, username=0eb5486e-e1b5-49c5-aa75-d15e54dfac9d, ws=true, tls=true, ws-path=/v2
+
+# Trojan (with TLS)
+Trojan = trojan, 4.3.2.1, 443, password=123456, sni=www.domain.com
+
+# Trojan over WebSocket over TLS (TLS + WebSocket + Trojan)
+TrojanWS = trojan, 4.3.2.1, 443, password=123456, sni=www.domain.com, ws=true, ws-path=/abc
+
+# Trojan over amux streams which use WebSocket over TLS as the underlying connection (TLS + WebSocket + amux + Trojan)
+tls-ws-amux-trojan = trojan, www.domain.com, 443, password=112358, tls=true, ws=true, ws-path=/amux, amux=true
+tls-ws-amux-trojan2 = trojan, 1.0.0.1, 443, password=123456, sni=www.domain.com, ws=true, ws-path=/amux, ws-host=www.domain.com, amux=true, amux-max=16, amux-con=1
+
+[Proxy Group]
+# fallback 等效于 failover
+Fallback = fallback, Trojan, VMessWSS, SS, interval=600, timeout=5
+
+# url-test 等效于 failover=false 的 failover
+UrlTest = url-test, Trojan, VMessWSS, SS, interval=600, timeout=5
+
+Failover = failover, Trojan, VMessWSS, SS, health-check=true, check-interval=600, fail-timeout=5, failover=true
+Tryall = tryall, Trojan, VMessWSS, delay-base=0
+Random = random, Trojan, VMessWSS
+
+[Rule]
+# Not supported yet
+# 执行文件目录当中必需有 `site.dat` 文件
+# EXTERNAL, site:category-ads-all, Reject
+
+# Not supported yet
+# 也可以指定 `dat` 文件所在绝对路径,不支持相对路径
+# EXTERNAL, site:/tmp/geosite.dat:category-ads-all, Reject
+
+IP-CIDR, 8.8.8.8/32, Fallback
+DOMAIN, www.google.com, Fallback
+DOMAIN-SUFFIX, google.com, Fallback
+DOMAIN-KEYWORD, google, Fallback
+
+# Not supported yet
+# 等效于 EXTERNAL, mmdb:us, Fallback
+# GEOIP, us, Fallback
+
+# Not supported yet
+# EXTERNAL, site:geolocation-!cn, Fallback
+
+# Not supported yet
+# 执行文件目录当中必需有 `geo.mmdb` 文件
+# EXTERNAL, mmdb:us, Fallback
+
+FINAL, Direct
+
+[Host]
+# 对指定域名返回一个或多个静态 IP
+example.com = 192.168.0.1, 192.168.0.2

+ 165 - 0
Maple.App/Config/default.json

@@ -0,0 +1,165 @@
+{
+  "log": {
+    "level": "error"
+  },
+  "dns": {
+    "servers": [
+      "1.1.1.1",
+      "8.8.8.8"
+    ],
+    "hosts": {
+      "example.com": [
+        "192.168.0.1",
+        "192.168.0.2"
+      ],
+      "server.com": [
+        "192.168.0.3"
+      ]
+    }
+  },
+  "inbounds": [
+    {
+      "address": "127.0.0.1",
+      "port": 1087,
+      "protocol": "http"
+    },
+    {
+      "address": "127.0.0.1",
+      "port": 1086,
+      "protocol": "socks"
+    },
+    {
+      "protocol": "tun",
+      "settings": {
+        "name": "utun8",
+        "address": "10.10.0.2",
+        "netmask": "255.255.255.0",
+        "gateway": "10.10.0.1",
+        "mtu": 1500,
+        "fakeDnsInclude": [
+          "google"
+        ]
+      },
+      "tag": "tun_in"
+    }
+  ],
+  "outbounds": [
+    {
+      "protocol": "failover",
+      "settings": {
+        "actors": [
+          "vmess_out",
+          "trojan_out"
+        ]
+      },
+      "tag": "failover_out"
+    },
+    {
+      "protocol": "chain",
+      "settings": {
+        "actors": [
+          "vmess_tls",
+          "vmess_ws",
+          "vmess"
+        ]
+      },
+      "tag": "vmess_out"
+    },
+    {
+      "protocol": "tls",
+      "tag": "vmess_tls"
+    },
+    {
+      "protocol": "ws",
+      "settings": {
+        "path": "/v2"
+      },
+      "tag": "vmess_ws"
+    },
+    {
+      "protocol": "vmess",
+      "settings": {
+        "address": "server.com",
+        "port": 443,
+        "uuid": "89ee4e17-aaad-49f6-91c4-6ea5990206bd"
+      },
+      "tag": "vmess"
+    },
+    {
+      "protocol": "chain",
+      "settings": {
+        "actors": [
+          "trojan_tls",
+          "trojan"
+        ]
+      },
+      "tag": "trojan_out"
+    },
+    {
+      "protocol": "tls",
+      "tag": "trojan_tls"
+    },
+    {
+      "protocol": "trojan",
+      "settings": {
+        "address": "server.com",
+        "password": "112358",
+        "port": 443
+      },
+      "tag": "trojan"
+    },
+    {
+      "protocol": "shadowsocks",
+      "settings": {
+        "address": "x.x.x.x",
+        "method": "chacha20-ietf-poly1305",
+        "password": "123456",
+        "port": 8389
+      },
+      "tag": "shadowsocks_out"
+    },
+    {
+      "protocol": "socks",
+      "settings": {
+        "address": "x.x.x.x",
+        "port": 1080
+      },
+      "tag": "socks_out"
+    },
+    {
+      "protocol": "direct",
+      "tag": "direct_out"
+    },
+    {
+      "protocol": "drop",
+      "tag": "drop_out"
+    }
+  ],
+  "rules": [
+    {
+      "ip": [
+        "8.8.8.8",
+        "8.8.4.4"
+      ],
+      "target": "failover_out"
+    },
+    {
+      "domain": [
+        "www.google.com"
+      ],
+      "target": "failover_out"
+    },
+    {
+      "domainSuffix": [
+        "google.com"
+      ],
+      "target": "failover_out"
+    },
+    {
+      "domainKeyword": [
+        "google"
+      ],
+      "target": "failover_out"
+    }
+  ]
+}

+ 20 - 0
Maple.App/Converter/DateTimeConverter.cpp

@@ -0,0 +1,20 @@
+#include "pch.h"
+#include "Converter/DateTimeConverter.h"
+#include "DateTimeConverter.g.cpp"
+
+namespace winrt::Maple_App::implementation
+{
+    IInspectable DateTimeConverter::Convert(IInspectable const& value, TypeName const&, IInspectable const&, hstring const&)
+    {
+        const auto &opt_val = value.try_as<DateTime>();
+        if (!opt_val.has_value()) {
+            return nullptr;
+        }
+
+        return box_value(DateFormatter.Format(opt_val.value()) + L" " + TimeFormatter.Format(opt_val.value()));
+    }
+    IInspectable DateTimeConverter::ConvertBack(IInspectable const&, TypeName const&, IInspectable const&, hstring const&)
+    {
+        throw hresult_not_implemented();
+    }
+}

+ 28 - 0
Maple.App/Converter/DateTimeConverter.h

@@ -0,0 +1,28 @@
+#pragma once
+#include "DateTimeConverter.g.h"
+#include <winrt/Windows.Globalization.DateTimeFormatting.h>
+
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Globalization::DateTimeFormatting;
+using namespace winrt::Windows::UI::Xaml::Interop;
+
+namespace winrt::Maple_App::implementation
+{
+    struct DateTimeConverter : DateTimeConverterT<DateTimeConverter>
+    {
+        DateTimeConverter() = default;
+
+        IInspectable Convert(IInspectable const& value, TypeName const& targetType, IInspectable const& parameter, hstring const& language);
+        IInspectable ConvertBack(IInspectable const& value, TypeName const& targetType, IInspectable const& parameter, hstring const& language);
+
+    private:
+        DateTimeFormatter DateFormatter{ DateTimeFormatter::LongDate() };
+        DateTimeFormatter TimeFormatter{ DateTimeFormatter::LongTime() };
+    };
+}
+namespace winrt::Maple_App::factory_implementation
+{
+    struct DateTimeConverter : DateTimeConverterT<DateTimeConverter, implementation::DateTimeConverter>
+    {
+    };
+}

+ 9 - 0
Maple.App/Converter/DateTimeConverter.idl

@@ -0,0 +1,9 @@
+namespace Maple_App
+{
+    [bindable]
+    [default_interface]
+    runtimeclass DateTimeConverter : Windows.UI.Xaml.Data.IValueConverter
+    {
+        DateTimeConverter();
+    }
+}

+ 73 - 0
Maple.App/EditPage.cpp

@@ -0,0 +1,73 @@
+#include "pch.h"
+#include "EditPage.h"
+#if __has_include("EditPage.g.cpp")
+#include "EditPage.g.cpp"
+#endif
+
+using namespace winrt;
+using namespace winrt::Windows::Storage::Streams;
+using namespace winrt::Windows::UI::Text;
+
+namespace winrt::Maple_App::implementation
+{
+    EditPage::EditPage()
+    {
+        InitializeComponent();
+    }
+
+    fire_and_forget EditPage::OnNavigatedTo(NavigationEventArgs const& e) {
+        const auto lifetime = get_strong();
+        const auto& param = e.Parameter().as<Maple_App::ConfigViewModel>();
+
+        m_file = param.File();
+        const auto& text = co_await FileIO::ReadTextAsync(param.File(), UnicodeEncoding::Utf8);
+        EditBox().Document().SetText(TextSetOptions::None, text);
+        const auto weakThis = lifetime->get_weak();
+        m_saveModifiedContent = [weakThis]() -> IAsyncAction {
+            if (const auto self{ weakThis.get() }) {
+                return self->SaveDocument();
+            }
+            return {};
+        };
+    }
+    void EditPage::OnNavigatingFrom(NavigatingCancelEventArgs const&) {
+        if (m_file == nullptr || !m_file.IsAvailable()) {
+            return;
+        }
+        SaveDocument();
+    }
+
+    void EditPage::EditBox_TextChanging(IInspectable const&, RichEditBoxTextChangingEventArgs const&)
+    {
+        if (m_loaded == 2) {
+            SaveModifiedContent = m_saveModifiedContent;
+            SaveButton().IsEnabled(true);
+        }
+        else {
+            m_loaded++;
+        }
+    }
+    void EditPage::SaveButton_Click(IInspectable const&, RoutedEventArgs const&) {
+        SaveDocument();
+    }
+    void EditPage::HelpButton_Click(IInspectable const&, RoutedEventArgs const&) {
+        const auto _ = winrt::Windows::System::Launcher::LaunchUriAsync(Uri{ L"https://github.com/eycorsican/leaf/blob/master/README.zh.md" });
+    }
+    IAsyncAction EditPage::SaveDocument()
+    {
+        if (!SaveButton().IsEnabled()) {
+            co_return;
+        }
+        SaveModifiedContent = nullptr;
+        SaveButton().IsEnabled(false);
+        hstring content{};
+        EditBox().Document().GetText(TextGetOptions::NoHidden | TextGetOptions::UseCrlf | TextGetOptions::AllowFinalEop, content);
+        const auto data = to_string(content);
+        co_return co_await FileIO::WriteBytesAsync(
+            m_file,
+            std::vector<uint8_t>(data.begin(), data.end()));
+    }
+}
+
+
+

+ 44 - 0
Maple.App/EditPage.h

@@ -0,0 +1,44 @@
+#pragma once
+
+#include <functional>
+
+#include <winrt/Windows.System.h>
+
+#include "EditPage.g.h"
+
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Storage;
+using namespace winrt::Windows::UI::Xaml;
+using namespace winrt::Windows::UI::Xaml::Controls;
+using namespace winrt::Windows::UI::Xaml::Navigation;
+
+namespace winrt::Maple_App::implementation
+{
+    struct EditPage : EditPageT<EditPage>
+    {
+        EditPage();
+
+        fire_and_forget OnNavigatedTo(NavigationEventArgs const& e);
+        void OnNavigatingFrom(NavigatingCancelEventArgs const& e);
+
+        void EditBox_TextChanging(IInspectable const& sender, RichEditBoxTextChangingEventArgs const& e);
+        void SaveButton_Click(IInspectable const& sender, RoutedEventArgs const& e);
+        void HelpButton_Click(IInspectable const& sender, RoutedEventArgs const& e);
+
+        inline static std::function<IAsyncAction()> SaveModifiedContent{ nullptr };
+
+    private:
+        StorageFile m_file{ nullptr };
+        uint8_t m_loaded{}; // TextChanging will be triggered twice before actual user inputs
+        std::function<IAsyncAction()> m_saveModifiedContent{ nullptr };
+
+        IAsyncAction SaveDocument();
+    };
+}
+
+namespace winrt::Maple_App::factory_implementation
+{
+    struct EditPage : EditPageT<EditPage, implementation::EditPage>
+    {
+    };
+}

+ 8 - 0
Maple.App/EditPage.idl

@@ -0,0 +1,8 @@
+namespace Maple_App
+{
+    [default_interface]
+    runtimeclass EditPage : Windows.UI.Xaml.Controls.Page
+    {
+        EditPage();
+    }
+}

+ 39 - 0
Maple.App/EditPage.xaml

@@ -0,0 +1,39 @@
+<Page
+    x:Class="Maple_App.EditPage"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:Maple_App"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:Windows10FallCreatorsUpdate="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 5)"
+    xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
+    mc:Ignorable="d"
+    NavigationCacheMode="Disabled">
+
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+        <CommandBar Grid.Row="0" DefaultLabelPosition="Right">
+            <CommandBar.PrimaryCommands>
+                <AppBarButton x:Name="SaveButton" IsEnabled="False" Icon="Save" Label="Save" Click="SaveButton_Click">
+                    <AppBarButton.Command>
+                        <Windows10version1809:StandardUICommand Kind="Save"/>
+                    </AppBarButton.Command>
+                </AppBarButton>
+                <AppBarButton Icon="Help" Label="Help" Click="HelpButton_Click">
+                    <Windows10FallCreatorsUpdate:AppBarButton.KeyboardAccelerators>
+                        <Windows10FallCreatorsUpdate:KeyboardAccelerator Key="F1"/>
+                    </Windows10FallCreatorsUpdate:AppBarButton.KeyboardAccelerators>
+                </AppBarButton>
+            </CommandBar.PrimaryCommands>
+        </CommandBar>
+        <RichEditBox
+            x:Name="EditBox"
+            Grid.Row="1"
+            FontFamily="Consolas"
+            IsColorFontEnabled="True"
+            TextChanging="EditBox_TextChanging"/>
+    </Grid>
+</Page>

+ 317 - 0
Maple.App/MainPage.cpp

@@ -0,0 +1,317 @@
+#include "pch.h"
+#include "MainPage.h"
+#include "MainPage.g.cpp"
+#include <winrt/Windows.Networking.Vpn.h>
+#include "Model\Netif.h"
+
+namespace winrt::Maple_App::implementation
+{
+    MainPage::MainPage()
+    {
+        InitializeComponent();
+    }
+
+    DependencyProperty MainPage::ConfigItemsProperty()
+    {
+        return m_configItemsProperty;
+    }
+    IObservableVector<Maple_App::ConfigViewModel> MainPage::ConfigItems()
+    {
+        return GetValue(m_configItemsProperty).as<IObservableVector<Maple_App::ConfigViewModel>>();
+    }
+
+    void MainPage::Page_Loaded(IInspectable const&, RoutedEventArgs const&)
+    {
+        const auto _ = LoadConfigs();
+        NavigationManager = SystemNavigationManager::GetForCurrentView();
+        NavigationManager.AppViewBackButtonVisibility(MainSplitView().IsPaneOpen()
+            ? AppViewBackButtonVisibility::Collapsed
+            : AppViewBackButtonVisibility::Visible);
+
+        const auto weakThis = get_weak();
+        NavigationManager.BackRequested([weakThis](const auto&, const auto&) {
+            if (const auto self{ weakThis.get() }) {
+                self->MainSplitView().IsPaneOpen(true);
+                const auto currentVisibility = NavigationManager.AppViewBackButtonVisibility();
+                if (currentVisibility == AppViewBackButtonVisibility::Visible) {
+                    NavigationManager.AppViewBackButtonVisibility(AppViewBackButtonVisibility::Disabled);
+                }
+            }
+            });
+    }
+
+    IAsyncAction MainPage::NotifyUser(const hstring& message) {
+        ContentDialog dialog;
+        dialog.Content(box_value(message));
+        dialog.PrimaryButtonCommand(StandardUICommand{ StandardUICommandKind::Close });
+        co_await dialog.ShowAsync();
+    }
+
+    IAsyncOperation<IStorageFolder> MainPage::InitializeConfigFolder()
+    {
+        const auto& appData = ApplicationData::Current();
+        const auto& localFolder = appData.LocalFolder();
+        auto configItem = co_await localFolder.TryGetItemAsync(L"config");
+        if (configItem == nullptr || configItem.IsOfType(StorageItemTypes::File)) {
+            configItem = co_await localFolder.CreateFolderAsync(L"config", CreationCollisionOption::ReplaceExisting);
+        }
+        co_return configItem.as<IStorageFolder>();
+    }
+
+    IAsyncOperation<StorageFile> MainPage::CopyDefaultConfig(const IStorageFolder& configFolder, const hstring& desiredName)
+    {
+        const auto& defaultConfigSrc = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{ L"ms-appx:///Config/default.conf" });
+        co_return co_await defaultConfigSrc.CopyAsync(configFolder, desiredName, NameCollisionOption::GenerateUniqueName);
+    }
+    IAsyncOperation<StorageFile> MainPage::CopyDefaultJsonConfig(const IStorageFolder& configFolder, const hstring& desiredName)
+    {
+        const auto& defaultConfigSrc = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{ L"ms-appx:///Config/default.json" });
+        co_return co_await defaultConfigSrc.CopyAsync(configFolder, desiredName, NameCollisionOption::GenerateUniqueName);
+    }
+
+    IAsyncAction MainPage::LoadConfigs()
+    {
+        const auto lifetime = get_strong();
+        const auto& appData = ApplicationData::Current();
+        m_configFolder = co_await InitializeConfigFolder();
+        auto configFiles = co_await m_configFolder.GetFilesAsync();
+        if (configFiles.Size() == 0) {
+            const auto& defaultConfigDst = co_await CopyDefaultConfig(m_configFolder, L"default.conf");
+            appData.LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(defaultConfigDst.Path()));
+            configFiles = co_await m_configFolder.GetFilesAsync();
+        }
+
+        std::vector<Maple_App::ConfigViewModel> configModels;
+        configModels.reserve(static_cast<size_t>(configFiles.Size()));
+
+        const auto& defaultConfigPath = appData.LocalSettings().Values().TryLookup(CONFIG_PATH_SETTING_KEY).try_as<hstring>();
+        for (const auto& file : configFiles) {
+            const auto isDefault = file.Path() == defaultConfigPath;
+            const auto& instance = configModels.emplace_back(co_await ConfigViewModel::FromFile(file, isDefault));
+            if (isDefault) {
+                m_defaultConfig = instance;
+            }
+        }
+
+        SetValue(m_configItemsProperty, single_threaded_observable_vector<Maple_App::ConfigViewModel>(std::move(configModels)));
+        ConfigListView().SelectedItem(m_defaultConfig);
+    }
+
+    void MainPage::ConfigSetAsDefaultMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
+    {
+        auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
+        if (item == nullptr) {
+            item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
+        }
+        SetAsDefault(item);
+    }
+
+    void MainPage::ConfigItem_DoubleTapped(IInspectable const& sender, DoubleTappedRoutedEventArgs const&)
+    {
+        const auto& item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
+        SetAsDefault(item);
+    }
+
+    void MainPage::SetAsDefault(const Maple_App::ConfigViewModel& item)
+    {
+        ApplicationData::Current().LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(item.File().Path()));
+        m_defaultConfig.IsDefault(false);
+        item.IsDefault(true);
+        m_defaultConfig = item;
+    }
+
+    void MainPage::ConfigRenameMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
+    {
+        auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
+        if (item == nullptr) {
+            item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
+        }
+        RequestRenameItem(item);
+    }
+
+    void MainPage::RequestRenameItem(const Maple_App::ConfigViewModel& item)
+    {
+        const auto& name = item.Name();
+        const auto& renameDialog = RenameDialog();
+        const auto& renameDialogText = RenameDialogText();
+        renameDialogText.Text(name);
+        auto it = std::find(name.rbegin(), name.rend(), '.');
+        if (it != name.rend()) {
+            RenameDialogText().Select(0, static_cast<int32_t>(name.rend() - it) - 1);
+        }
+        renameDialog.DataContext(item);
+        renameDialog.ShowAsync();
+    }
+
+    fire_and_forget MainPage::ConfigDeleteMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e)
+    {
+        const auto lifetime = get_strong();
+        auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
+        if (item == nullptr) {
+            item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
+        }
+        if (item.IsDefault()) {
+            co_await NotifyUser(L"Default configuration cannot be deleted.");
+            co_return;
+        }
+        uint32_t index;
+        auto configItems = ConfigItems();
+        if (!configItems.IndexOf(item, index)) {
+            co_return;
+        }
+        ContentDialog c{};
+        c.Title(box_value(L"Delete this configuration file?"));
+        c.Content(box_value(L"This operation cannot be undone."));
+        c.PrimaryButtonCommand(StandardUICommand{ StandardUICommandKind::Delete });
+        c.SecondaryButtonCommand(StandardUICommand{ StandardUICommandKind::Close });
+        const auto& result = co_await c.ShowAsync();
+        if (result != ContentDialogResult::Primary) {
+            co_return;
+        }
+
+        if (ConfigListView().SelectedItem() == item) {
+            ConfigListView().SelectedIndex(!ConfigListView().SelectedIndex());
+        }
+        configItems.RemoveAt(index);
+        co_await item.Delete();
+        if (configItems.Size() == 0) {
+            LoadConfigs();
+        }
+    }
+
+    void MainPage::RenameDialogPrimaryButton_Click(IInspectable const&, ContentDialogButtonClickEventArgs const&)
+    {
+        ConfirmRename();
+    }
+
+    void MainPage::RenameDialogText_KeyDown(IInspectable const&, KeyRoutedEventArgs const& e)
+    {
+        if (e.Key() == Windows::System::VirtualKey::Enter) {
+            ConfirmRename();
+            RenameDialog().Hide();
+        }
+    }
+
+    fire_and_forget MainPage::ConfirmRename() {
+        const auto lifetime = get_strong();
+        const auto& renameDialog = RenameDialog();
+        const auto& item = renameDialog.DataContext().as<Maple_App::ConfigViewModel>();
+        if (item == nullptr) {
+            return;
+        }
+        co_await item.Rename(RenameDialogText().Text());
+        if (item == m_defaultConfig) {
+            ApplicationData::Current().LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(item.File().Path()));
+        }
+    }
+
+    fire_and_forget MainPage::ConfigCreateMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
+    {
+        const auto lifetime = get_strong();
+        const auto& buttonText = sender.as<MenuFlyoutItem>().Text();
+        StorageFile newFile{ nullptr };
+        if (buttonText == L"Conf") {
+            newFile = co_await CopyDefaultConfig(m_configFolder, L"New Config.conf");
+        }
+        else if (buttonText == L"JSON") {
+            newFile = co_await CopyDefaultJsonConfig(m_configFolder, L"New Config.json");
+        }
+        else {
+            co_return;
+        }
+        const auto& item = co_await ConfigViewModel::FromFile(newFile, false);
+        ConfigItems().Append(item);
+        RequestRenameItem(item);
+    }
+
+    fire_and_forget MainPage::ConfigDuplicateMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
+    {
+        const auto lifetime = get_strong();
+        auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
+        if (item == nullptr) {
+            item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
+        }
+        const auto& file = item.File();
+        const auto& parent = co_await file.GetParentAsync();
+        const auto& newFile = co_await file.CopyAsync(parent, file.Name(), NameCollisionOption::GenerateUniqueName);
+        ConfigItems().Append(co_await ConfigViewModel::FromFile(newFile, false));
+    }
+
+    void MainPage::MainPivot_PivotItemLoaded(Pivot const&, PivotItemEventArgs const& args)
+    {
+        if (args.Item().Header().as<hstring>() == L"Setting") {
+            const auto& netifs = Netif::EnumerateInterfaces();
+            std::vector<IInspectable> boxed_netifs;
+            boxed_netifs.reserve(netifs.size());
+            std::transform(netifs.begin(), netifs.end(), std::back_inserter(boxed_netifs), [](const auto& netif) -> auto {
+                return netif;
+            });
+            NetifCombobox().ItemsSource(single_threaded_vector(std::move(boxed_netifs)));
+
+            const auto& currentNetif = ApplicationData::Current().LocalSettings().Values().TryLookup(NETIF_SETTING_KEY).try_as<hstring>();
+            if (currentNetif.has_value()) {
+                NetifCombobox().SelectedValue(box_value(currentNetif.value()));
+            }
+            else {
+                const auto it = std::find_if(netifs.begin(), netifs.end(), [](const auto& netif) -> bool {
+                    return netif.Desc().size() > 0 && netif.Desc()[0] == L'★';
+                    });
+                if (it != netifs.end()) {
+                    NetifCombobox().SelectedItem(*it);
+                }
+            }
+        }
+    }
+    void MainPage::NetifCombobox_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const& e)
+    {
+        const auto it = e.AddedItems().First();
+        if (!it.HasCurrent() || it.Current().try_as<Maple_App::Netif>() == nullptr) {
+            return;
+        }
+
+        const auto& netif = it.Current().as<Maple_App::Netif>();
+        ApplicationData::Current().LocalSettings().Values().Insert(NETIF_SETTING_KEY, box_value(netif.Addr()));
+    }
+
+    void MainPage::ConfigListView_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const& e)
+    {
+        MainContentFrame().BackStack().Clear();
+        MainContentFrame().Navigate(xaml_typename<EditPage>(), e.AddedItems().First().Current());
+    }
+
+    void MainPage::WindowWidth_CurrentStateChanged(IInspectable const&, VisualStateChangedEventArgs const& e)
+    {
+        const auto& state = e.NewState();
+
+        NavigationManager.AppViewBackButtonVisibility(state == nullptr
+            ? AppViewBackButtonVisibility::Visible
+            : AppViewBackButtonVisibility::Collapsed);
+    }
+
+    void MainPage::MainSplitView_PaneClosing(SplitView const&, SplitViewPaneClosingEventArgs const&)
+    {
+        NavigationManager.AppViewBackButtonVisibility(AppViewBackButtonVisibility::Visible);
+    }
+
+    fire_and_forget MainPage::GenerateProfileButton_Click(IInspectable const& sender, RoutedEventArgs const& e)
+    {
+        const auto lifetime = get_strong();
+        using namespace winrt::Windows::Networking::Vpn;
+        const auto& agent = VpnManagementAgent{};
+        const auto& profile = VpnPlugInProfile{};
+        profile.AlwaysOn(false);
+        profile.ProfileName(L"Maple");
+        profile.RequireVpnClientAppUI(true);
+        profile.VpnPluginPackageFamilyName(Windows::ApplicationModel::Package::Current().Id().FamilyName());
+        profile.RememberCredentials(false);
+        profile.ServerUris().Append(Uri{ L"https://github.com/YtFlow/Maple" });
+        const auto& result = co_await agent.AddProfileFromObjectAsync(profile);
+        if (result == VpnManagementErrorStatus::Ok) {
+            co_await NotifyUser(L"Profile generated.");
+        }
+        else {
+            co_await NotifyUser(L"Failed to generate a profile (" + to_hstring(static_cast<int32_t>(result)) + L").");
+        }
+    }
+}
+

+ 79 - 0
Maple.App/MainPage.h

@@ -0,0 +1,79 @@
+#pragma once
+
+#include "MainPage.g.h"
+#include "Model/ConfigViewModel.h"
+
+using namespace winrt;
+using namespace Windows::Foundation;
+using namespace Windows::Foundation::Collections;
+using namespace Windows::Storage;
+using namespace Windows::UI::Core;
+using namespace Windows::UI::Xaml;
+using namespace Windows::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Input;
+
+namespace winrt::Maple_App::implementation
+{
+    static const hstring CONFIG_PATH_SETTING_KEY = L"CONFIG_PATH";
+    static const hstring NETIF_SETTING_KEY = L"NETIF";
+    struct MainPage : MainPageT<MainPage>
+    {
+        MainPage();
+
+        static DependencyProperty ConfigItemsProperty();
+        IObservableVector<Maple_App::ConfigViewModel> ConfigItems();
+
+        void Page_Loaded(IInspectable const& sender, RoutedEventArgs const& e);
+        void ConfigSetAsDefaultMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
+        void ConfigRenameMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
+        fire_and_forget ConfigDeleteMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
+        void RenameDialogPrimaryButton_Click(IInspectable const& sender, ContentDialogButtonClickEventArgs const& e);
+        void RenameDialogText_KeyDown(IInspectable const& sender, KeyRoutedEventArgs const& e);
+        fire_and_forget ConfigCreateMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
+        fire_and_forget ConfigDuplicateMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
+        void MainPivot_PivotItemLoaded(Pivot const& sender, PivotItemEventArgs const& args);
+        void NetifCombobox_SelectionChanged(IInspectable const& sender, SelectionChangedEventArgs const& e);
+        void ConfigListView_SelectionChanged(IInspectable const& sender, SelectionChangedEventArgs const& e);
+        void ConfigItem_DoubleTapped(IInspectable const& sender, DoubleTappedRoutedEventArgs const& e);
+        void WindowWidth_CurrentStateChanged(IInspectable const& sender, VisualStateChangedEventArgs const& e);
+        void MainSplitView_PaneClosing(SplitView const& sender, SplitViewPaneClosingEventArgs const& args);
+        fire_and_forget GenerateProfileButton_Click(IInspectable const& sender, RoutedEventArgs const& e);
+
+    private:
+        inline static DependencyProperty m_configItemsProperty =
+            DependencyProperty::Register(
+                L"ConfigItems",
+                xaml_typename<IObservableVector<Maple_App::ConfigViewModel>>(),
+                xaml_typename<Maple_App::MainPage>(),
+                nullptr
+            );
+        inline static DependencyProperty m_renamedNameProperty =
+            DependencyProperty::Register(
+                L"RenamedName",
+                xaml_typename<winrt::hstring>(),
+                xaml_typename<Maple_App::MainPage>(),
+                nullptr
+            );
+        inline static SystemNavigationManager NavigationManager{ nullptr };
+
+        IStorageFolder m_configFolder{ nullptr };
+        Maple_App::ConfigViewModel m_defaultConfig{ nullptr };
+
+        static IAsyncAction NotifyUser(const hstring& message);
+        static IAsyncOperation<IStorageFolder> InitializeConfigFolder();
+        static IAsyncOperation<StorageFile> CopyDefaultConfig(const IStorageFolder& configFolder, const hstring& desiredName);
+        static IAsyncOperation<StorageFile> CopyDefaultJsonConfig(const IStorageFolder& configFolder, const hstring& desiredName);
+
+        void RequestRenameItem(const Maple_App::ConfigViewModel& item);
+        void SetAsDefault(const Maple_App::ConfigViewModel& item);
+        fire_and_forget ConfirmRename();
+        IAsyncAction LoadConfigs();
+    };
+}
+
+namespace winrt::Maple_App::factory_implementation
+{
+    struct MainPage : MainPageT<MainPage, implementation::MainPage>
+    {
+    };
+}

+ 15 - 0
Maple.App/MainPage.idl

@@ -0,0 +1,15 @@
+import "./Model/ConfigViewModel.idl";
+
+namespace Maple_App
+{
+    [default_interface]
+    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
+    {
+        MainPage();
+
+        static Windows.UI.Xaml.DependencyProperty ConfigItemsProperty{ get; };
+
+        Windows.Foundation.Collections.IObservableVector<ConfigViewModel> ConfigItems{ get; };
+        Windows.UI.Xaml.Controls.ListView ConfigListView{ get; };
+    }
+}

+ 195 - 0
Maple.App/MainPage.xaml

@@ -0,0 +1,195 @@
+<Page
+    x:Class="Maple_App.MainPage"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:maple_app="using:Maple_App"
+    xmlns:Windows10FallCreatorsUpdate="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 5)"
+    xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
+    mc:Ignorable="d"
+    NavigationCacheMode="Required"
+    Loaded="Page_Loaded">
+
+    <Page.Resources>
+        <maple_app:DateTimeConverter x:Key="DateTimeConverter"/>
+    </Page.Resources>
+
+    <SplitView x:Name="MainSplitView" DisplayMode="Overlay" PaneClosing="MainSplitView_PaneClosing">
+        <SplitView.Pane>
+            <Pivot PivotItemLoaded="MainPivot_PivotItemLoaded">
+                <PivotItem Header="Config" Margin="0">
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="*"/>
+                            <RowDefinition Height="Auto"/>
+                        </Grid.RowDefinitions>
+                        <ListView
+                            x:Name="ConfigListView"
+                            Grid.Row="0"
+                            SelectionMode="Single"
+                            ItemsSource="{x:Bind ConfigItems, Mode=OneWay}"
+                            SelectionChanged="ConfigListView_SelectionChanged">
+                            <ListView.ItemContainerStyle>
+                                <Style TargetType="ListViewItem">
+                                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
+                                </Style>
+                            </ListView.ItemContainerStyle>
+                            <ListView.Resources>
+                                <MenuFlyout x:Key="ItemContextFlyout">
+                                    <MenuFlyoutItem Icon="Favorite" Text="Set as Default" Click="ConfigSetAsDefaultMenuItem_Click"/>
+                                    <MenuFlyoutItem Icon="Copy"  Text="Duplicate" Click="ConfigDuplicateMenuItem_Click"/>
+                                    <MenuFlyoutItem Icon="Rename" Text="Rename" Click="ConfigRenameMenuItem_Click">
+                                        <Windows10FallCreatorsUpdate:MenuFlyoutItem.KeyboardAccelerators>
+                                            <Windows10FallCreatorsUpdate:KeyboardAccelerator Key="F2" ScopeOwner="{x:Bind ConfigListView}"/>
+                                        </Windows10FallCreatorsUpdate:MenuFlyoutItem.KeyboardAccelerators>
+                                    </MenuFlyoutItem>
+                                    <MenuFlyoutItem Icon="Delete" Text="Delete" Click="ConfigDeleteMenuItem_Click">
+                                        <MenuFlyoutItem.Command>
+                                            <Windows10version1809:StandardUICommand Kind="Delete"/>
+                                        </MenuFlyoutItem.Command>
+                                    </MenuFlyoutItem>
+                                </MenuFlyout>
+                            </ListView.Resources>
+                            <ListView.ItemTemplate>
+                                <DataTemplate x:DataType="maple_app:ConfigViewModel">
+                                    <Grid
+                                        HorizontalAlignment="Stretch"
+                                        VerticalAlignment="Stretch"
+                                        ContextFlyout="{StaticResource ItemContextFlyout}"
+                                        DoubleTapped="ConfigItem_DoubleTapped">
+                                        <Grid.RowDefinitions>
+                                            <RowDefinition Height="Auto"/>
+                                            <RowDefinition Height="Auto"/>
+                                        </Grid.RowDefinitions>
+                                        <Grid.ColumnDefinitions>
+                                            <ColumnDefinition Width="0"/>
+                                            <ColumnDefinition Width="*"/>
+                                        </Grid.ColumnDefinitions>
+                                        <Rectangle
+                                            Grid.Row="0"
+                                            Grid.RowSpan="2"
+                                            Grid.Column="0"
+                                            Margin="-12, 6, 4, 6"
+                                            Fill="{ThemeResource SystemAccentColor}"
+                                            Visibility="{x:Bind IsDefault, Mode=OneWay}"/>
+                                        <TextBlock
+                                            Grid.Row="0"
+                                            Grid.Column="1"
+                                            Text="{x:Bind Name, Mode=OneWay}"/>
+                                        <TextBlock
+                                            Grid.Row="1"
+                                            Grid.Column="1"
+                                            Text="{x:Bind DateUpdated, Mode=OneWay, Converter={StaticResource DateTimeConverter}}"
+                                            Style="{ThemeResource CaptionTextBlockStyle}"
+                                            Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}"/>
+                                    </Grid>
+                                </DataTemplate>
+                            </ListView.ItemTemplate>
+                        </ListView>
+                        <CommandBar Grid.Row="1">
+                            <AppBarButton Icon="Add" Label="Add">
+                                <AppBarButton.Flyout>
+                                    <MenuFlyout Placement="Top">
+                                        <MenuFlyoutItem Text="Conf" Click="ConfigCreateMenuItem_Click"/>
+                                        <MenuFlyoutItem Text="JSON" Click="ConfigCreateMenuItem_Click"/>
+                                    </MenuFlyout>
+                                </AppBarButton.Flyout>
+                            </AppBarButton>
+                        </CommandBar>
+                        <ContentDialog
+                            x:Name="RenameDialog"
+                            Height="10"
+                            Title="Specify a new file name"
+                            PrimaryButtonText="Rename"
+                            SecondaryButtonText="Close"
+                            PrimaryButtonClick="RenameDialogPrimaryButton_Click">
+                            <ContentDialog.SecondaryButtonCommand>
+                                <Windows10version1809:StandardUICommand Kind="Close"/>
+                            </ContentDialog.SecondaryButtonCommand>
+                            <TextBox
+                                x:Name="RenameDialogText"
+                                MaxLength="100"
+                                Height="32"
+                                AcceptsReturn="False"
+                                TextWrapping="NoWrap"
+                                KeyDown="RenameDialogText_KeyDown"/>
+                        </ContentDialog>
+                    </Grid>
+                </PivotItem>
+                <PivotItem Header="Setting">
+                    <ScrollViewer>
+                        <StackPanel>
+                            <TextBlock Margin="0, 18" Text="Network Interface" Style="{ThemeResource TitleTextBlockStyle}"/>
+                            <TextBlock
+                                Text="Choose a default network interface for DNS and outbound connections"
+                                TextWrapping="WrapWholeWords"/>
+                            <ComboBox
+                                x:Name="NetifCombobox"
+                                HorizontalAlignment="Stretch"
+                                Margin="0, 8"
+                                DisplayMemberPath="Desc"
+                                SelectedValuePath="Addr"
+                                SelectionChanged="NetifCombobox_SelectionChanged"/>
+
+                            <TextBlock Margin="0, 18" Text="VPN Connection" Style="{ThemeResource TitleTextBlockStyle}"/>
+                            <TextBlock Margin="0, 0, 0, 18" TextWrapping="WrapWholeWords">
+                                <Run Text="Connect to Maple in the"/>
+                                <Hyperlink TextDecorations="None" NavigateUri="ms-settings:network-vpn">
+                                    <Run Text="Windows Settings"/>
+                                </Hyperlink>
+                                <Run Text="app."/>
+                            </TextBlock>
+                            <TextBlock
+                                Margin="0, 0, 0, 8"
+                                Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}"
+                                TextWrapping="WrapWholeWords">
+                                <Run Text="For first time connection, create a VPN profile with provider"/>
+                                <Run FontFamily="Consolas" Text="Maple"/>
+                                <Run Text="and server name"/>
+                                <Run FontFamily="Consolas" Text="maple"/>
+                                <Run Text=". Alternatively, select &quot;Generate Profile&quot; to create one automatically."/>
+                            </TextBlock>
+                            <Button Content="Generate Profile" Click="GenerateProfileButton_Click"/>
+
+                            <TextBlock Margin="0, 18" Text="About" Style="{ThemeResource TitleTextBlockStyle}"/>
+                            <HyperlinkButton Padding="0" Content="Homepage" NavigateUri="https://github.com/YtFlow/Maple"/>
+                            <HyperlinkButton Padding="0" Content="Report Issues" NavigateUri="https://github.com/YtFlow/Maple/issues"/>
+                            <HyperlinkButton Padding="0" Content="License" NavigateUri="https://github.com/YtFlow/Maple/blob/main/LICENSE"/>
+
+                            <TextBlock Margin="0, 20, 0, 0" TextWrapping="WrapWholeWords">
+                                <Run>This product contains a</Run>
+                                <Hyperlink NavigateUri="https://github.com/YtFlow/leaf" TextDecorations="None">
+                                    <Run>modified version</Run>
+                                </Hyperlink>
+                                <Run>of</Run>
+                                <Hyperlink NavigateUri="https://github.com/eycorsican/leaf" TextDecorations="None">
+                                    <Run>eycorsican/leaf</Run>
+                                </Hyperlink>
+                                <Run>under</Run>
+                                <Hyperlink NavigateUri="https://github.com/eycorsican/leaf/blob/master/LICENSE" TextDecorations="None">
+                                    <Run>Apache License 2.0</Run>
+                                </Hyperlink>
+                                <Run>.</Run>
+                            </TextBlock>
+                        </StackPanel>
+                    </ScrollViewer>
+                </PivotItem>
+            </Pivot>
+        </SplitView.Pane>
+        <Frame x:Name="MainContentFrame" CacheSize="0"/>
+        <VisualStateManager.VisualStateGroups>
+            <VisualStateGroup CurrentStateChanged="WindowWidth_CurrentStateChanged">
+                <VisualState>
+                    <VisualState.StateTriggers>
+                        <AdaptiveTrigger MinWindowWidth="600"/>
+                    </VisualState.StateTriggers>
+                    <VisualState.Setters>
+                        <Setter Target="MainSplitView.DisplayMode" Value="Inline"/>
+                        <Setter Target="MainSplitView.IsPaneOpen" Value="True"/>
+                    </VisualState.Setters>
+                </VisualState>
+            </VisualStateGroup>
+        </VisualStateManager.VisualStateGroups>
+    </SplitView>
+</Page>

+ 324 - 0
Maple.App/Maple.App.vcxproj

@@ -0,0 +1,324 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.210304.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210304.5\build\native\Microsoft.Windows.CppWinRT.props')" />
+  <PropertyGroup Label="Globals">
+    <CppWinRTOptimized>true</CppWinRTOptimized>
+    <CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
+    <CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
+    <MinimalCoreWin>true</MinimalCoreWin>
+    <ProjectGuid>{4d5a2fcd-d3d8-4dd0-bdaf-96cd1d47bdbe}</ProjectGuid>
+    <ProjectName>Maple.App</ProjectName>
+    <RootNamespace>Maple_App</RootNamespace>
+    <DefaultLanguage>en-US</DefaultLanguage>
+    <MinimumVisualStudioVersion>15.0</MinimumVisualStudioVersion>
+    <AppContainerApplication>true</AppContainerApplication>
+    <ApplicationType>Windows Store</ApplicationType>
+    <ApplicationTypeRevision>10.0</ApplicationTypeRevision>
+    <WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.19041.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformMinVersion>10.0.15063.0</WindowsTargetPlatformMinVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|ARM">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM">
+      <Configuration>Release</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <PlatformToolset>v140</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <LinkIncremental>false</LinkIncremental>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="PropertySheet.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros">
+    <TargetPlatformMinVersion>10.0.15063.0</TargetPlatformMinVersion>
+    <GenerateAppInstallerFile>False</GenerateAppInstallerFile>
+    <AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
+    <PackageCertificateKeyFile>D:\proj\ytflow\src\YtFlow\YtFlowApp\YtFlowApp\YtFlowApp_TemporaryKey.pfx</PackageCertificateKeyFile>
+    <AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
+    <AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
+    <AppxSymbolPackageEnabled>False</AppxSymbolPackageEnabled>
+    <GenerateTestArtifacts>True</GenerateTestArtifacts>
+    <AppxBundlePlatforms>x64</AppxBundlePlatforms>
+    <HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
+      <WarningLevel>Level4</WarningLevel>
+      <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
+      <!--Temporarily disable cppwinrt heap enforcement to work around xaml compiler generated std::shared_ptr use -->
+      <AdditionalOptions Condition="'$(CppWinRTHeapEnforcement)'==''">/DWINRT_NO_MAKE_DETECTION %(AdditionalOptions)</AdditionalOptions>
+      <DisableSpecificWarnings>
+      </DisableSpecificWarnings>
+      <PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <GenerateWindowsMetadata>false</GenerateWindowsMetadata>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
+    <ClCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
+    <ClCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClInclude Include="Converter\DateTimeConverter.h">
+      <DependentUpon>Converter\DateTimeConverter.idl</DependentUpon>
+      <SubType>Code</SubType>
+    </ClInclude>
+    <ClInclude Include="EditPage.h">
+      <DependentUpon>EditPage.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </ClInclude>
+    <ClInclude Include="Model\ConfigViewModel.h">
+      <DependentUpon>Model\ConfigViewModel.idl</DependentUpon>
+      <SubType>Code</SubType>
+    </ClInclude>
+    <ClInclude Include="Model\Netif.h">
+      <DependentUpon>Model\Netif.idl</DependentUpon>
+      <SubType>Code</SubType>
+    </ClInclude>
+    <ClInclude Include="pch.h" />
+    <ClInclude Include="App.h">
+      <DependentUpon>App.xaml</DependentUpon>
+    </ClInclude>
+    <ClInclude Include="MainPage.h">
+      <DependentUpon>MainPage.xaml</DependentUpon>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml">
+      <SubType>Designer</SubType>
+    </ApplicationDefinition>
+    <Page Include="EditPage.xaml">
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="MainPage.xaml">
+      <SubType>Designer</SubType>
+    </Page>
+  </ItemGroup>
+  <ItemGroup>
+    <AppxManifest Include="Package.appxmanifest">
+      <SubType>Designer</SubType>
+    </AppxManifest>
+  </ItemGroup>
+  <ItemGroup>
+    <Image Include="Assets\LargeTile.scale-100.png" />
+    <Image Include="Assets\LargeTile.scale-125.png" />
+    <Image Include="Assets\LargeTile.scale-150.png" />
+    <Image Include="Assets\LargeTile.scale-200.png" />
+    <Image Include="Assets\LargeTile.scale-400.png" />
+    <Image Include="Assets\LockScreenLogo.scale-200.png" />
+    <Image Include="Assets\SmallTile.scale-100.png" />
+    <Image Include="Assets\SmallTile.scale-125.png" />
+    <Image Include="Assets\SmallTile.scale-150.png" />
+    <Image Include="Assets\SmallTile.scale-200.png" />
+    <Image Include="Assets\SmallTile.scale-400.png" />
+    <Image Include="Assets\SplashScreen.scale-100.png" />
+    <Image Include="Assets\SplashScreen.scale-125.png" />
+    <Image Include="Assets\SplashScreen.scale-150.png" />
+    <Image Include="Assets\SplashScreen.scale-200.png" />
+    <Image Include="Assets\SplashScreen.scale-400.png" />
+    <Image Include="Assets\Square150x150Logo.scale-100.png" />
+    <Image Include="Assets\Square150x150Logo.scale-125.png" />
+    <Image Include="Assets\Square150x150Logo.scale-150.png" />
+    <Image Include="Assets\Square150x150Logo.scale-200.png" />
+    <Image Include="Assets\Square150x150Logo.scale-400.png" />
+    <Image Include="Assets\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
+    <Image Include="Assets\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
+    <Image Include="Assets\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
+    <Image Include="Assets\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
+    <Image Include="Assets\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
+    <Image Include="Assets\Square44x44Logo.altform-unplated_targetsize-16.png" />
+    <Image Include="Assets\Square44x44Logo.altform-unplated_targetsize-256.png" />
+    <Image Include="Assets\Square44x44Logo.altform-unplated_targetsize-32.png" />
+    <Image Include="Assets\Square44x44Logo.altform-unplated_targetsize-48.png" />
+    <Image Include="Assets\Square44x44Logo.scale-100.png" />
+    <Image Include="Assets\Square44x44Logo.scale-125.png" />
+    <Image Include="Assets\Square44x44Logo.scale-150.png" />
+    <Image Include="Assets\Square44x44Logo.scale-200.png" />
+    <Image Include="Assets\Square44x44Logo.scale-400.png" />
+    <Image Include="Assets\Square44x44Logo.targetsize-16.png" />
+    <Image Include="Assets\Square44x44Logo.targetsize-24.png" />
+    <Image Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
+    <Image Include="Assets\Square44x44Logo.targetsize-256.png" />
+    <Image Include="Assets\Square44x44Logo.targetsize-32.png" />
+    <Image Include="Assets\Square44x44Logo.targetsize-48.png" />
+    <Image Include="Assets\StoreLogo.scale-100.png" />
+    <Image Include="Assets\StoreLogo.scale-125.png" />
+    <Image Include="Assets\StoreLogo.scale-150.png" />
+    <Image Include="Assets\StoreLogo.scale-200.png" />
+    <Image Include="Assets\StoreLogo.scale-400.png" />
+    <Image Include="Assets\Wide310x150Logo.scale-100.png" />
+    <Image Include="Assets\Wide310x150Logo.scale-125.png" />
+    <Image Include="Assets\Wide310x150Logo.scale-150.png" />
+    <Image Include="Assets\Wide310x150Logo.scale-200.png" />
+    <Image Include="Assets\Wide310x150Logo.scale-400.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="Converter\DateTimeConverter.cpp">
+      <DependentUpon>Converter\DateTimeConverter.idl</DependentUpon>
+      <SubType>Code</SubType>
+    </ClCompile>
+    <ClCompile Include="EditPage.cpp">
+      <DependentUpon>EditPage.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </ClCompile>
+    <ClCompile Include="Model\ConfigViewModel.cpp">
+      <DependentUpon>Model\ConfigViewModel.idl</DependentUpon>
+      <SubType>Code</SubType>
+    </ClCompile>
+    <ClCompile Include="Model\Netif.cpp">
+      <DependentUpon>Model\Netif.idl</DependentUpon>
+      <SubType>Code</SubType>
+    </ClCompile>
+    <ClCompile Include="pch.cpp">
+      <PrecompiledHeader>Create</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="App.cpp">
+      <DependentUpon>App.xaml</DependentUpon>
+    </ClCompile>
+    <ClCompile Include="MainPage.cpp">
+      <DependentUpon>MainPage.xaml</DependentUpon>
+    </ClCompile>
+    <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <Midl Include="App.idl">
+      <DependentUpon>App.xaml</DependentUpon>
+    </Midl>
+    <Midl Include="Converter\DateTimeConverter.idl">
+      <SubType>Designer</SubType>
+    </Midl>
+    <Midl Include="EditPage.idl">
+      <DependentUpon>EditPage.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Midl>
+    <Midl Include="MainPage.idl">
+      <DependentUpon>MainPage.xaml</DependentUpon>
+    </Midl>
+    <Midl Include="Model\ConfigViewModel.idl">
+      <SubType>Designer</SubType>
+    </Midl>
+    <Midl Include="Model\Netif.idl">
+      <SubType>Designer</SubType>
+    </Midl>
+  </ItemGroup>
+  <ItemGroup>
+    <CopyFileToFolders Include="Config\default.conf">
+      <FileType>Document</FileType>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(OutDir)/Config</DestinationFolders>
+    </CopyFileToFolders>
+    <CopyFileToFolders Include="Config\default.json">
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
+      <FileType>Document</FileType>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
+      <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(OutDir)/Config</DestinationFolders>
+      <DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(OutDir)/Config</DestinationFolders>
+    </CopyFileToFolders>
+    <None Include="packages.config" />
+    <None Include="PropertySheet.props" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Maple.Task\Maple.Task.vcxproj">
+      <Project>{596ec282-b44a-4e5d-8401-fa3ce8a8301a}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+    <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.210304.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210304.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
+  </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210304.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.210304.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210304.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.210304.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
+  </Target>
+</Project>

+ 181 - 0
Maple.App/Maple.App.vcxproj.filters

@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="pch.cpp" />
+    <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="pch.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <Image Include="Assets\LockScreenLogo.scale-200.png" />
+    <Image Include="Assets\SplashScreen.scale-200.png" />
+    <Image Include="Assets\Square150x150Logo.scale-200.png" />
+    <Image Include="Assets\Square44x44Logo.scale-200.png" />
+    <Image Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
+    <Image Include="Assets\Wide310x150Logo.scale-200.png" />
+    <Image Include="Assets\SmallTile.scale-100.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\SmallTile.scale-125.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\SmallTile.scale-150.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\SmallTile.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\SmallTile.scale-400.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square150x150Logo.scale-100.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square150x150Logo.scale-125.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square150x150Logo.scale-150.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square150x150Logo.scale-400.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Wide310x150Logo.scale-100.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Wide310x150Logo.scale-125.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Wide310x150Logo.scale-150.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Wide310x150Logo.scale-400.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\LargeTile.scale-100.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\LargeTile.scale-125.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\LargeTile.scale-150.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\LargeTile.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\LargeTile.scale-400.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.scale-100.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.scale-125.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.scale-150.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.scale-400.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.targetsize-16.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.targetsize-24.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.targetsize-32.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.targetsize-48.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.targetsize-256.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.altform-unplated_targetsize-16.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.altform-unplated_targetsize-32.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.altform-unplated_targetsize-48.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.altform-unplated_targetsize-256.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.altform-lightunplated_targetsize-16.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.altform-lightunplated_targetsize-24.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.altform-lightunplated_targetsize-32.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.altform-lightunplated_targetsize-48.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\Square44x44Logo.altform-lightunplated_targetsize-256.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\SplashScreen.scale-100.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\SplashScreen.scale-125.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\SplashScreen.scale-150.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\SplashScreen.scale-400.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\StoreLogo.scale-100.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\StoreLogo.scale-125.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\StoreLogo.scale-150.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\StoreLogo.scale-200.png">
+      <Filter>Assets</Filter>
+    </Image>
+    <Image Include="Assets\StoreLogo.scale-400.png">
+      <Filter>Assets</Filter>
+    </Image>
+  </ItemGroup>
+  <ItemGroup>
+    <AppxManifest Include="Package.appxmanifest" />
+  </ItemGroup>
+  <ItemGroup>
+    <CopyFileToFolders Include="Config\default.conf" />
+    <CopyFileToFolders Include="Config\default.json" />
+  </ItemGroup>
+  <ItemGroup>
+    <Midl Include="Converter\DateTimeConverter.idl" />
+    <Midl Include="Model\ConfigViewModel.idl" />
+    <Midl Include="Model\Netif.idl" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+    <None Include="PropertySheet.props" />
+  </ItemGroup>
+  <ItemGroup>
+    <Page Include="EditPage.xaml" />
+    <Page Include="MainPage.xaml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Filter Include="Assets">
+      <UniqueIdentifier>{8bef39be-8f66-4942-b9f1-92668c496b3c}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+</Project>

+ 52 - 0
Maple.App/Model/ConfigViewModel.cpp

@@ -0,0 +1,52 @@
+#include "pch.h"
+#include "ConfigViewModel.h"
+#include "ConfigViewModel.g.cpp"
+
+namespace winrt::Maple_App::implementation
+{
+    IAsyncOperation<Maple_App::ConfigViewModel> ConfigViewModel::FromFile(const StorageFile& file, bool isDefault) {
+        const auto& properties = co_await file.GetBasicPropertiesAsync();
+        const auto& modified = properties.DateModified();
+        co_return winrt::make<ConfigViewModel>(file, modified, isDefault);
+    }
+    ConfigViewModel::ConfigViewModel(const StorageFile& file, DateTime dateUpdated, bool isDefault)
+        : m_file(file), m_dateUpdated(dateUpdated), m_isDefault(isDefault)
+    {
+    }
+    StorageFile ConfigViewModel::File()
+    {
+        return m_file;
+    }
+    hstring ConfigViewModel::Name()
+    {
+        return m_file.Name();
+    }
+    Windows::Foundation::DateTime ConfigViewModel::DateUpdated()
+    {
+        return m_dateUpdated;
+    }
+    bool ConfigViewModel::IsDefault()
+    {
+        return m_isDefault;
+    }
+    void ConfigViewModel::IsDefault(bool value)
+    {
+        m_isDefault = value;
+        m_propertyChanged(*this, PropertyChangedEventArgs(L"IsDefault"));
+    }
+    winrt::event_token ConfigViewModel::PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
+    {
+        return m_propertyChanged.add(handler);
+    }
+    void ConfigViewModel::PropertyChanged(winrt::event_token const& token) noexcept
+    {
+        m_propertyChanged.remove(token);
+    }
+    IAsyncAction ConfigViewModel::Delete() {
+        return m_file.DeleteAsync();
+    }
+    IAsyncAction ConfigViewModel::Rename(hstring const& newName) {
+        co_await m_file.RenameAsync(newName, NameCollisionOption::GenerateUniqueName);
+        m_propertyChanged(*this, PropertyChangedEventArgs(L"Name"));
+    }
+}

+ 33 - 0
Maple.App/Model/ConfigViewModel.h

@@ -0,0 +1,33 @@
+#pragma once
+#include "ConfigViewModel.g.h"
+
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Storage;
+using namespace winrt::Windows::UI::Xaml::Data;
+
+namespace winrt::Maple_App::implementation
+{
+    struct ConfigViewModel : ConfigViewModelT<ConfigViewModel>
+    {
+        ConfigViewModel(const StorageFile& file, DateTime dateUpdated, bool isDefault);
+
+        static IAsyncOperation<Maple_App::ConfigViewModel> FromFile(const StorageFile& file, bool isDefault);
+
+        StorageFile File();
+        hstring Name();
+        DateTime DateUpdated();
+        bool IsDefault();
+        void IsDefault(bool value);
+        winrt::event_token PropertyChanged(PropertyChangedEventHandler const& handler);
+        void PropertyChanged(winrt::event_token const& token) noexcept;
+        IAsyncAction Delete();
+        IAsyncAction Rename(hstring const& newName);
+
+    private:
+
+        StorageFile m_file;
+        Windows::Foundation::DateTime m_dateUpdated;
+        bool m_isDefault;
+        event<PropertyChangedEventHandler> m_propertyChanged;
+    };
+}

+ 15 - 0
Maple.App/Model/ConfigViewModel.idl

@@ -0,0 +1,15 @@
+namespace Maple_App
+{
+    [bindable]
+    [default_interface]
+    runtimeclass ConfigViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
+    {
+        Windows.Storage.StorageFile File{ get; };
+        String Name{ get; };
+        Windows.Foundation.DateTime DateUpdated{ get; };
+        Boolean IsDefault{ get; set; };
+
+        Windows.Foundation.IAsyncAction Delete();
+        Windows.Foundation.IAsyncAction Rename(String newName);
+    }
+}

+ 164 - 0
Maple.App/Model/Netif.cpp

@@ -0,0 +1,164 @@
+#include "pch.h"
+#include <WinSock2.h>
+#include <iphlpapi.h>
+#include "Netif.h"
+#if __has_include("Netif.g.cpp")
+#include "Netif.g.cpp"
+#endif
+
+constexpr auto WORKING_BUFFER_SIZE = 15000;
+constexpr auto ADDR_BUFFER_SIZE = 64;
+constexpr auto MAX_TRIES = 3;
+
+#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
+#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
+
+namespace winrt::Maple_App::implementation
+{
+    Netif::Netif(const hstring& desc, const hstring& Addr)
+        : m_desc(desc), m_addr(Addr)
+    {
+    }
+    hstring Netif::Desc()
+    {
+        return m_desc;
+    }
+
+    hstring Netif::Addr()
+    {
+        return m_addr;
+    }
+
+    std::vector<Maple_App::Netif> Netif::EnumerateInterfaces() {
+
+        /* Declare and initialize variables */
+
+        DWORD dwRetVal = 0;
+
+        unsigned int i = 0;
+
+        // Set the flags to pass to GetAdaptersAddresses
+        ULONG flags =
+            GAA_FLAG_SKIP_ANYCAST
+            | GAA_FLAG_SKIP_MULTICAST
+            | GAA_FLAG_SKIP_DNS_SERVER
+            | GAA_FLAG_SKIP_FRIENDLY_NAME;
+
+        // default to unspecified address family (both)
+        ULONG family = AF_INET;
+
+
+        PIP_ADAPTER_ADDRESSES pAddresses = NULL;
+        ULONG outBufLen = 0;
+        ULONG Iterations = 0;
+
+        PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL;
+        PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
+
+        // Allocate a 15 KB buffer to start with.
+        outBufLen = WORKING_BUFFER_SIZE;
+        std::array<WCHAR, ADDR_BUFFER_SIZE> addrBuf{};
+        const auto& sniffed = Netif::SniffOutboundAddress();
+
+        do {
+
+            pAddresses = (IP_ADAPTER_ADDRESSES*)MALLOC(outBufLen);
+            if (pAddresses == NULL) {
+                throw std::bad_alloc{};
+            }
+
+            dwRetVal =
+                GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen);
+
+            if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
+                FREE(pAddresses);
+                pAddresses = NULL;
+            }
+            else {
+                break;
+            }
+
+            Iterations++;
+
+        } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES));
+
+        if (dwRetVal != NO_ERROR) {
+            if (pAddresses) {
+                FREE(pAddresses);
+            }
+
+            return {};
+        }
+
+        // If successful, output some information from the data we received
+        std::vector<Maple_App::Netif> ret;
+        pCurrAddresses = pAddresses;
+        while (pCurrAddresses) {
+            if (!(pCurrAddresses->Flags & IP_ADAPTER_IPV4_ENABLED)) {
+                pCurrAddresses = pCurrAddresses->Next;
+                continue;
+            }
+            const auto& friendlyName = to_hstring(pCurrAddresses->FriendlyName);
+
+            pUnicast = pCurrAddresses->FirstUnicastAddress;
+            if (pUnicast != NULL) {
+                for (i = 0; pUnicast != NULL; i++) {
+                    // pUnicast->Address.lpSockaddr->sa_family;
+                    auto bufSize = static_cast<DWORD>(addrBuf.size());
+                    if (FAILED(WSAAddressToStringW(pUnicast->Address.lpSockaddr, pUnicast->Address.iSockaddrLength, nullptr, addrBuf.data(), &bufSize))) {
+                        pUnicast = pUnicast->Next;
+                        continue;
+                    }
+                    if (bufSize > 0) {
+                        bufSize--;
+                    }
+                    hstring addr(addrBuf.data(), bufSize);
+                    hstring desc = addr == sniffed ? L"★" : L"";
+                    desc = desc + friendlyName + L" (" + addr + L")";
+                    ret.emplace_back(winrt::make<Netif>(desc, addr));
+                    pUnicast = pUnicast->Next;
+                }
+            }
+
+            pCurrAddresses = pCurrAddresses->Next;
+        }
+
+        FREE(pAddresses);
+        return ret;
+    }
+
+    hstring Netif::SniffOutboundAddress()
+    {
+        const auto s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+        if (s == INVALID_SOCKET) {
+            return {};
+        }
+
+        sockaddr saddr{};
+        saddr.sa_family = AF_INET;
+        saddr.sa_data[1] = 53;
+        memset(&saddr.sa_data[2], 8, 4);
+
+        if (connect(s, &saddr, sizeof(saddr)) == SOCKET_ERROR) {
+            return {};
+        }
+
+        sockaddr_storage localAddr{};
+        int localAddrLen = sizeof(localAddr);
+
+        getsockname(s, (sockaddr*)&localAddr, &localAddrLen);
+        localAddr.__ss_pad1[0] = 0;
+        localAddr.__ss_pad1[1] = 0;
+
+        std::array<WCHAR, ADDR_BUFFER_SIZE> addrBuf{};
+        auto bufSize = static_cast<DWORD>(addrBuf.size());
+        if (FAILED(WSAAddressToStringW((LPSOCKADDR)&localAddr, localAddrLen, nullptr, addrBuf.data(), &bufSize))) {
+            return {};
+        }
+        if (bufSize > 0) {
+            bufSize--;
+        }
+        return hstring(addrBuf.data(), bufSize);
+    }
+
+}

+ 29 - 0
Maple.App/Model/Netif.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include "Netif.g.h"
+
+namespace winrt::Maple_App::implementation
+{
+    struct Netif : NetifT<Netif>
+    {
+        Netif() = default;
+        Netif(const hstring& desc, const hstring& Addr);
+
+        hstring Desc();
+        hstring Addr();
+
+        static std::vector<Maple_App::Netif> EnumerateInterfaces();
+        static hstring SniffOutboundAddress();
+
+    private:
+        hstring m_desc;
+        hstring m_addr;
+    };
+}
+
+namespace winrt::Maple_App::factory_implementation
+{
+    struct Netif : NetifT<Netif, implementation::Netif>
+    {
+    };
+}

+ 11 - 0
Maple.App/Model/Netif.idl

@@ -0,0 +1,11 @@
+namespace Maple_App
+{
+    [bindable]
+    [default_interface]
+    runtimeclass Netif 
+    {
+        Netif();
+        String Desc{ get; };
+        String Addr{ get; };
+    }
+}

+ 52 - 0
Maple.App/Package.appxmanifest

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Package
+  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
+  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
+  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
+  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
+  IgnorableNamespaces="uap mp rescap">
+  <Identity
+    Name="56263bdbai.Maple"
+    Publisher="CN=E18F459A-56CA-490C-9E8B-6892A762C635"
+    Version="0.1.0.0" />
+  <mp:PhoneIdentity PhoneProductId="b18d4afd-fd70-4477-8391-d605077b7858" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
+  <Properties>
+    <DisplayName>Maple</DisplayName>
+    <PublisherDisplayName>bdbai</PublisherDisplayName>
+    <Logo>Assets\StoreLogo.png</Logo>
+  </Properties>
+  <Dependencies>
+    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
+  </Dependencies>
+  <Resources>
+    <Resource Language="x-generate" />
+  </Resources>
+  <Applications>
+    <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="Maple_App">
+      <uap:VisualElements DisplayName="Maple" Description="Maple flowers are green, yellow, orange or red."
+        Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" BackgroundColor="transparent">
+        <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square71x71Logo="Assets\SmallTile.png" Square310x310Logo="Assets\LargeTile.png">
+          <uap:ShowNameOnTiles>
+            <uap:ShowOn Tile="square150x150Logo"/>
+            <uap:ShowOn Tile="wide310x150Logo"/>
+            <uap:ShowOn Tile="square310x310Logo"/>
+          </uap:ShowNameOnTiles>
+        </uap:DefaultTile>
+        <uap:SplashScreen Image="Assets\SplashScreen.png" />
+      </uap:VisualElements>
+      <Extensions>
+        <Extension Category="windows.backgroundTasks" Executable="$targetnametoken$.exe" EntryPoint="Maple_Task.VpnTask" ResourceGroup="tunnel">
+          <BackgroundTasks>
+            <uap:Task Type="vpnClient" />
+          </BackgroundTasks>
+        </Extension>
+      </Extensions>
+    </Application>
+  </Applications>
+  <Capabilities>
+    <Capability Name="internetClient" />
+    <Capability Name="privateNetworkClientServer" />
+    <Capability Name="internetClientServer" />
+    <rescap:Capability Name="networkingVpnProvider" />
+  </Capabilities>
+</Package>

+ 16 - 0
Maple.App/PropertySheet.props

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets" />
+  <PropertyGroup Label="UserMacros" />
+  <!--
+    To customize common C++/WinRT project properties: 
+    * right-click the project node
+    * expand the Common Properties item
+    * select the C++/WinRT property page
+
+    For more advanced scenarios, and complete documentation, please see:
+    https://github.com/Microsoft/cppwinrt/tree/master/nuget 
+    -->
+  <PropertyGroup />
+  <ItemDefinitionGroup />
+</Project>

+ 4 - 0
Maple.App/packages.config

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.Windows.CppWinRT" version="2.0.210304.5" targetFramework="native" />
+</packages>

+ 1 - 0
Maple.App/pch.cpp

@@ -0,0 +1 @@
+#include "pch.h"

+ 22 - 0
Maple.App/pch.h

@@ -0,0 +1,22 @@
+#pragma once
+#include <windows.h>
+#include <unknwn.h>
+#include <restrictederrorinfo.h>
+#include <hstring.h>
+#include <winrt/Windows.Foundation.h>
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.Foundation.Metadata.h>
+#include <winrt/Windows.ApplicationModel.Activation.h>
+#include <winrt/Windows.Storage.h>
+#include <winrt/Windows.Storage.FileProperties.h>
+#include <winrt/Windows.UI.Core.h>
+#include <winrt/Windows.UI.Text.h>
+#include <winrt/Windows.UI.Xaml.h>
+#include <winrt/Windows.UI.Xaml.Controls.h>
+#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Windows.UI.Xaml.Data.h>
+#include <winrt/Windows.UI.Xaml.Input.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Windows.UI.Xaml.Markup.h>
+#include <winrt/Windows.UI.Xaml.Navigation.h>
+#include <winrt/Windows.UI.Xaml.Shapes.h>

+ 46 - 0
Maple.Task/CustomBuffer.h

@@ -0,0 +1,46 @@
+#pragma once
+#include "pch.h"
+#include "unknwnbase.h"
+#include "winrt/Windows.Storage.Streams.h"
+
+struct __declspec(uuid("905a0fef-bc53-11df-8c49-001e4fc686da")) IBufferByteAccess : ::IUnknown
+{
+    virtual HRESULT __stdcall Buffer(uint8_t** value) = 0;
+};
+
+struct CustomBuffer : winrt::implements<CustomBuffer, winrt::Windows::Storage::Streams::IBuffer, IBufferByteAccess>
+{
+    uint8_t* m_buffer;
+    uint32_t m_length{};
+
+    CustomBuffer(uint8_t *buffer, uint32_t size) :
+        m_buffer(buffer), m_length(size)
+    {
+    }
+
+    uint32_t Capacity() const
+    {
+        return m_length;
+    }
+
+    uint32_t Length() const
+    {
+        return m_length;
+    }
+
+    void Length(uint32_t value)
+    {
+        if (value > m_length)
+        {
+            throw winrt::hresult_invalid_argument();
+        }
+
+        m_length = value;
+    }
+
+    HRESULT __stdcall Buffer(uint8_t** value) final
+    {
+        *value = m_buffer;
+        return S_OK;
+    }
+};

+ 198 - 0
Maple.Task/Maple.Task.vcxproj

@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.210211.2\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210211.2\build\native\Microsoft.Windows.CppWinRT.props')" />
+  <PropertyGroup Label="Globals">
+    <CppWinRTOptimized>true</CppWinRTOptimized>
+    <CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
+    <CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
+    <MinimalCoreWin>true</MinimalCoreWin>
+    <ProjectGuid>{596ec282-b44a-4e5d-8401-fa3ce8a8301a}</ProjectGuid>
+    <ProjectName>Maple.Task</ProjectName>
+    <RootNamespace>Maple_Task</RootNamespace>
+    <DefaultLanguage>en-US</DefaultLanguage>
+    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
+    <AppContainerApplication>true</AppContainerApplication>
+    <ApplicationType>Windows Store</ApplicationType>
+    <ApplicationTypeRevision>10.0</ApplicationTypeRevision>
+    <WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.19041.0</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformMinVersion>10.0.10240.0</WindowsTargetPlatformMinVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|ARM">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM">
+      <Configuration>Release</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <PlatformToolset>v140</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
+    <PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+    <GenerateManifest>false</GenerateManifest>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <LinkIncremental>false</LinkIncremental>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="PropertySheet.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <LibraryPath>..\leaf\target\x86_64-uwp-windows-msvc\debug;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <LibraryPath>..\leaf\target\x86_64-uwp-windows-msvc\release;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LibraryPath>..\leaf\target\i686-uwp-windows-msvc\release;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <LibraryPath>..\leaf\target\i686-uwp-windows-msvc\debug;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
+    <LibraryPath>..\leaf\target\thumbv7a-uwp-windows-msvc\debug;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
+    <LibraryPath>..\leaf\target\thumbv7a-uwp-windows-msvc\release;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
+    <LibraryPath>..\leaf\target\aarch64-uwp-windows-msvc\release;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
+    <LibraryPath>..\leaf\target\aarch64-uwp-windows-msvc\debug;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
+      <WarningLevel>Level4</WarningLevel>
+      <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
+      <!--Temporarily disable cppwinrt heap enforcement to work around xaml compiler generated std::shared_ptr use -->
+      <AdditionalOptions Condition="'$(CppWinRTHeapEnforcement)'==''">/DWINRT_NO_MAKE_DETECTION %(AdditionalOptions)</AdditionalOptions>
+      <DisableSpecificWarnings>
+      </DisableSpecificWarnings>
+      <PreprocessorDefinitions>_WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateWindowsMetadata>false</GenerateWindowsMetadata>
+      <ModuleDefinitionFile>Maple_Task.def</ModuleDefinitionFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
+    <ClCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">leaf.lib;WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <Link>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">leaf.lib;WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <Link>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">leaf.lib;WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <Link>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">leaf.lib;WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <Link>
+      <IgnoreSpecificDefaultLibraries Condition="'$(Configuration)'=='Debug'">MSVCRT</IgnoreSpecificDefaultLibraries>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
+    <ClCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">leaf.lib;WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">leaf.lib;WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">leaf.lib;WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">leaf.lib;WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClInclude Include="CustomBuffer.h" />
+    <ClInclude Include="leaf.h" />
+    <ClInclude Include="pch.h" />
+    <ClInclude Include="VpnPlugin.h" />
+    <ClInclude Include="VpnTask.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="pch.cpp">
+      <PrecompiledHeader>Create</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
+    <ClCompile Include="VpnPlugin.cpp" />
+    <ClCompile Include="VpnTask.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Maple_Task.def" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+    <None Include="PropertySheet.props" />
+  </ItemGroup>
+  <ItemGroup>
+    <Midl Include="VpnPlugin.idl" />
+    <Midl Include="VpnTask.idl" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+    <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.210211.2\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210211.2\build\native\Microsoft.Windows.CppWinRT.targets')" />
+  </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210211.2\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.210211.2\build\native\Microsoft.Windows.CppWinRT.props'))" />
+    <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.210211.2\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.210211.2\build\native\Microsoft.Windows.CppWinRT.targets'))" />
+  </Target>
+</Project>

+ 3 - 0
Maple.Task/Maple_Task.def

@@ -0,0 +1,3 @@
+EXPORTS
+DllCanUnloadNow = WINRT_CanUnloadNow                    PRIVATE
+DllGetActivationFactory = WINRT_GetActivationFactory    PRIVATE

+ 16 - 0
Maple.Task/PropertySheet.props

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets" />
+  <PropertyGroup Label="UserMacros" />
+  <!--
+    To customize common C++/WinRT project properties: 
+    * right-click the project node
+    * expand the Common Properties item
+    * select the C++/WinRT property page
+
+    For more advanced scenarios, and complete documentation, please see:
+    https://github.com/Microsoft/cppwinrt/tree/master/nuget 
+    -->
+  <PropertyGroup />
+  <ItemDefinitionGroup />
+</Project>

+ 161 - 0
Maple.Task/VpnPlugin.cpp

@@ -0,0 +1,161 @@
+#include "pch.h"
+#include "VpnPlugin.h"
+#include "VpnPlugin.g.cpp"
+#include "winrt/Windows.Storage.h"
+#include "winrt/Windows.Storage.Streams.h"
+
+extern "C" void* lwip_strerr(uint8_t) {
+    return "";
+}
+
+namespace winrt::Maple_Task::implementation
+{
+    using Windows::Networking::HostName;
+    using Windows::Networking::Sockets::DatagramSocket;
+    using Windows::Storage::Streams::IOutputStream;
+    using namespace Windows::Networking::Vpn;
+    using namespace Windows::Storage;
+
+    void VpnPlugin::Connect(VpnChannel const& channel)
+    {
+        const auto localhost = HostName{ L"127.0.0.1" };
+        DatagramSocket transport{}, backTransport{};
+        channel.AssociateTransport(transport, nullptr);
+        transport.BindEndpointAsync(localhost, L"").get();
+        backTransport.BindEndpointAsync(localhost, L"").get();
+        transport.ConnectAsync(localhost, backTransport.Information().LocalPort()).get();
+        backTransport.ConnectAsync(localhost, transport.Information().LocalPort()).get();
+
+        VpnRouteAssignment routeScope{};
+        routeScope.ExcludeLocalSubnets(true);
+        routeScope.Ipv4InclusionRoutes(std::vector<VpnRoute>{
+            // 直接写 0.0.0.0/0 哪怕绑了接口也会绕回环
+            // VpnRoute(HostName{ L"0.0.0.0" }, 0)
+            VpnRoute(HostName{ L"0.0.0.0" }, 1),
+                VpnRoute(HostName{ L"128.0.0.0" }, 1),
+        });
+        // 排除代理服务器的话就会 os 10023 以一种访问权限不允许的方式做了一个访问套接字的尝试
+        // routeScope.Ipv4ExclusionRoutes(std::vector<VpnRoute>{
+        //     VpnRoute(HostName{ L"172.25.0.0" }, 16)
+        // });
+        VpnDomainNameAssignment dnsAssignment{};
+
+        const auto outputStreamAbi = winrt::detach_abi(backTransport.OutputStream());
+        StopLeaf();
+        {
+            std::lock_guard _guard{ m_decapQueueLock };
+            while (!m_decapQueue.empty()) {
+                m_decapQueue.pop();
+            }
+        }
+        m_backTransport = backTransport;
+        m_netStackHandle = netstack_register([](uint8_t* data, size_t size, void* outputStreamAbi) {
+            bool needSendDummyBuffer = false;
+            {
+                std::lock_guard _guard{ VpnPluginInstance->m_decapQueueLock };
+                auto& q = VpnPluginInstance->m_decapQueue;
+                {
+                    std::vector<uint8_t> buf(size);
+                    if (memcpy_s(buf.data(), buf.capacity(), data, size)) {
+                        return;
+                    }
+                    needSendDummyBuffer = q.empty();
+                    q.emplace(buf);
+                }
+            }
+            if (!needSendDummyBuffer) {
+                return;
+            }
+            IOutputStream outputStream{ nullptr };
+            winrt::attach_abi(outputStream, outputStreamAbi);
+            try {
+                const auto _ = outputStream.WriteAsync(dummyBuffer);
+            }
+            catch (...) {}
+            winrt::detach_abi(outputStream);
+            }, outputStreamAbi);
+        if (m_netStackHandle == nullptr) {
+            channel.TerminateConnection(L"Error initializing Leaf netstack.");
+            return;
+        }
+
+        const auto& confPathW = ApplicationData::Current().LocalSettings().Values().TryLookup(CONFIG_PATH_SETTING_KEY).try_as<hstring>().value_or(L"");
+        const auto& outNetifW = ApplicationData::Current().LocalSettings().Values().TryLookup(NETIF_SETTING_KEY).try_as<hstring>().value_or(L"");
+        const auto& confPath = winrt::to_string(confPathW);
+        const auto& outNetif = winrt::to_string(outNetifW);
+        m_leaf = run_leaf(confPath.data(), outNetif == "" ? nullptr : outNetif.data());
+        if (m_leaf == nullptr) {
+            channel.TerminateConnection(L"Error initializing Leaf runtime.\r\nPlease check your configuration file and default interface.");
+            StopLeaf();
+            return;
+        }
+        channel.StartWithMainTransport(
+            std::vector<HostName> { HostName{ L"192.168.3.1" } },
+            nullptr,
+            nullptr,
+            routeScope,
+            dnsAssignment,
+            1500,
+            1512,
+            false,
+            transport
+        );
+    }
+    void VpnPlugin::StopLeaf() {
+        m_backTransport = nullptr;
+
+        auto leafHandle = m_leaf;
+        if (leafHandle != nullptr) {
+            stop_leaf(leafHandle);
+            m_leaf = nullptr;
+        }
+
+        auto netStackHandle = m_netStackHandle;
+        if (netStackHandle != nullptr) {
+            const auto context = netstack_release(netStackHandle);
+            m_netStackHandle = nullptr;
+            // Release context, which is an ABI of IOutputStream
+            IInspectable obj{};
+            winrt::attach_abi(obj, context);
+            m_netStackHandle = nullptr;
+        }
+    }
+    void VpnPlugin::Disconnect(VpnChannel const& channel)
+    {
+        try {
+            channel.Stop();
+        }
+        catch (...) {}
+        StopLeaf();
+    }
+    void VpnPlugin::GetKeepAlivePayload(VpnChannel const&, VpnPacketBuffer&)
+    {
+    }
+    void VpnPlugin::Encapsulate([[maybe_unused]] VpnChannel const& channel, VpnPacketBufferList const& packets, VpnPacketBufferList const&)
+    {
+        auto packetCount = packets.Size();
+        while (packetCount-- > 0) {
+            const auto packet = packets.RemoveAtBegin();
+            const auto buffer = packet.Buffer();
+            netstack_send(m_netStackHandle, buffer.data(), static_cast<size_t>(buffer.Length()));
+            packets.Append(packet);
+        }
+    }
+    void VpnPlugin::Decapsulate(VpnChannel const& channel, [[maybe_unused]] VpnPacketBuffer const& encapBuffer, VpnPacketBufferList const& decapsulatedPackets, VpnPacketBufferList const&)
+    {
+        std::lock_guard _guard{ VpnPluginInstance->m_decapQueueLock };
+        auto& q = VpnPluginInstance->m_decapQueue;
+        while (!q.empty()) {
+            auto&& incomingBuffer = q.front();
+            const auto outBuffer = channel.GetVpnReceivePacketBuffer();
+            decapsulatedPackets.Append(outBuffer);
+            const auto outBuf = outBuffer.Buffer();
+            const auto size = incomingBuffer.size();
+            if (memcpy_s(outBuf.data(), outBuf.Capacity(), incomingBuffer.data(), size)) {
+                return;
+            }
+            outBuf.Length(static_cast<uint32_t>(size));
+            q.pop();
+        }
+    }
+}

+ 35 - 0
Maple.Task/VpnPlugin.h

@@ -0,0 +1,35 @@
+#pragma once
+#include <queue>
+#include <mutex>
+#include "VpnPlugin.g.h"
+#include "leaf.h"
+#include "CustomBuffer.h"
+#include "winrt/Windows.Networking.Sockets.h"
+
+namespace winrt::Maple_Task::implementation
+{
+    static const hstring CONFIG_PATH_SETTING_KEY = L"CONFIG_PATH";
+    static const hstring NETIF_SETTING_KEY = L"NETIF";
+    struct VpnPlugin : VpnPluginT<VpnPlugin>
+    {
+        VpnPlugin() = default;
+
+        void Connect(Windows::Networking::Vpn::VpnChannel const& channel);
+        void Disconnect(Windows::Networking::Vpn::VpnChannel const& channel);
+        void GetKeepAlivePayload(Windows::Networking::Vpn::VpnChannel const& channel, Windows::Networking::Vpn::VpnPacketBuffer& keepAlivePacket);
+        void Encapsulate(Windows::Networking::Vpn::VpnChannel const& channel, Windows::Networking::Vpn::VpnPacketBufferList const& packets, Windows::Networking::Vpn::VpnPacketBufferList const& encapulatedPackets);
+        void Decapsulate(Windows::Networking::Vpn::VpnChannel const& channel, Windows::Networking::Vpn::VpnPacketBuffer const& encapBuffer, Windows::Networking::Vpn::VpnPacketBufferList const& decapsulatedPackets, Windows::Networking::Vpn::VpnPacketBufferList const& controlPacketsToSend);
+
+    private:
+        void StopLeaf();
+
+        Leaf* m_leaf{};
+        NetStackHandle* m_netStackHandle{};
+        Windows::Networking::Sockets::DatagramSocket m_backTransport{ nullptr };
+        std::mutex m_decapQueueLock{};
+        std::queue<std::vector<uint8_t>> m_decapQueue{};
+    };
+    static const uint8_t dummyArr[] = { 0 };
+    static const auto dummyBuffer = winrt::make<CustomBuffer>(const_cast<uint8_t *>(static_cast<const uint8_t *>(dummyArr)), static_cast<uint32_t>(sizeof(dummyArr)));
+    static auto VpnPluginInstance = winrt::make_self<VpnPlugin>();
+}

+ 7 - 0
Maple.Task/VpnPlugin.idl

@@ -0,0 +1,7 @@
+namespace Maple_Task
+{
+    [default_interface]
+    runtimeclass VpnPlugin : Windows.Networking.Vpn.IVpnPlugIn {
+
+}
+}

+ 12 - 0
Maple.Task/VpnTask.cpp

@@ -0,0 +1,12 @@
+#include "pch.h"
+#include "VpnTask.h"
+#include "VpnTask.g.cpp"
+#include "VpnPlugin.h"
+
+namespace winrt::Maple_Task::implementation
+{
+    void VpnTask::Run(Windows::ApplicationModel::Background::IBackgroundTaskInstance const& taskInstance)
+    {
+        Windows::Networking::Vpn::VpnChannel::ProcessEventAsync(*VpnPluginInstance, taskInstance.TriggerDetails());
+    }
+}

+ 18 - 0
Maple.Task/VpnTask.h

@@ -0,0 +1,18 @@
+#pragma once
+#include "VpnTask.g.h"
+
+namespace winrt::Maple_Task::implementation
+{
+    struct VpnTask : VpnTaskT<VpnTask>
+    {
+        VpnTask() = default;
+
+        void Run(Windows::ApplicationModel::Background::IBackgroundTaskInstance const& taskInstance);
+    };
+}
+namespace winrt::Maple_Task::factory_implementation
+{
+    struct VpnTask : VpnTaskT<VpnTask, implementation::VpnTask>
+    {
+    };
+}

+ 8 - 0
Maple.Task/VpnTask.idl

@@ -0,0 +1,8 @@
+namespace Maple_Task
+{
+    [default_interface]
+    runtimeclass VpnTask : Windows.ApplicationModel.Background.IBackgroundTask
+    {
+        VpnTask();
+    }
+}

+ 14 - 0
Maple.Task/leaf.h

@@ -0,0 +1,14 @@
+#pragma once
+
+extern "C" {
+    typedef void Leaf;
+    Leaf* run_leaf(const char* path, const char* bind_host);
+    void stop_leaf(Leaf* leaf);
+
+    typedef void NetStackHandle;
+    typedef int32_t NetStackSendResult;
+    NetStackHandle* netstack_register(void on_receive(uint8_t*, size_t, void*), void* context);
+    NetStackSendResult netstack_send(NetStackHandle*, uint8_t*, size_t);
+    void* netstack_release(NetStackHandle* ptr);
+}
+

+ 4 - 0
Maple.Task/packages.config

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.Windows.CppWinRT" version="2.0.210304.5" targetFramework="native" />
+</packages>

+ 1 - 0
Maple.Task/pch.cpp

@@ -0,0 +1 @@
+#include "pch.h"

+ 4 - 0
Maple.Task/pch.h

@@ -0,0 +1,4 @@
+#pragma once
+#include <unknwn.h>
+#include <winrt/Windows.Foundation.h>
+#include <winrt/Windows.Foundation.Collections.h>

+ 69 - 0
Maple.sln

@@ -0,0 +1,69 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31019.35
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Maple.Task", "Maple.Task\Maple.Task.vcxproj", "{596EC282-B44A-4E5D-8401-FA3CE8A8301A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Maple.App", "Maple.App\Maple.App.vcxproj", "{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|ARM = Debug|ARM
+		Debug|ARM64 = Debug|ARM64
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|ARM = Release|ARM
+		Release|ARM64 = Release|ARM64
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Debug|ARM.ActiveCfg = Debug|ARM
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Debug|ARM.Build.0 = Debug|ARM
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Debug|ARM64.ActiveCfg = Debug|ARM64
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Debug|ARM64.Build.0 = Debug|ARM64
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Debug|x64.ActiveCfg = Debug|x64
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Debug|x64.Build.0 = Debug|x64
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Debug|x86.ActiveCfg = Debug|Win32
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Debug|x86.Build.0 = Debug|Win32
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Release|ARM.ActiveCfg = Release|ARM
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Release|ARM.Build.0 = Release|ARM
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Release|ARM64.ActiveCfg = Release|ARM64
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Release|ARM64.Build.0 = Release|ARM64
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Release|x64.ActiveCfg = Release|x64
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Release|x64.Build.0 = Release|x64
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Release|x86.ActiveCfg = Release|Win32
+		{596EC282-B44A-4E5D-8401-FA3CE8A8301A}.Release|x86.Build.0 = Release|Win32
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|ARM.ActiveCfg = Debug|ARM
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|ARM.Build.0 = Debug|ARM
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|ARM.Deploy.0 = Debug|ARM
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|ARM64.ActiveCfg = Debug|ARM64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|ARM64.Build.0 = Debug|ARM64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|ARM64.Deploy.0 = Debug|ARM64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|x64.ActiveCfg = Debug|x64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|x64.Build.0 = Debug|x64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|x64.Deploy.0 = Debug|x64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|x86.ActiveCfg = Debug|Win32
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|x86.Build.0 = Debug|Win32
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Debug|x86.Deploy.0 = Debug|Win32
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|ARM.ActiveCfg = Release|ARM
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|ARM.Build.0 = Release|ARM
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|ARM.Deploy.0 = Release|ARM
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|ARM64.ActiveCfg = Release|ARM64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|ARM64.Build.0 = Release|ARM64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|ARM64.Deploy.0 = Release|ARM64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|x64.ActiveCfg = Release|x64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|x64.Build.0 = Release|x64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|x64.Deploy.0 = Release|x64
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|x86.ActiveCfg = Release|Win32
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|x86.Build.0 = Release|Win32
+		{4D5A2FCD-D3D8-4DD0-BDAF-96CD1D47BDBE}.Release|x86.Deploy.0 = Release|Win32
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {7545FAD5-8272-4987-A25A-9FDB157C5ECE}
+	EndGlobalSection
+EndGlobal

+ 32 - 0
README.md

@@ -1,2 +1,34 @@
 # Maple
 A lightweight Universal Windows proxy app based on https://github.com/eycorsican/leaf
+
+## Features
+
+- Configuration management
+- Outbound network adapter selection
+- UWP VPN Platform as TUN provider
+
+## TODO
+
+- Log collection (currently logs are sent to Visual Studio Output window for debugging only)
+- `external` entries
+- VPN On Demand
+- Configurable routing entries
+
+## Screenshots
+
+![Settings Page](image/screenshot-setting1.png?raw=true)
+
+
+## Build
+
+To build Leaf and Maple, a Rust nightly toolchain, Windows 10 SDK 10.0.19041 and Visual Studio 2019 with C++ Development Workflow is required.
+
+1. **Recursively** clone this repository.
+2. Open an *x64 Native Tools Command Prompt for VS 2019*.
+3. Change working directory to `leaf/leaf-mobile`.
+4. Build `leaf.lib` with target `x86_64-uwp-windows-msvc` and Cargo command line flag `-Z build-std=std,panic_abort`.  
+   For Release builds, use `--release`.  
+   See also https://github.com/eycorsican/leaf#build .
+5. Open `Maple.sln` in Visual Studio.
+6. Build Solution.
+

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно