- Update byedpi to v0.12

- Switch to original tun2socks
- Fix crash when socket opening failed
- Update Readme
- Remove redundant strings
This commit is contained in:
dovecoteescapee 2024-08-08 22:55:29 +03:00
parent 312dc4b264
commit 6d7b0bf63f
16 changed files with 238 additions and 96 deletions

View File

@ -2,8 +2,8 @@
[English](README.md) | **Русский** [English](README.md) | **Русский**
<div align="center"> <div style="text-align: center;">
<img src=".github/images/logo.svg" height="200px" width="200px" /> <img src=".github/images/logo.svg" width="100%" height="200px">
</div> </div>
--- ---
@ -55,8 +55,7 @@ DPI (Deep Packet Inspection) - это технология для анализа
## Зависимости ## Зависимости
- [ByeDPI](https://github.com/hufrea/byedpi) - [ByeDPI](https://github.com/hufrea/byedpi)
- [Tun2Socks](https://github.com/dovecoteescapee/tun2socks)* - [Tun2Socks](https://github.com/xjasonlyu/tun2socks)
*форк с добавлением раздельного тунелирования TCP и UDP
## Сборка ## Сборка
@ -70,8 +69,17 @@ DPI (Deep Packet Inspection) - это технология для анализа
Сборка приложения: Сборка приложения:
1. Клонируйте репозиторий с подмодулями: 1. Установите gomobile:
`git clone --recurse-submodules` ```bash
2. Запустите скрипт сборки из корня репозитория: go install golang.org/x/mobile/cmd/gomobile@latest
`./gradlew assembleRelease` gomobile init
3. APK будет лежать в `app/build/outputs/apk/release/` ```
2. Клонируйте репозиторий с подмодулями:
```bash
git clone --recurse-submodules
```
3. Запустите скрипт сборки из корня репозитория:
```bash
./gradlew assembleRelease`
```
4. APK будет лежать в `app/build/outputs/apk/release/`

View File

@ -2,8 +2,8 @@
**English** | [Русский](README-ru.md) **English** | [Русский](README-ru.md)
<div align="center"> <div style="text-align: center;">
<img src=".github/images/logo.svg" height="200px" width="200px" /> <img src=".github/images/logo.svg" width="100%" height="200px">
</div> </div>
--- ---
@ -55,8 +55,7 @@ DPI (Deep Packet Inspection) is a technology for analyzing and filtering traffic
## Dependencies ## Dependencies
- [ByeDPI](https://github.com/hufrea/byedpi) - [ByeDPI](https://github.com/hufrea/byedpi)
- [Tun2Socks](https://github.com/dovecoteescapee/tun2socks)* - [Tun2Socks](https://github.com/xjasonlyu/tun2socks)
*fork with the addition of separate tunneling of TCP and UDP
## Building ## Building
@ -70,8 +69,17 @@ For building the application, you need:
To build the application: To build the application:
1. Clone the repository with submodules: 1. Install gomobile:
`git clone --recurse-submodules` ```bash
2. Run the build script from the root of the repository: go install golang.org/x/mobile/cmd/gomobile@latest
`./gradlew assembleRelease` gomobile init
3. The APK will be in `app/build/outputs/apk/release/` ```
2. Clone the repository with submodules:
```bash
git clone --recurse-submodules
```
3. Run the build script from the root of the repository:
```bash
./gradlew assembleRelease
```
4. The APK will be in `app/build/outputs/apk/release/`

View File

@ -69,26 +69,75 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
} }
abstract class BuildTun2Socks : DefaultTask() { abstract class BaseTun2SocksTask : DefaultTask() {
@TaskAction @get:InputDirectory
fun buildTun2Socks() { val tun2socksDir: File
val projectDir = project.projectDir get() = project.file("libs/tun2socks")
val tun2socksDir = projectDir.resolve("libs/tun2socks")
val tun2socksOutput = projectDir.resolve("libs/tun2socks.aar")
@get:OutputFile
val tun2socksOutput: File
get() = project.file("libs/tun2socks.aar")
@Internal
protected fun isUpToDate(): Boolean {
if (tun2socksOutput.exists()) { if (tun2socksOutput.exists()) {
val lastModified = tun2socksOutput.lastModified()
return !tun2socksDir.walkTopDown().any {
it.isFile && it.lastModified() > lastModified
}
}
return false
}
}
abstract class GetGomobileBind : BaseTun2SocksTask() {
@TaskAction
fun getBind() {
if (isUpToDate()) {
logger.lifecycle("No changes detected, skipping getBind.")
return return
} }
project.exec { project.exec {
workingDir = tun2socksDir workingDir = tun2socksDir
commandLine("gomobile", "bind", "-o", tun2socksOutput, "-trimpath", "./engine")
commandLine("go", "get", "golang.org/x/mobile/bind")
} }
} }
} }
abstract class BuildTun2Socks : BaseTun2SocksTask() {
@TaskAction
fun buildTun2Socks() {
if (isUpToDate()) {
logger.lifecycle("No changes detected, skipping buildTun2Socks.")
return
}
project.exec {
workingDir = tun2socksDir
commandLine(
"gomobile", "bind",
"-target", "android",
"-androidapi", "21",
"-o", tun2socksOutput,
"-trimpath",
"./engine"
)
}
}
}
tasks.register<GetGomobileBind>("getGomobileBind") {
group = "build"
description = "Get gomobile bind for compiling tun2socks"
}
tasks.register<BuildTun2Socks>("buildTun2Socks") { tasks.register<BuildTun2Socks>("buildTun2Socks") {
group = "build" group = "build"
description = "Build tun2socks" description = "Build tun2socks for Android"
dependsOn("getGomobileBind")
} }
tasks.withType(KotlinCompile::class).configureEach { tasks.withType(KotlinCompile::class).configureEach {

@ -1 +1 @@
Subproject commit b357c5b50ac07aa36aed843b803df06a307fe61a Subproject commit e083dafcf534d85e7fce86b3d48ee5c7a63c604e

View File

@ -28,10 +28,13 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt. # List C/C++ source files with relative paths to this CMakeLists.txt.
byedpi/conev.c byedpi/conev.c
byedpi/desync.c byedpi/desync.c
byedpi/extend.c
byedpi/packets.c byedpi/packets.c
byedpi/proxy.c byedpi/proxy.c
byedpi/main.c byedpi/main.c
byedpi/mpool.c
native-lib.c native-lib.c
utils.c
) )
include_directories("byedpi") include_directories("byedpi")
@ -46,4 +49,5 @@ add_compile_definitions(ANDROID_APP)
target_link_libraries(${CMAKE_PROJECT_NAME} target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library # List libraries link to the target library
android android
log) log
)

@ -1 +1 @@
Subproject commit 6b484d598817cffd61344a812721fb00089f5095 Subproject commit dcf5ed727c996d0073a6cb95d5eec45a793d28a2

12
app/src/main/cpp/main.h Normal file
View File

@ -0,0 +1,12 @@
extern char *oob_char;
extern int NOT_EXIT;
struct sockaddr_ina;
int get_default_ttl();
int get_addr(const char *str, struct sockaddr_ina *addr);
void *add(void **root, int *n, size_t ss);
void clear_params(void);

View File

@ -1,14 +1,16 @@
#include <error.h>
#include <proxy.h>
#include <params.h>
#include <packets.h>
#include <unistd.h>
#include <string.h> #include <string.h>
#include <netdb.h> #include <netdb.h>
#include <jni.h> #include <jni.h>
#include <android/log.h> #include <android/log.h>
#include "byedpi/error.h"
#include "byedpi/proxy.h"
#include "byedpi/params.h"
#include "byedpi/packets.h"
#include "main.h"
#include "utils.h"
const enum demode DESYNC_METHODS[] = { const enum demode DESYNC_METHODS[] = {
DESYNC_NONE, DESYNC_NONE,
DESYNC_SPLIT, DESYNC_SPLIT,
@ -17,14 +19,9 @@ const enum demode DESYNC_METHODS[] = {
DESYNC_OOB, DESYNC_OOB,
}; };
extern int NOT_EXIT;
extern int get_default_ttl();
extern int get_addr(const char *str, struct sockaddr_ina *addr);
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, __attribute__((unused)) void *reserved) { JNIEXPORT jint JNI_OnLoad(JavaVM *vm, __attribute__((unused)) void *reserved) {
oob_data.data = NULL; oob_data.data = NULL;
default_params = params;
return JNI_VERSION_1_6; return JNI_VERSION_1_6;
} }
@ -37,8 +34,8 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
jint max_connections, jint max_connections,
jint buffer_size, jint buffer_size,
jint default_ttl, jint default_ttl,
jboolean custom_ttl,
jboolean no_domain, jboolean no_domain,
jboolean desync_known,
jint desync_method, jint desync_method,
jint split_position, jint split_position,
jboolean split_at_host, jboolean split_at_host,
@ -52,52 +49,90 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
jint tls_record_split_position, jint tls_record_split_position,
jboolean tls_record_split_at_sni) { jboolean tls_record_split_at_sni) {
struct sockaddr_ina s = { struct sockaddr_ina s;
.in.sin_family = AF_INET,
.in.sin_addr.s_addr = inet_addr("0.0.0.0"),
};
const char *address = (*env)->GetStringUTFChars(env, ip, 0); const char *address = (*env)->GetStringUTFChars(env, ip, 0);
if (get_addr(address, &s) < 0) { int res = get_addr(address, &s);
(*env)->ReleaseStringUTFChars(env, ip, address);
if (res < 0) {
uniperror("get_addr");
return -1; return -1;
} }
(*env)->ReleaseStringUTFChars(env, ip, address);
s.in.sin_port = htons(port); s.in.sin_port = htons(port);
params.max_open = max_connections; params.max_open = max_connections;
params.bfsize = buffer_size; params.bfsize = buffer_size;
params.def_ttl = default_ttl;
params.resolve = !no_domain; params.resolve = !no_domain;
params.de_known = desync_known;
params.attack = DESYNC_METHODS[desync_method];
params.split = split_position;
params.split_host = split_at_host;
params.ttl = fake_ttl;
params.mod_http =
MH_HMIX * host_mixed_case |
MH_DMIX * domain_mixed_case |
MH_SPACE * host_remove_spaces;
params.tlsrec = tls_record_split;
params.tlsrec_pos = tls_record_split_position;
params.tlsrec_sni = tls_record_split_at_sni;
if (!params.def_ttl && params.attack != DESYNC_NONE) { if (custom_ttl) {
params.def_ttl = default_ttl;
params.custom_ttl = 1;
}
if (!params.def_ttl) {
if ((params.def_ttl = get_default_ttl()) < 1) { if ((params.def_ttl = get_default_ttl()) < 1) {
uniperror("get_default_ttl");
reset_params();
return -1; return -1;
} }
} }
int fd = listen_socket(&s); struct desync_params *dp = add(
if (fd < 0) { (void *) &params.dp,
uniperror("listen_socket"); &params.dp_count,
return get_e(); sizeof(struct desync_params)
);
if (!dp) {
uniperror("add");
reset_params();
return -1;
} }
if (params.attack == DESYNC_FAKE) { dp->ttl = fake_ttl;
dp->mod_http =
MH_HMIX * host_mixed_case |
MH_DMIX * domain_mixed_case |
MH_SPACE * host_remove_spaces;
struct part *part = add(
(void *) &dp->parts,
&dp->parts_n,
sizeof(struct part)
);
if (!part) {
uniperror("add");
reset_params();
return -1;
}
enum demode mode = DESYNC_METHODS[desync_method];
part->flag = split_at_host ? OFFSET_SNI : 0;
part->pos = split_position;
part->m = mode;
if (tls_record_split) {
struct part *tlsrec_part = add(
(void *) &dp->tlsrec,
&dp->tlsrec_n,
sizeof(struct part)
);
if (!tlsrec_part) {
uniperror("add");
reset_params();
return -1;
}
tlsrec_part->flag = tls_record_split_at_sni ? OFFSET_SNI : 0;
tlsrec_part->pos = tls_record_split_position;
}
if (mode == DESYNC_FAKE) {
const char *sni = (*env)->GetStringUTFChars(env, fake_sni, 0); const char *sni = (*env)->GetStringUTFChars(env, fake_sni, 0);
LOG(LOG_S, "fake_sni: %s", sni); LOG(LOG_S, "fake_sni: %s", sni);
int res = change_tls_sni(sni, fake_tls.data, fake_tls.size); res = change_tls_sni(sni, fake_tls.data, fake_tls.size);
(*env)->ReleaseStringUTFChars(env, fake_sni, sni); (*env)->ReleaseStringUTFChars(env, fake_sni, sni);
if (res) { if (res) {
fprintf(stderr, "error chsni\n"); fprintf(stderr, "error chsni\n");
@ -105,14 +140,11 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
} }
} }
if (params.attack == DESYNC_OOB) { if (mode == DESYNC_OOB) {
const char *oob = (*env)->GetStringUTFChars(env, custom_oob_data, 0); const char *oob = (*env)->GetStringUTFChars(env, custom_oob_data, 0);
const size_t oob_len = strlen(oob); const size_t oob_len = strlen(oob);
LOG(LOG_L, "custom_oob_data: %s", oob);
oob_data.size = oob_len; oob_data.size = oob_len;
LOG(LOG_L, "before free");
free(oob_data.data);
LOG(LOG_L, "after free");
oob_data.data = malloc(oob_len); oob_data.data = malloc(oob_len);
if (oob_data.data == NULL) { if (oob_data.data == NULL) {
uniperror("malloc"); uniperror("malloc");
@ -122,7 +154,20 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
(*env)->ReleaseStringUTFChars(env, custom_oob_data, oob); (*env)->ReleaseStringUTFChars(env, custom_oob_data, oob);
} }
params.mempool = mem_pool(0);
if (!params.mempool) {
uniperror("mem_pool");
clear_params();
return -1;
}
int fd = listen_socket(&s);
if (fd < 0) {
uniperror("listen_socket");
return -1;
}
LOG(LOG_S, "listen_socket, fd: %d", fd); LOG(LOG_S, "listen_socket, fd: %d", fd);
return fd; return fd;
} }
@ -134,6 +179,7 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniStartProxy(
LOG(LOG_S, "start_proxy, fd: %d", fd); LOG(LOG_S, "start_proxy, fd: %d", fd);
NOT_EXIT = 1; NOT_EXIT = 1;
if (event_loop(fd) < 0) { if (event_loop(fd) < 0) {
uniperror("event_loop");
return get_e(); return get_e();
} }
return 0; return 0;
@ -145,7 +191,12 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniStopProxy(
__attribute__((unused)) jobject thiz, __attribute__((unused)) jobject thiz,
jint fd) { jint fd) {
LOG(LOG_S, "stop_proxy, fd: %d", fd); LOG(LOG_S, "stop_proxy, fd: %d", fd);
if (shutdown(fd, SHUT_RDWR) < 0) {
int res = shutdown(fd, SHUT_RDWR);
reset_params();
if (res < 0) {
uniperror("shutdown");
return get_e(); return get_e();
} }
return 0; return 0;

10
app/src/main/cpp/utils.c Normal file
View File

@ -0,0 +1,10 @@
#include "utils.h"
#include "byedpi/params.h"
#include "main.h"
struct params default_params;
void reset_params(void) {
clear_params();
params = default_params;
}

3
app/src/main/cpp/utils.h Normal file
View File

@ -0,0 +1,3 @@
extern struct params default_params;
void reset_params(void);

View File

@ -2,7 +2,6 @@ package io.github.dovecoteescapee.byedpi.core
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import java.io.IOException
class ByeDpiProxy { class ByeDpiProxy {
companion object { companion object {
@ -14,8 +13,13 @@ class ByeDpiProxy {
private val mutex = Mutex() private val mutex = Mutex()
private var fd = -1 private var fd = -1
suspend fun startProxy(preferences: ByeDpiProxyPreferences): Int = suspend fun startProxy(preferences: ByeDpiProxyPreferences): Int {
jniStartProxy(createSocket(preferences)) val fd = createSocket(preferences)
if (fd < 0) {
return -1 // TODO: should be error code
}
return jniStartProxy(fd)
}
suspend fun stopProxy(): Int { suspend fun stopProxy(): Int {
mutex.withLock { mutex.withLock {
@ -43,8 +47,8 @@ class ByeDpiProxy {
maxConnections = preferences.maxConnections, maxConnections = preferences.maxConnections,
bufferSize = preferences.bufferSize, bufferSize = preferences.bufferSize,
defaultTtl = preferences.defaultTtl, defaultTtl = preferences.defaultTtl,
customTtl = preferences.customTtl,
noDomain = preferences.noDomain, noDomain = preferences.noDomain,
desyncKnown = preferences.desyncKnown,
desyncMethod = preferences.desyncMethod.ordinal, desyncMethod = preferences.desyncMethod.ordinal,
splitPosition = preferences.splitPosition, splitPosition = preferences.splitPosition,
splitAtHost = preferences.splitAtHost, splitAtHost = preferences.splitAtHost,
@ -60,7 +64,7 @@ class ByeDpiProxy {
) )
if (fd < 0) { if (fd < 0) {
throw IOException("Failed to create socket") return -1
} }
this.fd = fd this.fd = fd
@ -73,8 +77,8 @@ class ByeDpiProxy {
maxConnections: Int, maxConnections: Int,
bufferSize: Int, bufferSize: Int,
defaultTtl: Int, defaultTtl: Int,
customTtl: Boolean,
noDomain: Boolean, noDomain: Boolean,
desyncKnown: Boolean,
desyncMethod: Int, desyncMethod: Int,
splitPosition: Int, splitPosition: Int,
splitAtHost: Boolean, splitAtHost: Boolean,

View File

@ -9,7 +9,6 @@ class ByeDpiProxyPreferences(
bufferSize: Int? = null, bufferSize: Int? = null,
defaultTtl: Int? = null, defaultTtl: Int? = null,
noDomain: Boolean? = null, noDomain: Boolean? = null,
desyncKnown: Boolean? = null,
desyncMethod: DesyncMethod? = null, desyncMethod: DesyncMethod? = null,
splitPosition: Int? = null, splitPosition: Int? = null,
splitAtHost: Boolean? = null, splitAtHost: Boolean? = null,
@ -28,8 +27,8 @@ class ByeDpiProxyPreferences(
val maxConnections: Int = maxConnections ?: 512 val maxConnections: Int = maxConnections ?: 512
val bufferSize: Int = bufferSize ?: 16384 val bufferSize: Int = bufferSize ?: 16384
val defaultTtl: Int = defaultTtl ?: 0 val defaultTtl: Int = defaultTtl ?: 0
val customTtl: Boolean = defaultTtl != null
val noDomain: Boolean = noDomain ?: false val noDomain: Boolean = noDomain ?: false
val desyncKnown: Boolean = desyncKnown ?: false
val desyncMethod: DesyncMethod = desyncMethod ?: DesyncMethod.Disorder val desyncMethod: DesyncMethod = desyncMethod ?: DesyncMethod.Disorder
val splitPosition: Int = splitPosition ?: 3 val splitPosition: Int = splitPosition ?: 3
val splitAtHost: Boolean = splitAtHost ?: false val splitAtHost: Boolean = splitAtHost ?: false
@ -50,7 +49,6 @@ class ByeDpiProxyPreferences(
bufferSize = preferences.getString("byedpi_buffer_size", null)?.toIntOrNull(), bufferSize = preferences.getString("byedpi_buffer_size", null)?.toIntOrNull(),
defaultTtl = preferences.getString("byedpi_default_ttl", null)?.toIntOrNull(), defaultTtl = preferences.getString("byedpi_default_ttl", null)?.toIntOrNull(),
noDomain = preferences.getBoolean("byedpi_no_domain", false), noDomain = preferences.getBoolean("byedpi_no_domain", false),
desyncKnown = preferences.getBoolean("byedpi_desync_known", false),
desyncMethod = preferences.getString("byedpi_desync_method", null) desyncMethod = preferences.getString("byedpi_desync_method", null)
?.let { DesyncMethod.fromName(it) }, ?.let { DesyncMethod.fromName(it) },
splitPosition = preferences.getString("byedpi_split_position", null)?.toIntOrNull(), splitPosition = preferences.getString("byedpi_split_position", null)?.toIntOrNull(),

View File

@ -170,7 +170,10 @@ class ByeDpiProxyService : LifecycleService() {
when (newStatus) { when (newStatus) {
ServiceStatus.Connected -> AppStatus.Running ServiceStatus.Connected -> AppStatus.Running
ServiceStatus.Disconnected, ServiceStatus.Disconnected,
ServiceStatus.Failed -> AppStatus.Halted ServiceStatus.Failed -> {
proxyJob = null
AppStatus.Halted
}
}, },
Mode.Proxy Mode.Proxy
) )

View File

@ -224,7 +224,10 @@ class ByeDpiVpnService : LifecycleVpnService() {
when (newStatus) { when (newStatus) {
ServiceStatus.Connected -> AppStatus.Running ServiceStatus.Connected -> AppStatus.Running
ServiceStatus.Disconnected, ServiceStatus.Disconnected,
ServiceStatus.Failed -> AppStatus.Halted ServiceStatus.Failed -> {
proxyJob = null
AppStatus.Halted
}
}, },
Mode.VPN Mode.VPN
) )
@ -281,8 +284,7 @@ class ByeDpiVpnService : LifecycleVpnService() {
setInterface("") setInterface("")
logLevel = if (BuildConfig.DEBUG) "debug" else "info" logLevel = if (BuildConfig.DEBUG) "debug" else "info"
udpProxy = "direct://" proxy = "socks5://127.0.0.1:$port"
tcpProxy = "socks5://127.0.0.1:$port"
restAPI = "" restAPI = ""
tcpSendBufferSize = "" tcpSendBufferSize = ""

View File

@ -8,10 +8,6 @@
<string name="proxy_down">Proxy is down</string> <string name="proxy_down">Proxy is down</string>
<string name="proxy_start">Start</string> <string name="proxy_start">Start</string>
<string name="proxy_stop">Stop</string> <string name="proxy_stop">Stop</string>
<string name="vpn_connecting">Connecting…</string>
<string name="vpn_disconnecting">Disconnecting…</string>
<string name="proxy_starting">Starting</string>
<string name="proxy_stopping">Stopping</string>
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="vpn_permission_denied">VPN permission denied</string> <string name="vpn_permission_denied">VPN permission denied</string>
<string name="settings_unavailable">Please stop the VPN service before changing settings</string> <string name="settings_unavailable">Please stop the VPN service before changing settings</string>
@ -29,7 +25,6 @@
<string name="byedpi_buffer_size_setting">Buffer size</string> <string name="byedpi_buffer_size_setting">Buffer size</string>
<string name="byedpi_default_ttl_setting">Default TTL</string> <string name="byedpi_default_ttl_setting">Default TTL</string>
<string name="byedpi_no_domain_setting">No domain</string> <string name="byedpi_no_domain_setting">No domain</string>
<string name="byedpi_desync_known_setting">Desync only HTTPS and TLS</string>
<string name="byedpi_desync_method_setting">Desync method</string> <string name="byedpi_desync_method_setting">Desync method</string>
<string name="byedpi_split_position_setting">Split position</string> <string name="byedpi_split_position_setting">Split position</string>
<string name="byedpi_split_at_host_setting">Split at host</string> <string name="byedpi_split_at_host_setting">Split at host</string>

View File

@ -83,11 +83,6 @@
android:title="@string/byedpi_no_domain_setting" android:title="@string/byedpi_no_domain_setting"
android:defaultValue="false"/> android:defaultValue="false"/>
<CheckBoxPreference
android:key="byedpi_desync_known"
android:title="@string/byedpi_desync_known_setting"
android:defaultValue="false"/>
<DropDownPreference <DropDownPreference
android:key="byedpi_desync_method" android:key="byedpi_desync_method"
android:title="@string/byedpi_desync_method_setting" android:title="@string/byedpi_desync_method_setting"