From d0d6f7c891f69c19086ff3a8b614462de6524af0 Mon Sep 17 00:00:00 2001
From: mkmark <mark@mkmark.net>
Date: Mon, 12 Aug 2024 02:32:37 +0800
Subject: [PATCH] feat: custom core version

---
 .github/workflows/build-qv2ray-cmake.yml      |  3 -
 CMakeLists.txt                                |  8 --
 src/base/Qv2rayBase.hpp                       |  4 +-
 src/base/models/QvSettingsObject.hpp          | 17 +++-
 src/core/kernel/V2RayKernelInteractions.cpp   | 88 ++++++++++++-------
 src/core/kernel/V2RayKernelInteractions.hpp   |  4 +-
 src/core/settings/SettingsBackend.cpp         |  1 +
 .../widgets/windows/w_PreferencesWindow.cpp   | 58 ++++++------
 .../widgets/windows/w_PreferencesWindow.hpp   |  3 +-
 src/ui/widgets/windows/w_PreferencesWindow.ui | 62 ++++++++++---
 translations/en_US.ts                         |  4 +
 11 files changed, 163 insertions(+), 89 deletions(-)

diff --git a/.github/workflows/build-qv2ray-cmake.yml b/.github/workflows/build-qv2ray-cmake.yml
index 997c858b5..063113f0c 100644
--- a/.github/workflows/build-qv2ray-cmake.yml
+++ b/.github/workflows/build-qv2ray-cmake.yml
@@ -123,7 +123,6 @@ jobs:
             -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
             -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \
             -DDS_STORE_SCRIPT=ON \
-            -DQV2RAY_USE_V5_CORE=ON \
             -DQV2RAY_DEFAULT_VASSETS_PATH=/usr/local/opt/v2ray/share/v2ray \
             -DQV2RAY_DEFAULT_VCORE_PATH=/usr/local/opt/v2ray/bin/v2ray
           cmake --build . --parallel $(sysctl -n hw.logicalcpu)
@@ -141,7 +140,6 @@ jobs:
           cd build
           cmake .. -GNinja \
             -DCMAKE_INSTALL_PREFIX=./deployment \
-            -DQV2RAY_USE_V5_CORE=ON \
             -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
           cmake --build . --parallel $(nproc)
           cmake --install .
@@ -155,7 +153,6 @@ jobs:
           cd build
           cmake .. -GNinja \
             -DCMAKE_INSTALL_PREFIX=./AppDir/usr \
-            -DQV2RAY_USE_V5_CORE=ON \
             -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
             -DQV2RAY_TRANSLATION_PATH=QApplication::applicationDirPath\(\)+"/../share/qv2ray/lang"
           cmake --build . --parallel $(nproc)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 27c605e18..f329d591d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -130,9 +130,6 @@ option(QV2RAY_HAS_SINGLEAPPLICATION "Build With SingleApplication" ON)
 set(QV2RAY_UI_TYPE "QWidget" CACHE STRING "Qv2ray GUI Component")
 QVLOG(QV2RAY_UI_TYPE)
 
-option(QV2RAY_USE_V5_CORE "Use V5 Core" ON)
-QVLOG(QV2RAY_USE_V5_CORE)
-
 option(QV2RAY_QT6 "Use Qt6" OFF)
 
 if(QV2RAY_UI_TYPE STREQUAL "QWidget")
@@ -334,11 +331,6 @@ target_include_directories(qv2ray_baselib PUBLIC
     ${Protobuf_INCLUDE_DIRS}
     )
 
-if(QV2RAY_USE_V5_CORE)
-    message("-- Use V2Ray Core V5")
-    target_compile_definitions(qv2ray_baselib PUBLIC QV2RAY_USE_V5_CORE)
-endif()
-
 # ==================================================================================
 # Qv2ray Builtin Plugins
 # ==================================================================================
diff --git a/src/base/Qv2rayBase.hpp b/src/base/Qv2rayBase.hpp
index d369e10ca..ba9a1b178 100644
--- a/src/base/Qv2rayBase.hpp
+++ b/src/base/Qv2rayBase.hpp
@@ -63,9 +63,7 @@ using namespace Qv2ray::base::objects::transfer;
 #if !defined(QV2RAY_DEFAULT_VCORE_PATH) && !defined(QV2RAY_DEFAULT_VASSETS_PATH)
 #define QV2RAY_DEFAULT_VASSETS_PATH (QV2RAY_CONFIG_DIR + "vcore/")
 #define QV2RAY_DEFAULT_VCORE_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ray" QV2RAY_EXECUTABLE_SUFFIX)
-#if !defined(QV2RAY_USE_V5_CORE)
-#define QV2RAY_DEFAULT_VCTL_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ctl" QV2RAY_EXECUTABLE_SUFFIX)
-#endif
+// #define QV2RAY_DEFAULT_VCTL_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ctl" QV2RAY_EXECUTABLE_SUFFIX)
 #elif defined(QV2RAY_DEFAULT_VCORE_PATH) && defined(QV2RAY_DEFAULT_VASSETS_PATH)
 // ---- Using user-specified VCore and VAssets path
 #else
diff --git a/src/base/models/QvSettingsObject.hpp b/src/base/models/QvSettingsObject.hpp
index e04fdfc8f..777a8a806 100644
--- a/src/base/models/QvSettingsObject.hpp
+++ b/src/base/models/QvSettingsObject.hpp
@@ -14,7 +14,7 @@ namespace Qv2ray::base::config
         int R = 150, G = 150, B = 150;
         float width = 1.5f;
         Qt::PenStyle style = Qt::SolidLine;
-        QvGraphPenConfig(){};
+        QvGraphPenConfig() {};
         QvGraphPenConfig(int R, int G, int B, float w, Qt::PenStyle s)
         {
             this->R = R;
@@ -64,8 +64,9 @@ namespace Qv2ray::base::config
         bool exitByCloseEvent = false;
         JSONSTRUCT_COMPARE(Qv2rayConfig_UI, theme, language, quietMode, graphConfig, useDarkTheme, useDarkTrayIcon, useGlyphTrayIcon, maximumLogLines,
                            maxJumpListCount, recentConnections, useOldShareLinkFormat, startMinimized, exitByCloseEvent)
-        JSONSTRUCT_REGISTER(Qv2rayConfig_UI, F(theme, language, quietMode, graphConfig, useDarkTheme, useDarkTrayIcon, useGlyphTrayIcon,
-                                               maximumLogLines, maxJumpListCount, recentConnections, useOldShareLinkFormat, startMinimized, exitByCloseEvent))
+        JSONSTRUCT_REGISTER(Qv2rayConfig_UI,
+                            F(theme, language, quietMode, graphConfig, useDarkTheme, useDarkTrayIcon, useGlyphTrayIcon, maximumLogLines,
+                              maxJumpListCount, recentConnections, useOldShareLinkFormat, startMinimized, exitByCloseEvent))
     };
 
     struct Qv2rayConfig_Plugin
@@ -77,6 +78,12 @@ namespace Qv2ray::base::config
         JSONSTRUCT_REGISTER(Qv2rayConfig_Plugin, F(pluginStates, v2rayIntegration, portAllocationStart))
     };
 
+    enum CoreVersion
+    {
+        COREVERSION_V2RAY_V5,
+        COREVERSION_V2RAY_V4
+    };
+
     struct Qv2rayConfig_Kernel
     {
         bool enableAPI = true;
@@ -89,6 +96,8 @@ namespace Qv2ray::base::config
         QString v2CorePath_win;
         QString v2AssetsPath_win;
 
+        CoreVersion coreVersion = (CoreVersion) 0;
+
 #ifdef Q_OS_LINUX
 #define _VARNAME_VCOREPATH_ v2CorePath_linux
 #define _VARNAME_VASSETSPATH_ v2AssetsPath_linux
@@ -113,11 +122,13 @@ namespace Qv2ray::base::config
 #undef _VARNAME_VASSETSPATH_
 
         JSONSTRUCT_COMPARE(Qv2rayConfig_Kernel, enableAPI, statsPort, //
+                           coreVersion,                               //
                            v2CorePath_linux, v2AssetsPath_linux,      //
                            v2CorePath_macx, v2AssetsPath_macx,        //
                            v2CorePath_win, v2AssetsPath_win)
         JSONSTRUCT_REGISTER(Qv2rayConfig_Kernel,                     //
                             F(enableAPI, statsPort),                 //
+                            F(coreVersion),                          //
                             F(v2CorePath_linux, v2AssetsPath_linux), //
                             F(v2CorePath_macx, v2AssetsPath_macx),   //
                             F(v2CorePath_win, v2AssetsPath_win))
diff --git a/src/core/kernel/V2RayKernelInteractions.cpp b/src/core/kernel/V2RayKernelInteractions.cpp
index e4f8d89a7..6aa3fc3a5 100644
--- a/src/core/kernel/V2RayKernelInteractions.cpp
+++ b/src/core/kernel/V2RayKernelInteractions.cpp
@@ -9,18 +9,11 @@
 #define QV2RAY_GENERATED_FILE_PATH (QV2RAY_GENERATED_DIR + "config.gen.json")
 #define QV_MODULE_NAME "V2RayInteraction"
 
-#ifdef QV2RAY_USE_V5_CORE
-#define V2RAY_CORE_VERSION_ARGV "version"
-#define V2RAY_CORE_CONFIG_ARGV "run", "-config"
-#else
-#define V2RAY_CORE_VERSION_ARGV "--version"
-#define V2RAY_CORE_CONFIG_ARGV "--config"
-#endif
-
 namespace Qv2ray::core::kernel
 {
 #if QV2RAY_FEATURE(kernel_check_permission)
-    std::pair<bool, std::optional<QString>> V2RayKernelInstance::CheckAndSetCoreExecutableState(const QString &vCorePath)
+    std::pair<bool, std::optional<QString>> V2RayKernelInstance::CheckAndSetCoreExecutableState(const Qv2ray::base::config::CoreVersion vCoreVersion,
+                                                                                                const QString &vCorePath)
     {
 #ifdef Q_OS_UNIX
         // For Linux/macOS users: if they cannot execute the core,
@@ -59,7 +52,8 @@ namespace Qv2ray::core::kernel
     }
 #endif
 
-    std::pair<bool, std::optional<QString>> V2RayKernelInstance::ValidateKernel(const QString &corePath, const QString &assetsPath)
+    std::pair<bool, std::optional<QString>> V2RayKernelInstance::ValidateKernel(const Qv2ray::base::config::CoreVersion coreVersion,
+                                                                                const QString &corePath, const QString &assetsPath)
     {
         QFile coreFile(corePath);
 
@@ -114,7 +108,7 @@ namespace Qv2ray::core::kernel
 
 #if QV2RAY_FEATURE(kernel_check_permission)
         // Check executable permissions.
-        const auto [isExecutableOk, strExecutableErr] = CheckAndSetCoreExecutableState(corePath);
+        const auto [isExecutableOk, strExecutableErr] = CheckAndSetCoreExecutableState(coreVersion, corePath);
         if (!isExecutableOk)
             return { false, strExecutableErr.value_or("") };
 #endif
@@ -140,10 +134,23 @@ namespace Qv2ray::core::kernel
         // reason...
         proc.setProcessChannelMode(QProcess::MergedChannels);
         proc.setProgram(corePath);
-        proc.setNativeArguments(V2RAY_CORE_VERSION_ARGV);
+        switch (coreVersion)
+        {
+            case Qv2ray::base::config::COREVERSION_V2RAY_V5: proc.setNativeArguments("version"); break;
+            case Qv2ray::base::config::COREVERSION_V2RAY_V4: proc.setNativeArguments("--version"); break;
+
+            default: break;
+        }
+
         proc.start();
 #else
-        proc.start(corePath, { V2RAY_CORE_VERSION_ARGV });
+        switch (coreVersion)
+        {
+            case Qv2ray::base::config::COREVERSION_V2RAY_V5: proc.start(corePath, { "version" }); break;
+            case Qv2ray::base::config::COREVERSION_V2RAY_V4: proc.start(corePath, { "--version" }); break;
+
+            default: break;
+        }
 #endif
         proc.waitForStarted();
         proc.waitForFinished();
@@ -165,7 +172,8 @@ namespace Qv2ray::core::kernel
     {
         const auto kernelPath = GlobalConfig.kernelConfig.KernelPath();
         const auto assetsPath = GlobalConfig.kernelConfig.AssetsPath();
-        if (const auto &[result, msg] = ValidateKernel(kernelPath, assetsPath); result)
+        const auto coreVersion = GlobalConfig.kernelConfig.coreVersion;
+        if (const auto &[result, msg] = ValidateKernel(coreVersion, kernelPath, assetsPath); result)
         {
             DEBUG("V2Ray version: " + *msg);
             // Append assets location env.
@@ -176,11 +184,17 @@ namespace Qv2ray::core::kernel
             QProcess process;
             process.setProcessEnvironment(env);
             DEBUG("Starting V2Ray core with test options");
-#ifdef QV2RAY_USE_V5_CORE
-            process.start(kernelPath, { "test", "-c", path }, QIODevice::ReadWrite | QIODevice::Text);
-#else
-            process.start(kernelPath, { "-test", "-config", path }, QIODevice::ReadWrite | QIODevice::Text);
-#endif
+            switch (coreVersion)
+            {
+                case Qv2ray::base::config::COREVERSION_V2RAY_V5:
+                    process.start(kernelPath, { "test", "-c", path }, QIODevice::ReadWrite | QIODevice::Text);
+                    break;
+                case Qv2ray::base::config::COREVERSION_V2RAY_V4:
+                    process.start(kernelPath, { "-test", "-config", path }, QIODevice::ReadWrite | QIODevice::Text);
+                    break;
+
+                default: break;
+            }
             process.waitForFinished();
 
             if (process.exitCode() != 0)
@@ -204,17 +218,19 @@ namespace Qv2ray::core::kernel
         vProcess = new QProcess();
         connect(vProcess, &QProcess::readyReadStandardOutput, this,
                 [&]() { emit OnProcessOutputReadyRead(vProcess->readAllStandardOutput().trimmed()); });
-        connect(vProcess, &QProcess::stateChanged, [&](QProcess::ProcessState state) {
-            DEBUG("V2Ray kernel process status changed: " + QVariant::fromValue(state).toString());
-
-            // If V2Ray crashed AFTER we start it.
-            if (kernelStarted && state == QProcess::NotRunning)
-            {
-                LOG("V2Ray kernel crashed.");
-                StopConnection();
-                emit OnProcessErrored("V2Ray kernel crashed.");
-            }
-        });
+        connect(vProcess, &QProcess::stateChanged,
+                [&](QProcess::ProcessState state)
+                {
+                    DEBUG("V2Ray kernel process status changed: " + QVariant::fromValue(state).toString());
+
+                    // If V2Ray crashed AFTER we start it.
+                    if (kernelStarted && state == QProcess::NotRunning)
+                    {
+                        LOG("V2Ray kernel crashed.");
+                        StopConnection();
+                        emit OnProcessErrored("V2Ray kernel crashed.");
+                    }
+                });
         apiWorker = new APIWorker();
         qRegisterMetaType<StatisticsType>();
         qRegisterMetaType<QMap<StatisticsType, QvStatsSpeed>>();
@@ -244,7 +260,17 @@ namespace Qv2ray::core::kernel
         env.insert("v2ray.location.asset", GlobalConfig.kernelConfig.AssetsPath());
         env.insert("XRAY_LOCATION_ASSET", GlobalConfig.kernelConfig.AssetsPath());
         vProcess->setProcessEnvironment(env);
-        vProcess->start(GlobalConfig.kernelConfig.KernelPath(), { V2RAY_CORE_CONFIG_ARGV, filePath }, QIODevice::ReadWrite | QIODevice::Text);
+        switch (GlobalConfig.kernelConfig.coreVersion)
+        {
+            case Qv2ray::base::config::COREVERSION_V2RAY_V5:
+                vProcess->start(GlobalConfig.kernelConfig.KernelPath(), { "run", "-config", filePath }, QIODevice::ReadWrite | QIODevice::Text);
+                break;
+            case Qv2ray::base::config::COREVERSION_V2RAY_V4:
+                vProcess->start(GlobalConfig.kernelConfig.KernelPath(), { "--config", filePath }, QIODevice::ReadWrite | QIODevice::Text);
+                break;
+
+            default: break;
+        }
         vProcess->waitForStarted();
         kernelStarted = true;
 
diff --git a/src/core/kernel/V2RayKernelInteractions.hpp b/src/core/kernel/V2RayKernelInteractions.hpp
index 513d199b5..61991e735 100644
--- a/src/core/kernel/V2RayKernelInteractions.hpp
+++ b/src/core/kernel/V2RayKernelInteractions.hpp
@@ -22,9 +22,9 @@ namespace Qv2ray::core::kernel
         }
         //
         static std::optional<QString> ValidateConfig(const QString &path);
-        static std::pair<bool, std::optional<QString>> ValidateKernel(const QString &vCorePath, const QString &vAssetsPath);
+        static std::pair<bool, std::optional<QString>> ValidateKernel(const Qv2ray::base::config::CoreVersion coreVersion, const QString &vCorePath, const QString &vAssetsPath);
 #if QV2RAY_FEATURE(kernel_check_permission)
-        static std::pair<bool, std::optional<QString>> CheckAndSetCoreExecutableState(const QString &vCorePath);
+        static std::pair<bool, std::optional<QString>> CheckAndSetCoreExecutableState(const Qv2ray::base::config::CoreVersion coreVersion, const QString &vCorePath);
 #endif
 
       signals:
diff --git a/src/core/settings/SettingsBackend.cpp b/src/core/settings/SettingsBackend.cpp
index 8e60cf70f..f3c79f6bb 100644
--- a/src/core/settings/SettingsBackend.cpp
+++ b/src/core/settings/SettingsBackend.cpp
@@ -214,6 +214,7 @@ namespace Qv2ray::core::config
 
             GlobalConfig.kernelConfig.KernelPath(QV2RAY_DEFAULT_VCORE_PATH);
             GlobalConfig.kernelConfig.AssetsPath(QV2RAY_DEFAULT_VASSETS_PATH);
+            GlobalConfig.kernelConfig.coreVersion = Qv2ray::base::config::COREVERSION_V2RAY_V5;
             GlobalConfig.logLevel = 3;
             GlobalConfig.uiConfig.language = QLocale::system().name();
             GlobalConfig.defaultRouteConfig.dnsConfig.servers.append({ "1.1.1.1" });
diff --git a/src/ui/widgets/windows/w_PreferencesWindow.cpp b/src/ui/widgets/windows/w_PreferencesWindow.cpp
index 1ad6d57f4..4eec822e5 100644
--- a/src/ui/widgets/windows/w_PreferencesWindow.cpp
+++ b/src/ui/widgets/windows/w_PreferencesWindow.cpp
@@ -93,6 +93,7 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QvDialog("PreferenceWind
     glyphTrayCB->setChecked(CurrentConfig.uiConfig.useGlyphTrayIcon);
     languageComboBox->setCurrentText(CurrentConfig.uiConfig.language);
     logLevelComboBox->setCurrentIndex(CurrentConfig.logLevel);
+    coreVersionComboBox->setCurrentIndex(CurrentConfig.kernelConfig.coreVersion);
     quietModeCB->setChecked(CurrentConfig.uiConfig.quietMode);
     useOldShareLinkFormatCB->setChecked(CurrentConfig.uiConfig.useOldShareLinkFormat);
     startMinimizedCB->setChecked(CurrentConfig.uiConfig.startMinimized);
@@ -317,7 +318,7 @@ QvMessageBusSlotImpl(PreferencesWindow)
     }
 }
 
-PreferencesWindow::~PreferencesWindow(){};
+PreferencesWindow::~PreferencesWindow() {};
 
 std::optional<QString> PreferencesWindow::checkTProxySettings() const
 {
@@ -463,6 +464,12 @@ void PreferencesWindow::on_logLevelComboBox_currentIndexChanged(int index)
     CurrentConfig.logLevel = index;
 }
 
+void PreferencesWindow::on_coreVersionComboBox_currentIndexChanged(int index)
+{
+    NEEDRESTART
+    CurrentConfig.kernelConfig.coreVersion = (Qv2ray::base::config::CoreVersion) index;
+}
+
 void PreferencesWindow::on_vCoreAssetsPathTxt_textEdited(const QString &arg1)
 {
     NEEDRESTART
@@ -752,6 +759,7 @@ void PreferencesWindow::on_fpPortSB_valueChanged(int arg1)
 
 void PreferencesWindow::on_checkVCoreSettings_clicked()
 {
+    auto vcoreVersion = (Qv2ray::base::config::CoreVersion) coreVersionComboBox->currentIndex();
     auto vcorePath = vCorePathTxt->text();
     auto vAssetsPath = vCoreAssetsPathTxt->text();
 
@@ -764,7 +772,6 @@ void PreferencesWindow::on_checkVCoreSettings_clicked()
                                 "If your V2Ray core filename happened to be 'qv2ray'-something, you are totally free to ignore this warning.");
         QvMessageBoxWarn(this, tr("Watch Out!"), content);
     }
-#if !defined(QV2RAY_USE_V5_CORE)
     else if (vCorePathSmallCased.endsWith("v2ctl") || vCorePathSmallCased.endsWith("v2ctl.exe"))
     {
         const auto content = tr("You may be about to set V2Ray core incorrectly to V2Ray Control executable, which is absolutely not correct.\r\n"
@@ -772,10 +779,9 @@ void PreferencesWindow::on_checkVCoreSettings_clicked()
                                 "If you insist to proceed, we're not providing with any support.");
         QvMessageBoxWarn(this, tr("Watch Out!"), content);
     }
-#endif
 #endif
 
-    if (const auto &&[result, msg] = V2RayKernelInstance::ValidateKernel(vcorePath, vAssetsPath); !result)
+    if (const auto &&[result, msg] = V2RayKernelInstance::ValidateKernel(vcoreVersion, vcorePath, vAssetsPath); !result)
     {
         QvMessageBoxWarn(this, tr("V2Ray Core Settings"), *msg);
     }
@@ -1131,27 +1137,29 @@ void PreferencesWindow::on_pushButton_clicked()
         return;
 
     auto client = new ntp::NtpClient(this);
-    connect(client, &ntp::NtpClient::replyReceived, [&](const QHostAddress &, quint16, const ntp::NtpReply &reply) {
-        const int offsetSecTotal = reply.localClockOffset() / 1000;
-        if (offsetSecTotal >= 90 || offsetSecTotal <= -90)
-        {
-            const auto inaccurateWarning = tr("Your time offset is %1 seconds, which is too high.") + NEWLINE + //
-                                           tr("Please synchronize your system to use the VMess protocol.");
-            QvMessageBoxWarn(this, tr("Time Inaccurate"), inaccurateWarning.arg(offsetSecTotal));
-        }
-        else if (offsetSecTotal > 15 || offsetSecTotal < -15)
-        {
-            const auto smallErrorWarning = tr("Your time offset is %1 seconds, which is a little high.") + NEWLINE + //
-                                           tr("VMess protocol may still work, but we suggest you synchronize your clock.");
-            QvMessageBoxInfo(this, tr("Time Somewhat Inaccurate"), smallErrorWarning.arg(offsetSecTotal));
-        }
-        else
-        {
-            const auto accurateInfo = tr("Your time offset is %1 seconds, which looks good.") + NEWLINE + //
-                                      tr("VMess protocol may not suffer from time inaccuracy.");
-            QvMessageBoxInfo(this, tr("Time Accurate"), accurateInfo.arg(offsetSecTotal));
-        }
-    });
+    connect(client, &ntp::NtpClient::replyReceived,
+            [&](const QHostAddress &, quint16, const ntp::NtpReply &reply)
+            {
+                const int offsetSecTotal = reply.localClockOffset() / 1000;
+                if (offsetSecTotal >= 90 || offsetSecTotal <= -90)
+                {
+                    const auto inaccurateWarning = tr("Your time offset is %1 seconds, which is too high.") + NEWLINE + //
+                                                   tr("Please synchronize your system to use the VMess protocol.");
+                    QvMessageBoxWarn(this, tr("Time Inaccurate"), inaccurateWarning.arg(offsetSecTotal));
+                }
+                else if (offsetSecTotal > 15 || offsetSecTotal < -15)
+                {
+                    const auto smallErrorWarning = tr("Your time offset is %1 seconds, which is a little high.") + NEWLINE + //
+                                                   tr("VMess protocol may still work, but we suggest you synchronize your clock.");
+                    QvMessageBoxInfo(this, tr("Time Somewhat Inaccurate"), smallErrorWarning.arg(offsetSecTotal));
+                }
+                else
+                {
+                    const auto accurateInfo = tr("Your time offset is %1 seconds, which looks good.") + NEWLINE + //
+                                              tr("VMess protocol may not suffer from time inaccuracy.");
+                    QvMessageBoxInfo(this, tr("Time Accurate"), accurateInfo.arg(offsetSecTotal));
+                }
+            });
 
     const auto hostInfo = QHostInfo::fromName(ntpServer);
     if (hostInfo.error() == QHostInfo::NoError)
diff --git a/src/ui/widgets/windows/w_PreferencesWindow.hpp b/src/ui/widgets/windows/w_PreferencesWindow.hpp
index 356f80748..2423507dc 100644
--- a/src/ui/widgets/windows/w_PreferencesWindow.hpp
+++ b/src/ui/widgets/windows/w_PreferencesWindow.hpp
@@ -39,7 +39,7 @@ class PreferencesWindow
     }
 
   private:
-    void updateColorScheme() override{};
+    void updateColorScheme() override {};
     QvMessageBusSlotDecl override;
 
   private slots:
@@ -48,6 +48,7 @@ class PreferencesWindow
     void on_socksAuthCB_stateChanged(int arg1);
     void on_languageComboBox_currentTextChanged(const QString &arg1);
     void on_logLevelComboBox_currentIndexChanged(int index);
+    void on_coreVersionComboBox_currentIndexChanged(int index);
     void on_vCoreAssetsPathTxt_textEdited(const QString &arg1);
     void on_listenIPTxt_textEdited(const QString &arg1);
     void on_socksPortLE_valueChanged(int arg1);
diff --git a/src/ui/widgets/windows/w_PreferencesWindow.ui b/src/ui/widgets/windows/w_PreferencesWindow.ui
index 396f28fb2..5cf1eb09e 100644
--- a/src/ui/widgets/windows/w_PreferencesWindow.ui
+++ b/src/ui/widgets/windows/w_PreferencesWindow.ui
@@ -752,6 +752,42 @@ Qv2ray will give a more accurate latency value if Enabled, but makes it easy to
            </widget>
           </item>
           <item row="1" column="0">
+           <widget class="QLabel" name="label_95">
+            <property name="text">
+             <string>Core Version</string>
+            </property>
+            <property name="textFormat">
+             <enum>Qt::PlainText</enum>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QComboBox" name="coreVersionComboBox">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="minimumSize">
+             <size>
+              <width>150</width>
+              <height>0</height>
+             </size>
+            </property>
+            <item>
+             <property name="text">
+              <string>5</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>4</string>
+             </property>
+            </item>
+           </widget>
+          </item>
+          <item row="2" column="0">
            <widget class="QLabel" name="label_46">
             <property name="text">
              <string>V2Ray Core Executable Path</string>
@@ -761,7 +797,7 @@ Qv2ray will give a more accurate latency value if Enabled, but makes it easy to
             </property>
            </widget>
           </item>
-          <item row="1" column="1">
+          <item row="2" column="1">
            <layout class="QHBoxLayout" name="horizontalLayout_3">
             <item>
              <widget class="QLineEdit" name="vCorePathTxt">
@@ -779,7 +815,7 @@ Qv2ray will give a more accurate latency value if Enabled, but makes it easy to
             </item>
            </layout>
           </item>
-          <item row="2" column="0">
+          <item row="3" column="0">
            <widget class="QLabel" name="label_15">
             <property name="text">
              <string>V2Ray Assets Directory</string>
@@ -789,7 +825,7 @@ Qv2ray will give a more accurate latency value if Enabled, but makes it easy to
             </property>
            </widget>
           </item>
-          <item row="2" column="1">
+          <item row="3" column="1">
            <layout class="QHBoxLayout" name="horizontalLayout_7">
             <item>
              <widget class="QLineEdit" name="vCoreAssetsPathTxt"/>
@@ -803,21 +839,21 @@ Qv2ray will give a more accurate latency value if Enabled, but makes it easy to
             </item>
            </layout>
           </item>
-          <item row="7" column="0" colspan="2">
+          <item row="8" column="0" colspan="2">
            <widget class="QPushButton" name="checkVCoreSettings">
             <property name="text">
              <string>Check V2Ray Core Settings</string>
             </property>
            </widget>
           </item>
-          <item row="8" column="0" colspan="2">
+          <item row="9" column="0" colspan="2">
            <widget class="QPushButton" name="pushButton">
             <property name="text">
              <string>Check System Date and Time from the Internet</string>
             </property>
            </widget>
           </item>
-          <item row="3" column="0">
+          <item row="4" column="0">
            <widget class="QLabel" name="label_68">
             <property name="text">
              <string>V2Ray API Subsystem</string>
@@ -827,14 +863,14 @@ Qv2ray will give a more accurate latency value if Enabled, but makes it easy to
             </property>
            </widget>
           </item>
-          <item row="3" column="1">
+          <item row="4" column="1">
            <widget class="QCheckBox" name="enableAPI">
             <property name="text">
              <string>Enabled</string>
             </property>
            </widget>
           </item>
-          <item row="4" column="0">
+          <item row="5" column="0">
            <widget class="QLabel" name="label_8">
             <property name="text">
              <string>V2Ray API Port</string>
@@ -844,7 +880,7 @@ Qv2ray will give a more accurate latency value if Enabled, but makes it easy to
             </property>
            </widget>
           </item>
-          <item row="4" column="1">
+          <item row="5" column="1">
            <widget class="QSpinBox" name="statsPortBox">
             <property name="minimumSize">
              <size>
@@ -863,14 +899,14 @@ Qv2ray will give a more accurate latency value if Enabled, but makes it easy to
             </property>
            </widget>
           </item>
-          <item row="5" column="0">
+          <item row="6" column="0">
            <widget class="QLabel" name="label_37">
             <property name="text">
              <string>Outbound Statistics (V2Ray Core v4.26+)</string>
             </property>
            </widget>
           </item>
-          <item row="5" column="1">
+          <item row="6" column="1">
            <widget class="QCheckBox" name="V2RayOutboundStatsCB">
             <property name="toolTip">
              <string>Currently:
@@ -883,14 +919,14 @@ Qv2ray will give a more accurate latency value if Enabled, but makes it easy to
             </property>
            </widget>
           </item>
-          <item row="6" column="0">
+          <item row="7" column="0">
            <widget class="QLabel" name="label_31">
             <property name="text">
              <string>Include Direct Connection</string>
             </property>
            </widget>
           </item>
-          <item row="6" column="1">
+          <item row="7" column="1">
            <widget class="QCheckBox" name="hasDirectStatisticsCB">
             <property name="text">
              <string>Enabled</string>
diff --git a/translations/en_US.ts b/translations/en_US.ts
index 10524c3f3..148ed234b 100644
--- a/translations/en_US.ts
+++ b/translations/en_US.ts
@@ -1402,6 +1402,10 @@ For example, for updating subscriptions.</source>
         <source>Log Level</source>
         <translation type="unfinished"></translation>
     </message>
+    <message>
+        <source>Core Version</source>
+        <translation type="unfinished"></translation>
+    </message>
     <message>
         <source>none</source>
         <translation type="unfinished"></translation>
