From ee1347411e2b64dfa9306e8eaef50e62a39b6e96 Mon Sep 17 00:00:00 2001 From: Stas Skokov <7090stas@gmail.com> Date: Wed, 8 Apr 2026 23:13:32 +1000 Subject: [PATCH 1/5] camouflage-tls --- app/src/main/cpp/CMakeLists.txt | 6 ++- app/src/main/cpp/conanfile.py | 3 ++ app/src/main/cpp/libs/fptn | 2 +- app/src/main/cpp/src/https_client.cpp | 12 ++++- app/src/main/cpp/src/websocket_client.cpp | 22 ++++++--- .../vpn/enums/BypassCensorshipMethod.java | 6 ++- .../speedtest/NativeSpeedTestTask.java | 3 -- .../websocket/NativeHttpsClientImpl.java | 11 ++++- .../websocket/NativeWebSocketClientImpl.java | 18 +++++-- .../bypassmethod/BypassMethodsActivity.java | 48 ++++++++++++++++--- .../layout/settings_bypass_methods_layout.xml | 48 +++++++++++++++---- app/src/main/res/values-fa-rIR/strings.xml | 4 ++ app/src/main/res/values-ru-rRU/strings.xml | 4 ++ app/src/main/res/values/strings.xml | 5 ++ 14 files changed, 155 insertions(+), 37 deletions(-) diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 9ff43316..1083b650 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -24,6 +24,8 @@ include_directories(src/) include_directories(libs/fptn/src/) include_directories(libs/fptn/src/fptn-protocol-lib) +include(libs/fptn/depends/cmake/CamouflageTLS.cmake) # FIXME + # disable pcap++ add_compile_definitions(FPTN_IP_ADDRESS_WITHOUT_PCAP) @@ -46,7 +48,9 @@ target_link_libraries( android log fptn::fptn - nlohmann_json::nlohmann_json) + nlohmann_json::nlohmann_json + camouflage-tls # FIXME +) target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE FPTN_VERSION=\"0.0.0\") set_target_properties( diff --git a/app/src/main/cpp/conanfile.py b/app/src/main/cpp/conanfile.py index b8e46aa7..909f04f7 100644 --- a/app/src/main/cpp/conanfile.py +++ b/app/src/main/cpp/conanfile.py @@ -23,6 +23,9 @@ class FptnLib(ConanFile): def requirements(self): self._register_local_recipe("fptn", "fptn", "0.0.0") + def build_requirements(self): + self.test_requires("gtest/1.17.0") + def layout(self): cmake_layout(self) diff --git a/app/src/main/cpp/libs/fptn b/app/src/main/cpp/libs/fptn index 9cbfdb3a..d7197c2e 160000 --- a/app/src/main/cpp/libs/fptn +++ b/app/src/main/cpp/libs/fptn @@ -1 +1 @@ -Subproject commit 9cbfdb3ac88ac274c4c571884687482a2ea1aa23 +Subproject commit d7197c2ecf72ceb5d4a91aba4db61f568a6ffe21 diff --git a/app/src/main/cpp/src/https_client.cpp b/app/src/main/cpp/src/https_client.cpp index c0fbebfb..71406ad0 100644 --- a/app/src/main/cpp/src/https_client.cpp +++ b/app/src/main/cpp/src/https_client.cpp @@ -111,10 +111,18 @@ Java_org_fptn_vpn_services_websocket_NativeHttpsClientImpl_nativeCreate( fptn::protocol::https::CensorshipStrategy::kSni; if (censorship_strategy_name == "OBFUSCATION") { censorship_strategy = fptn::protocol::https::CensorshipStrategy::kTlsObfuscator; - } else if (censorship_strategy_name == "SNI-REALITY") { + } else if (censorship_strategy_name == "SNI-REALITY") { // deprecated censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityMode; + } else if (censorship_strategy_name == "SNI-REALITY-CHROME-146") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeChrome146; + } else if (censorship_strategy_name == "SNI-REALITY-FIREFOX-149") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeFirefox149; + } else if (censorship_strategy_name == "SNI-REALITY-YANDEX-26") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeYandex26; + } else if (censorship_strategy_name == "SNI-REALITY-YANDEX-25") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeYandex25; } - + auto* https_client = new WrapperHttpsClient(env, global_object_ref, std::move(host), port, std::move(sni), std::move(md5_fingerprint), censorship_strategy); return reinterpret_cast(https_client); diff --git a/app/src/main/cpp/src/websocket_client.cpp b/app/src/main/cpp/src/websocket_client.cpp index de45448f..9b29da97 100644 --- a/app/src/main/cpp/src/websocket_client.cpp +++ b/app/src/main/cpp/src/websocket_client.cpp @@ -81,15 +81,23 @@ Java_org_fptn_vpn_services_websocket_NativeWebSocketClientImpl_nativeCreate( auto expected_md5_fingerprint = fptn::wrapper::ConvertToCString(env, expected_md5_fingerprint_param); - const auto censorship_strategy_name = fptn::wrapper::ConvertToCString( + const auto censorship_strategy_name = fptn::wrapper::ConvertToCString( env,censorship_strategy_name_param); - fptn::protocol::https::CensorshipStrategy censorship_strategy = + fptn::protocol::https::CensorshipStrategy censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSni; - if (censorship_strategy_name == "OBFUSCATION") { - censorship_strategy = fptn::protocol::https::CensorshipStrategy::kTlsObfuscator; - } else if (censorship_strategy_name == "SNI-REALITY") { - censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityMode; - } + if (censorship_strategy_name == "OBFUSCATION") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kTlsObfuscator; + } else if (censorship_strategy_name == "SNI-REALITY") { // deprecated + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityMode; + } else if (censorship_strategy_name == "SNI-REALITY-CHROME-146") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeChrome146; + } else if (censorship_strategy_name == "SNI-REALITY-FIREFOX-149") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeFirefox149; + } else if (censorship_strategy_name == "SNI-REALITY-YANDEX-26") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeYandex26; + } else if (censorship_strategy_name == "SNI-REALITY-YANDEX-25") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeYandex25; + } jobject global_object_ref = env->NewWeakGlobalRef(thiz); auto* websocket_client = new WrapperWebsocketClient(global_object_ref, diff --git a/app/src/main/java/org/fptn/vpn/enums/BypassCensorshipMethod.java b/app/src/main/java/org/fptn/vpn/enums/BypassCensorshipMethod.java index 779f699e..e67f4163 100644 --- a/app/src/main/java/org/fptn/vpn/enums/BypassCensorshipMethod.java +++ b/app/src/main/java/org/fptn/vpn/enums/BypassCensorshipMethod.java @@ -3,5 +3,9 @@ public enum BypassCensorshipMethod { SNI_SPOOFING, TLS_OBFUSCATION, - SNI_REALITY + SNI_REALITY, // deprecated + SNI_REALITY_CHROME_146, + SNI_REALITY_FIREFOX_149, + SNI_REALITY_YANDEX_26, + SNI_REALITY_YANDEX_25 } diff --git a/app/src/main/java/org/fptn/vpn/services/speedtest/NativeSpeedTestTask.java b/app/src/main/java/org/fptn/vpn/services/speedtest/NativeSpeedTestTask.java index c4e0a7db..a3e08b9f 100644 --- a/app/src/main/java/org/fptn/vpn/services/speedtest/NativeSpeedTestTask.java +++ b/app/src/main/java/org/fptn/vpn/services/speedtest/NativeSpeedTestTask.java @@ -36,15 +36,12 @@ public NativeSpeedTestTask(ServerEntity serverEntity, String sniHost, BypassCens @Override public NativeSpeedTestResult call() throws PVNClientException { Instant start = Instant.now(); - Log.d(getTag(), "call() start test: " + serverEntity.getName()); NativeResponse response = nativeHttpsClient.Get(GET_FILE_PATH, TIMEOUT); if (response.code == 200) { Instant end = Instant.now(); long durationsMillis = Duration.between(start, end).toMillis(); - Log.d(getTag(), "call() end test: " + serverEntity.getName() + " duration: " + durationsMillis + " ms"); return new NativeSpeedTestResult(serverEntity, durationsMillis); } else { - Log.d(getTag(), "call() end test: " + serverEntity.getName() + " error: " + response.errorMessage); throw new PVNClientException(response.errorMessage); } } diff --git a/app/src/main/java/org/fptn/vpn/services/websocket/NativeHttpsClientImpl.java b/app/src/main/java/org/fptn/vpn/services/websocket/NativeHttpsClientImpl.java index 9e3bdc2f..8e126829 100644 --- a/app/src/main/java/org/fptn/vpn/services/websocket/NativeHttpsClientImpl.java +++ b/app/src/main/java/org/fptn/vpn/services/websocket/NativeHttpsClientImpl.java @@ -21,10 +21,17 @@ public NativeHttpsClientImpl(String serverIP, String censorshipStrategyName = "SNI"; if (censorshipStrategy == BypassCensorshipMethod.TLS_OBFUSCATION) { censorshipStrategyName = "OBFUSCATION"; - } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY) { + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY) { // deprecated censorshipStrategyName = "SNI-REALITY"; + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_CHROME_146) { + censorshipStrategyName = "SNI-REALITY-CHROME-146"; + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_FIREFOX_149) { + censorshipStrategyName = "SNI-REALITY-FIREFOX-149"; + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_26) { + censorshipStrategyName = "SNI-REALITY-YANDEX-26"; + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_25) { + censorshipStrategyName = "SNI-REALITY-YANDEX-25"; } - this.nativeHandle = nativeCreate( serverIP, serverPort, diff --git a/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java b/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java index 83cf84d2..027ff0b6 100644 --- a/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java +++ b/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java @@ -43,11 +43,19 @@ public NativeWebSocketClientImpl( this.onMessageReceivedCallback = onMessageReceivedCallback; this.onFailureCallback = onFailureCallback; - String censorship_strategy_name = "SNI"; + String censorshipStrategyName = "SNI"; if (censorship_strategy == BypassCensorshipMethod.TLS_OBFUSCATION) { - censorship_strategy_name = "OBFUSCATION"; - } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY) { - censorship_strategy_name = "SNI-REALITY"; + censorshipStrategyName = "OBFUSCATION"; + } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY) { // deprecated + censorshipStrategyName = "SNI-REALITY"; + } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY_CHROME_146) { + censorshipStrategyName = "SNI-REALITY-CHROME-146"; + } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY_FIREFOX_149) { + censorshipStrategyName = "SNI-REALITY-FIREFOX-149"; + } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_26) { + censorshipStrategyName = "SNI-REALITY-YANDEX-26"; + } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_25) { + censorshipStrategyName = "SNI-REALITY-YANDEX-25"; } this.nativeHandle = nativeCreate( @@ -57,7 +65,7 @@ public NativeWebSocketClientImpl( sniHostName, accessToken, md5ServerFingerprint, - censorship_strategy_name + censorshipStrategyName ); this.serialNum = SERIAL_NUM.getAndIncrement(); diff --git a/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java b/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java index ed40d512..cb92d241 100644 --- a/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java +++ b/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java @@ -56,27 +56,61 @@ private void initializeVariable() { } else if (checkedId == R.id.obfuscation_radio_button) { Log.d(TAG, "Selected TLS obfuscation"); viewModel.setBypassMethod(BypassCensorshipMethod.TLS_OBFUSCATION); - } else if (checkedId == R.id.sni_reality_radio_button) { - Log.d(TAG, "Selected SNI Reality"); - viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY); + } else if (checkedId == R.id.sni_reality_radio_button_chrome_146) { + Log.d(TAG, "Selected SNI Reality Chrome 146"); + viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_CHROME_146); + } else if (checkedId == R.id.sni_reality_radio_button_firefox_149) { + Log.d(TAG, "Selected SNI Reality Firefox 149"); + viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_FIREFOX_149); + } else if (checkedId == R.id.sni_reality_radio_button_yandex_26) { + Log.d(TAG, "Selected SNI Reality Yandex 26"); + viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_YANDEX_26); + } else if (checkedId == R.id.sni_reality_radio_button_yandex_25) { + Log.d(TAG, "Selected SNI Reality Yandex 25"); + viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_YANDEX_25); + } else { + // default + viewModel.setBypassMethod(BypassCensorshipMethod.SNI_SPOOFING); } }); RadioButton sniSpoofingRadioButton = findViewById(R.id.sni_spoofing_radio_button); RadioButton obfuscationRadioButton = findViewById(R.id.obfuscation_radio_button); - RadioButton sniRealityRadioButton = findViewById(R.id.sni_reality_radio_button); + + RadioButton sniRealityRadioButtonChrome146 = findViewById(R.id.sni_reality_radio_button_chrome_146); + RadioButton sniRealityRadioButtonFirefox149 = findViewById(R.id.sni_reality_radio_button_firefox_149); + RadioButton sniRealityRadioButtonYandex26 = findViewById(R.id.sni_reality_radio_button_yandex_26); + RadioButton sniRealityRadioButtonYandex25 = findViewById(R.id.sni_reality_radio_button_yandex_25); + viewModel.getBypassCensorshipMethodMutableLiveData().observe(this, bypassCensorshipMethod -> { switch (bypassCensorshipMethod) { case SNI_SPOOFING: sniSpoofingRadioButton.setChecked(true); - ViewUtils.showView(sniLayout); + ViewUtils.hideView(sniLayout); break; case TLS_OBFUSCATION: obfuscationRadioButton.setChecked(true); ViewUtils.hideView(sniLayout); break; - case SNI_REALITY: - sniRealityRadioButton.setChecked(true); + case SNI_REALITY: // deprecated + case SNI_REALITY_CHROME_146: + sniRealityRadioButtonChrome146.setChecked(true); + ViewUtils.showView(sniLayout); + break; + case SNI_REALITY_FIREFOX_149: + sniRealityRadioButtonFirefox149.setChecked(true); + ViewUtils.showView(sniLayout); + break; + case SNI_REALITY_YANDEX_26: + sniRealityRadioButtonYandex26.setChecked(true); + ViewUtils.showView(sniLayout); + break; + case SNI_REALITY_YANDEX_25: + sniRealityRadioButtonYandex25.setChecked(true); + ViewUtils.showView(sniLayout); + break; + default: + sniSpoofingRadioButton.setChecked(true); ViewUtils.showView(sniLayout); break; } diff --git a/app/src/main/res/layout/settings_bypass_methods_layout.xml b/app/src/main/res/layout/settings_bypass_methods_layout.xml index c87cd3d1..62b0eb38 100644 --- a/app/src/main/res/layout/settings_bypass_methods_layout.xml +++ b/app/src/main/res/layout/settings_bypass_methods_layout.xml @@ -9,7 +9,7 @@ @@ -99,24 +99,56 @@ android:id="@+id/obfuscation_radio_button" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="1dp" - android:layout_marginEnd="12dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" android:text="@string/tls_handshake_obfuscation_radio_button_label" android:textSize="14sp" android:textStyle="bold" /> + + + + + + diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 955fdd16..357041a1 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -162,6 +162,10 @@ https://play.google.com/store/apps/details?id=org.fptn.vpn جعل دامنه (SNI) مخفی‌سازی ترافیک (Obfuscation) جعل پیشرفتهٔ دامنه (SNI + REALITY) + Chrome 146 (SNI + REALITY) جعل پیشرفتهٔ دامنه + Firefox 149 (SNI + REALITY) جعل پیشرفتهٔ دامنه + Yandex 26 (SNI + REALITY) جعل پیشرفتهٔ دامنه + Yandex 25 (SNI + REALITY) جعل پیشرفتهٔ دامنه نیازمند یک دامنهٔ واقعی و فعال است (در غیر این صورت ممکن است خطا رخ دهد) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index becfbeaa..ca341bfd 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -161,6 +161,10 @@ https://play.google.com/store/apps/details?id=org.fptn.vpn Подмена домена (SNI) Маскировка трафика (обфускация) Продвинутая подмена домена (SNI + REALITY) + Продвинутая подмена домена (SNI + REALITY) - Chrome 146 + Продвинутая подмена домена(SNI + REALITY) - Firefox 149 + Продвинутая подмена домена (SNI + REALITY) - Yandex 26 + Продвинутая подмена домена (SNI + REALITY) - Yandex 25 Требуется реальный существующий домен (иначе возможны ошибки) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 58748446..768997ae 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -227,6 +227,11 @@ https://play.google.com/store/apps/details?id=org.fptn.vpn Domain spoofing (SNI) Traffic masking (obfuscation) Advanced domain spoofing (SNI + REALITY) + Advanced domain spoofing (SNI + REALITY) - Chrome 146 + Advanced domain spoofing (SNI + REALITY) - Firefox 149 + Advanced domain spoofing (SNI + REALITY) - Yandex 26 + Advanced domain spoofing (SNI + REALITY) - Yandex 25 + Requires a real existing domain (otherwise errors may occur) Selecting the best server... From b6578caf17ec24524122a3326089aab62eb8bdf7 Mon Sep 17 00:00:00 2001 From: Stas Skokov <7090stas@gmail.com> Date: Wed, 22 Apr 2026 17:06:35 +1000 Subject: [PATCH 2/5] fix camouflage --- app/src/main/cpp/libs/fptn | 2 +- app/src/main/cpp/src/https_client.cpp | 23 +- app/src/main/cpp/src/websocket_client.cpp | 20 +- .../wrapper_websocket_client.cpp | 7 +- .../vpn/enums/BypassCensorshipMethod.java | 12 +- .../websocket/NativeHttpsClientImpl.java | 22 +- .../websocket/NativeWebSocketClientImpl.java | 32 ++- .../bypassmethod/BypassMethodsActivity.java | 80 +++++-- .../layout/settings_bypass_methods_layout.xml | 209 ++++++++++-------- app/src/main/res/values-fa-rIR/strings.xml | 16 +- app/src/main/res/values-ru-rRU/strings.xml | 16 +- app/src/main/res/values/strings.xml | 16 +- 12 files changed, 305 insertions(+), 150 deletions(-) diff --git a/app/src/main/cpp/libs/fptn b/app/src/main/cpp/libs/fptn index d7197c2e..b86b9b25 160000 --- a/app/src/main/cpp/libs/fptn +++ b/app/src/main/cpp/libs/fptn @@ -1 +1 @@ -Subproject commit d7197c2ecf72ceb5d4a91aba4db61f568a6ffe21 +Subproject commit b86b9b25cfe455ff48f4face60dfc515ca69ee68 diff --git a/app/src/main/cpp/src/https_client.cpp b/app/src/main/cpp/src/https_client.cpp index 71406ad0..51b1b4e1 100644 --- a/app/src/main/cpp/src/https_client.cpp +++ b/app/src/main/cpp/src/https_client.cpp @@ -113,16 +113,33 @@ Java_org_fptn_vpn_services_websocket_NativeHttpsClientImpl_nativeCreate( censorship_strategy = fptn::protocol::https::CensorshipStrategy::kTlsObfuscator; } else if (censorship_strategy_name == "SNI-REALITY") { // deprecated censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityMode; + } + /* Chrome */ + else if (censorship_strategy_name == "SNI-REALITY-CHROME-147") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeChrome147; } else if (censorship_strategy_name == "SNI-REALITY-CHROME-146") { censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeChrome146; - } else if (censorship_strategy_name == "SNI-REALITY-FIREFOX-149") { + } else if (censorship_strategy_name == "SNI-REALITY-CHROME-145") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeChrome145; + } + /* Firefox */ + else if (censorship_strategy_name == "SNI-REALITY-FIREFOX-149") { censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeFirefox149; - } else if (censorship_strategy_name == "SNI-REALITY-YANDEX-26") { + } + /* Yandex */ + else if (censorship_strategy_name == "SNI-REALITY-YANDEX-26") { censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeYandex26; } else if (censorship_strategy_name == "SNI-REALITY-YANDEX-25") { censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeYandex25; + } else if (censorship_strategy_name == "SNI-REALITY-YANDEX-24") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeYandex24; } - + /* Safari */ + else if (censorship_strategy_name == "SNI-REALITY-SAFARI-26") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeSafari26; + } + + auto* https_client = new WrapperHttpsClient(env, global_object_ref, std::move(host), port, std::move(sni), std::move(md5_fingerprint), censorship_strategy); return reinterpret_cast(https_client); diff --git a/app/src/main/cpp/src/websocket_client.cpp b/app/src/main/cpp/src/websocket_client.cpp index 9b29da97..3ed77974 100644 --- a/app/src/main/cpp/src/websocket_client.cpp +++ b/app/src/main/cpp/src/websocket_client.cpp @@ -89,14 +89,30 @@ Java_org_fptn_vpn_services_websocket_NativeWebSocketClientImpl_nativeCreate( censorship_strategy = fptn::protocol::https::CensorshipStrategy::kTlsObfuscator; } else if (censorship_strategy_name == "SNI-REALITY") { // deprecated censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityMode; + } + /* Chrome */ + else if (censorship_strategy_name == "SNI-REALITY-CHROME-147") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeChrome147; } else if (censorship_strategy_name == "SNI-REALITY-CHROME-146") { censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeChrome146; - } else if (censorship_strategy_name == "SNI-REALITY-FIREFOX-149") { + } else if (censorship_strategy_name == "SNI-REALITY-CHROME-145") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeChrome145; + } + /* Firefox */ + else if (censorship_strategy_name == "SNI-REALITY-FIREFOX-149") { censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeFirefox149; - } else if (censorship_strategy_name == "SNI-REALITY-YANDEX-26") { + } + /* Yandex */ + else if (censorship_strategy_name == "SNI-REALITY-YANDEX-26") { censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeYandex26; } else if (censorship_strategy_name == "SNI-REALITY-YANDEX-25") { censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeYandex25; + } else if (censorship_strategy_name == "SNI-REALITY-YANDEX-24") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeYandex24; + } + /* Safari */ + else if (censorship_strategy_name == "SNI-REALITY-SAFARI-26") { + censorship_strategy = fptn::protocol::https::CensorshipStrategy::kSniRealityModeSafari26; } jobject global_object_ref = env->NewWeakGlobalRef(thiz); diff --git a/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.cpp b/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.cpp index 9439fc87..211b09d6 100644 --- a/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.cpp +++ b/app/src/main/cpp/src/wrappers/wrapper_websocket_client/wrapper_websocket_client.cpp @@ -310,9 +310,12 @@ bool WrapperWebsocketClient::Send(std::string pkt) { if (!running_) { return false; } - try { - auto ip_packet = fptn::common::network::IPPacket::Parse(std::move(pkt)); + std::vector packet_data; + packet_data.reserve(pkt.size()); + std::move(pkt.begin(), pkt.end(), std::back_inserter(packet_data)); + + auto ip_packet = fptn::common::network::IPPacket::Parse(std::move(packet_data)); if (!ip_packet) { SPDLOG_ERROR("Failed to parse IP packet in Send"); return false; diff --git a/app/src/main/java/org/fptn/vpn/enums/BypassCensorshipMethod.java b/app/src/main/java/org/fptn/vpn/enums/BypassCensorshipMethod.java index e67f4163..d230c409 100644 --- a/app/src/main/java/org/fptn/vpn/enums/BypassCensorshipMethod.java +++ b/app/src/main/java/org/fptn/vpn/enums/BypassCensorshipMethod.java @@ -1,11 +1,19 @@ package org.fptn.vpn.enums; public enum BypassCensorshipMethod { - SNI_SPOOFING, + SNI_SPOOFING, // deprecated TLS_OBFUSCATION, SNI_REALITY, // deprecated + /* Chrome */ + SNI_REALITY_CHROME_147, SNI_REALITY_CHROME_146, + SNI_REALITY_CHROME_145, + /* Firefox */ SNI_REALITY_FIREFOX_149, + /* Yandex Browser */ SNI_REALITY_YANDEX_26, - SNI_REALITY_YANDEX_25 + SNI_REALITY_YANDEX_25, + SNI_REALITY_YANDEX_24, + /* Safari */ + SNI_REALITY_SAFARI_26 } diff --git a/app/src/main/java/org/fptn/vpn/services/websocket/NativeHttpsClientImpl.java b/app/src/main/java/org/fptn/vpn/services/websocket/NativeHttpsClientImpl.java index 8e126829..2e4824d4 100644 --- a/app/src/main/java/org/fptn/vpn/services/websocket/NativeHttpsClientImpl.java +++ b/app/src/main/java/org/fptn/vpn/services/websocket/NativeHttpsClientImpl.java @@ -18,19 +18,35 @@ public NativeHttpsClientImpl(String serverIP, String md5Fingerprint, String sni, BypassCensorshipMethod censorshipStrategy) { - String censorshipStrategyName = "SNI"; + String censorshipStrategyName = "SNI-REALITY-YANDEX-25"; if (censorshipStrategy == BypassCensorshipMethod.TLS_OBFUSCATION) { censorshipStrategyName = "OBFUSCATION"; } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY) { // deprecated censorshipStrategyName = "SNI-REALITY"; + } + /* Chrome */ + else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_CHROME_147) { + censorshipStrategyName = "SNI-REALITY-CHROME-147"; } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_CHROME_146) { censorshipStrategyName = "SNI-REALITY-CHROME-146"; - } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_FIREFOX_149) { + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_CHROME_145) { + censorshipStrategyName = "SNI-REALITY-CHROME-145"; + } + /* Firefox */ + else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_FIREFOX_149) { censorshipStrategyName = "SNI-REALITY-FIREFOX-149"; - } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_26) { + } + /* Yandex */ + else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_26) { censorshipStrategyName = "SNI-REALITY-YANDEX-26"; } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_25) { censorshipStrategyName = "SNI-REALITY-YANDEX-25"; + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_24) { + censorshipStrategyName = "SNI-REALITY-YANDEX-24"; + } + /* Safari */ + else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_SAFARI_26) { + censorshipStrategyName = "SNI-REALITY-SAFARI-26"; } this.nativeHandle = nativeCreate( serverIP, diff --git a/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java b/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java index 027ff0b6..2130d93a 100644 --- a/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java +++ b/app/src/main/java/org/fptn/vpn/services/websocket/NativeWebSocketClientImpl.java @@ -38,24 +38,40 @@ public NativeWebSocketClientImpl( OnMessageReceivedCallback onMessageReceivedCallback, OnFailureCallback onFailureCallback, String sniHostName, - BypassCensorshipMethod censorship_strategy) throws PVNClientException { + BypassCensorshipMethod censorshipStrategy) throws PVNClientException { this.onOpenCallback = onOpenCallback; this.onMessageReceivedCallback = onMessageReceivedCallback; this.onFailureCallback = onFailureCallback; - String censorshipStrategyName = "SNI"; - if (censorship_strategy == BypassCensorshipMethod.TLS_OBFUSCATION) { + String censorshipStrategyName = "SNI-REALITY-YANDEX-25"; + if (censorshipStrategy == BypassCensorshipMethod.TLS_OBFUSCATION) { censorshipStrategyName = "OBFUSCATION"; - } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY) { // deprecated + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY) { // deprecated censorshipStrategyName = "SNI-REALITY"; - } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY_CHROME_146) { + } + /* Chrome */ + else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_CHROME_147) { + censorshipStrategyName = "SNI-REALITY-CHROME-147"; + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_CHROME_146) { censorshipStrategyName = "SNI-REALITY-CHROME-146"; - } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY_FIREFOX_149) { + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_CHROME_145) { + censorshipStrategyName = "SNI-REALITY-CHROME-145"; + } + /* Firefox */ + else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_FIREFOX_149) { censorshipStrategyName = "SNI-REALITY-FIREFOX-149"; - } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_26) { + } + /* Yandex */ + else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_26) { censorshipStrategyName = "SNI-REALITY-YANDEX-26"; - } else if (censorship_strategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_25) { + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_25) { censorshipStrategyName = "SNI-REALITY-YANDEX-25"; + } else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_YANDEX_24) { + censorshipStrategyName = "SNI-REALITY-YANDEX-24"; + } + /* Safari */ + else if (censorshipStrategy == BypassCensorshipMethod.SNI_REALITY_SAFARI_26) { + censorshipStrategyName = "SNI-REALITY-SAFARI-26"; } this.nativeHandle = nativeCreate( diff --git a/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java b/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java index cb92d241..99c2cc7e 100644 --- a/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java +++ b/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java @@ -50,67 +50,107 @@ private void initializeVariable() { // Setup RadioGroup listener RadioGroup protocolRadioGroup = findViewById(R.id.bypass_method_radio_button_group); protocolRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { - if (checkedId == R.id.sni_spoofing_radio_button) { - Log.d(TAG, "Selected SNI spoofing"); - viewModel.setBypassMethod(BypassCensorshipMethod.SNI_SPOOFING); - } else if (checkedId == R.id.obfuscation_radio_button) { + if (checkedId == R.id.obfuscation_radio_button) { Log.d(TAG, "Selected TLS obfuscation"); viewModel.setBypassMethod(BypassCensorshipMethod.TLS_OBFUSCATION); + } + /* Chrome */ + else if (checkedId == R.id.sni_reality_radio_button_chrome_147) { + Log.d(TAG, "Selected SNI Reality Chrome 147"); + viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_CHROME_147); } else if (checkedId == R.id.sni_reality_radio_button_chrome_146) { Log.d(TAG, "Selected SNI Reality Chrome 146"); viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_CHROME_146); - } else if (checkedId == R.id.sni_reality_radio_button_firefox_149) { + } else if (checkedId == R.id.sni_reality_radio_button_chrome_145) { + Log.d(TAG, "Selected SNI Reality Chrome 145"); + viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_CHROME_145); + } + /* Firefox */ + else if (checkedId == R.id.sni_reality_radio_button_firefox_149) { Log.d(TAG, "Selected SNI Reality Firefox 149"); viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_FIREFOX_149); - } else if (checkedId == R.id.sni_reality_radio_button_yandex_26) { + } + /* Yandex */ + else if (checkedId == R.id.sni_reality_radio_button_yandex_26) { Log.d(TAG, "Selected SNI Reality Yandex 26"); viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_YANDEX_26); } else if (checkedId == R.id.sni_reality_radio_button_yandex_25) { Log.d(TAG, "Selected SNI Reality Yandex 25"); viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_YANDEX_25); + } else if (checkedId == R.id.sni_reality_radio_button_yandex_24) { + Log.d(TAG, "Selected SNI Reality Yandex 24"); + viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_YANDEX_24); + } + /* Safari */ + else if (checkedId == R.id.sni_reality_radio_button_safari_26) { + Log.d(TAG, "Selected SNI Reality Safari 26"); + viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_SAFARI_26); } else { // default - viewModel.setBypassMethod(BypassCensorshipMethod.SNI_SPOOFING); + viewModel.setBypassMethod(BypassCensorshipMethod.SNI_REALITY_YANDEX_25); } }); - RadioButton sniSpoofingRadioButton = findViewById(R.id.sni_spoofing_radio_button); RadioButton obfuscationRadioButton = findViewById(R.id.obfuscation_radio_button); + RadioButton sniRealityRadioButtonChrome147 = findViewById(R.id.sni_reality_radio_button_chrome_147); RadioButton sniRealityRadioButtonChrome146 = findViewById(R.id.sni_reality_radio_button_chrome_146); + RadioButton sniRealityRadioButtonChrome145 = findViewById(R.id.sni_reality_radio_button_chrome_145); + RadioButton sniRealityRadioButtonFirefox149 = findViewById(R.id.sni_reality_radio_button_firefox_149); + RadioButton sniRealityRadioButtonYandex26 = findViewById(R.id.sni_reality_radio_button_yandex_26); RadioButton sniRealityRadioButtonYandex25 = findViewById(R.id.sni_reality_radio_button_yandex_25); + RadioButton sniRealityRadioButtonYandex24 = findViewById(R.id.sni_reality_radio_button_yandex_24); + + RadioButton sniRealityRadioButtonSafari26 = findViewById(R.id.sni_reality_radio_button_safari_26); viewModel.getBypassCensorshipMethodMutableLiveData().observe(this, bypassCensorshipMethod -> { switch (bypassCensorshipMethod) { - case SNI_SPOOFING: - sniSpoofingRadioButton.setChecked(true); - ViewUtils.hideView(sniLayout); - break; case TLS_OBFUSCATION: obfuscationRadioButton.setChecked(true); ViewUtils.hideView(sniLayout); break; - case SNI_REALITY: // deprecated + case SNI_SPOOFING: // deprecated + case SNI_REALITY: // deprecated + /* Yandex */ + case SNI_REALITY_YANDEX_25: + sniRealityRadioButtonYandex25.setChecked(true); + ViewUtils.showView(sniLayout); + break; + case SNI_REALITY_YANDEX_26: + sniRealityRadioButtonYandex26.setChecked(true); + ViewUtils.showView(sniLayout); + break; + case SNI_REALITY_YANDEX_24: + sniRealityRadioButtonYandex24.setChecked(true); + ViewUtils.showView(sniLayout); + break; + /* Chrome */ + case SNI_REALITY_CHROME_147: + sniRealityRadioButtonChrome147.setChecked(true); + ViewUtils.showView(sniLayout); + break; case SNI_REALITY_CHROME_146: sniRealityRadioButtonChrome146.setChecked(true); ViewUtils.showView(sniLayout); break; - case SNI_REALITY_FIREFOX_149: - sniRealityRadioButtonFirefox149.setChecked(true); + case SNI_REALITY_CHROME_145: + sniRealityRadioButtonChrome145.setChecked(true); ViewUtils.showView(sniLayout); break; - case SNI_REALITY_YANDEX_26: - sniRealityRadioButtonYandex26.setChecked(true); + /* Firefox */ + case SNI_REALITY_FIREFOX_149: + sniRealityRadioButtonFirefox149.setChecked(true); ViewUtils.showView(sniLayout); break; - case SNI_REALITY_YANDEX_25: - sniRealityRadioButtonYandex25.setChecked(true); + /* Safari */ + case SNI_REALITY_SAFARI_26: + sniRealityRadioButtonSafari26.setChecked(true); ViewUtils.showView(sniLayout); break; default: - sniSpoofingRadioButton.setChecked(true); + sniRealityRadioButtonYandex25.setChecked(true); ViewUtils.showView(sniLayout); break; } diff --git a/app/src/main/res/layout/settings_bypass_methods_layout.xml b/app/src/main/res/layout/settings_bypass_methods_layout.xml index 62b0eb38..e7005a10 100644 --- a/app/src/main/res/layout/settings_bypass_methods_layout.xml +++ b/app/src/main/res/layout/settings_bypass_methods_layout.xml @@ -39,6 +39,83 @@ android:textSize="18sp" android:textStyle="bold" /> + + + + + + + + + + + + + + + + + - - - - - - - - - - - + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:text="@string/sni_reality_radio_button_label_chrome_145" + android:textSize="14sp" + android:textStyle="bold" /> - - + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:text="@string/sni_reality_radio_button_label_firefox_149" + android:textSize="14sp" + android:textStyle="bold" /> - - - - - + diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 357041a1..fe189b3a 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -162,10 +162,18 @@ https://play.google.com/store/apps/details?id=org.fptn.vpn جعل دامنه (SNI) مخفی‌سازی ترافیک (Obfuscation) جعل پیشرفتهٔ دامنه (SNI + REALITY) - Chrome 146 (SNI + REALITY) جعل پیشرفتهٔ دامنه - Firefox 149 (SNI + REALITY) جعل پیشرفتهٔ دامنه - Yandex 26 (SNI + REALITY) جعل پیشرفتهٔ دامنه - Yandex 25 (SNI + REALITY) جعل پیشرفتهٔ دامنه + + (Chrome 147) جعل پیشرفتهٔ دامنه + (Chrome 146) جعل پیشرفتهٔ دامنه + (Chrome 145) جعل پیشرفتهٔ دامنه + + (Firefox 149) جعل پیشرفتهٔ دامنه + + (Yandex 26) جعل پیشرفتهٔ دامنه + Yandex 25) جعل پیشرفتهٔ دامنه + Yandex 24) جعل پیشرفتهٔ دامنه + + (Safari 26) جعل پیشرفتهٔ دامنه نیازمند یک دامنهٔ واقعی و فعال است (در غیر این صورت ممکن است خطا رخ دهد) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index ca341bfd..75394a17 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -161,10 +161,18 @@ https://play.google.com/store/apps/details?id=org.fptn.vpn Подмена домена (SNI) Маскировка трафика (обфускация) Продвинутая подмена домена (SNI + REALITY) - Продвинутая подмена домена (SNI + REALITY) - Chrome 146 - Продвинутая подмена домена(SNI + REALITY) - Firefox 149 - Продвинутая подмена домена (SNI + REALITY) - Yandex 26 - Продвинутая подмена домена (SNI + REALITY) - Yandex 25 + + Подмена домена (Chrome 147) + Подмена домена (Chrome 146) + Подмена домена (Chrome 145) + + Продвинутая подмена домена (Firefox 149) + + Подмена домена (Yandex 26) + Подмена домена (Yandex 25) + Подмена домена (Yandex 24) + + Подмена домена (Safari 26) Требуется реальный существующий домен (иначе возможны ошибки) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 768997ae..5a3da541 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -227,10 +227,18 @@ https://play.google.com/store/apps/details?id=org.fptn.vpn Domain spoofing (SNI) Traffic masking (obfuscation) Advanced domain spoofing (SNI + REALITY) - Advanced domain spoofing (SNI + REALITY) - Chrome 146 - Advanced domain spoofing (SNI + REALITY) - Firefox 149 - Advanced domain spoofing (SNI + REALITY) - Yandex 26 - Advanced domain spoofing (SNI + REALITY) - Yandex 25 + + Domain spoofing (Chrome 147) + Domain spoofing (Chrome 146) + Domain spoofing (Chrome 145) + + Domain spoofing (Firefox 149) + + Domain spoofing (Yandex 26) + Domain spoofing (Yandex 25) + Domain spoofing (Yandex 24) + + Domain spoofing (Safari 26) Requires a real existing domain (otherwise errors may occur) From 0b727b1b268336081bb8654b0f12607358809f52 Mon Sep 17 00:00:00 2001 From: Stas Skokov <7090stas@gmail.com> Date: Wed, 22 Apr 2026 17:13:32 +1000 Subject: [PATCH 3/5] fix camouflage --- app/src/main/cpp/libs/fptn | 2 +- app/src/main/res/values-ru-rRU/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/cpp/libs/fptn b/app/src/main/cpp/libs/fptn index b86b9b25..9f867896 160000 --- a/app/src/main/cpp/libs/fptn +++ b/app/src/main/cpp/libs/fptn @@ -1 +1 @@ -Subproject commit b86b9b25cfe455ff48f4face60dfc515ca69ee68 +Subproject commit 9f8678964357b6fb7b5814169d82e8e4a3637916 diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 75394a17..96e9f467 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -166,7 +166,7 @@ https://play.google.com/store/apps/details?id=org.fptn.vpn Подмена домена (Chrome 146) Подмена домена (Chrome 145) - Продвинутая подмена домена (Firefox 149) + Подмена домена (Firefox 149) Подмена домена (Yandex 26) Подмена домена (Yandex 25) From 6033881394c4eba166b45a5d7a18ed2aed407d4d Mon Sep 17 00:00:00 2001 From: Stas Skokov <7090stas@gmail.com> Date: Tue, 28 Apr 2026 22:12:00 +1000 Subject: [PATCH 4/5] update libfptn --- app/src/main/cpp/libs/fptn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/cpp/libs/fptn b/app/src/main/cpp/libs/fptn index 9f867896..2b2d0ff6 160000 --- a/app/src/main/cpp/libs/fptn +++ b/app/src/main/cpp/libs/fptn @@ -1 +1 @@ -Subproject commit 9f8678964357b6fb7b5814169d82e8e4a3637916 +Subproject commit 2b2d0ff67f9c68f98f87ce644b3b6e5adfbbd12f From 1662dfaeb7fc49750f81a767b5dcbf8d8b6a74e2 Mon Sep 17 00:00:00 2001 From: Stas Skokov <7090stas@gmail.com> Date: Tue, 28 Apr 2026 22:31:55 +1000 Subject: [PATCH 5/5] fix merge --- .../org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java b/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java index 63f8d8d4..770cc160 100644 --- a/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java +++ b/app/src/main/java/org/fptn/vpn/views/bypassmethod/BypassMethodsActivity.java @@ -49,6 +49,9 @@ public class BypassMethodsActivity extends AppCompatActivity { private final String TAG = this.getClass().getSimpleName(); + + private View sniLayout; + private BypassMethodsViewModel viewModel; private ActivityResultLauncher filePickerLauncher; private AlertDialog autoSelectDialog;