├── .github
├── ISSUE_TEMPLATE
│ ├── bug.yml
│ ├── config.yml
│ └── feature.yml
└── images
│ └── logo.svg
├── .gitignore
├── .gitmodules
├── .idea
├── .gitignore
├── .name
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── gradle.xml
├── kotlinc.xml
├── migrations.xml
├── misc.xml
├── render.experimental.xml
└── vcs.xml
├── LICENSE
├── README-ru.md
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── github
│ │ └── dovecoteescapee
│ │ └── byedpi
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── cpp
│ │ ├── CMakeLists.txt
│ │ ├── main.h
│ │ ├── native-lib.c
│ │ ├── utils.c
│ │ └── utils.h
│ ├── ic_launcher-playstore.png
│ ├── java
│ │ └── io
│ │ │ └── github
│ │ │ └── dovecoteescapee
│ │ │ └── byedpi
│ │ │ ├── activities
│ │ │ ├── MainActivity.kt
│ │ │ └── SettingsActivity.kt
│ │ │ ├── core
│ │ │ ├── ByeDpiProxy.kt
│ │ │ ├── ByeDpiProxyPreferences.kt
│ │ │ └── TProxyService.kt
│ │ │ ├── data
│ │ │ ├── Actions.kt
│ │ │ ├── AppStatus.kt
│ │ │ ├── Broadcasts.kt
│ │ │ └── ServiceStatus.kt
│ │ │ ├── fragments
│ │ │ ├── ByeDpiCommandLineSettingsFragment.kt
│ │ │ ├── ByeDpiUISettingsFragment.kt
│ │ │ └── MainSettingsFragment.kt
│ │ │ ├── services
│ │ │ ├── ByeDpiProxyService.kt
│ │ │ ├── ByeDpiStatus.kt
│ │ │ ├── ByeDpiVpnService.kt
│ │ │ ├── LifecycleVpnService.kt
│ │ │ ├── QuickTileService.kt
│ │ │ └── ServiceManager.kt
│ │ │ └── utility
│ │ │ ├── ArgumentsUtils.kt
│ │ │ ├── NotificationUtils.kt
│ │ │ ├── PreferencesUtils.kt
│ │ │ └── ValidateUtils.kt
│ ├── jni
│ │ ├── Android.mk
│ │ └── Application.mk
│ └── res
│ │ ├── drawable
│ │ ├── baseline_settings_24.xml
│ │ ├── ic_github_36.xml
│ │ └── ic_notification.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── activity_settings.xml
│ │ ├── menu
│ │ ├── menu_main.xml
│ │ └── menu_settings.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ ├── byedpi_cmd_settings.xml
│ │ ├── byedpi_ui_settings.xml
│ │ ├── data_extraction_rules.xml
│ │ └── main_settings.xml
│ └── test
│ └── java
│ └── io
│ └── github
│ └── dovecoteescapee
│ └── byedpi
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── fastlane
└── metadata
│ └── android
│ ├── en-US
│ ├── full_description.txt
│ ├── images
│ │ ├── icon.png
│ │ └── phoneScreenshots
│ │ │ ├── screenshot1.png
│ │ │ ├── screenshot2.png
│ │ │ └── screenshot3.png
│ └── short_description.txt
│ └── ru-RU
│ ├── full_description.txt
│ └── short_description.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report / Сообщение об ошибке
2 | description: File a bug report / Сообщить об ошибке в программе
3 | labels: [ bug ]
4 | assignees: dovecoteescapee
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | ### If you have trouble with specific websites or or can't configure the app, please use the [discussions section](https://github.com/dovecoteescapee/ByeDPIAndroid/discussions) to discuss it. This template is only for reporting bugs in the ByeDPI program.
10 | ### Если у вас не работают какие-то сайты или не получается настроить приложение, используйте [раздел дискуссий](https://github.com/dovecoteescapee/ByeDPIAndroid/discussions) для обсуждения этого. Этот шаблон предназначен только для сообщений об ошибках в программе ByeDPI.
11 | - type: textarea
12 | id: bug
13 | attributes:
14 | label: Describe the bug / Описание ошибки
15 | description: A clear and concise description of what the bug is / Четкое и краткое описание ошибки
16 | validations:
17 | required: true
18 | - type: textarea
19 | id: reproduce
20 | attributes:
21 | label: To Reproduce / Как воспроизвести
22 | description: Steps to reproduce the behavior / Шаги для воспроизведения проблемы
23 | value: |
24 | 1. Go to '...'
25 | 2. Click on '....'
26 | 3. Scroll down to '....'
27 | 4. See error
28 | validations:
29 | required: true
30 | - type: textarea
31 | id: expected
32 | attributes:
33 | label: Expected behavior / Ожидаемое поведение
34 | description: A clear and concise description of what you expected to happen / Четкое и краткое описание ожидаемого поведения
35 | validations:
36 | required: true
37 | - type: textarea
38 | id: screenshots
39 | attributes:
40 | label: Screenshots / Скриншоты
41 | description: If applicable, add screenshots to help explain your problem / При необходимости добавьте скриншоты, чтобы объяснить вашу проблему
42 | - type: textarea
43 | id: environment
44 | attributes:
45 | label: Environment / Окружение
46 | description: Please complete the following information / Пожалуйста, заполните следующую информацию
47 | value: |
48 | **Smartphone / Смартфон:**
49 | - Device: [e.g. Pixel Fold, Samsung Galaxy S24]
50 | - Android version: [e.g. 10]
51 | - ByeDPI version: [e.g. 1.0.2]
52 | validations:
53 | required: true
54 | - type: textarea
55 | id: context
56 | attributes:
57 | label: Additional context / Дополнительная информация
58 | description: Add any other context about the problem here / Добавьте любую другую информацию о проблеме здесь
59 | - type: checkboxes
60 | id: checklist
61 | attributes:
62 | label: Before you submit / Прежде чем отправить
63 | options:
64 | - label: I have searched the open and closed issues for duplicates / Я искал(а) открытые и закрытые задачи на предмет дубликатов
65 | required: true
66 | - label: This is not a question, feature request, or general feedback / Это не вопрос, предложение новой функциональности или общий отзыв
67 | required: true
68 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Questions about the program on Github / Вопросы по програме на Github
4 | url: https://github.com/dovecoteescapee/ByeDPIAndroid/discussions
5 | about: Please do not ask questions in the issue tracker / Пожалуйста, не задавайте вопросы в трекере задач
6 | - name: Questions about the program on NTC.party / Вопросы по програме на NTC.party
7 | url: https://ntc.party/c/community-software/byedpi/39
8 | about: Please do not ask questions in the issue tracker / Пожалуйста, не задавайте вопросы в трекере задач
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.yml:
--------------------------------------------------------------------------------
1 | name: Feature request / Предложить новую функциональность
2 | description: Suggest an idea for this project / Предложить идею для проекта
3 | labels: [ enhancement ]
4 | assignees: dovecoteescapee
5 | body:
6 | - type: textarea
7 | id: problem
8 | attributes:
9 | label: What problem does your proposal solve? / Какую проблему решает ваше предложение?
10 | description: A clear and concise description of what the problem is / Четкое и краткое описание проблемы
11 | validations:
12 | required: true
13 | - type: textarea
14 | id: solution
15 | attributes:
16 | label: Describe the solution you'd like / Опишите решение, которое вы предлагаете
17 | description: A clear and concise description of what you want to happen / Четкое и краткое описание того, что вы хотите, чтобы произошло
18 | validations:
19 | required: true
20 | - type: textarea
21 | id: alternatives
22 | attributes:
23 | label: Describe alternatives you've considered / Опишите альтернативные варианты, которые вы рассматривали
24 | description: A clear and concise description of any alternative solutions or features you've considered / Четкое и краткое описание любых альтернативных решений или функций, которые вы рассматривали
25 | validations:
26 | required: true
27 | - type: checkboxes
28 | id: checklist
29 | attributes:
30 | label: Before you submit / Прежде чем отправить
31 | options:
32 | - label: I have searched the open and closed issues for duplicates / Я искал(а) открытые и закрытые задачи на предмет дубликатов
33 | required: true
34 | - label: I have verified that this feature does not already exist / Я убедился(ась), что эта функция еще не существует
35 | required: true
36 |
--------------------------------------------------------------------------------
/.github/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/deploymentTargetDropDown.xml
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 | /app/src/main/jniLibs
18 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "hev-socks5-tunnel"]
2 | path = app/src/main/jni/hev-socks5-tunnel
3 | url = https://github.com/heiher/hev-socks5-tunnel.git
4 | [submodule "byedpi"]
5 | path = app/src/main/cpp/byedpi
6 | url = https://github.com/hufrea/byedpi.git
7 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | ByeDpi
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/render.experimental.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README-ru.md:
--------------------------------------------------------------------------------
1 | # ByeDPI for Android
2 |
3 | [English](README.md) | **Русский**
4 |
5 |
6 |
7 |
8 |
9 | ---
10 |
11 | Приложение для Android, которое запускает локальный VPN-сервис для обхода DPI (Deep Packet Inspection) и цензуры.
12 |
13 | Приложение локально запускает SOCKS5-прокси [ByeDPI](https://github.com/hufrea/byedpi) и перенаправляет весь трафик через него.
14 |
15 | ## Установка
16 |
17 | [ ](https://github.com/dovecoteescapee/ByeDPIAndroid/releases)
20 | [ ](https://apt.izzysoft.de/fdroid/index/apk/io.github.dovecoteescapee.byedpi)
23 |
24 | ### Или используйте Obtainium
25 |
26 | 1. Установите [Obtainium](https://github.com/ImranR98/Obtainium/blob/main/README.md#installation)
27 | 2. Добавьте приложение по URL:
28 | `https://github.com/dovecoteescapee/ByeDPIAndroid`
29 |
30 | ## Настройки
31 |
32 | Для обхода некоторых блокировок может потребоваться изменить настройки. Подробнее о различных настройках можно прочитать в [документации ByeDPI](https://github.com/hufrea/byedpi/blob/v0.13/README.md).
33 |
34 | ## FAQ
35 |
36 | ### У меня не получается настроить. Что делать?
37 |
38 | Вы можете попросить помощи в [discussion](https://github.com/dovecoteescapee/ByeDPIAndroid/discussions).
39 |
40 | ### Приложение требует root-права?
41 |
42 | Нет. Все функции приложения работают без root-прав.
43 |
44 | ### Это VPN?
45 |
46 | Нет. Приложение использует VPN-режим на Android для перенаправления трафика, но не передает ничего на удаленный сервер. Оно не шифрует трафик и не скрывает ваш IP-адрес.
47 |
48 | ### Какие данные собирает приложение?
49 |
50 | Никакие. Приложения не отправляет никакие данные на удаленный сервер. Весь трафик обрабатывается на устройстве.
51 |
52 | ### Как использовать ByeDPI вместе с AdGuard?
53 |
54 | 1. Запустите ByeDPI в режиме прокси.
55 | 2. Добавьте ByeDPI в исключения AdGuard на вкладке "Управление приложениями".
56 | 3. В настройках AdGuard укажите прокси:
57 |
58 | ```plaintext
59 | Тип прокси: SOCKS5
60 | Хост: 127.0.0.1
61 | Порт: 1080 (по умолчанию)
62 | ```
63 |
64 | ### А есть для других платформ?
65 |
66 | [Список аналогов](https://github.com/ValdikSS/GoodbyeDPI/blob/master/README.md#similar-projects)
67 |
68 | ### Что такое DPI?
69 |
70 | DPI (Deep Packet Inspection) - это технология для анализа и фильтрации трафика. Она используется провайдерами и государственными органами для блокировки сайтов и сервисов.
71 |
72 | ## Зависимости
73 |
74 | - [ByeDPI](https://github.com/hufrea/byedpi)
75 | - [hev-socks5-tunnel](https://github.com/heiher/hev-socks5-tunnel)
76 |
77 | ## Сборка
78 |
79 | Для сборки приложения требуется:
80 |
81 | 1. JDK 8 или новее
82 | 2. Android SDK
83 | 3. Android NDK
84 | 4. CMake 3.22.1 или новее
85 |
86 | Сборка приложения:
87 |
88 | 1. Клонируйте репозиторий с подмодулями:
89 | ```bash
90 | git clone --recurse-submodules
91 | ```
92 | 2. Запустите скрипт сборки из корня репозитория:
93 | ```bash
94 | ./gradlew assembleRelease`
95 | ```
96 | 3. APK будет лежать в `app/build/outputs/apk/release/`
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ByeDPI for Android
2 |
3 | **English** | [Русский](README-ru.md)
4 |
5 |
6 |
7 |
8 |
9 | ---
10 |
11 | Android application that runs a local VPN service to bypass DPI (Deep Packet Inspection) and censorship.
12 |
13 |
14 | This application runs a SOCKS5 proxy [ByeDPI](https://github.com/hufrea/byedpi) and redirects all traffic through it.
15 |
16 | ## Installation
17 |
18 | [ ](https://github.com/dovecoteescapee/ByeDPIAndroid/releases)
21 | [ ](https://apt.izzysoft.de/fdroid/index/apk/io.github.dovecoteescapee.byedpi)
24 |
25 | ### Or use Obtainium
26 |
27 | 1. Install [Obtainium](https://github.com/ImranR98/Obtainium/blob/main/README.md#installation)
28 | 2. Add the app by URL:
29 | `https://github.com/dovecoteescapee/ByeDPIAndroid`
30 |
31 | ## Settings
32 |
33 | To bypass some blocks, you may need to change the settings. More about the various settings can be found in the [ByeDPI documentation](https://github.com/hufrea/byedpi/blob/v0.13/README.md).
34 |
35 | ## FAQ
36 |
37 | ### I can't configure it. What to do?
38 |
39 | You can ask for help in [discussion](https://github.com/dovecoteescapee/ByeDPIAndroid/discussions).
40 |
41 | ### Does the application require root access?
42 |
43 | No. All application features work without root.
44 |
45 | ### Is this a VPN?
46 |
47 | No. The application uses the VPN mode on Android to redirect traffic, but does not send anything to a remote server. It does not encrypt traffic and does not hide your IP address.
48 |
49 | ### How to use ByeDPI with AdGuard?
50 |
51 | 1. Run ByeDPI in proxy mode.
52 | 2. Add ByeDPI to AdGuard exceptions on the "App management" tab.
53 | 3. In AdGuard settings, specify the proxy:
54 |
55 | ```plaintext
56 | Proxy type: SOCKS5
57 | Proxy host: 127.0.0.1
58 | Proxy port: 1080 (default)
59 | ```
60 |
61 | ### What data does the application collect?
62 |
63 | None. The application does not send any data to a remote server. All traffic is processed on the device.
64 |
65 | ### Are there any for other platforms?
66 |
67 | [Similar projects](https://github.com/ValdikSS/GoodbyeDPI/blob/master/README.md#similar-projects))
68 |
69 | ### What is DPI?
70 |
71 | DPI (Deep Packet Inspection) is a technology for analyzing and filtering traffic. It is used by providers and government agencies to block sites and services.
72 |
73 | ## Dependencies
74 |
75 | - [ByeDPI](https://github.com/hufrea/byedpi)
76 | - [hev-socks5-tunnel](https://github.com/heiher/hev-socks5-tunnel)
77 |
78 | ## Building
79 |
80 | For building the application, you need:
81 |
82 | 1. JDK 8 or later
83 | 2. Android SDK
84 | 3. Android NDK
85 | 4. CMake 3.22.1 or later
86 |
87 | To build the application:
88 |
89 | 1. Clone the repository with submodules:
90 | ```bash
91 | git clone --recurse-submodules
92 | ```
93 | 2. Run the build script from the root of the repository:
94 | ```bash
95 | ./gradlew assembleRelease
96 | ```
97 | 3. The APK will be in `app/build/outputs/apk/release/`
98 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /debug
3 | /release
4 | *.aar
5 | *.jar
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | id("org.jetbrains.kotlin.android")
4 | }
5 |
6 | android {
7 | namespace = "io.github.dovecoteescapee.byedpi"
8 | compileSdk = 34
9 |
10 | defaultConfig {
11 | applicationId = "io.github.dovecoteescapee.byedpi"
12 | minSdk = 21
13 | targetSdk = 34
14 | versionCode = 10
15 | versionName = "1.2.0"
16 |
17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18 |
19 | ndk {
20 | abiFilters.add("armeabi-v7a")
21 | abiFilters.add("arm64-v8a")
22 | abiFilters.add("x86")
23 | abiFilters.add("x86_64")
24 | }
25 | }
26 |
27 | buildFeatures {
28 | buildConfig = true
29 | }
30 |
31 | buildTypes {
32 | release {
33 | buildConfigField("String", "VERSION_NAME", "\"${defaultConfig.versionName}\"")
34 |
35 | isMinifyEnabled = false
36 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
37 | }
38 | debug {
39 | buildConfigField("String", "VERSION_NAME", "\"${defaultConfig.versionName}-debug\"")
40 | }
41 | }
42 | compileOptions {
43 | sourceCompatibility = JavaVersion.VERSION_1_8
44 | targetCompatibility = JavaVersion.VERSION_1_8
45 | }
46 | kotlinOptions {
47 | jvmTarget = "1.8"
48 | }
49 | externalNativeBuild {
50 | cmake {
51 | path = file("src/main/cpp/CMakeLists.txt")
52 | version = "3.22.1"
53 | }
54 | }
55 | buildFeatures {
56 | viewBinding = true
57 | }
58 |
59 | // https://android.izzysoft.de/articles/named/iod-scan-apkchecks?lang=en#blobs
60 | dependenciesInfo {
61 | // Disables dependency metadata when building APKs.
62 | includeInApk = false
63 | // Disables dependency metadata when building Android App Bundles.
64 | includeInBundle = false
65 | }
66 | }
67 |
68 | dependencies {
69 | implementation("androidx.fragment:fragment-ktx:1.8.2")
70 | implementation("androidx.core:core-ktx:1.13.1")
71 | implementation("androidx.appcompat:appcompat:1.7.0")
72 | implementation("androidx.preference:preference-ktx:1.2.1")
73 | implementation("com.takisoft.preferencex:preferencex:1.1.0")
74 | implementation("com.google.android.material:material:1.12.0")
75 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4")
76 | implementation("androidx.lifecycle:lifecycle-service:2.8.4")
77 | testImplementation("junit:junit:4.13.2")
78 | androidTestImplementation("androidx.test.ext:junit:1.2.1")
79 | androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
80 | }
81 |
82 | tasks.register("runNdkBuild") {
83 | group = "build"
84 |
85 | val ndkDir = android.ndkDirectory
86 | executable = if (System.getProperty("os.name").startsWith("Windows", ignoreCase = true)) {
87 | "$ndkDir\\ndk-build.cmd"
88 | } else {
89 | "$ndkDir/ndk-build"
90 | }
91 | setArgs(listOf(
92 | "NDK_PROJECT_PATH=build/intermediates/ndkBuild",
93 | "NDK_LIBS_OUT=src/main/jniLibs",
94 | "APP_BUILD_SCRIPT=src/main/jni/Android.mk",
95 | "NDK_APPLICATION_MK=src/main/jni/Application.mk"
96 | ))
97 |
98 | println("Command: $commandLine")
99 | }
100 |
101 | tasks.preBuild {
102 | dependsOn("runNdkBuild")
103 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/io/github/dovecoteescapee/byedpi/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("io.github.dovecoteescapee.byedpi", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
13 |
14 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
42 |
43 |
47 |
48 |
49 |
50 |
53 |
56 |
57 |
58 |
61 |
64 |
65 |
66 |
72 |
73 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/app/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.22.1)
2 |
3 | project(byedpi_native)
4 |
5 | file(GLOB BYE_DPI_SRC byedpi/*.c)
6 | list(REMOVE_ITEM BYE_DPI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/byedpi/win_service.c)
7 |
8 | add_library(byedpi SHARED ${BYE_DPI_SRC} native-lib.c utils.c)
9 | target_include_directories(byedpi PRIVATE byedpi)
10 |
11 | target_compile_options(byedpi PRIVATE -std=c99 -O2 -D_XOPEN_SOURCE=500)
12 | target_compile_definitions(byedpi PRIVATE ANDROID_APP)
13 |
14 | target_link_libraries(byedpi PRIVATE android log)
15 |
--------------------------------------------------------------------------------
/app/src/main/cpp/main.h:
--------------------------------------------------------------------------------
1 | extern char *oob_char;
2 | extern int NOT_EXIT;
3 |
4 | struct sockaddr_ina;
5 |
6 | int get_default_ttl();
7 |
8 | int get_addr(const char *str, struct sockaddr_ina *addr);
9 |
10 | void *add(void **root, int *n, size_t ss);
11 |
12 | void clear_params(void);
13 |
14 | char *ftob(const char *str, ssize_t *sl);
15 |
16 | char *data_from_str(const char *str, ssize_t *size);
17 |
18 | size_t parse_cform(char *buffer, size_t blen,
19 | const char *str, size_t slen);
20 |
21 | struct mphdr *parse_hosts(char *buffer, size_t size);
22 |
23 | int parse_offset(struct part *part, const char *str);
24 |
--------------------------------------------------------------------------------
/app/src/main/cpp/native-lib.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include
5 | #include
6 |
7 | #include "byedpi/error.h"
8 | #include "byedpi/proxy.h"
9 | #include "byedpi/params.h"
10 | #include "byedpi/packets.h"
11 | #include "main.h"
12 | #include "utils.h"
13 |
14 | const enum demode DESYNC_METHODS[] = {
15 | DESYNC_NONE,
16 | DESYNC_SPLIT,
17 | DESYNC_DISORDER,
18 | DESYNC_FAKE,
19 | DESYNC_OOB,
20 | DESYNC_DISOOB,
21 | };
22 |
23 | enum hosts_mode {
24 | HOSTS_DISABLE,
25 | HOSTS_BLACKLIST,
26 | HOSTS_WHITELIST,
27 | };
28 |
29 | JNIEXPORT jint JNI_OnLoad(
30 | __attribute__((unused)) JavaVM *vm,
31 | __attribute__((unused)) void *reserved) {
32 | default_params = params;
33 | return JNI_VERSION_1_6;
34 | }
35 |
36 | JNIEXPORT jint JNICALL
37 | Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocketWithCommandLine(
38 | JNIEnv *env,
39 | __attribute__((unused)) jobject thiz,
40 | jobjectArray args) {
41 | int argc = (*env)->GetArrayLength(env, args);
42 | char *argv[argc];
43 | for (int i = 0; i < argc; i++) {
44 | jstring arg = (jstring) (*env)->GetObjectArrayElement(env, args, i);
45 | const char *arg_str = (*env)->GetStringUTFChars(env, arg, 0);
46 | argv[i] = strdup(arg_str);
47 | (*env)->ReleaseStringUTFChars(env, arg, arg_str);
48 | }
49 |
50 | int res = parse_args(argc, argv);
51 | if (res < 0) {
52 | uniperror("parse_args");
53 | return -1;
54 | }
55 |
56 | int fd = listen_socket((struct sockaddr_ina *)¶ms.laddr);
57 | if (fd < 0) {
58 | uniperror("listen_socket");
59 | return -1;
60 | }
61 | LOG(LOG_S, "listen_socket, fd: %d", fd);
62 |
63 | return fd;
64 | }
65 |
66 | JNIEXPORT jint JNICALL
67 | Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
68 | JNIEnv *env,
69 | __attribute__((unused)) jobject thiz,
70 | jstring ip,
71 | jint port,
72 | jint max_connections,
73 | jint buffer_size,
74 | jint default_ttl,
75 | jboolean custom_ttl,
76 | jboolean no_domain,
77 | jboolean desync_http,
78 | jboolean desync_https,
79 | jboolean desync_udp,
80 | jint desync_method,
81 | jint split_position,
82 | jboolean split_at_host,
83 | jint fake_ttl,
84 | jstring fake_sni,
85 | jbyte custom_oob_char,
86 | jboolean host_mixed_case,
87 | jboolean domain_mixed_case,
88 | jboolean host_remove_spaces,
89 | jboolean tls_record_split,
90 | jint tls_record_split_position,
91 | jboolean tls_record_split_at_sni,
92 | jint hosts_mode,
93 | jstring hosts,
94 | jboolean tfo,
95 | jint udp_fake_count,
96 | jboolean drop_sack,
97 | jint fake_offset) {
98 | struct sockaddr_ina s;
99 |
100 | const char *address = (*env)->GetStringUTFChars(env, ip, 0);
101 | int res = get_addr(address, &s);
102 | (*env)->ReleaseStringUTFChars(env, ip, address);
103 | if (res < 0) {
104 | uniperror("get_addr");
105 | return -1;
106 | }
107 |
108 | s.in.sin_port = htons(port);
109 |
110 | params.max_open = max_connections;
111 | params.bfsize = buffer_size;
112 | params.resolve = !no_domain;
113 | params.tfo = tfo;
114 |
115 | if (custom_ttl) {
116 | params.def_ttl = default_ttl;
117 | params.custom_ttl = 1;
118 | }
119 |
120 | if (!params.def_ttl) {
121 | if ((params.def_ttl = get_default_ttl()) < 1) {
122 | uniperror("get_default_ttl");
123 | reset_params();
124 | return -1;
125 | }
126 | }
127 |
128 | if (hosts_mode == HOSTS_WHITELIST) {
129 | struct desync_params *dp = add(
130 | (void *) ¶ms.dp,
131 | ¶ms.dp_count,
132 | sizeof(struct desync_params)
133 | );
134 | if (!dp) {
135 | uniperror("add");
136 | reset_params();
137 | return -1;
138 | }
139 |
140 | const char *str = (*env)->GetStringUTFChars(env, hosts, 0);
141 | dp->file_ptr = data_from_str(str, &dp->file_size);
142 | (*env)->ReleaseStringUTFChars(env, hosts, str);
143 | dp->hosts = parse_hosts(dp->file_ptr, dp->file_size);
144 | if (!dp->hosts) {
145 | perror("parse_hosts");
146 | clear_params();
147 | return -1;
148 | }
149 | }
150 |
151 | struct desync_params *dp = add(
152 | (void *) ¶ms.dp,
153 | ¶ms.dp_count,
154 | sizeof(struct desync_params)
155 | );
156 | if (!dp) {
157 | uniperror("add");
158 | reset_params();
159 | return -1;
160 | }
161 |
162 | if (hosts_mode == HOSTS_BLACKLIST) {
163 | const char *str = (*env)->GetStringUTFChars(env, hosts, 0);
164 | dp->file_ptr = data_from_str(str, &dp->file_size);
165 | (*env)->ReleaseStringUTFChars(env, hosts, str);
166 | dp->hosts = parse_hosts(dp->file_ptr, dp->file_size);
167 | if (!dp->hosts) {
168 | perror("parse_hosts");
169 | clear_params();
170 | return -1;
171 | }
172 | }
173 |
174 | dp->ttl = fake_ttl;
175 | dp->udp_fake_count = udp_fake_count;
176 | dp->drop_sack = drop_sack;
177 | dp->proto =
178 | IS_HTTP * desync_http |
179 | IS_HTTPS * desync_https |
180 | IS_UDP * desync_udp;
181 | dp->mod_http =
182 | MH_HMIX * host_mixed_case |
183 | MH_DMIX * domain_mixed_case |
184 | MH_SPACE * host_remove_spaces;
185 |
186 | struct part *part = add(
187 | (void *) &dp->parts,
188 | &dp->parts_n,
189 | sizeof(struct part)
190 | );
191 | if (!part) {
192 | uniperror("add");
193 | reset_params();
194 | return -1;
195 | }
196 |
197 | enum demode mode = DESYNC_METHODS[desync_method];
198 |
199 | int offset_flag = dp->proto || desync_https ? OFFSET_SNI : OFFSET_HOST;
200 |
201 | part->flag = split_at_host ? offset_flag : 0;
202 | part->pos = split_position;
203 | part->m = mode;
204 |
205 | if (tls_record_split) {
206 | struct part *tlsrec_part = add(
207 | (void *) &dp->tlsrec,
208 | &dp->tlsrec_n,
209 | sizeof(struct part)
210 | );
211 |
212 | if (!tlsrec_part) {
213 | uniperror("add");
214 | reset_params();
215 | return -1;
216 | }
217 |
218 | tlsrec_part->flag = tls_record_split_at_sni ? offset_flag : 0;
219 | tlsrec_part->pos = tls_record_split_position;
220 | }
221 |
222 | if (mode == DESYNC_FAKE) {
223 | dp->fake_offset = fake_offset;
224 |
225 | const char *sni = (*env)->GetStringUTFChars(env, fake_sni, 0);
226 | LOG(LOG_S, "fake_sni: %s", sni);
227 | res = change_tls_sni(sni, fake_tls.data, fake_tls.size);
228 | (*env)->ReleaseStringUTFChars(env, fake_sni, sni);
229 | if (res) {
230 | fprintf(stderr, "error chsni\n");
231 | return -1;
232 | }
233 | }
234 |
235 | if (mode == DESYNC_OOB) {
236 | dp->oob_char[0] = custom_oob_char;
237 | dp->oob_char[1] = 1;
238 | }
239 |
240 | if (dp->proto) {
241 | dp = add((void *)¶ms.dp,
242 | ¶ms.dp_count, sizeof(struct desync_params));
243 | if (!dp) {
244 | uniperror("add");
245 | clear_params();
246 | return -1;
247 | }
248 | }
249 |
250 | params.mempool = mem_pool(0);
251 | if (!params.mempool) {
252 | uniperror("mem_pool");
253 | clear_params();
254 | return -1;
255 | }
256 |
257 | int fd = listen_socket(&s);
258 | if (fd < 0) {
259 | uniperror("listen_socket");
260 | return -1;
261 | }
262 | LOG(LOG_S, "listen_socket, fd: %d", fd);
263 |
264 | return fd;
265 | }
266 |
267 | JNIEXPORT jint JNICALL
268 | Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniStartProxy(
269 | __attribute__((unused)) JNIEnv *env,
270 | __attribute__((unused)) jobject thiz,
271 | jint fd) {
272 | LOG(LOG_S, "start_proxy, fd: %d", fd);
273 | NOT_EXIT = 1;
274 | if (event_loop(fd) < 0) {
275 | uniperror("event_loop");
276 | return get_e();
277 | }
278 | return 0;
279 | }
280 |
281 | JNIEXPORT jint JNICALL
282 | Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniStopProxy(
283 | __attribute__((unused)) JNIEnv *env,
284 | __attribute__((unused)) jobject thiz,
285 | jint fd) {
286 | LOG(LOG_S, "stop_proxy, fd: %d", fd);
287 |
288 | int res = shutdown(fd, SHUT_RDWR);
289 | reset_params();
290 |
291 | if (res < 0) {
292 | uniperror("shutdown");
293 | return get_e();
294 | }
295 | return 0;
296 | }
--------------------------------------------------------------------------------
/app/src/main/cpp/utils.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "byedpi/params.h"
6 | #include "error.h"
7 | #include "main.h"
8 | #include "packets.h"
9 | #include "utils.h"
10 |
11 | struct params default_params;
12 |
13 | void reset_params(void) {
14 | clear_params();
15 | params = default_params;
16 | }
17 |
18 | extern const struct option options[38];
19 |
20 | int parse_args(int argc, char **argv)
21 | {
22 | int optc = sizeof(options)/sizeof(*options);
23 | for (int i = 0, e = optc; i < e; i++)
24 | optc += options[i].has_arg;
25 |
26 | char opt[optc + 1];
27 | opt[optc] = 0;
28 |
29 | for (int i = 0, o = 0; o < optc; i++, o++) {
30 | opt[o] = options[i].val;
31 | for (int c = options[i].has_arg; c; c--) {
32 | o++;
33 | opt[o] = ':';
34 | }
35 | }
36 |
37 | params.laddr.sin6_port = htons(1080);
38 |
39 | int rez;
40 | int invalid = 0;
41 |
42 | long val;
43 | char *end = 0;
44 |
45 | struct desync_params *dp = add((void *)¶ms.dp,
46 | ¶ms.dp_count, sizeof(struct desync_params));
47 | if (!dp) {
48 | reset_params();
49 | return -1;
50 | }
51 |
52 | optind = optreset = 1;
53 |
54 | while (!invalid && (rez = getopt_long(
55 | argc, argv, opt, options, 0)) != -1) {
56 |
57 | switch (rez) {
58 |
59 | case 'N':
60 | params.resolve = 0;
61 | break;
62 | case 'X':
63 | params.ipv6 = 0;
64 | break;
65 | case 'U':
66 | params.udp = 0;
67 | break;
68 |
69 | // case 'h':
70 | // printf(help_text);
71 | // reset_params();
72 | // return 0;
73 | // case 'v':
74 | // printf("%s\n", VERSION);
75 | // reset_params();
76 | // return 0;
77 |
78 | case 'i':
79 | if (get_addr(optarg,
80 | (struct sockaddr_ina *)¶ms.laddr) < 0)
81 | invalid = 1;
82 | break;
83 |
84 | case 'p':
85 | val = strtol(optarg, &end, 0);
86 | if (val <= 0 || val > 0xffff || *end)
87 | invalid = 1;
88 | else
89 | params.laddr.sin6_port = htons(val);
90 | break;
91 |
92 | case 'I':
93 | if (get_addr(optarg,
94 | (struct sockaddr_ina *)¶ms.baddr) < 0)
95 | invalid = 1;
96 | break;
97 |
98 | case 'b':
99 | val = strtol(optarg, &end, 0);
100 | if (val <= 0 || val > INT_MAX/4 || *end)
101 | invalid = 1;
102 | else
103 | params.bfsize = val;
104 | break;
105 |
106 | case 'c':
107 | val = strtol(optarg, &end, 0);
108 | if (val <= 0 || val >= (0xffff/2) || *end)
109 | invalid = 1;
110 | else
111 | params.max_open = val;
112 | break;
113 |
114 | case 'x': //
115 | params.debug = strtol(optarg, 0, 0);
116 | if (params.debug < 0)
117 | invalid = 1;
118 | break;
119 |
120 | // desync options
121 |
122 | case 'F':
123 | params.tfo = 1;
124 | break;
125 |
126 | case 'A':
127 | dp = add((void *)¶ms.dp, ¶ms.dp_count,
128 | sizeof(struct desync_params));
129 | if (!dp) {
130 | reset_params();
131 | return -1;
132 | }
133 | end = optarg;
134 | while (end && !invalid) {
135 | switch (*end) {
136 | case 't':
137 | dp->detect |= DETECT_TORST;
138 | break;
139 | case 'r':
140 | dp->detect |= DETECT_HTTP_LOCAT;
141 | break;
142 | case 'a':
143 | case 's':
144 | dp->detect |= DETECT_TLS_ERR;
145 | break;
146 | case 'n':
147 | break;
148 | default:
149 | invalid = 1;
150 | continue;
151 | }
152 | end = strchr(end, ',');
153 | if (end) end++;
154 | }
155 | break;
156 |
157 | case 'u':
158 | val = strtol(optarg, &end, 0);
159 | if (val <= 0 || *end)
160 | invalid = 1;
161 | else
162 | params.cache_ttl = val;
163 | break;
164 |
165 | case 'T':;
166 | #ifdef __linux__
167 | float f = strtof(optarg, &end);
168 | val = (long)(f * 1000);
169 | #else
170 | val = strtol(optarg, &end, 0);
171 | #endif
172 | if (val <= 0 || val > UINT_MAX || *end)
173 | invalid = 1;
174 | else
175 | params.timeout = val;
176 | break;
177 |
178 | case 'K':
179 | end = optarg;
180 | while (end && !invalid) {
181 | switch (*end) {
182 | case 't':
183 | dp->proto |= IS_HTTPS;
184 | break;
185 | case 'h':
186 | dp->proto |= IS_HTTP;
187 | break;
188 | case 'u':
189 | dp->proto |= IS_UDP;
190 | break;
191 | default:
192 | invalid = 1;
193 | continue;
194 | }
195 | end = strchr(end, ',');
196 | if (end) end++;
197 | }
198 | break;
199 |
200 | case 'H':;
201 | if (dp->file_ptr) {
202 | continue;
203 | }
204 | dp->file_ptr = ftob(optarg, &dp->file_size);
205 | if (!dp->file_ptr) {
206 | uniperror("read/parse");
207 | invalid = 1;
208 | continue;
209 | }
210 | dp->hosts = parse_hosts(dp->file_ptr, dp->file_size);
211 | if (!dp->hosts) {
212 | perror("parse_hosts");
213 | reset_params();
214 | return -1;
215 | }
216 | break;
217 |
218 | case 's':
219 | case 'd':
220 | case 'o':
221 | case 'q':
222 | case 'f':
223 | ;
224 | struct part *part = add((void *)&dp->parts,
225 | &dp->parts_n, sizeof(struct part));
226 | if (!part) {
227 | reset_params();
228 | return -1;
229 | }
230 | if (parse_offset(part, optarg)) {
231 | invalid = 1;
232 | break;
233 | }
234 | switch (rez) {
235 | case 's': part->m = DESYNC_SPLIT;
236 | break;
237 | case 'd': part->m = DESYNC_DISORDER;
238 | break;
239 | case 'o': part->m = DESYNC_OOB;
240 | break;
241 | case 'q': part->m = DESYNC_DISOOB;
242 | break;
243 | case 'f': part->m = DESYNC_FAKE;
244 | }
245 | break;
246 |
247 | case 't':
248 | val = strtol(optarg, &end, 0);
249 | if (val <= 0 || val > 255 || *end)
250 | invalid = 1;
251 | else
252 | dp->ttl = val;
253 | break;
254 |
255 | case 'k':
256 | if (dp->ip_options) {
257 | continue;
258 | }
259 | if (optarg)
260 | dp->ip_options = ftob(optarg, &dp->ip_options_len);
261 | else {
262 | dp->ip_options = ip_option;
263 | dp->ip_options_len = sizeof(ip_option);
264 | }
265 | if (!dp->ip_options) {
266 | uniperror("read/parse");
267 | invalid = 1;
268 | }
269 | break;
270 |
271 | case 'S':
272 | dp->md5sig = 1;
273 | break;
274 |
275 | case 'O':
276 | val = strtol(optarg, &end, 0);
277 | if (val <= 0 || *end)
278 | invalid = 1;
279 | else
280 | dp->fake_offset = val;
281 | break;
282 |
283 | case 'n':
284 | if (change_tls_sni(optarg, fake_tls.data, fake_tls.size)) {
285 | perror("change_tls_sni");
286 | reset_params();
287 | return -1;
288 | }
289 | LOG(LOG_S, "sni: %s", optarg);
290 | break;
291 |
292 | case 'l':
293 | if (dp->fake_data.data) {
294 | continue;
295 | }
296 | dp->fake_data.data = ftob(optarg, &dp->fake_data.size);
297 | if (!dp->fake_data.data) {
298 | uniperror("read/parse");
299 | invalid = 1;
300 | }
301 | break;
302 |
303 | case 'e':
304 | val = parse_cform(dp->oob_char, 1, optarg, strlen(optarg));
305 | if (val != 1) {
306 | invalid = 1;
307 | }
308 | else dp->oob_char[1] = 1;
309 | break;
310 |
311 | case 'M':
312 | end = optarg;
313 | while (end && !invalid) {
314 | switch (*end) {
315 | case 'r':
316 | dp->mod_http |= MH_SPACE;
317 | break;
318 | case 'h':
319 | dp->mod_http |= MH_HMIX;
320 | break;
321 | case 'd':
322 | dp->mod_http |= MH_DMIX;
323 | break;
324 | default:
325 | invalid = 1;
326 | continue;
327 | }
328 | end = strchr(end, ',');
329 | if (end) end++;
330 | }
331 | break;
332 |
333 | case 'r':
334 | part = add((void *)&dp->tlsrec,
335 | &dp->tlsrec_n, sizeof(struct part));
336 | if (!part) {
337 | reset_params();
338 | return -1;
339 | }
340 | if (parse_offset(part, optarg)
341 | || part->pos > 0xffff) {
342 | invalid = 1;
343 | break;
344 | }
345 | break;
346 |
347 | case 'a':
348 | val = strtol(optarg, &end, 0);
349 | if (val < 0 || val > INT_MAX || *end)
350 | invalid = 1;
351 | else
352 | dp->udp_fake_count = val;
353 | break;
354 |
355 | case 'V':
356 | val = strtol(optarg, &end, 0);
357 | if (val <= 0 || val > USHRT_MAX)
358 | invalid = 1;
359 | else {
360 | dp->pf[0] = htons(val);
361 | if (*end == '-') {
362 | val = strtol(end + 1, &end, 0);
363 | if (val <= 0 || val > USHRT_MAX)
364 | invalid = 1;
365 | }
366 | if (*end)
367 | invalid = 1;
368 | else
369 | dp->pf[1] = htons(val);
370 | }
371 | break;
372 |
373 | case 'g':
374 | val = strtol(optarg, &end, 0);
375 | if (val <= 0 || val > 255 || *end)
376 | invalid = 1;
377 | else {
378 | params.def_ttl = val;
379 | params.custom_ttl = 1;
380 | }
381 | break;
382 |
383 | case 'Y':
384 | dp->drop_sack = 1;
385 | break;
386 |
387 | case 'w': //
388 | params.sfdelay = strtol(optarg, &end, 0);
389 | if (params.sfdelay < 0 || optarg == end
390 | || params.sfdelay >= 1000 || *end)
391 | invalid = 1;
392 | break;
393 |
394 | case 'W':
395 | params.wait_send = 0;
396 | break;
397 | #ifdef __linux__
398 | case 'P':
399 | params.protect_path = optarg;
400 | break;
401 | #endif
402 | case 0:
403 | break;
404 |
405 | case '?':
406 | reset_params();
407 | return -1;
408 |
409 | default:
410 | LOG(LOG_S, "Unknown option: -%c", rez);
411 | reset_params();
412 | return -1;
413 | }
414 | }
415 | if (invalid) {
416 | LOG(LOG_S, "invalid value: -%c %s", rez, optarg);
417 | reset_params();
418 | return -1;
419 | }
420 | if (dp->hosts || dp->proto || dp->pf[0]) {
421 | dp = add((void *)¶ms.dp,
422 | ¶ms.dp_count, sizeof(struct desync_params));
423 | if (!dp) {
424 | reset_params();
425 | return -1;
426 | }
427 | }
428 |
429 | if (params.baddr.sin6_family != AF_INET6) {
430 | params.ipv6 = 0;
431 | }
432 | if (!params.def_ttl) {
433 | if ((params.def_ttl = get_default_ttl()) < 1) {
434 | reset_params();
435 | return -1;
436 | }
437 | }
438 | params.mempool = mem_pool(0);
439 | if (!params.mempool) {
440 | uniperror("mem_pool");
441 | reset_params();
442 | return -1;
443 | }
444 |
445 | return 0;
446 | }
447 |
--------------------------------------------------------------------------------
/app/src/main/cpp/utils.h:
--------------------------------------------------------------------------------
1 | extern struct params default_params;
2 |
3 | void reset_params(void);
4 | int parse_args(int argc, char **argv);
5 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/activities/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.activities
2 |
3 | import android.Manifest
4 | import android.annotation.SuppressLint
5 | import android.content.BroadcastReceiver
6 | import android.content.Context
7 | import android.content.Intent
8 | import android.content.IntentFilter
9 | import android.content.pm.PackageManager
10 | import android.net.VpnService
11 | import android.os.Build
12 | import android.os.Bundle
13 | import android.util.Log
14 | import android.view.Menu
15 | import android.view.MenuItem
16 | import android.widget.Toast
17 | import androidx.activity.result.contract.ActivityResultContracts
18 | import androidx.appcompat.app.AppCompatActivity
19 | import androidx.core.content.ContextCompat
20 | import androidx.lifecycle.lifecycleScope
21 | import io.github.dovecoteescapee.byedpi.R
22 | import io.github.dovecoteescapee.byedpi.data.*
23 | import io.github.dovecoteescapee.byedpi.fragments.MainSettingsFragment
24 | import io.github.dovecoteescapee.byedpi.databinding.ActivityMainBinding
25 | import io.github.dovecoteescapee.byedpi.services.ServiceManager
26 | import io.github.dovecoteescapee.byedpi.services.appStatus
27 | import io.github.dovecoteescapee.byedpi.utility.*
28 | import kotlinx.coroutines.Dispatchers
29 | import kotlinx.coroutines.launch
30 | import java.io.IOException
31 |
32 | class MainActivity : AppCompatActivity() {
33 | private lateinit var binding: ActivityMainBinding
34 |
35 | companion object {
36 | private val TAG: String = MainActivity::class.java.simpleName
37 |
38 | private fun collectLogs(): String? =
39 | try {
40 | Runtime.getRuntime()
41 | .exec("logcat *:D -d")
42 | .inputStream.bufferedReader()
43 | .use { it.readText() }
44 | } catch (e: Exception) {
45 | Log.e(TAG, "Failed to collect logs", e)
46 | null
47 | }
48 | }
49 |
50 | private val vpnRegister =
51 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
52 | if (it.resultCode == RESULT_OK) {
53 | ServiceManager.start(this, Mode.VPN)
54 | } else {
55 | Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_SHORT).show()
56 | updateStatus()
57 | }
58 | }
59 |
60 | private val logsRegister =
61 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
62 | lifecycleScope.launch(Dispatchers.IO) {
63 | val logs = collectLogs()
64 |
65 | if (logs == null) {
66 | Toast.makeText(
67 | this@MainActivity,
68 | R.string.logs_failed,
69 | Toast.LENGTH_SHORT
70 | ).show()
71 | } else {
72 | val uri = it.data?.data ?: run {
73 | Log.e(TAG, "No data in result")
74 | return@launch
75 | }
76 | contentResolver.openOutputStream(uri)?.use {
77 | try {
78 | it.write(logs.toByteArray())
79 | } catch (e: IOException) {
80 | Log.e(TAG, "Failed to save logs", e)
81 | }
82 | } ?: run {
83 | Log.e(TAG, "Failed to open output stream")
84 | }
85 | }
86 | }
87 | }
88 |
89 | private val receiver = object : BroadcastReceiver() {
90 | override fun onReceive(context: Context?, intent: Intent?) {
91 | Log.d(TAG, "Received intent: ${intent?.action}")
92 |
93 | if (intent == null) {
94 | Log.w(TAG, "Received null intent")
95 | return
96 | }
97 |
98 | val senderOrd = intent.getIntExtra(SENDER, -1)
99 | val sender = Sender.entries.getOrNull(senderOrd)
100 | if (sender == null) {
101 | Log.w(TAG, "Received intent with unknown sender: $senderOrd")
102 | return
103 | }
104 |
105 | when (val action = intent.action) {
106 | STARTED_BROADCAST,
107 | STOPPED_BROADCAST -> updateStatus()
108 |
109 | FAILED_BROADCAST -> {
110 | Toast.makeText(
111 | context,
112 | getString(R.string.failed_to_start, sender.name),
113 | Toast.LENGTH_SHORT,
114 | ).show()
115 | updateStatus()
116 | }
117 |
118 | else -> Log.w(TAG, "Unknown action: $action")
119 | }
120 | }
121 | }
122 |
123 | override fun onCreate(savedInstanceState: Bundle?) {
124 | super.onCreate(savedInstanceState)
125 |
126 | binding = ActivityMainBinding.inflate(layoutInflater)
127 | setContentView(binding.root)
128 |
129 | val intentFilter = IntentFilter().apply {
130 | addAction(STARTED_BROADCAST)
131 | addAction(STOPPED_BROADCAST)
132 | addAction(FAILED_BROADCAST)
133 | }
134 |
135 | @SuppressLint("UnspecifiedRegisterReceiverFlag")
136 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
137 | registerReceiver(receiver, intentFilter, RECEIVER_EXPORTED)
138 | } else {
139 | registerReceiver(receiver, intentFilter)
140 | }
141 |
142 | binding.statusButton.setOnClickListener {
143 | val (status, _) = appStatus
144 | when (status) {
145 | AppStatus.Halted -> start()
146 | AppStatus.Running -> stop()
147 | }
148 | }
149 |
150 | val theme = getPreferences()
151 | .getString("app_theme", null)
152 | MainSettingsFragment.setTheme(theme ?: "system")
153 |
154 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
155 | ContextCompat.checkSelfPermission(
156 | this,
157 | Manifest.permission.POST_NOTIFICATIONS
158 | ) != PackageManager.PERMISSION_GRANTED
159 | ) {
160 | requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1)
161 | }
162 | }
163 |
164 | override fun onResume() {
165 | super.onResume()
166 | updateStatus()
167 | }
168 |
169 | override fun onDestroy() {
170 | super.onDestroy()
171 | unregisterReceiver(receiver)
172 | }
173 |
174 | override fun onCreateOptionsMenu(menu: Menu?): Boolean {
175 | menuInflater.inflate(R.menu.menu_main, menu)
176 | return true
177 | }
178 |
179 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
180 | val (status, _) = appStatus
181 |
182 | return when (item.itemId) {
183 | R.id.action_settings -> {
184 | if (status == AppStatus.Halted) {
185 | val intent = Intent(this, SettingsActivity::class.java)
186 | startActivity(intent)
187 | } else {
188 | Toast.makeText(this, R.string.settings_unavailable, Toast.LENGTH_SHORT)
189 | .show()
190 | }
191 | true
192 | }
193 |
194 | R.id.action_save_logs -> {
195 | val intent =
196 | Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
197 | addCategory(Intent.CATEGORY_OPENABLE)
198 | type = "text/plain"
199 | putExtra(Intent.EXTRA_TITLE, "byedpi.log")
200 | }
201 |
202 | logsRegister.launch(intent)
203 | true
204 | }
205 |
206 | else -> super.onOptionsItemSelected(item)
207 | }
208 | }
209 |
210 | private fun start() {
211 | when (getPreferences().mode()) {
212 | Mode.VPN -> {
213 | val intentPrepare = VpnService.prepare(this)
214 | if (intentPrepare != null) {
215 | vpnRegister.launch(intentPrepare)
216 | } else {
217 | ServiceManager.start(this, Mode.VPN)
218 | }
219 | }
220 |
221 | Mode.Proxy -> ServiceManager.start(this, Mode.Proxy)
222 | }
223 | }
224 |
225 | private fun stop() {
226 | ServiceManager.stop(this)
227 | }
228 |
229 | private fun updateStatus() {
230 | val (status, mode) = appStatus
231 |
232 | Log.i(TAG, "Updating status: $status, $mode")
233 |
234 | val preferences = getPreferences()
235 | val proxyIp = preferences.getStringNotNull("byedpi_proxy_ip", "127.0.0.1")
236 | val proxyPort = preferences.getStringNotNull("byedpi_proxy_port", "1080")
237 | binding.proxyAddress.text = getString(R.string.proxy_address, proxyIp, proxyPort)
238 |
239 | when (status) {
240 | AppStatus.Halted -> {
241 | when (preferences.mode()) {
242 | Mode.VPN -> {
243 | binding.statusText.setText(R.string.vpn_disconnected)
244 | binding.statusButton.setText(R.string.vpn_connect)
245 | }
246 |
247 | Mode.Proxy -> {
248 | binding.statusText.setText(R.string.proxy_down)
249 | binding.statusButton.setText(R.string.proxy_start)
250 | }
251 | }
252 | binding.statusButton.isEnabled = true
253 | }
254 |
255 | AppStatus.Running -> {
256 | when (mode) {
257 | Mode.VPN -> {
258 | binding.statusText.setText(R.string.vpn_connected)
259 | binding.statusButton.setText(R.string.vpn_disconnect)
260 | }
261 |
262 | Mode.Proxy -> {
263 | binding.statusText.setText(R.string.proxy_up)
264 | binding.statusButton.setText(R.string.proxy_stop)
265 | }
266 | }
267 | binding.statusButton.isEnabled = true
268 | }
269 | }
270 | }
271 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/activities/SettingsActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.activities
2 |
3 | import android.os.Bundle
4 | import android.view.Menu
5 | import android.view.MenuItem
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.fragment.app.FragmentManager
8 | import io.github.dovecoteescapee.byedpi.R
9 | import io.github.dovecoteescapee.byedpi.fragments.MainSettingsFragment
10 | import io.github.dovecoteescapee.byedpi.utility.getPreferences
11 |
12 | class SettingsActivity : AppCompatActivity() {
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(R.layout.activity_settings)
16 |
17 | supportFragmentManager
18 | .beginTransaction()
19 | .replace(R.id.settings, MainSettingsFragment())
20 | .commit()
21 |
22 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
23 | }
24 |
25 | override fun onCreateOptionsMenu(menu: Menu?): Boolean {
26 | menuInflater.inflate(R.menu.menu_settings, menu)
27 | return true
28 | }
29 |
30 | override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
31 | android.R.id.home -> {
32 | onBackPressedDispatcher.onBackPressed()
33 | true
34 | }
35 |
36 | R.id.action_reset_settings -> {
37 | getPreferences().edit().clear().apply()
38 |
39 | supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
40 | supportFragmentManager
41 | .beginTransaction()
42 | .replace(R.id.settings, MainSettingsFragment())
43 | .commit()
44 | true
45 | }
46 |
47 | else -> super.onOptionsItemSelected(item)
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/core/ByeDpiProxy.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.core
2 |
3 | import kotlinx.coroutines.sync.Mutex
4 | import kotlinx.coroutines.sync.withLock
5 |
6 | class ByeDpiProxy {
7 | companion object {
8 | init {
9 | System.loadLibrary("byedpi")
10 | }
11 | }
12 |
13 | private val mutex = Mutex()
14 | private var fd = -1
15 |
16 | suspend fun startProxy(preferences: ByeDpiProxyPreferences): Int {
17 | val fd = createSocket(preferences)
18 | if (fd < 0) {
19 | return -1 // TODO: should be error code
20 | }
21 | return jniStartProxy(fd)
22 | }
23 |
24 | suspend fun stopProxy(): Int {
25 | mutex.withLock {
26 | if (fd < 0) {
27 | throw IllegalStateException("Proxy is not running")
28 | }
29 |
30 | val result = jniStopProxy(fd)
31 | if (result == 0) {
32 | fd = -1
33 | }
34 | return result
35 | }
36 | }
37 |
38 | private suspend fun createSocket(preferences: ByeDpiProxyPreferences): Int =
39 | mutex.withLock {
40 | if (fd >= 0) {
41 | throw IllegalStateException("Proxy is already running")
42 | }
43 |
44 | val fd = createSocketFromPreferences(preferences)
45 | if (fd < 0) {
46 | return -1
47 | }
48 | this.fd = fd
49 | fd
50 | }
51 |
52 | private fun createSocketFromPreferences(preferences: ByeDpiProxyPreferences) =
53 | when (preferences) {
54 | is ByeDpiProxyCmdPreferences -> jniCreateSocketWithCommandLine(preferences.args)
55 |
56 | is ByeDpiProxyUIPreferences -> jniCreateSocket(
57 | ip = preferences.ip,
58 | port = preferences.port,
59 | maxConnections = preferences.maxConnections,
60 | bufferSize = preferences.bufferSize,
61 | defaultTtl = preferences.defaultTtl,
62 | customTtl = preferences.customTtl,
63 | noDomain = preferences.noDomain,
64 | desyncHttp = preferences.desyncHttp,
65 | desyncHttps = preferences.desyncHttps,
66 | desyncUdp = preferences.desyncUdp,
67 | desyncMethod = preferences.desyncMethod.ordinal,
68 | splitPosition = preferences.splitPosition,
69 | splitAtHost = preferences.splitAtHost,
70 | fakeTtl = preferences.fakeTtl,
71 | fakeSni = preferences.fakeSni,
72 | oobChar = preferences.oobChar,
73 | hostMixedCase = preferences.hostMixedCase,
74 | domainMixedCase = preferences.domainMixedCase,
75 | hostRemoveSpaces = preferences.hostRemoveSpaces,
76 | tlsRecordSplit = preferences.tlsRecordSplit,
77 | tlsRecordSplitPosition = preferences.tlsRecordSplitPosition,
78 | tlsRecordSplitAtSni = preferences.tlsRecordSplitAtSni,
79 | hostsMode = preferences.hostsMode.ordinal,
80 | hosts = preferences.hosts,
81 | tcpFastOpen = preferences.tcpFastOpen,
82 | udpFakeCount = preferences.udpFakeCount,
83 | dropSack = preferences.dropSack,
84 | fakeOffset = preferences.fakeOffset,
85 | )
86 | }
87 |
88 | private external fun jniCreateSocketWithCommandLine(args: Array): Int
89 |
90 | private external fun jniCreateSocket(
91 | ip: String,
92 | port: Int,
93 | maxConnections: Int,
94 | bufferSize: Int,
95 | defaultTtl: Int,
96 | customTtl: Boolean,
97 | noDomain: Boolean,
98 | desyncHttp: Boolean,
99 | desyncHttps: Boolean,
100 | desyncUdp: Boolean,
101 | desyncMethod: Int,
102 | splitPosition: Int,
103 | splitAtHost: Boolean,
104 | fakeTtl: Int,
105 | fakeSni: String,
106 | oobChar: Byte,
107 | hostMixedCase: Boolean,
108 | domainMixedCase: Boolean,
109 | hostRemoveSpaces: Boolean,
110 | tlsRecordSplit: Boolean,
111 | tlsRecordSplitPosition: Int,
112 | tlsRecordSplitAtSni: Boolean,
113 | hostsMode: Int,
114 | hosts: String?,
115 | tcpFastOpen: Boolean,
116 | udpFakeCount: Int,
117 | dropSack: Boolean,
118 | fakeOffset: Int,
119 | ): Int
120 |
121 | private external fun jniStartProxy(fd: Int): Int
122 |
123 | private external fun jniStopProxy(fd: Int): Int
124 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/core/ByeDpiProxyPreferences.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.core
2 |
3 | import android.content.SharedPreferences
4 | import io.github.dovecoteescapee.byedpi.utility.getStringNotNull
5 | import io.github.dovecoteescapee.byedpi.utility.shellSplit
6 |
7 | sealed interface ByeDpiProxyPreferences {
8 | companion object {
9 | fun fromSharedPreferences(preferences: SharedPreferences): ByeDpiProxyPreferences =
10 | when (preferences.getBoolean("byedpi_enable_cmd_settings", false)) {
11 | true -> ByeDpiProxyCmdPreferences(preferences)
12 | false -> ByeDpiProxyUIPreferences(preferences)
13 | }
14 | }
15 | }
16 |
17 | class ByeDpiProxyCmdPreferences(val args: Array) : ByeDpiProxyPreferences {
18 | constructor(cmd: String) : this(cmdToArgs(cmd))
19 |
20 | constructor(preferences: SharedPreferences) : this(
21 | preferences.getStringNotNull(
22 | "byedpi_cmd_args",
23 | ""
24 | )
25 | )
26 |
27 | companion object {
28 | private fun cmdToArgs(cmd: String): Array {
29 | val firstArgIndex = cmd.indexOf("-")
30 | val argsStr = (if (firstArgIndex > 0) cmd.substring(firstArgIndex) else cmd).trim()
31 | return arrayOf("ciadpi") + shellSplit(argsStr)
32 | }
33 | }
34 | }
35 |
36 | class ByeDpiProxyUIPreferences(
37 | ip: String? = null,
38 | port: Int? = null,
39 | maxConnections: Int? = null,
40 | bufferSize: Int? = null,
41 | defaultTtl: Int? = null,
42 | noDomain: Boolean? = null,
43 | desyncHttp: Boolean? = null,
44 | desyncHttps: Boolean? = null,
45 | desyncUdp: Boolean? = null,
46 | desyncMethod: DesyncMethod? = null,
47 | splitPosition: Int? = null,
48 | splitAtHost: Boolean? = null,
49 | fakeTtl: Int? = null,
50 | fakeSni: String? = null,
51 | oobChar: String? = null,
52 | hostMixedCase: Boolean? = null,
53 | domainMixedCase: Boolean? = null,
54 | hostRemoveSpaces: Boolean? = null,
55 | tlsRecordSplit: Boolean? = null,
56 | tlsRecordSplitPosition: Int? = null,
57 | tlsRecordSplitAtSni: Boolean? = null,
58 | hostsMode: HostsMode? = null,
59 | hosts: String? = null,
60 | tcpFastOpen: Boolean? = null,
61 | udpFakeCount: Int? = null,
62 | dropSack: Boolean? = null,
63 | byedpiFakeOffset: Int? = null,
64 | ) : ByeDpiProxyPreferences {
65 | val ip: String = ip ?: "127.0.0.1"
66 | val port: Int = port ?: 1080
67 | val maxConnections: Int = maxConnections ?: 512
68 | val bufferSize: Int = bufferSize ?: 16384
69 | val defaultTtl: Int = defaultTtl ?: 0
70 | val customTtl: Boolean = defaultTtl != null
71 | val noDomain: Boolean = noDomain ?: false
72 | val desyncHttp: Boolean = desyncHttp ?: true
73 | val desyncHttps: Boolean = desyncHttps ?: true
74 | val desyncUdp: Boolean = desyncUdp ?: false
75 | val desyncMethod: DesyncMethod = desyncMethod ?: DesyncMethod.Disorder
76 | val splitPosition: Int = splitPosition ?: 1
77 | val splitAtHost: Boolean = splitAtHost ?: false
78 | val fakeTtl: Int = fakeTtl ?: 8
79 | val fakeSni: String = fakeSni ?: "www.iana.org"
80 | val oobChar: Byte = (oobChar ?: "a")[0].code.toByte()
81 | val hostMixedCase: Boolean = hostMixedCase ?: false
82 | val domainMixedCase: Boolean = domainMixedCase ?: false
83 | val hostRemoveSpaces: Boolean = hostRemoveSpaces ?: false
84 | val tlsRecordSplit: Boolean = tlsRecordSplit ?: false
85 | val tlsRecordSplitPosition: Int = tlsRecordSplitPosition ?: 0
86 | val tlsRecordSplitAtSni: Boolean = tlsRecordSplitAtSni ?: false
87 | val hostsMode: HostsMode =
88 | if (hosts?.isBlank() != false) HostsMode.Disable
89 | else hostsMode ?: HostsMode.Disable
90 | val hosts: String? =
91 | if (this.hostsMode == HostsMode.Disable) null
92 | else hosts?.trim()
93 | val tcpFastOpen: Boolean = tcpFastOpen ?: false
94 | val udpFakeCount: Int = udpFakeCount ?: 0
95 | val dropSack: Boolean = dropSack ?: false
96 | val fakeOffset: Int = byedpiFakeOffset ?: 0
97 |
98 | constructor(preferences: SharedPreferences) : this(
99 | ip = preferences.getString("byedpi_proxy_ip", null),
100 | port = preferences.getString("byedpi_proxy_port", null)?.toIntOrNull(),
101 | maxConnections = preferences.getString("byedpi_max_connections", null)?.toIntOrNull(),
102 | bufferSize = preferences.getString("byedpi_buffer_size", null)?.toIntOrNull(),
103 | defaultTtl = preferences.getString("byedpi_default_ttl", null)?.toIntOrNull(),
104 | noDomain = preferences.getBoolean("byedpi_no_domain", false),
105 | desyncHttp = preferences.getBoolean("byedpi_desync_http", true),
106 | desyncHttps = preferences.getBoolean("byedpi_desync_https", true),
107 | desyncUdp = preferences.getBoolean("byedpi_desync_udp", false),
108 | desyncMethod = preferences.getString("byedpi_desync_method", null)
109 | ?.let { DesyncMethod.fromName(it) },
110 | splitPosition = preferences.getString("byedpi_split_position", null)?.toIntOrNull(),
111 | splitAtHost = preferences.getBoolean("byedpi_split_at_host", false),
112 | fakeTtl = preferences.getString("byedpi_fake_ttl", null)?.toIntOrNull(),
113 | fakeSni = preferences.getString("byedpi_fake_sni", null),
114 | oobChar = preferences.getString("byedpi_oob_data", null),
115 | hostMixedCase = preferences.getBoolean("byedpi_host_mixed_case", false),
116 | domainMixedCase = preferences.getBoolean("byedpi_domain_mixed_case", false),
117 | hostRemoveSpaces = preferences.getBoolean("byedpi_host_remove_spaces", false),
118 | tlsRecordSplit = preferences.getBoolean("byedpi_tlsrec_enabled", false),
119 | tlsRecordSplitPosition = preferences.getString("byedpi_tlsrec_position", null)
120 | ?.toIntOrNull(),
121 | tlsRecordSplitAtSni = preferences.getBoolean("byedpi_tlsrec_at_sni", false),
122 | hostsMode = preferences.getString("byedpi_hosts_mode", null)
123 | ?.let { HostsMode.fromName(it) },
124 | hosts = preferences.getString("byedpi_hosts_mode", null)?.let {
125 | when (HostsMode.fromName(it)) {
126 | HostsMode.Blacklist -> preferences.getString("byedpi_hosts_blacklist", null)
127 | HostsMode.Whitelist -> preferences.getString("byedpi_hosts_whitelist", null)
128 | else -> null
129 | }
130 | },
131 | tcpFastOpen = preferences.getBoolean("byedpi_tcp_fast_open", false),
132 | udpFakeCount = preferences.getString("byedpi_udp_fake_count", null)?.toIntOrNull(),
133 | dropSack = preferences.getBoolean("byedpi_drop_sack", false),
134 | byedpiFakeOffset = preferences.getString("byedpi_fake_offset", null)?.toIntOrNull(),
135 | )
136 |
137 | enum class DesyncMethod {
138 | None,
139 | Split,
140 | Disorder,
141 | Fake,
142 | OOB,
143 | DISOOB;
144 |
145 | companion object {
146 | fun fromName(name: String): DesyncMethod {
147 | return when (name) {
148 | "none" -> None
149 | "split" -> Split
150 | "disorder" -> Disorder
151 | "fake" -> Fake
152 | "oob" -> OOB
153 | "disoob" -> DISOOB
154 | else -> throw IllegalArgumentException("Unknown desync method: $name")
155 | }
156 | }
157 | }
158 | }
159 |
160 | enum class HostsMode {
161 | Disable,
162 | Blacklist,
163 | Whitelist;
164 |
165 | companion object {
166 | fun fromName(name: String): HostsMode {
167 | return when (name) {
168 | "disable" -> Disable
169 | "blacklist" -> Blacklist
170 | "whitelist" -> Whitelist
171 | else -> throw IllegalArgumentException("Unknown hosts mode: $name")
172 | }
173 | }
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/core/TProxyService.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.core
2 |
3 | object TProxyService {
4 | init {
5 | System.loadLibrary("hev-socks5-tunnel")
6 | }
7 |
8 | @JvmStatic
9 | external fun TProxyStartService(configPath: String, fd: Int)
10 |
11 | @JvmStatic
12 | external fun TProxyStopService()
13 |
14 | @JvmStatic
15 | @Suppress("unused")
16 | external fun TProxyGetStats(): LongArray
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/data/Actions.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.data
2 |
3 | const val START_ACTION = "start"
4 | const val STOP_ACTION = "stop"
5 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/data/AppStatus.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.data
2 |
3 | enum class AppStatus {
4 | Halted,
5 | Running,
6 | }
7 |
8 | enum class Mode {
9 | Proxy,
10 | VPN;
11 |
12 | companion object {
13 | fun fromSender(sender: Sender): Mode = when (sender) {
14 | Sender.Proxy -> Proxy
15 | Sender.VPN -> VPN
16 | }
17 |
18 | fun fromString(name: String): Mode = when (name) {
19 | "proxy" -> Proxy
20 | "vpn" -> VPN
21 | else -> throw IllegalArgumentException("Invalid mode: $name")
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/data/Broadcasts.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.data
2 |
3 | const val STARTED_BROADCAST = "io.github.dovecoteescapee.byedpi.STARTED"
4 | const val STOPPED_BROADCAST = "io.github.dovecoteescapee.byedpi.STOPPED"
5 | const val FAILED_BROADCAST = "io.github.dovecoteescapee.byedpi.FAILED"
6 |
7 | const val SENDER = "sender"
8 |
9 | enum class Sender(val senderName: String) {
10 | Proxy("Proxy"),
11 | VPN("VPN")
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/data/ServiceStatus.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.data
2 |
3 | enum class ServiceStatus {
4 | Disconnected,
5 | Connected,
6 | Failed,
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/fragments/ByeDpiCommandLineSettingsFragment.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.fragments
2 |
3 | import android.os.Bundle
4 | import androidx.preference.PreferenceFragmentCompat
5 | import io.github.dovecoteescapee.byedpi.R
6 |
7 | class ByeDpiCommandLineSettingsFragment : PreferenceFragmentCompat() {
8 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
9 | setPreferencesFromResource(R.xml.byedpi_cmd_settings, rootKey)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/fragments/ByeDpiUISettingsFragment.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.fragments
2 |
3 | import android.content.SharedPreferences
4 | import android.os.Bundle
5 | import androidx.preference.*
6 | import io.github.dovecoteescapee.byedpi.R
7 | import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyUIPreferences
8 | import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyUIPreferences.DesyncMethod.*
9 | import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyUIPreferences.HostsMode.*
10 | import io.github.dovecoteescapee.byedpi.utility.*
11 |
12 | class ByeDpiUISettingsFragment : PreferenceFragmentCompat() {
13 |
14 | private val preferenceListener =
15 | SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
16 | updatePreferences()
17 | }
18 |
19 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
20 | setPreferencesFromResource(R.xml.byedpi_ui_settings, rootKey)
21 |
22 | setEditTextPreferenceListener("byedpi_proxy_ip") { checkIp(it) }
23 | setEditTestPreferenceListenerPort("byedpi_proxy_port")
24 | setEditTestPreferenceListenerInt(
25 | "byedpi_max_connections",
26 | 1,
27 | Short.MAX_VALUE.toInt()
28 | )
29 | setEditTestPreferenceListenerInt(
30 | "byedpi_buffer_size",
31 | 1,
32 | Int.MAX_VALUE / 4
33 | )
34 | setEditTestPreferenceListenerInt("byedpi_default_ttl", 0, 255)
35 | setEditTestPreferenceListenerInt(
36 | "byedpi_split_position",
37 | Int.MIN_VALUE,
38 | Int.MAX_VALUE
39 | )
40 | setEditTestPreferenceListenerInt("byedpi_fake_ttl", 1, 255)
41 | setEditTestPreferenceListenerInt(
42 | "byedpi_tlsrec_position",
43 | 2 * Short.MIN_VALUE,
44 | 2 * Short.MAX_VALUE,
45 | )
46 |
47 | findPreferenceNotNull("byedpi_oob_data")
48 | .setOnBindEditTextListener {
49 | it.filters = arrayOf(android.text.InputFilter.LengthFilter(1))
50 | }
51 |
52 | updatePreferences()
53 | }
54 |
55 | override fun onResume() {
56 | super.onResume()
57 | sharedPreferences?.registerOnSharedPreferenceChangeListener(preferenceListener)
58 | }
59 |
60 | override fun onPause() {
61 | super.onPause()
62 | sharedPreferences?.unregisterOnSharedPreferenceChangeListener(preferenceListener)
63 | }
64 |
65 | private fun updatePreferences() {
66 | val desyncMethod =
67 | findPreferenceNotNull("byedpi_desync_method")
68 | .value.let { ByeDpiProxyUIPreferences.DesyncMethod.fromName(it) }
69 | val hostsMode = findPreferenceNotNull("byedpi_hosts_mode")
70 | .value.let { ByeDpiProxyUIPreferences.HostsMode.fromName(it) }
71 |
72 | val hostsBlacklist = findPreferenceNotNull("byedpi_hosts_blacklist")
73 | val hostsWhitelist = findPreferenceNotNull("byedpi_hosts_whitelist")
74 | val desyncHttp = findPreferenceNotNull("byedpi_desync_http")
75 | val desyncHttps = findPreferenceNotNull("byedpi_desync_https")
76 | val desyncUdp = findPreferenceNotNull("byedpi_desync_udp")
77 | val splitPosition = findPreferenceNotNull("byedpi_split_position")
78 | val splitAtHost = findPreferenceNotNull("byedpi_split_at_host")
79 | val ttlFake = findPreferenceNotNull("byedpi_fake_ttl")
80 | val fakeSni = findPreferenceNotNull("byedpi_fake_sni")
81 | val fakeOffset = findPreferenceNotNull("byedpi_fake_offset")
82 | val oobChar = findPreferenceNotNull("byedpi_oob_data")
83 | val udpFakeCount = findPreferenceNotNull("byedpi_udp_fake_count")
84 | val hostMixedCase = findPreferenceNotNull("byedpi_host_mixed_case")
85 | val domainMixedCase = findPreferenceNotNull("byedpi_domain_mixed_case")
86 | val hostRemoveSpaces =
87 | findPreferenceNotNull("byedpi_host_remove_spaces")
88 | val splitTlsRec = findPreferenceNotNull("byedpi_tlsrec_enabled")
89 | val splitTlsRecPosition =
90 | findPreferenceNotNull("byedpi_tlsrec_position")
91 | val splitTlsRecAtSni = findPreferenceNotNull("byedpi_tlsrec_at_sni")
92 |
93 | hostsBlacklist.isVisible = hostsMode == Blacklist
94 | hostsWhitelist.isVisible = hostsMode == Whitelist
95 |
96 | val desyncEnabled = desyncMethod != None
97 | splitPosition.isVisible = desyncEnabled
98 | splitAtHost.isVisible = desyncEnabled
99 |
100 | val isFake = desyncMethod == Fake
101 | ttlFake.isVisible = isFake
102 | fakeSni.isVisible = isFake
103 | fakeOffset.isVisible = isFake
104 |
105 | val isOob = desyncMethod == OOB || desyncMethod == DISOOB
106 | oobChar.isVisible = isOob
107 |
108 | val desyncAllProtocols =
109 | !desyncHttp.isChecked && !desyncHttps.isChecked && !desyncUdp.isChecked
110 |
111 | val desyncHttpEnabled = desyncAllProtocols || desyncHttp.isChecked
112 | hostMixedCase.isEnabled = desyncHttpEnabled
113 | domainMixedCase.isEnabled = desyncHttpEnabled
114 | hostRemoveSpaces.isEnabled = desyncHttpEnabled
115 |
116 | val desyncUdpEnabled = desyncAllProtocols || desyncUdp.isChecked
117 | udpFakeCount.isEnabled = desyncUdpEnabled
118 |
119 | val desyncHttpsEnabled = desyncAllProtocols || desyncHttps.isChecked
120 | splitTlsRec.isEnabled = desyncHttpsEnabled
121 | val tlsRecEnabled = desyncHttpsEnabled && splitTlsRec.isChecked
122 | splitTlsRecPosition.isEnabled = tlsRecEnabled
123 | splitTlsRecAtSni.isEnabled = tlsRecEnabled
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/fragments/MainSettingsFragment.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.fragments
2 |
3 | import android.content.SharedPreferences
4 | import android.os.Bundle
5 | import android.util.Log
6 | import androidx.appcompat.app.AppCompatDelegate
7 | import androidx.preference.*
8 | import io.github.dovecoteescapee.byedpi.BuildConfig
9 | import io.github.dovecoteescapee.byedpi.R
10 | import io.github.dovecoteescapee.byedpi.data.Mode
11 | import io.github.dovecoteescapee.byedpi.utility.*
12 |
13 | class MainSettingsFragment : PreferenceFragmentCompat() {
14 | companion object {
15 | private val TAG: String = MainSettingsFragment::class.java.simpleName
16 |
17 | fun setTheme(name: String) =
18 | themeByName(name)?.let {
19 | AppCompatDelegate.setDefaultNightMode(it)
20 | } ?: throw IllegalStateException("Invalid value for app_theme: $name")
21 |
22 | private fun themeByName(name: String): Int? = when (name) {
23 | "system" -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
24 | "light" -> AppCompatDelegate.MODE_NIGHT_NO
25 | "dark" -> AppCompatDelegate.MODE_NIGHT_YES
26 | else -> {
27 | Log.w(TAG, "Invalid value for app_theme: $name")
28 | null
29 | }
30 | }
31 | }
32 |
33 | private val preferenceListener =
34 | SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
35 | updatePreferences()
36 | }
37 |
38 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
39 | setPreferencesFromResource(R.xml.main_settings, rootKey)
40 |
41 | setEditTextPreferenceListener("dns_ip") {
42 | it.isBlank() || checkNotLocalIp(it)
43 | }
44 |
45 | findPreferenceNotNull("app_theme")
46 | .setOnPreferenceChangeListener { _, newValue ->
47 | setTheme(newValue as String)
48 | true
49 | }
50 |
51 | val switchCommandLineSettings = findPreferenceNotNull(
52 | "byedpi_enable_cmd_settings"
53 | )
54 | val uiSettings = findPreferenceNotNull("byedpi_ui_settings")
55 | val cmdSettings = findPreferenceNotNull("byedpi_cmd_settings")
56 |
57 | val setByeDpiSettingsMode = { enable: Boolean ->
58 | uiSettings.isEnabled = !enable
59 | cmdSettings.isEnabled = enable
60 | }
61 |
62 | setByeDpiSettingsMode(switchCommandLineSettings.isChecked)
63 |
64 | switchCommandLineSettings.setOnPreferenceChangeListener { _, newValue ->
65 | setByeDpiSettingsMode(newValue as Boolean)
66 | true
67 | }
68 |
69 | findPreferenceNotNull("version").summary = BuildConfig.VERSION_NAME
70 |
71 | updatePreferences()
72 | }
73 |
74 | override fun onResume() {
75 | super.onResume()
76 | sharedPreferences?.registerOnSharedPreferenceChangeListener(preferenceListener)
77 | }
78 |
79 | override fun onPause() {
80 | super.onPause()
81 | sharedPreferences?.unregisterOnSharedPreferenceChangeListener(preferenceListener)
82 | }
83 |
84 | private fun updatePreferences() {
85 | val mode = findPreferenceNotNull("byedpi_mode")
86 | .value.let { Mode.fromString(it) }
87 | val dns = findPreferenceNotNull("dns_ip")
88 | val ipv6 = findPreferenceNotNull("ipv6_enable")
89 |
90 | when (mode) {
91 | Mode.VPN -> {
92 | dns.isVisible = true
93 | ipv6.isVisible = true
94 | }
95 |
96 | Mode.Proxy -> {
97 | dns.isVisible = false
98 | ipv6.isVisible = false
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ByeDpiProxyService.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.services
2 |
3 | import android.app.Notification
4 | import android.content.Intent
5 | import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
6 | import android.os.Build
7 | import android.util.Log
8 | import androidx.lifecycle.LifecycleService
9 | import androidx.lifecycle.lifecycleScope
10 | import io.github.dovecoteescapee.byedpi.R
11 | import io.github.dovecoteescapee.byedpi.core.ByeDpiProxy
12 | import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyPreferences
13 | import io.github.dovecoteescapee.byedpi.data.*
14 | import io.github.dovecoteescapee.byedpi.utility.*
15 | import kotlinx.coroutines.Dispatchers
16 | import kotlinx.coroutines.Job
17 | import kotlinx.coroutines.launch
18 | import kotlinx.coroutines.sync.Mutex
19 | import kotlinx.coroutines.sync.withLock
20 | import kotlinx.coroutines.withContext
21 |
22 | class ByeDpiProxyService : LifecycleService() {
23 | private var proxy = ByeDpiProxy()
24 | private var proxyJob: Job? = null
25 | private val mutex = Mutex()
26 |
27 | companion object {
28 | private val TAG: String = ByeDpiProxyService::class.java.simpleName
29 | private const val FOREGROUND_SERVICE_ID: Int = 2
30 | private const val NOTIFICATION_CHANNEL_ID: String = "ByeDPI Proxy"
31 |
32 | private var status: ServiceStatus = ServiceStatus.Disconnected
33 | }
34 |
35 | override fun onCreate() {
36 | super.onCreate()
37 | registerNotificationChannel(
38 | this,
39 | NOTIFICATION_CHANNEL_ID,
40 | R.string.proxy_channel_name,
41 | )
42 | }
43 |
44 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
45 | super.onStartCommand(intent, flags, startId)
46 | return when (val action = intent?.action) {
47 | START_ACTION -> {
48 | lifecycleScope.launch { start() }
49 | START_STICKY
50 | }
51 |
52 | STOP_ACTION -> {
53 | lifecycleScope.launch { stop() }
54 | START_NOT_STICKY
55 | }
56 |
57 | else -> {
58 | Log.w(TAG, "Unknown action: $action")
59 | START_NOT_STICKY
60 | }
61 | }
62 | }
63 |
64 | private suspend fun start() {
65 | Log.i(TAG, "Starting")
66 |
67 | if (status == ServiceStatus.Connected) {
68 | Log.w(TAG, "Proxy already connected")
69 | return
70 | }
71 |
72 | try {
73 | mutex.withLock {
74 | startProxy()
75 | }
76 | updateStatus(ServiceStatus.Connected)
77 | startForeground()
78 | } catch (e: Exception) {
79 | Log.e(TAG, "Failed to start proxy", e)
80 | updateStatus(ServiceStatus.Failed)
81 | stop()
82 | }
83 | }
84 |
85 | private fun startForeground() {
86 | val notification: Notification = createNotification()
87 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
88 | startForeground(
89 | FOREGROUND_SERVICE_ID,
90 | notification,
91 | FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
92 | )
93 | } else {
94 | startForeground(FOREGROUND_SERVICE_ID, notification)
95 | }
96 | }
97 |
98 | private suspend fun stop() {
99 | Log.i(TAG, "Stopping VPN")
100 |
101 | mutex.withLock {
102 | stopProxy()
103 | }
104 | updateStatus(ServiceStatus.Disconnected)
105 | stopSelf()
106 | }
107 |
108 | private suspend fun startProxy() {
109 | Log.i(TAG, "Starting proxy")
110 |
111 | if (proxyJob != null) {
112 | Log.w(TAG, "Proxy fields not null")
113 | throw IllegalStateException("Proxy fields not null")
114 | }
115 |
116 | proxy = ByeDpiProxy()
117 | val preferences = getByeDpiPreferences()
118 |
119 | proxyJob = lifecycleScope.launch(Dispatchers.IO) {
120 | val code = proxy.startProxy(preferences)
121 |
122 | withContext(Dispatchers.Main) {
123 | if (code != 0) {
124 | Log.e(TAG, "Proxy stopped with code $code")
125 | updateStatus(ServiceStatus.Failed)
126 | } else {
127 | updateStatus(ServiceStatus.Disconnected)
128 | }
129 | }
130 | }
131 |
132 | Log.i(TAG, "Proxy started")
133 | }
134 |
135 | private suspend fun stopProxy() {
136 | Log.i(TAG, "Stopping proxy")
137 |
138 | if (status == ServiceStatus.Disconnected) {
139 | Log.w(TAG, "Proxy already disconnected")
140 | return
141 | }
142 |
143 | proxy.stopProxy()
144 | proxyJob?.join()
145 | proxyJob = null
146 |
147 | Log.i(TAG, "Proxy stopped")
148 | }
149 |
150 | private fun getByeDpiPreferences(): ByeDpiProxyPreferences =
151 | ByeDpiProxyPreferences.fromSharedPreferences(getPreferences())
152 |
153 | private fun updateStatus(newStatus: ServiceStatus) {
154 | Log.d(TAG, "Proxy status changed from $status to $newStatus")
155 |
156 | status = newStatus
157 |
158 | setStatus(
159 | when (newStatus) {
160 | ServiceStatus.Connected -> AppStatus.Running
161 | ServiceStatus.Disconnected,
162 | ServiceStatus.Failed -> {
163 | proxyJob = null
164 | AppStatus.Halted
165 | }
166 | },
167 | Mode.Proxy
168 | )
169 |
170 | val intent = Intent(
171 | when (newStatus) {
172 | ServiceStatus.Connected -> STARTED_BROADCAST
173 | ServiceStatus.Disconnected -> STOPPED_BROADCAST
174 | ServiceStatus.Failed -> FAILED_BROADCAST
175 | }
176 | )
177 | intent.putExtra(SENDER, Sender.Proxy.ordinal)
178 | sendBroadcast(intent)
179 | }
180 |
181 | private fun createNotification(): Notification =
182 | createConnectionNotification(
183 | this,
184 | NOTIFICATION_CHANNEL_ID,
185 | R.string.notification_title,
186 | R.string.proxy_notification_content,
187 | ByeDpiProxyService::class.java,
188 | )
189 | }
190 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ByeDpiStatus.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.services
2 |
3 | import io.github.dovecoteescapee.byedpi.data.AppStatus
4 | import io.github.dovecoteescapee.byedpi.data.Mode
5 |
6 | var appStatus = AppStatus.Halted to Mode.VPN
7 | private set
8 |
9 | fun setStatus(status: AppStatus, mode: Mode) {
10 | appStatus = status to mode
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ByeDpiVpnService.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.services
2 |
3 | import android.app.Notification
4 | import android.app.PendingIntent
5 | import android.content.Intent
6 | import android.content.pm.ServiceInfo
7 | import android.os.Build
8 | import android.os.ParcelFileDescriptor
9 | import android.util.Log
10 | import androidx.lifecycle.lifecycleScope
11 | import io.github.dovecoteescapee.byedpi.R
12 | import io.github.dovecoteescapee.byedpi.activities.MainActivity
13 | import io.github.dovecoteescapee.byedpi.core.ByeDpiProxy
14 | import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyPreferences
15 | import io.github.dovecoteescapee.byedpi.core.TProxyService
16 | import io.github.dovecoteescapee.byedpi.data.*
17 | import io.github.dovecoteescapee.byedpi.utility.*
18 | import kotlinx.coroutines.Dispatchers
19 | import kotlinx.coroutines.Job
20 | import kotlinx.coroutines.launch
21 | import kotlinx.coroutines.sync.Mutex
22 | import kotlinx.coroutines.sync.withLock
23 | import kotlinx.coroutines.withContext
24 | import java.io.File
25 |
26 | class ByeDpiVpnService : LifecycleVpnService() {
27 | private val byeDpiProxy = ByeDpiProxy()
28 | private var proxyJob: Job? = null
29 | private var tunFd: ParcelFileDescriptor? = null
30 | private val mutex = Mutex()
31 | private var stopping: Boolean = false
32 |
33 | companion object {
34 | private val TAG: String = ByeDpiVpnService::class.java.simpleName
35 | private const val FOREGROUND_SERVICE_ID: Int = 1
36 | private const val NOTIFICATION_CHANNEL_ID: String = "ByeDPIVpn"
37 |
38 | private var status: ServiceStatus = ServiceStatus.Disconnected
39 | }
40 |
41 | override fun onCreate() {
42 | super.onCreate()
43 | registerNotificationChannel(
44 | this,
45 | NOTIFICATION_CHANNEL_ID,
46 | R.string.vpn_channel_name,
47 | )
48 | }
49 |
50 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
51 | super.onStartCommand(intent, flags, startId)
52 | return when (val action = intent?.action) {
53 | START_ACTION -> {
54 | lifecycleScope.launch { start() }
55 | START_STICKY
56 | }
57 |
58 | STOP_ACTION -> {
59 | lifecycleScope.launch { stop() }
60 | START_NOT_STICKY
61 | }
62 |
63 | else -> {
64 | Log.w(TAG, "Unknown action: $action")
65 | START_NOT_STICKY
66 | }
67 | }
68 | }
69 |
70 | override fun onRevoke() {
71 | Log.i(TAG, "VPN revoked")
72 | lifecycleScope.launch { stop() }
73 | }
74 |
75 | private suspend fun start() {
76 | Log.i(TAG, "Starting")
77 |
78 | if (status == ServiceStatus.Connected) {
79 | Log.w(TAG, "VPN already connected")
80 | return
81 | }
82 |
83 | try {
84 | mutex.withLock {
85 | startProxy()
86 | startTun2Socks()
87 | }
88 | updateStatus(ServiceStatus.Connected)
89 | startForeground()
90 | } catch (e: Exception) {
91 | Log.e(TAG, "Failed to start VPN", e)
92 | updateStatus(ServiceStatus.Failed)
93 | stop()
94 | }
95 | }
96 |
97 | private fun startForeground() {
98 | val notification: Notification = createNotification()
99 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
100 | startForeground(
101 | FOREGROUND_SERVICE_ID,
102 | notification,
103 | ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
104 | )
105 | } else {
106 | startForeground(FOREGROUND_SERVICE_ID, notification)
107 | }
108 | }
109 |
110 | private suspend fun stop() {
111 | Log.i(TAG, "Stopping")
112 |
113 | mutex.withLock {
114 | stopping = true
115 | try {
116 | stopTun2Socks()
117 | stopProxy()
118 | } catch (e: Exception) {
119 | Log.e(TAG, "Failed to stop VPN", e)
120 | } finally {
121 | stopping = false
122 | }
123 | }
124 |
125 | updateStatus(ServiceStatus.Disconnected)
126 | stopSelf()
127 | }
128 |
129 | private suspend fun startProxy() {
130 | Log.i(TAG, "Starting proxy")
131 |
132 | if (proxyJob != null) {
133 | Log.w(TAG, "Proxy fields not null")
134 | throw IllegalStateException("Proxy fields not null")
135 | }
136 |
137 | val preferences = getByeDpiPreferences()
138 |
139 | proxyJob = lifecycleScope.launch(Dispatchers.IO) {
140 | val code = byeDpiProxy.startProxy(preferences)
141 |
142 | withContext(Dispatchers.Main) {
143 | if (code != 0) {
144 | Log.e(TAG, "Proxy stopped with code $code")
145 | updateStatus(ServiceStatus.Failed)
146 | } else {
147 | if (!stopping) {
148 | stop()
149 | updateStatus(ServiceStatus.Disconnected)
150 | }
151 | }
152 | }
153 | }
154 |
155 | Log.i(TAG, "Proxy started")
156 | }
157 |
158 | private suspend fun stopProxy() {
159 | Log.i(TAG, "Stopping proxy")
160 |
161 | if (status == ServiceStatus.Disconnected) {
162 | Log.w(TAG, "Proxy already disconnected")
163 | return
164 | }
165 |
166 | byeDpiProxy.stopProxy()
167 | proxyJob?.join() ?: throw IllegalStateException("ProxyJob field null")
168 | proxyJob = null
169 |
170 | Log.i(TAG, "Proxy stopped")
171 | }
172 |
173 | private fun startTun2Socks() {
174 | Log.i(TAG, "Starting tun2socks")
175 |
176 | if (tunFd != null) {
177 | throw IllegalStateException("VPN field not null")
178 | }
179 |
180 | val sharedPreferences = getPreferences()
181 | val port = sharedPreferences.getString("byedpi_proxy_port", null)?.toInt() ?: 1080
182 | val dns = sharedPreferences.getStringNotNull("dns_ip", "1.1.1.1")
183 | val ipv6 = sharedPreferences.getBoolean("ipv6_enable", false)
184 |
185 | val tun2socksConfig = """
186 | | misc:
187 | | task-stack-size: 81920
188 | | socks5:
189 | | mtu: 8500
190 | | address: 127.0.0.1
191 | | port: $port
192 | | udp: udp
193 | """.trimMargin("| ")
194 |
195 | val configPath = try {
196 | File.createTempFile("config", "tmp", cacheDir).apply {
197 | writeText(tun2socksConfig)
198 | }
199 | } catch (e: Exception) {
200 | Log.e(TAG, "Failed to create config file", e)
201 | throw e
202 | }
203 |
204 | val fd = createBuilder(dns, ipv6).establish()
205 | ?: throw IllegalStateException("VPN connection failed")
206 |
207 | this.tunFd = fd
208 |
209 | TProxyService.TProxyStartService(configPath.absolutePath, fd.fd)
210 |
211 | Log.i(TAG, "Tun2Socks started")
212 | }
213 |
214 | private fun stopTun2Socks() {
215 | Log.i(TAG, "Stopping tun2socks")
216 |
217 | TProxyService.TProxyStopService()
218 |
219 | try {
220 | File(cacheDir, "config.tmp").delete()
221 | } catch (e: SecurityException) {
222 | Log.e(TAG, "Failed to delete config file", e)
223 | }
224 |
225 | tunFd?.close() ?: Log.w(TAG, "VPN not running")
226 | tunFd = null
227 |
228 | Log.i(TAG, "Tun2socks stopped")
229 | }
230 |
231 | private fun getByeDpiPreferences(): ByeDpiProxyPreferences =
232 | ByeDpiProxyPreferences.fromSharedPreferences(getPreferences())
233 |
234 | private fun updateStatus(newStatus: ServiceStatus) {
235 | Log.d(TAG, "VPN status changed from $status to $newStatus")
236 |
237 | status = newStatus
238 |
239 | setStatus(
240 | when (newStatus) {
241 | ServiceStatus.Connected -> AppStatus.Running
242 |
243 | ServiceStatus.Disconnected,
244 | ServiceStatus.Failed -> {
245 | proxyJob = null
246 | AppStatus.Halted
247 | }
248 | },
249 | Mode.VPN
250 | )
251 |
252 | val intent = Intent(
253 | when (newStatus) {
254 | ServiceStatus.Connected -> STARTED_BROADCAST
255 | ServiceStatus.Disconnected -> STOPPED_BROADCAST
256 | ServiceStatus.Failed -> FAILED_BROADCAST
257 | }
258 | )
259 | intent.putExtra(SENDER, Sender.VPN.ordinal)
260 | sendBroadcast(intent)
261 | }
262 |
263 | private fun createNotification(): Notification =
264 | createConnectionNotification(
265 | this,
266 | NOTIFICATION_CHANNEL_ID,
267 | R.string.notification_title,
268 | R.string.vpn_notification_content,
269 | ByeDpiVpnService::class.java,
270 | )
271 |
272 | private fun createBuilder(dns: String, ipv6: Boolean): Builder {
273 | Log.d(TAG, "DNS: $dns")
274 | val builder = Builder()
275 | builder.setSession("ByeDPI")
276 | builder.setConfigureIntent(
277 | PendingIntent.getActivity(
278 | this,
279 | 0,
280 | Intent(this, MainActivity::class.java),
281 | PendingIntent.FLAG_IMMUTABLE,
282 | )
283 | )
284 |
285 | builder.addAddress("10.10.10.10", 32)
286 | .addRoute("0.0.0.0", 0)
287 |
288 | if (ipv6) {
289 | builder.addAddress("fd00::1", 128)
290 | .addRoute("::", 0)
291 | }
292 |
293 | if (dns.isNotBlank()) {
294 | builder.addDnsServer(dns)
295 | }
296 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
297 | builder.setMetered(false)
298 | }
299 |
300 | builder.addDisallowedApplication(applicationContext.packageName)
301 |
302 | return builder
303 | }
304 | }
305 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/services/LifecycleVpnService.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.services
2 |
3 | import android.content.Intent
4 | import android.net.VpnService
5 | import android.os.IBinder
6 | import androidx.annotation.CallSuper
7 | import androidx.lifecycle.Lifecycle
8 | import androidx.lifecycle.LifecycleOwner
9 | import androidx.lifecycle.ServiceLifecycleDispatcher
10 |
11 | /**
12 | * Based on [androidx.lifecycle.LifecycleService]
13 | */
14 | open class LifecycleVpnService : VpnService(), LifecycleOwner {
15 | @Suppress("LeakingThis")
16 | private val dispatcher = ServiceLifecycleDispatcher(this)
17 |
18 | @CallSuper
19 | override fun onCreate() {
20 | dispatcher.onServicePreSuperOnCreate()
21 | super.onCreate()
22 | }
23 |
24 | @CallSuper
25 | override fun onBind(intent: Intent): IBinder? {
26 | dispatcher.onServicePreSuperOnBind()
27 | return super.onBind(intent)
28 | }
29 |
30 | @Deprecated("Deprecated in Java")
31 | @CallSuper
32 | override fun onStart(intent: Intent?, startId: Int) {
33 | dispatcher.onServicePreSuperOnStart()
34 | @Suppress("DEPRECATION")
35 | super.onStart(intent, startId)
36 | }
37 |
38 | // this method is added only to annotate it with @CallSuper.
39 | // In usual Service, super.onStartCommand is no-op, but in LifecycleService
40 | // it results in dispatcher.onServicePreSuperOnStart() call, because
41 | // super.onStartCommand calls onStart().
42 | @CallSuper
43 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
44 | return super.onStartCommand(intent, flags, startId)
45 | }
46 |
47 | @CallSuper
48 | override fun onDestroy() {
49 | dispatcher.onServicePreSuperOnDestroy()
50 | super.onDestroy()
51 | }
52 |
53 | override val lifecycle: Lifecycle
54 | get() = dispatcher.lifecycle
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/services/QuickTileService.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.services
2 |
3 | import android.app.PendingIntent
4 | import android.content.BroadcastReceiver
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.IntentFilter
8 | import android.net.VpnService
9 | import android.os.Build
10 | import android.service.quicksettings.Tile
11 | import android.service.quicksettings.TileService
12 | import android.util.Log
13 | import android.widget.Toast
14 | import androidx.annotation.RequiresApi
15 | import androidx.core.content.ContextCompat
16 | import androidx.core.service.quicksettings.PendingIntentActivityWrapper
17 | import androidx.core.service.quicksettings.TileServiceCompat
18 | import io.github.dovecoteescapee.byedpi.R
19 | import io.github.dovecoteescapee.byedpi.activities.MainActivity
20 | import io.github.dovecoteescapee.byedpi.data.*
21 | import io.github.dovecoteescapee.byedpi.utility.getPreferences
22 | import io.github.dovecoteescapee.byedpi.utility.mode
23 |
24 |
25 | @RequiresApi(Build.VERSION_CODES.N)
26 | class QuickTileService : TileService() {
27 |
28 | companion object {
29 | private val TAG: String = QuickTileService::class.java.simpleName
30 | }
31 |
32 | private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
33 | override fun onReceive(context: Context, intent: Intent) {
34 | val senderOrd = intent.getIntExtra(SENDER, -1)
35 | val sender = Sender.entries.getOrNull(senderOrd)
36 | if (sender == null) {
37 | Log.w(TAG, "Received intent with unknown sender: $senderOrd")
38 | return
39 | }
40 |
41 | when (val action = intent.action) {
42 | STARTED_BROADCAST,
43 | STOPPED_BROADCAST -> updateStatus()
44 |
45 | FAILED_BROADCAST -> {
46 | Toast.makeText(
47 | context,
48 | getString(R.string.failed_to_start, sender.name),
49 | Toast.LENGTH_SHORT,
50 | ).show()
51 | updateStatus()
52 | }
53 |
54 | else -> Log.w(TAG, "Unknown action: $action")
55 | }
56 | }
57 | }
58 |
59 | override fun onStartListening() {
60 | updateStatus()
61 | ContextCompat.registerReceiver(
62 | this,
63 | receiver,
64 | IntentFilter().apply {
65 | addAction(STARTED_BROADCAST)
66 | addAction(STOPPED_BROADCAST)
67 | addAction(FAILED_BROADCAST)
68 | },
69 | ContextCompat.RECEIVER_EXPORTED,
70 | )
71 | }
72 |
73 | override fun onStopListening() {
74 | unregisterReceiver(receiver)
75 | }
76 |
77 | private fun launchActivity() {
78 | TileServiceCompat.startActivityAndCollapse(
79 | this, PendingIntentActivityWrapper(
80 | this, 0, Intent(this, MainActivity::class.java),
81 | PendingIntent.FLAG_UPDATE_CURRENT, false
82 | )
83 | )
84 | }
85 |
86 | override fun onClick() {
87 | if (qsTile.state == Tile.STATE_UNAVAILABLE) {
88 | return
89 | }
90 |
91 | unlockAndRun(this::handleClick)
92 | }
93 |
94 | private fun setState(newState: Int) {
95 | qsTile.apply {
96 | state = newState
97 | updateTile()
98 | }
99 | }
100 |
101 | private fun updateStatus() {
102 | val (status) = appStatus
103 | setState(if (status == AppStatus.Halted) Tile.STATE_INACTIVE else Tile.STATE_ACTIVE)
104 | }
105 |
106 | private fun handleClick() {
107 | setState(Tile.STATE_ACTIVE)
108 | setState(Tile.STATE_UNAVAILABLE)
109 |
110 | val (status) = appStatus
111 | when (status) {
112 | AppStatus.Halted -> {
113 | val mode = getPreferences().mode()
114 |
115 | if (mode == Mode.VPN && VpnService.prepare(this) != null) {
116 | updateStatus()
117 | launchActivity()
118 | return
119 | }
120 |
121 | ServiceManager.start(this, mode)
122 | }
123 |
124 | AppStatus.Running -> ServiceManager.stop(this)
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ServiceManager.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.services
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.util.Log
6 | import androidx.core.content.ContextCompat
7 | import io.github.dovecoteescapee.byedpi.data.Mode
8 | import io.github.dovecoteescapee.byedpi.data.START_ACTION
9 | import io.github.dovecoteescapee.byedpi.data.STOP_ACTION
10 |
11 | object ServiceManager {
12 | private val TAG: String = ServiceManager::class.java.simpleName
13 |
14 | fun start(context: Context, mode: Mode) {
15 | when (mode) {
16 | Mode.VPN -> {
17 | Log.i(TAG, "Starting VPN")
18 | val intent = Intent(context, ByeDpiVpnService::class.java)
19 | intent.action = START_ACTION
20 | ContextCompat.startForegroundService(context, intent)
21 | }
22 |
23 | Mode.Proxy -> {
24 | Log.i(TAG, "Starting proxy")
25 | val intent = Intent(context, ByeDpiProxyService::class.java)
26 | intent.action = START_ACTION
27 | ContextCompat.startForegroundService(context, intent)
28 | }
29 | }
30 | }
31 |
32 | fun stop(context: Context) {
33 | val (_, mode) = appStatus
34 | when (mode) {
35 | Mode.VPN -> {
36 | Log.i(TAG, "Stopping VPN")
37 | val intent = Intent(context, ByeDpiVpnService::class.java)
38 | intent.action = STOP_ACTION
39 | ContextCompat.startForegroundService(context, intent)
40 | }
41 |
42 | Mode.Proxy -> {
43 | Log.i(TAG, "Stopping proxy")
44 | val intent = Intent(context, ByeDpiProxyService::class.java)
45 | intent.action = STOP_ACTION
46 | ContextCompat.startForegroundService(context, intent)
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/utility/ArgumentsUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.utility
2 |
3 | // Based on https://gist.github.com/raymyers/8077031
4 | fun shellSplit(string: CharSequence): List {
5 | val tokens: MutableList = ArrayList()
6 | var escaping = false
7 | var quoteChar = ' '
8 | var quoting = false
9 | var lastCloseQuoteIndex = Int.MIN_VALUE
10 | var current = StringBuilder()
11 |
12 | for (i in string.indices) {
13 | val c = string[i]
14 |
15 | if (escaping) {
16 | current.append(c)
17 | escaping = false
18 | } else if (c == '\\' && !(quoting && quoteChar == '\'')) {
19 | escaping = true
20 | } else if (quoting && c == quoteChar) {
21 | quoting = false
22 | lastCloseQuoteIndex = i
23 | } else if (!quoting && (c == '\'' || c == '"')) {
24 | quoting = true
25 | quoteChar = c
26 | } else if (!quoting && Character.isWhitespace(c)) {
27 | if (current.isNotEmpty() || lastCloseQuoteIndex == i - 1) {
28 | tokens.add(current.toString())
29 | current = StringBuilder()
30 | }
31 | } else {
32 | current.append(c)
33 | }
34 | }
35 |
36 | if (current.isNotEmpty() || lastCloseQuoteIndex == string.length - 1) {
37 | tokens.add(current.toString())
38 | }
39 |
40 | return tokens
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/utility/NotificationUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.utility
2 |
3 | import android.app.Notification
4 | import android.app.NotificationChannel
5 | import android.app.NotificationManager
6 | import android.app.PendingIntent
7 | import android.content.Context
8 | import android.content.Intent
9 | import android.os.Build
10 | import androidx.annotation.StringRes
11 | import androidx.core.app.NotificationCompat
12 | import io.github.dovecoteescapee.byedpi.R
13 | import io.github.dovecoteescapee.byedpi.activities.MainActivity
14 | import io.github.dovecoteescapee.byedpi.data.STOP_ACTION
15 |
16 | fun registerNotificationChannel(context: Context, id: String, @StringRes name: Int) {
17 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
18 | val manager = context.getSystemService(NotificationManager::class.java) ?: return
19 |
20 | val channel = NotificationChannel(
21 | id,
22 | context.getString(name),
23 | NotificationManager.IMPORTANCE_DEFAULT
24 | )
25 | channel.enableLights(false)
26 | channel.enableVibration(false)
27 | channel.setShowBadge(false)
28 |
29 | manager.createNotificationChannel(channel)
30 | }
31 | }
32 |
33 | fun createConnectionNotification(
34 | context: Context,
35 | channelId: String,
36 | @StringRes title: Int,
37 | @StringRes content: Int,
38 | service: Class<*>,
39 | ): Notification =
40 | NotificationCompat.Builder(context, channelId)
41 | .setSmallIcon(R.drawable.ic_notification)
42 | .setSilent(true)
43 | .setContentTitle(context.getString(title))
44 | .setContentText(context.getString(content))
45 | .addAction(0, "Stop",
46 | PendingIntent.getService(
47 | context,
48 | 0,
49 | Intent(context, service).setAction(STOP_ACTION),
50 | PendingIntent.FLAG_IMMUTABLE,
51 | )
52 | )
53 | .setContentIntent(
54 | PendingIntent.getActivity(
55 | context,
56 | 0,
57 | Intent(context, MainActivity::class.java),
58 | PendingIntent.FLAG_IMMUTABLE,
59 | )
60 | )
61 | .build()
62 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/utility/PreferencesUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.utility
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import androidx.preference.Preference
6 | import androidx.preference.PreferenceFragmentCompat
7 | import androidx.preference.PreferenceManager
8 | import io.github.dovecoteescapee.byedpi.data.Mode
9 |
10 | val PreferenceFragmentCompat.sharedPreferences
11 | get() = preferenceScreen.sharedPreferences
12 |
13 | fun Context.getPreferences(): SharedPreferences =
14 | PreferenceManager.getDefaultSharedPreferences(this)
15 |
16 | fun SharedPreferences.getStringNotNull(key: String, defValue: String): String =
17 | getString(key, defValue) ?: defValue
18 |
19 | fun SharedPreferences.mode(): Mode =
20 | Mode.fromString(getStringNotNull("byedpi_mode", "vpn"))
21 |
22 | fun PreferenceFragmentCompat.findPreferenceNotNull(key: CharSequence): T =
23 | findPreference(key) ?: throw IllegalStateException("Preference $key not found")
24 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/dovecoteescapee/byedpi/utility/ValidateUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi.utility
2 |
3 | import android.net.InetAddresses
4 | import android.os.Build
5 | import android.util.Log
6 | import android.widget.Toast
7 | import androidx.preference.EditTextPreference
8 | import androidx.preference.PreferenceFragmentCompat
9 |
10 | private const val TAG = "ValidateUtils"
11 |
12 | fun checkIp(ip: String): Boolean =
13 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
14 | InetAddresses.isNumericAddress(ip)
15 | } else {
16 | // This pattern doesn't not support IPv6
17 | // @Suppress("DEPRECATION")
18 | // Patterns.IP_ADDRESS.matcher(ip).matches()
19 | true
20 | }
21 |
22 | fun checkNotLocalIp(ip: String): Boolean =
23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
24 | InetAddresses.isNumericAddress(ip) && InetAddresses.parseNumericAddress(ip).let {
25 | !it.isAnyLocalAddress && !it.isLoopbackAddress
26 | }
27 | } else {
28 | // This pattern doesn't not support IPv6
29 | // @Suppress("DEPRECATION")
30 | // Patterns.IP_ADDRESS.matcher(ip).matches()
31 | true
32 | }
33 |
34 | fun PreferenceFragmentCompat.setEditTestPreferenceListenerPort(key: String) {
35 | setEditTestPreferenceListenerInt(key, 1, 65535)
36 | }
37 |
38 | fun PreferenceFragmentCompat.setEditTestPreferenceListenerInt(
39 | key: String,
40 | min: Int = Int.MIN_VALUE,
41 | max: Int = Int.MAX_VALUE
42 | ) {
43 | setEditTextPreferenceListener(key) { value ->
44 | value.toIntOrNull()?.let { it in min..max } ?: false
45 | }
46 | }
47 |
48 | fun PreferenceFragmentCompat.setEditTextPreferenceListener(
49 | key: String,
50 | check: (String) -> Boolean
51 | ) {
52 | findPreferenceNotNull(key)
53 | .setOnPreferenceChangeListener { preference, newValue ->
54 | when (newValue) {
55 | is String -> {
56 | val valid = check(newValue)
57 | if (!valid) {
58 | Toast.makeText(
59 | requireContext(),
60 | "Invalid value for ${preference.title}: $newValue",
61 | Toast.LENGTH_SHORT
62 | ).show()
63 | }
64 | valid
65 | }
66 |
67 | else -> {
68 | Log.w(
69 | TAG,
70 | "Invalid type for ${preference.key}: " +
71 | "$newValue has type ${newValue::class.java}"
72 | )
73 | false
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/jni/Android.mk:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 The Android Open Source Project
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
16 | include $(call all-subdir-makefiles)
17 |
--------------------------------------------------------------------------------
/app/src/main/jni/Application.mk:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 The Android Open Source Project
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
16 | APP_OPTIM := release
17 | APP_PLATFORM := android-21
18 | APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
19 | APP_CFLAGS := -O3 -DPKGNAME=io/github/dovecoteescapee/byedpi/core
20 | APP_CPPFLAGS := -O3 -std=c++11
21 | NDK_TOOLCHAIN_VERSION := clang
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_settings_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_github_36.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_notification.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
23 |
24 |
32 |
33 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - System
5 | - Light
6 | - Dark
7 |
8 |
9 | - system
10 | - light
11 | - dark
12 |
13 |
14 |
15 | - VPN
16 | - Proxy
17 |
18 |
19 | - vpn
20 | - proxy
21 |
22 |
23 |
24 | - None
25 | - Split
26 | - Disorder
27 | - Fake
28 | - Out-of-band
29 | - Disordered out-of-band
30 |
31 |
32 | - none
33 | - split
34 | - disorder
35 | - fake
36 | - oob
37 | - disoob
38 |
39 |
40 |
41 | - Disable
42 | - Blacklist
43 | - Whitelist
44 |
45 |
46 | - disable
47 | - blacklist
48 | - whitelist
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #061A23
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | https://github.com/hufrea/byedpi/blob/v0.13/README.md
3 | ByeDPI
4 | Connect
5 | Disconnect
6 | Connected
7 | Disconnected
8 | Proxy is up
9 | Proxy is down
10 | Start
11 | Stop
12 | Settings
13 | VPN permission denied
14 | Please stop the VPN service before changing settings
15 | Settings
16 | Save logs
17 | Reset
18 | Failed to collect logs
19 | %1$s:%2$s
20 | Theme
21 | Mode
22 | DNS
23 | Listen address
24 | Port
25 | Maximum number of connections
26 | Buffer size
27 | Default TTL
28 | No domain
29 | Desync method
30 | Split position
31 | Split at host
32 | TTL of fake packets
33 | Host mixed case
34 | Domain mixed case
35 | Host remove spaces
36 | Split TLS record
37 | TLS record split position
38 | Split TLS record at SNI
39 | Version
40 | Source code
41 | What does all of this mean?
42 | Failed to start %1$s
43 | VPN
44 | Proxy
45 | ByeDPI
46 | VPN is running
47 | Proxy is running
48 | General
49 | ByeDPI
50 | About
51 | Command line editor
52 | UI editor
53 | Use command line settings
54 | Desync HTTP
55 | Desync HTTPS
56 | Desync UDP
57 | OOB Data
58 | SNI of fake packet
59 | Proxy
60 | Desync
61 | Command line arguments
62 | Hosts
63 | Hosts blacklist
64 | Hosts whitelist
65 | TCP Fast Open
66 | UDP fake count
67 | IPv6
68 | Fake offset
69 | Drop SACK
70 | UDP
71 | HTTP
72 | HTTPS
73 | Protocols
74 | Uncheck all to desync all traffic
75 |
76 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/byedpi_cmd_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/byedpi_ui_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
13 |
14 |
15 |
17 |
18 |
23 |
24 |
30 |
31 |
37 |
38 |
44 |
45 |
49 |
50 |
54 |
55 |
56 |
57 |
59 |
60 |
67 |
68 |
74 |
75 |
81 |
82 |
88 |
89 |
96 |
97 |
103 |
104 |
108 |
109 |
113 |
114 |
120 |
121 |
127 |
128 |
133 |
134 |
139 |
140 |
141 |
142 |
145 |
146 |
150 |
151 |
155 |
156 |
160 |
161 |
162 |
163 |
165 |
166 |
170 |
171 |
175 |
176 |
180 |
181 |
182 |
183 |
185 |
186 |
190 |
191 |
197 |
198 |
202 |
203 |
204 |
205 |
207 |
208 |
214 |
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/main_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
10 |
17 |
18 |
25 |
26 |
31 |
32 |
36 |
37 |
38 |
39 |
41 |
42 |
46 |
47 |
52 |
53 |
58 |
59 |
60 |
61 |
63 |
64 |
69 |
70 |
74 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/app/src/test/java/io/github/dovecoteescapee/byedpi/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.dovecoteescapee.byedpi
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id("com.android.application") version "8.3.0" apply false
4 | id("org.jetbrains.kotlin.android") version "1.9.22" apply false
5 | }
6 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | ByeDPI runs a local VPN service to bypass DPI (Deep Packet Inspection) and censorship. It runs a SOCKS5 proxy ByeDPI and redirects all traffic through it.
To bypass some blocks, you may need to change the settings. More about the various settings can be found in the ByeDPI documentation .
The application uses the VPN mode on Android to redirect traffic, but does not send anything to a remote server. It does not encrypt traffic and does not hide your IP address.
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot3.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Bypass censorship on Android
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru-RU/full_description.txt:
--------------------------------------------------------------------------------
1 | ByeDPI запускает локальный VPN-сервис для обхода DPI (Deep Packet Inspection) и цензуры. Приложение локально запускает SOCKS5-прокси ByeDPI и перенаправляет весь трафик через него.
Для обхода некоторых блокировок может потребоваться изменить настройки. Подробнее о различных настройках можно прочитать в документации ByeDPI .
Приложение использует VPN-режим на Android для перенаправления трафика, но не передает ничего на удаленный сервер. Оно не шифрует трафик и не скрывает ваш IP-адрес.
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru-RU/short_description.txt:
--------------------------------------------------------------------------------
1 | Обход цензуры на Android
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dovecoteescapee/ByeDPIAndroid/43db0955de687d43f9473374dc7c5548eaa55359/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Feb 19 00:00:21 MSK 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | rootProject.name = "ByeDpi"
17 | include(":app")
18 |
--------------------------------------------------------------------------------