Add command line style settings and refactor settings

This commit is contained in:
dovecoteescapee 2024-08-10 15:41:32 +03:00
parent 8085b5f1dc
commit 78f63c6aa0
24 changed files with 1090 additions and 282 deletions

View File

@ -10,3 +10,9 @@ int get_addr(const char *str, struct sockaddr_ina *addr);
void *add(void **root, int *n, size_t ss);
void clear_params(void);
char *ftob(const char *str, ssize_t *sl);
struct mphdr *parse_hosts(char *buffer, size_t size);
int parse_offset(struct part *part, const char *str);

View File

@ -19,12 +19,43 @@ const enum demode DESYNC_METHODS[] = {
DESYNC_OOB,
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, __attribute__((unused)) void *reserved) {
oob_data.data = NULL;
JNIEXPORT jint JNI_OnLoad(
__attribute__((unused)) JavaVM *vm,
__attribute__((unused)) void *reserved) {
default_params = params;
return JNI_VERSION_1_6;
}
JNIEXPORT jint JNICALL
Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocketWithCommandLine(
JNIEnv *env,
__attribute__((unused)) jobject thiz,
jobjectArray args) {
int argc = (*env)->GetArrayLength(env, args);
char *argv[argc];
for (int i = 0; i < argc; i++) {
jstring arg = (jstring) (*env)->GetObjectArrayElement(env, args, i);
const char *arg_str = (*env)->GetStringUTFChars(env, arg, 0);
argv[i] = strdup(arg_str);
(*env)->ReleaseStringUTFChars(env, arg, arg_str);
}
int res = parse_args(argc, argv);
if (res < 0) {
uniperror("parse_args");
return -1;
}
int fd = listen_socket((struct sockaddr_ina *)&params.laddr);
if (fd < 0) {
uniperror("listen_socket");
return -1;
}
LOG(LOG_S, "listen_socket, fd: %d", fd);
return fd;
}
JNIEXPORT jint JNICALL
Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
JNIEnv *env,
@ -36,6 +67,9 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
jint default_ttl,
jboolean custom_ttl,
jboolean no_domain,
jboolean desync_http,
jboolean desync_https,
jboolean desync_udp,
jint desync_method,
jint split_position,
jboolean split_at_host,
@ -90,6 +124,10 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
}
dp->ttl = fake_ttl;
dp->proto =
IS_HTTP * desync_http |
IS_HTTPS * desync_https |
IS_UDP * desync_udp;
dp->mod_http =
MH_HMIX * host_mixed_case |
MH_DMIX * domain_mixed_case |
@ -108,7 +146,9 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
enum demode mode = DESYNC_METHODS[desync_method];
part->flag = split_at_host ? OFFSET_SNI : 0;
int offset_flag = dp->proto || desync_https ? OFFSET_SNI : OFFSET_HOST;
part->flag = split_at_host ? offset_flag : 0;
part->pos = split_position;
part->m = mode;
@ -125,7 +165,7 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
return -1;
}
tlsrec_part->flag = tls_record_split_at_sni ? OFFSET_SNI : 0;
tlsrec_part->flag = tls_record_split_at_sni ? offset_flag : 0;
tlsrec_part->pos = tls_record_split_position;
}
@ -154,6 +194,16 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
(*env)->ReleaseStringUTFChars(env, custom_oob_data, oob);
}
if (dp->proto) {
dp = add((void *)&params.dp,
&params.dp_count, sizeof(struct desync_params));
if (!dp) {
uniperror("add");
clear_params();
return -1;
}
}
params.mempool = mem_pool(0);
if (!params.mempool) {
uniperror("mem_pool");
@ -200,4 +250,4 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniStopProxy(
return get_e();
}
return 0;
}
}

View File

@ -1,6 +1,12 @@
#include "utils.h"
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include "byedpi/params.h"
#include "error.h"
#include "main.h"
#include "packets.h"
#include "utils.h"
struct params default_params;
@ -8,3 +14,413 @@ void reset_params(void) {
clear_params();
params = default_params;
}
extern const struct option options[35];
int parse_args(int argc, char **argv) {
int optc = sizeof(options)/sizeof(*options);
for (int i = 0, e = optc; i < e; i++)
optc += options[i].has_arg;
char opt[optc + 1];
opt[optc] = 0;
for (int i = 0, o = 0; o < optc; i++, o++) {
opt[o] = options[i].val;
for (int c = options[i].has_arg; c; c--) {
o++;
opt[o] = ':';
}
}
params.laddr.sin6_port = htons(1080);
int rez;
int invalid = 0;
long val;
char *end = 0;
struct desync_params *dp = add((void *)&params.dp,
&params.dp_count, sizeof(struct desync_params));
if (!dp) {
reset_params();
return -1;
}
optind = optreset = 1;
while (!invalid && (rez = getopt_long_only(
argc, argv, opt, options, 0)) != -1) {
switch (rez) {
case 'N':
params.resolve = 0;
break;
case 'X':
params.ipv6 = 0;
break;
case 'U':
params.udp = 0;
break;
case 'i':
if (get_addr(optarg,
(struct sockaddr_ina *)&params.laddr) < 0)
invalid = 1;
break;
case 'p':
val = strtol(optarg, &end, 0);
if (val <= 0 || val > 0xffff || *end)
invalid = 1;
else
params.laddr.sin6_port = htons(val);
break;
case 'I':
if (get_addr(optarg,
(struct sockaddr_ina *)&params.baddr) < 0)
invalid = 1;
break;
case 'b':
val = strtol(optarg, &end, 0);
if (val <= 0 || val > INT_MAX/4 || *end)
invalid = 1;
else
params.bfsize = val;
break;
case 'c':
val = strtol(optarg, &end, 0);
if (val <= 0 || val >= (0xffff/2) || *end)
invalid = 1;
else
params.max_open = val;
break;
case 'x': //
params.debug = strtol(optarg, 0, 0);
if (params.debug < 0)
invalid = 1;
break;
// desync options
case 'F':
params.tfo = 1;
break;
case 'A':
dp = add((void *)&params.dp, &params.dp_count,
sizeof(struct desync_params));
if (!dp) {
reset_params();
return -1;
}
if (!optarg) {
dp->detect |= DETECT_TORST;
break;
}
end = optarg;
while (end && !invalid) {
switch (*end) {
case 't':
dp->detect |= DETECT_TORST;
break;
case 'r':
dp->detect |= DETECT_HTTP_LOCAT;
break;
case 'c':
dp->detect |= DETECT_HTTP_CLERR;
break;
case 's':
dp->detect |= DETECT_TLS_INVSID;
break;
case 'a':
dp->detect |= DETECT_TLS_ALERT;
break;
case 'n':
break;
default:
invalid = 1;
continue;
}
end = strchr(end, ',');
if (end) end++;
}
break;
case 'u':
val = strtol(optarg, &end, 0);
if (val <= 0 || *end)
invalid = 1;
else
params.cache_ttl = val;
break;
case 'T':;
float f = strtof(optarg, &end);
val = (long)(f * 1000);
if (val <= 0 || val > UINT_MAX || *end)
invalid = 1;
else
params.timeout = val;
break;
case 'K':
end = optarg;
while (end && !invalid) {
switch (*end) {
case 't':
dp->proto |= IS_HTTPS;
break;
case 'h':
dp->proto |= IS_HTTP;
break;
case 'u':
dp->proto |= IS_UDP;
break;
default:
invalid = 1;
continue;
}
end = strchr(end, ',');
if (end) end++;
}
break;
case 'H':;
if (dp->file_ptr) {
continue;
}
dp->file_ptr = ftob(optarg, &dp->file_size);
if (!dp->file_ptr) {
uniperror("read/parse");
invalid = 1;
continue;
}
dp->hosts = parse_hosts(dp->file_ptr, dp->file_size);
if (!dp->hosts) {
perror("parse_hosts");
reset_params();
return -1;
}
break;
case 's':
case 'd':
case 'o':
case 'f':
;
struct part *part = add((void *)&dp->parts,
&dp->parts_n, sizeof(struct part));
if (!part) {
reset_params();
return -1;
}
if (parse_offset(part, optarg)) {
invalid = 1;
break;
}
switch (rez) {
case 's': part->m = DESYNC_SPLIT;
break;
case 'd': part->m = DESYNC_DISORDER;
break;
case 'o': part->m = DESYNC_OOB;
break;
case 'f': part->m = DESYNC_FAKE;
}
break;
case 't':
val = strtol(optarg, &end, 0);
if (val <= 0 || val > 255 || *end)
invalid = 1;
else
dp->ttl = val;
break;
case 'k':
if (dp->ip_options) {
continue;
}
if (optarg)
dp->ip_options = ftob(optarg, &dp->ip_options_len);
else {
dp->ip_options = ip_option;
dp->ip_options_len = sizeof(ip_option);
}
if (!dp->ip_options) {
uniperror("read/parse");
invalid = 1;
}
break;
case 'S':
dp->md5sig = 1;
break;
case 'n':
if (change_tls_sni(optarg, fake_tls.data, fake_tls.size)) {
fprintf(stderr, "error chsni\n");
reset_params();
return -1;
}
printf("sni: %s\n", optarg);
break;
case 'l':
if (dp->fake_data.data) {
continue;
}
dp->fake_data.data = ftob(optarg, &dp->fake_data.size);
if (!dp->fake_data.data) {
uniperror("read/parse");
invalid = 1;
}
break;
case 'e':
if (oob_data.data != oob_char) {
continue;
}
oob_data.data = ftob(optarg, &oob_data.size);
if (!oob_data.data) {
uniperror("read/parse");
invalid = 1;
}
break;
case 'M':
end = optarg;
while (end && !invalid) {
switch (*end) {
case 'r':
dp->mod_http |= MH_SPACE;
break;
case 'h':
dp->mod_http |= MH_HMIX;
break;
case 'd':
dp->mod_http |= MH_DMIX;
break;
default:
invalid = 1;
continue;
}
end = strchr(end, ',');
if (end) end++;
}
break;
case 'r':
part = add((void *)&dp->tlsrec,
&dp->tlsrec_n, sizeof(struct part));
if (!part) {
reset_params();
return -1;
}
if (parse_offset(part, optarg)
|| part->pos > 0xffff) {
invalid = 1;
break;
}
break;
case 'a':
val = strtol(optarg, &end, 0);
if (val < 0 || val > INT_MAX || *end)
invalid = 1;
else
dp->udp_fake_count = val;
break;
case 'V':
val = strtol(optarg, &end, 0);
if (val <= 0 || val > USHRT_MAX)
invalid = 1;
else {
dp->pf[0] = htons(val);
if (*end == '-') {
val = strtol(end + 1, &end, 0);
if (val <= 0 || val > USHRT_MAX)
invalid = 1;
}
if (*end)
invalid = 1;
else
dp->pf[1] = htons(val);
}
break;
case 'g':
val = strtol(optarg, &end, 0);
if (val <= 0 || val > 255 || *end)
invalid = 1;
else {
params.def_ttl = val;
params.custom_ttl = 1;
}
break;
case 'w': //
params.sfdelay = strtol(optarg, &end, 0);
if (params.sfdelay < 0 || optarg == end
|| params.sfdelay >= 1000 || *end)
invalid = 1;
break;
case 'W':
params.wait_send = 0;
break;
case 'P':
params.protect_path = optarg;
break;
case 0:
break;
case '?':
reset_params();
return -1;
default:
printf("?: %c\n", rez);
reset_params();
return -1;
}
}
if (invalid) {
fprintf(stderr, "invalid value: -%c %s\n", rez, optarg);
reset_params();
return -1;
}
if (dp->hosts || dp->proto || dp->pf[0]) {
dp = add((void *)&params.dp,
&params.dp_count, sizeof(struct desync_params));
if (!dp) {
reset_params();
return -1;
}
}
if (params.baddr.sin6_family != AF_INET6) {
params.ipv6 = 0;
}
if (!params.def_ttl) {
if ((params.def_ttl = get_default_ttl()) < 1) {
reset_params();
return -1;
}
}
params.mempool = mem_pool(0);
if (!params.mempool) {
uniperror("mem_pool");
reset_params();
return -1;
}
return 0;
}

View File

@ -1,3 +1,4 @@
extern struct params default_params;
void reset_params(void);
int parse_args(int argc, char **argv);

View File

@ -19,19 +19,12 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import io.github.dovecoteescapee.byedpi.R
import io.github.dovecoteescapee.byedpi.data.AppStatus
import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.Mode
import io.github.dovecoteescapee.byedpi.data.SENDER
import io.github.dovecoteescapee.byedpi.data.STARTED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.STOPPED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.Sender
import io.github.dovecoteescapee.byedpi.fragments.SettingsFragment
import io.github.dovecoteescapee.byedpi.data.*
import io.github.dovecoteescapee.byedpi.fragments.MainSettingsFragment
import io.github.dovecoteescapee.byedpi.databinding.ActivityMainBinding
import io.github.dovecoteescapee.byedpi.services.ServiceManager
import io.github.dovecoteescapee.byedpi.services.appStatus
import io.github.dovecoteescapee.byedpi.utility.getPreferences
import io.github.dovecoteescapee.byedpi.utility.mode
import io.github.dovecoteescapee.byedpi.utility.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
@ -154,9 +147,9 @@ class MainActivity : AppCompatActivity() {
}
}
val theme = getPreferences(this)
val theme = getPreferences()
.getString("app_theme", null)
SettingsFragment.setTheme(theme ?: "system")
MainSettingsFragment.setTheme(theme ?: "system")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(
@ -215,7 +208,7 @@ class MainActivity : AppCompatActivity() {
}
private fun start() {
when (getPreferences(this).mode()) {
when (getPreferences().mode()) {
Mode.VPN -> {
val intentPrepare = VpnService.prepare(this)
if (intentPrepare != null) {
@ -238,9 +231,9 @@ class MainActivity : AppCompatActivity() {
Log.i(TAG, "Updating status: $status, $mode")
val preferences = getPreferences(this)
val proxyIp = preferences.getString("byedpi_proxy_ip", null) ?: "127.0.0.1"
val proxyPort = preferences.getString("byedpi_proxy_port", null) ?: "1080"
val preferences = getPreferences()
val proxyIp = preferences.getStringNotNull("byedpi_proxy_ip", "127.0.0.1")
val proxyPort = preferences.getStringNotNull("byedpi_proxy_port", "1080")
binding.proxyAddress.text = getString(R.string.proxy_address, proxyIp, proxyPort)
when (status) {

View File

@ -5,7 +5,7 @@ import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import io.github.dovecoteescapee.byedpi.R
import io.github.dovecoteescapee.byedpi.fragments.SettingsFragment
import io.github.dovecoteescapee.byedpi.fragments.MainSettingsFragment
import io.github.dovecoteescapee.byedpi.utility.getPreferences
class SettingsActivity : AppCompatActivity() {
@ -15,7 +15,7 @@ class SettingsActivity : AppCompatActivity() {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.replace(R.id.settings, MainSettingsFragment())
.commit()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
@ -33,14 +33,11 @@ class SettingsActivity : AppCompatActivity() {
}
R.id.action_reset_settings -> {
getPreferences(this)
.edit()
.clear()
.apply()
getPreferences().edit().clear().apply()
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.replace(R.id.settings, MainSettingsFragment())
.commit()
true
}

View File

@ -41,7 +41,19 @@ class ByeDpiProxy {
throw IllegalStateException("Proxy is already running")
}
val fd = jniCreateSocket(
val fd = createSocketFromPreferences(preferences)
if (fd < 0) {
return -1
}
this.fd = fd
fd
}
private fun createSocketFromPreferences(preferences: ByeDpiProxyPreferences) =
when (preferences) {
is ByeDpiProxyCmdPreferences -> jniCreateSocketWithCommandLine(preferences.args)
is ByeDpiProxyUIPreferences -> jniCreateSocket(
ip = preferences.ip,
port = preferences.port,
maxConnections = preferences.maxConnections,
@ -49,6 +61,9 @@ class ByeDpiProxy {
defaultTtl = preferences.defaultTtl,
customTtl = preferences.customTtl,
noDomain = preferences.noDomain,
desyncHttp = preferences.desyncHttp,
desyncHttps = preferences.desyncHttps,
desyncUdp = preferences.desyncUdp,
desyncMethod = preferences.desyncMethod.ordinal,
splitPosition = preferences.splitPosition,
splitAtHost = preferences.splitAtHost,
@ -62,15 +77,10 @@ class ByeDpiProxy {
tlsRecordSplitPosition = preferences.tlsRecordSplitPosition,
tlsRecordSplitAtSni = preferences.tlsRecordSplitAtSni,
)
if (fd < 0) {
return -1
}
this.fd = fd
fd
}
private external fun jniCreateSocketWithCommandLine(args: Array<String>): Int
private external fun jniCreateSocket(
ip: String,
port: Int,
@ -79,6 +89,9 @@ class ByeDpiProxy {
defaultTtl: Int,
customTtl: Boolean,
noDomain: Boolean,
desyncHttp: Boolean,
desyncHttps: Boolean,
desyncUdp: Boolean,
desyncMethod: Int,
splitPosition: Int,
splitAtHost: Boolean,

View File

@ -1,14 +1,48 @@
package io.github.dovecoteescapee.byedpi.core
import android.content.SharedPreferences
import io.github.dovecoteescapee.byedpi.utility.getStringNotNull
import io.github.dovecoteescapee.byedpi.utility.shellSplit
class ByeDpiProxyPreferences(
sealed interface ByeDpiProxyPreferences {
companion object {
fun fromSharedPreferences(preferences: SharedPreferences): ByeDpiProxyPreferences =
when (preferences.getBoolean("byedpi_enable_cmd_settings", false)) {
true -> ByeDpiProxyCmdPreferences(preferences)
false -> ByeDpiProxyUIPreferences(preferences)
}
}
}
class ByeDpiProxyCmdPreferences(val args: Array<String>) : ByeDpiProxyPreferences {
constructor(cmd: String) : this(cmdToArgs(cmd))
constructor(preferences: SharedPreferences) : this(
preferences.getStringNotNull(
"byedpi_cmd_args",
""
)
)
companion object {
private fun cmdToArgs(cmd: String): Array<String> {
val firstArgIndex = cmd.indexOf("-")
val argsStr = (if (firstArgIndex > 0) cmd.substring(firstArgIndex) else cmd).trim()
return arrayOf("ciadpi") + shellSplit(argsStr)
}
}
}
class ByeDpiProxyUIPreferences(
ip: String? = null,
port: Int? = null,
maxConnections: Int? = null,
bufferSize: Int? = null,
defaultTtl: Int? = null,
noDomain: Boolean? = null,
desyncHttp: Boolean? = null,
desyncHttps: Boolean? = null,
desyncUdp: Boolean? = null,
desyncMethod: DesyncMethod? = null,
splitPosition: Int? = null,
splitAtHost: Boolean? = null,
@ -21,7 +55,7 @@ class ByeDpiProxyPreferences(
tlsRecordSplit: Boolean? = null,
tlsRecordSplitPosition: Int? = null,
tlsRecordSplitAtSni: Boolean? = null,
) {
) : ByeDpiProxyPreferences {
val ip: String = ip ?: "127.0.0.1"
val port: Int = port ?: 1080
val maxConnections: Int = maxConnections ?: 512
@ -29,6 +63,9 @@ class ByeDpiProxyPreferences(
val defaultTtl: Int = defaultTtl ?: 0
val customTtl: Boolean = defaultTtl != null
val noDomain: Boolean = noDomain ?: false
val desyncHttp: Boolean = desyncHttp ?: true
val desyncHttps: Boolean = desyncHttps ?: true
val desyncUdp: Boolean = desyncUdp ?: false
val desyncMethod: DesyncMethod = desyncMethod ?: DesyncMethod.Disorder
val splitPosition: Int = splitPosition ?: 3
val splitAtHost: Boolean = splitAtHost ?: false
@ -49,6 +86,9 @@ class ByeDpiProxyPreferences(
bufferSize = preferences.getString("byedpi_buffer_size", null)?.toIntOrNull(),
defaultTtl = preferences.getString("byedpi_default_ttl", null)?.toIntOrNull(),
noDomain = preferences.getBoolean("byedpi_no_domain", false),
desyncHttp = preferences.getBoolean("byedpi_desync_http", true),
desyncHttps = preferences.getBoolean("byedpi_desync_https", true),
desyncUdp = preferences.getBoolean("byedpi_desync_udp", false),
desyncMethod = preferences.getString("byedpi_desync_method", null)
?.let { DesyncMethod.fromName(it) },
splitPosition = preferences.getString("byedpi_split_position", null)?.toIntOrNull(),
@ -60,9 +100,10 @@ class ByeDpiProxyPreferences(
domainMixedCase = preferences.getBoolean("byedpi_domain_mixed_case", false),
hostRemoveSpaces = preferences.getBoolean("byedpi_host_remove_spaces", false),
tlsRecordSplit = preferences.getBoolean("byedpi_tlsrec_enabled", false),
tlsRecordSplitPosition = preferences.getString("byedpi_tlsrec_position", null)?.toIntOrNull(),
tlsRecordSplitPosition = preferences.getString("byedpi_tlsrec_position", null)
?.toIntOrNull(),
tlsRecordSplitAtSni = preferences.getBoolean("byedpi_tlsrec_at_sni", false),
)
)
enum class DesyncMethod {
None,

View File

@ -0,0 +1,11 @@
package io.github.dovecoteescapee.byedpi.fragments
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import io.github.dovecoteescapee.byedpi.R
class ByeDpiCommandLineSettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.byedpi_cmd_settings, rootKey)
}
}

View File

@ -0,0 +1,146 @@
package io.github.dovecoteescapee.byedpi.fragments
import android.content.SharedPreferences
import android.os.Bundle
import androidx.preference.*
import io.github.dovecoteescapee.byedpi.R
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyUIPreferences
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyUIPreferences.DesyncMethod.*
import io.github.dovecoteescapee.byedpi.utility.*
class ByeDpiUISettingsFragment : PreferenceFragmentCompat() {
private val preferenceListener =
SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
updatePreferences()
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.byedpi_ui_settings, rootKey)
setEditTextPreferenceListener("byedpi_proxy_ip") { checkIp(it) }
setEditTestPreferenceListenerPort("byedpi_proxy_port")
setEditTestPreferenceListenerInt(
"byedpi_max_connections",
1,
Short.MAX_VALUE.toInt()
)
setEditTestPreferenceListenerInt(
"byedpi_buffer_size",
1,
Int.MAX_VALUE / 4
)
setEditTestPreferenceListenerInt("byedpi_default_ttl", 0, 255)
setEditTestPreferenceListenerInt(
"byedpi_split_position",
Int.MIN_VALUE,
Int.MAX_VALUE
)
setEditTestPreferenceListenerInt("byedpi_fake_ttl", 1, 255)
setEditTestPreferenceListenerInt(
"byedpi_tlsrec_position",
2 * Short.MIN_VALUE,
2 * Short.MAX_VALUE,
)
findPreferenceNotNull<EditTextPreference>("byedpi_oob_data")
.setOnBindEditTextListener {
it.filters = arrayOf(android.text.InputFilter.LengthFilter(1))
}
updatePreferences()
}
override fun onResume() {
super.onResume()
sharedPreferences?.registerOnSharedPreferenceChangeListener(preferenceListener)
}
override fun onPause() {
super.onPause()
sharedPreferences?.unregisterOnSharedPreferenceChangeListener(preferenceListener)
}
private fun updatePreferences() {
val desyncMethod =
findPreferenceNotNull<ListPreference>("byedpi_desync_method")
.value.let { ByeDpiProxyUIPreferences.DesyncMethod.fromName(it) }
val desyncHttp = findPreferenceNotNull<CheckBoxPreference>("byedpi_desync_http")
val desyncHttps = findPreferenceNotNull<CheckBoxPreference>("byedpi_desync_https")
val desyncUdp = findPreferenceNotNull<CheckBoxPreference>("byedpi_desync_udp")
val splitPosition = findPreferenceNotNull<EditTextPreference>("byedpi_split_position")
val splitAtHost = findPreferenceNotNull<CheckBoxPreference>("byedpi_split_at_host")
val ttlFake = findPreferenceNotNull<EditTextPreference>("byedpi_fake_ttl")
val fakeSni = findPreferenceNotNull<EditTextPreference>("byedpi_fake_sni")
val oobData = findPreferenceNotNull<EditTextPreference>("byedpi_oob_data")
val hostMixedCase = findPreferenceNotNull<CheckBoxPreference>("byedpi_host_mixed_case")
val domainMixedCase = findPreferenceNotNull<CheckBoxPreference>("byedpi_domain_mixed_case")
val hostRemoveSpaces =
findPreferenceNotNull<CheckBoxPreference>("byedpi_host_remove_spaces")
val splitTlsRec = findPreferenceNotNull<CheckBoxPreference>("byedpi_tlsrec_enabled")
val splitTlsRecPosition =
findPreferenceNotNull<EditTextPreference>("byedpi_tlsrec_position")
val splitTlsRecAtSni = findPreferenceNotNull<CheckBoxPreference>("byedpi_tlsrec_at_sni")
when (desyncMethod) {
None -> {
desyncHttp.isVisible = false
desyncHttps.isVisible = false
desyncUdp.isVisible = false
splitPosition.isVisible = false
splitAtHost.isVisible = false
ttlFake.isVisible = false
fakeSni.isVisible = false
oobData.isVisible = false
hostMixedCase.isVisible = false
domainMixedCase.isVisible = false
hostRemoveSpaces.isVisible = false
}
else -> {
desyncHttp.isVisible = true
desyncHttps.isVisible = true
desyncUdp.isVisible = true
splitPosition.isVisible = true
splitAtHost.isVisible = true
val desyncAllProtocols =
!desyncHttp.isChecked && !desyncHttps.isChecked && !desyncUdp.isChecked
if (desyncAllProtocols || desyncHttp.isChecked) {
hostMixedCase.isVisible = true
domainMixedCase.isVisible = true
hostRemoveSpaces.isVisible = true
} else {
hostMixedCase.isVisible = false
domainMixedCase.isVisible = false
hostRemoveSpaces.isVisible = false
}
when (desyncMethod) {
Fake -> {
ttlFake.isVisible = true
fakeSni.isVisible = true
oobData.isVisible = false
}
OOB -> {
ttlFake.isVisible = false
fakeSni.isVisible = false
oobData.isVisible = true
}
else -> {
ttlFake.isVisible = false
fakeSni.isVisible = false
oobData.isVisible = false
}
}
}
}
splitTlsRecPosition.isVisible = splitTlsRec.isChecked
splitTlsRecAtSni.isVisible = splitTlsRec.isChecked
}
}

View File

@ -0,0 +1,92 @@
package io.github.dovecoteescapee.byedpi.fragments
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.*
import io.github.dovecoteescapee.byedpi.BuildConfig
import io.github.dovecoteescapee.byedpi.R
import io.github.dovecoteescapee.byedpi.data.Mode
import io.github.dovecoteescapee.byedpi.utility.*
class MainSettingsFragment : PreferenceFragmentCompat() {
companion object {
private val TAG: String = MainSettingsFragment::class.java.simpleName
fun setTheme(name: String) =
themeByName(name)?.let {
AppCompatDelegate.setDefaultNightMode(it)
} ?: throw IllegalStateException("Invalid value for app_theme: $name")
private fun themeByName(name: String): Int? = when (name) {
"system" -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
"light" -> AppCompatDelegate.MODE_NIGHT_NO
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
else -> {
Log.w(TAG, "Invalid value for app_theme: $name")
null
}
}
}
private val preferenceListener =
SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
updatePreferences()
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.main_settings, rootKey)
setEditTextPreferenceListener("dns_ip") { checkIp(it) }
findPreferenceNotNull<DropDownPreference>("app_theme")
.setOnPreferenceChangeListener { _, newValue ->
setTheme(newValue as String)
true
}
val switchCommandLineSettings = findPreferenceNotNull<SwitchPreference>(
"byedpi_enable_cmd_settings"
)
val uiSettings = findPreferenceNotNull<Preference>("byedpi_ui_settings")
val cmdSettings = findPreferenceNotNull<Preference>("byedpi_cmd_settings")
val setByeDpiSettingsMode = { enable: Boolean ->
uiSettings.isEnabled = !enable
cmdSettings.isEnabled = enable
}
setByeDpiSettingsMode(switchCommandLineSettings.isChecked)
switchCommandLineSettings.setOnPreferenceChangeListener { _, newValue ->
setByeDpiSettingsMode(newValue as Boolean)
true
}
findPreferenceNotNull<Preference>("version").summary = BuildConfig.VERSION_NAME
updatePreferences()
}
override fun onResume() {
super.onResume()
sharedPreferences?.registerOnSharedPreferenceChangeListener(preferenceListener)
}
override fun onPause() {
super.onPause()
sharedPreferences?.unregisterOnSharedPreferenceChangeListener(preferenceListener)
}
private fun updatePreferences() {
val mode = findPreferenceNotNull<ListPreference>("byedpi_mode")
.value.let { Mode.fromString(it) }
val dns = findPreferenceNotNull<EditTextPreference>("dns_ip")
when (mode) {
Mode.VPN -> dns.isVisible = true
Mode.Proxy -> dns.isVisible = false
}
}
}

View File

@ -1,131 +0,0 @@
package io.github.dovecoteescapee.byedpi.fragments
import android.net.InetAddresses
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.util.Patterns
import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.DropDownPreference
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import io.github.dovecoteescapee.byedpi.BuildConfig
import io.github.dovecoteescapee.byedpi.R
class SettingsFragment : PreferenceFragmentCompat() {
companion object {
private val TAG: String = SettingsFragment::class.java.simpleName
fun setTheme(name: String): Boolean = when (val theme = themeByName(name)) {
null -> {
Log.w(TAG, "Invalid value for app_theme: $name")
false
}
else -> {
AppCompatDelegate.setDefaultNightMode(theme)
true
}
}
private fun themeByName(name: String): Int? = when (name) {
"system" -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
"light" -> AppCompatDelegate.MODE_NIGHT_NO
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
else -> {
Log.w(TAG, "Invalid value for app_theme: $name")
null
}
}
private fun checkIp(ip: String): Boolean =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
InetAddresses.isNumericAddress(ip)
} else {
// This pattern doesn't not support IPv6
Patterns.IP_ADDRESS.matcher(ip).matches()
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings, rootKey)
findPreference<DropDownPreference>("app_theme")
?.setOnPreferenceChangeListener { preference, newValue ->
setTheme(newValue as String)
}
setEditTextPreferenceListener("dns_ip") { checkIp(it) }
setEditTextPreferenceListener("byedpi_proxy_ip") { checkIp(it) }
setEditTestPreferenceListenerPort("byedpi_proxy_port")
setEditTestPreferenceListenerInt(
"byedpi_max_connections",
1,
Short.MAX_VALUE.toInt()
)
setEditTestPreferenceListenerInt(
"byedpi_buffer_size",
1,
Int.MAX_VALUE / 4
)
setEditTestPreferenceListenerInt("byedpi_default_ttl", 0, 255)
setEditTestPreferenceListenerInt(
"byedpi_split_position",
Int.MIN_VALUE,
Int.MAX_VALUE
)
setEditTestPreferenceListenerInt("byedpi_fake_ttl", 1, 255)
setEditTestPreferenceListenerInt(
"byedpi_tlsrec_position",
2 * Short.MIN_VALUE,
2 * Short.MAX_VALUE,
)
findPreference<Preference>("version")?.summary =
BuildConfig.VERSION_NAME
}
private fun setEditTestPreferenceListenerPort(key: String) {
setEditTestPreferenceListenerInt(key, 1, 65535)
}
private fun setEditTestPreferenceListenerInt(
key: String,
min: Int = Int.MIN_VALUE,
max: Int = Int.MAX_VALUE
) {
setEditTextPreferenceListener(key) { value ->
value.toIntOrNull()?.let { it in min..max } ?: false
}
}
private fun setEditTextPreferenceListener(key: String, check: (String) -> Boolean) {
findPreference<EditTextPreference>(key)
?.setOnPreferenceChangeListener { preference, newValue ->
when (newValue) {
is String -> {
val valid = check(newValue)
if (!valid) {
Toast.makeText(
requireContext(),
"Invalid value for ${preference.title}: $newValue",
Toast.LENGTH_SHORT
).show()
}
valid
}
else -> {
Log.w(
TAG,
"Invalid type for ${preference.key}: " +
"$newValue has type ${newValue::class.java}"
)
false
}
}
}
}
}

View File

@ -10,19 +10,8 @@ import androidx.lifecycle.lifecycleScope
import io.github.dovecoteescapee.byedpi.R
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxy
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyPreferences
import io.github.dovecoteescapee.byedpi.data.AppStatus
import io.github.dovecoteescapee.byedpi.data.START_ACTION
import io.github.dovecoteescapee.byedpi.data.STOP_ACTION
import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.Mode
import io.github.dovecoteescapee.byedpi.data.SENDER
import io.github.dovecoteescapee.byedpi.data.STARTED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.STOPPED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.Sender
import io.github.dovecoteescapee.byedpi.data.ServiceStatus
import io.github.dovecoteescapee.byedpi.utility.createConnectionNotification
import io.github.dovecoteescapee.byedpi.utility.getPreferences
import io.github.dovecoteescapee.byedpi.utility.registerNotificationChannel
import io.github.dovecoteescapee.byedpi.data.*
import io.github.dovecoteescapee.byedpi.utility.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
@ -159,7 +148,7 @@ class ByeDpiProxyService : LifecycleService() {
}
private fun getByeDpiPreferences(): ByeDpiProxyPreferences =
ByeDpiProxyPreferences(getPreferences(this))
ByeDpiProxyPreferences.fromSharedPreferences(getPreferences())
private fun updateStatus(newStatus: ServiceStatus) {
Log.d(TAG, "Proxy status changed from $status to $newStatus")

View File

@ -15,19 +15,8 @@ import io.github.dovecoteescapee.byedpi.R
import io.github.dovecoteescapee.byedpi.activities.MainActivity
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxy
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyPreferences
import io.github.dovecoteescapee.byedpi.data.AppStatus
import io.github.dovecoteescapee.byedpi.data.START_ACTION
import io.github.dovecoteescapee.byedpi.data.STOP_ACTION
import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.Mode
import io.github.dovecoteescapee.byedpi.data.SENDER
import io.github.dovecoteescapee.byedpi.data.STARTED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.STOPPED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.Sender
import io.github.dovecoteescapee.byedpi.data.ServiceStatus
import io.github.dovecoteescapee.byedpi.utility.createConnectionNotification
import io.github.dovecoteescapee.byedpi.utility.getPreferences
import io.github.dovecoteescapee.byedpi.utility.registerNotificationChannel
import io.github.dovecoteescapee.byedpi.data.*
import io.github.dovecoteescapee.byedpi.utility.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
@ -189,9 +178,9 @@ class ByeDpiVpnService : LifecycleVpnService() {
throw IllegalStateException("VPN field not null")
}
val sharedPreferences = getPreferences(this)
val sharedPreferences = getPreferences()
val port = sharedPreferences.getString("byedpi_proxy_port", null)?.toInt() ?: 1080
val dns = sharedPreferences.getString("dns_ip", null) ?: "1.1.1.1"
val dns = sharedPreferences.getStringNotNull("dns_ip", "1.1.1.1")
val vpn = createBuilder(dns).establish()
?: throw IllegalStateException("VPN connection failed")
@ -213,7 +202,7 @@ class ByeDpiVpnService : LifecycleVpnService() {
}
private fun getByeDpiPreferences(): ByeDpiProxyPreferences =
ByeDpiProxyPreferences(getPreferences(this))
ByeDpiProxyPreferences.fromSharedPreferences(getPreferences())
private fun updateStatus(newStatus: ServiceStatus) {
Log.d(TAG, "VPN status changed from $status to $newStatus")

View File

@ -8,7 +8,11 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ServiceLifecycleDispatcher
/**
* Based on @link [androidx.lifecycle.LifecycleService]
*/
open class LifecycleVpnService : VpnService(), LifecycleOwner {
@Suppress("LeakingThis")
private val dispatcher = ServiceLifecycleDispatcher(this)
@CallSuper
@ -48,4 +52,4 @@ open class LifecycleVpnService : VpnService(), LifecycleOwner {
override val lifecycle: Lifecycle
get() = dispatcher.lifecycle
}
}

View File

@ -17,13 +17,7 @@ import androidx.core.service.quicksettings.PendingIntentActivityWrapper
import androidx.core.service.quicksettings.TileServiceCompat
import io.github.dovecoteescapee.byedpi.R
import io.github.dovecoteescapee.byedpi.activities.MainActivity
import io.github.dovecoteescapee.byedpi.data.AppStatus
import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.Mode
import io.github.dovecoteescapee.byedpi.data.SENDER
import io.github.dovecoteescapee.byedpi.data.STARTED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.STOPPED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.Sender
import io.github.dovecoteescapee.byedpi.data.*
import io.github.dovecoteescapee.byedpi.utility.getPreferences
import io.github.dovecoteescapee.byedpi.utility.mode
@ -116,7 +110,7 @@ class QuickTileService : TileService() {
val (status) = appStatus
when (status) {
AppStatus.Halted -> {
val mode = getPreferences(this).mode()
val mode = getPreferences().mode()
if (mode == Mode.VPN && VpnService.prepare(this) != null) {
updateStatus()
@ -130,4 +124,4 @@ class QuickTileService : TileService() {
AppStatus.Running -> ServiceManager.stop(this)
}
}
}
}

View File

@ -47,4 +47,4 @@ object ServiceManager {
}
}
}
}
}

View File

@ -0,0 +1,41 @@
package io.github.dovecoteescapee.byedpi.utility
// Based on https://gist.github.com/raymyers/8077031
fun shellSplit(string: CharSequence): List<String> {
val tokens: MutableList<String> = ArrayList()
var escaping = false
var quoteChar = ' '
var quoting = false
var lastCloseQuoteIndex = Int.MIN_VALUE
var current = StringBuilder()
for (i in string.indices) {
val c = string[i]
if (escaping) {
current.append(c)
escaping = false
} else if (c == '\\' && !(quoting && quoteChar == '\'')) {
escaping = true
} else if (quoting && c == quoteChar) {
quoting = false
lastCloseQuoteIndex = i
} else if (!quoting && (c == '\'' || c == '"')) {
quoting = true
quoteChar = c
} else if (!quoting && Character.isWhitespace(c)) {
if (current.isNotEmpty() || lastCloseQuoteIndex == i - 1) {
tokens.add(current.toString())
current = StringBuilder()
}
} else {
current.append(c)
}
}
if (current.isNotEmpty() || lastCloseQuoteIndex == string.length - 1) {
tokens.add(current.toString())
}
return tokens
}

View File

@ -2,14 +2,22 @@ package io.github.dovecoteescapee.byedpi.utility
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import io.github.dovecoteescapee.byedpi.data.Mode
fun getPreferences(context: Context): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context)
val PreferenceFragmentCompat.sharedPreferences
get() = preferenceScreen.sharedPreferences
fun Context.getPreferences(): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this)
fun SharedPreferences.getStringNotNull(key: String, defValue: String): String =
getString(key, defValue) ?: defValue
fun SharedPreferences.mode(): Mode =
Mode.fromString(getStringNotNull("byedpi_mode", "vpn"))
fun <T : Preference> PreferenceFragmentCompat.findPreferenceNotNull(key: CharSequence): T =
findPreference(key) ?: throw IllegalStateException("Preference $key not found")

View File

@ -0,0 +1,65 @@
package io.github.dovecoteescapee.byedpi.utility
import android.net.InetAddresses
import android.os.Build
import android.util.Log
import android.util.Patterns
import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
private const val TAG = "ValidateUtils"
fun checkIp(ip: String): Boolean =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
InetAddresses.isNumericAddress(ip)
} else {
// This pattern doesn't not support IPv6
@Suppress("DEPRECATION")
Patterns.IP_ADDRESS.matcher(ip).matches()
}
fun PreferenceFragmentCompat.setEditTestPreferenceListenerPort(key: String) {
setEditTestPreferenceListenerInt(key, 1, 65535)
}
fun PreferenceFragmentCompat.setEditTestPreferenceListenerInt(
key: String,
min: Int = Int.MIN_VALUE,
max: Int = Int.MAX_VALUE
) {
setEditTextPreferenceListener(key) { value ->
value.toIntOrNull()?.let { it in min..max } ?: false
}
}
fun PreferenceFragmentCompat.setEditTextPreferenceListener(
key: String,
check: (String) -> Boolean
) {
findPreferenceNotNull<EditTextPreference>(key)
.setOnPreferenceChangeListener { preference, newValue ->
when (newValue) {
is String -> {
val valid = check(newValue)
if (!valid) {
Toast.makeText(
requireContext(),
"Invalid value for ${preference.title}: $newValue",
Toast.LENGTH_SHORT
).show()
}
valid
}
else -> {
Log.w(
TAG,
"Invalid type for ${preference.key}: " +
"$newValue has type ${newValue::class.java}"
)
false
}
}
}
}

View File

@ -47,4 +47,14 @@
<string name="general_category">General</string>
<string name="byedpi_category">ByeDPI</string>
<string name="about_category">About</string>
<string name="command_line_editor">Command line editor</string>
<string name="ui_editor">UI editor</string>
<string name="use_command_line_settings">Use command line settings</string>
<string name="desync_http">Desync HTTP</string>
<string name="desync_https">Desync HTTPS</string>
<string name="desync_udp">Desync UDP</string>
<string name="oob_data">OOB Data</string>
<string name="sni_of_fake_packet">SNI of fake packet</string>
<string name="byedpi_proxy">Proxy</string>
<string name="byedpi_desync">Desync</string>
</resources>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:tag="byedpi_cmd_settings">
<Preference
android:key="byedpi_readme"
android:title="Documentation"
android:textColor="?attr/textFillColor"
android:icon="@drawable/ic_github_36">
<intent
android:action="android.intent.action.VIEW"
android:data="https://github.com/hufrea/byedpi/tree/main#readme-ov-file" />
</Preference>
<androidx.preference.PreferenceCategory>
<com.takisoft.preferencex.EditTextPreference
android:key="byedpi_cmd_args"
android:title="Command line arguments"
android:dialogTitle="Command line arguments"
android:inputType="textMultiLine" />
</androidx.preference.PreferenceCategory>
</PreferenceScreen>

View File

@ -1,48 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:tag="settings_screen">
android:tag="byedpi_ui_settings">
<Preference
android:key="byedpi_readme"
android:title="@string/byedpi_readme_link"
android:textColor="?attr/textFillColor"
android:icon="@drawable/ic_github_36">
<intent
android:action="android.intent.action.VIEW"
android:data="https://github.com/hufrea/byedpi/tree/main#readme-ov-file" />
</Preference>
<androidx.preference.PreferenceCategory
android:title="@string/general_category">
<DropDownPreference
android:key="app_theme"
android:title="@string/theme_settings"
android:entries="@array/themes"
android:entryValues="@array/themes_entries"
android:defaultValue="system"
app:useSimpleSummaryProvider="true" />
<DropDownPreference
android:key="byedpi_mode"
android:title="@string/mode_setting"
android:entries="@array/byedpi_modes"
android:entryValues="@array/byedpi_modes_entries"
android:defaultValue="vpn"
app:useSimpleSummaryProvider="true" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:title="@string/byedpi_category">
<Preference
android:key="byedpi_readme"
android:title="@string/byedpi_readme_link"
android:textColor="?attr/textFillColor"
android:icon="@drawable/ic_github_36">
<intent
android:action="android.intent.action.VIEW"
android:data="https://github.com/hufrea/byedpi/tree/main#readme-ov-file" />
</Preference>
<com.takisoft.preferencex.EditTextPreference
android:key="dns_ip"
android:title="@string/dbs_ip_setting"
android:defaultValue="1.1.1.1"
app:useSimpleSummaryProvider="true"/>
android:title="@string/byedpi_proxy">
<com.takisoft.preferencex.EditTextPreference
android:key="byedpi_proxy_ip"
@ -71,6 +43,16 @@
android:defaultValue="16384"
app:useSimpleSummaryProvider="true" />
<CheckBoxPreference
android:key="byedpi_no_domain"
android:title="@string/byedpi_no_domain_setting"
android:defaultValue="false" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:title="@string/byedpi_desync">
<com.takisoft.preferencex.EditTextPreference
android:key="byedpi_default_ttl"
android:title="@string/byedpi_default_ttl_setting"
@ -78,11 +60,6 @@
android:defaultValue="0"
app:useSimpleSummaryProvider="true" />
<CheckBoxPreference
android:key="byedpi_no_domain"
android:title="@string/byedpi_no_domain_setting"
android:defaultValue="false"/>
<DropDownPreference
android:key="byedpi_desync_method"
android:title="@string/byedpi_desync_method_setting"
@ -91,6 +68,21 @@
android:defaultValue="disorder"
app:useSimpleSummaryProvider="true" />
<CheckBoxPreference
android:key="byedpi_desync_http"
android:title="@string/desync_http"
android:defaultValue="true" />
<CheckBoxPreference
android:key="byedpi_desync_https"
android:title="@string/desync_https"
android:defaultValue="true" />
<CheckBoxPreference
android:key="byedpi_desync_udp"
android:title="@string/desync_udp"
android:defaultValue="false" />
<com.takisoft.preferencex.EditTextPreference
android:key="byedpi_split_position"
android:title="@string/byedpi_split_position_setting"
@ -112,13 +104,13 @@
<com.takisoft.preferencex.EditTextPreference
android:key="byedpi_fake_sni"
android:title="SNI of fake packet"
android:title="@string/sni_of_fake_packet"
android:defaultValue="www.iana.org"
app:useSimpleSummaryProvider="true" />
<com.takisoft.preferencex.EditTextPreference
android:key="byedpi_oob_data"
android:title="OOB Data"
android:title="@string/oob_data"
android:defaultValue="a"
app:useSimpleSummaryProvider="true" />
@ -156,24 +148,4 @@
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:title="@string/about_category">
<Preference
android:key="version"
android:title="@string/version"
app:useSimpleSummaryProvider="true"
tools:summary="1.0.0" />
<Preference
android:key="source_code"
android:title="@string/source_code_link"
android:icon="@drawable/ic_github_36">
<intent
android:action="android.intent.action.VIEW"
android:data="https://github.com/dovecoteescapee/ByeDPIAndroid" />
</Preference>
</androidx.preference.PreferenceCategory>
</PreferenceScreen>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:tag="main_settings">
<androidx.preference.PreferenceCategory
android:title="@string/general_category">
<DropDownPreference
android:key="app_theme"
android:title="@string/theme_settings"
android:entries="@array/themes"
android:entryValues="@array/themes_entries"
android:defaultValue="system"
app:useSimpleSummaryProvider="true" />
<DropDownPreference
android:key="byedpi_mode"
android:title="@string/mode_setting"
android:entries="@array/byedpi_modes"
android:entryValues="@array/byedpi_modes_entries"
android:defaultValue="vpn"
app:useSimpleSummaryProvider="true" />
<com.takisoft.preferencex.EditTextPreference
android:key="dns_ip"
android:title="@string/dbs_ip_setting"
android:defaultValue="1.1.1.1"
app:useSimpleSummaryProvider="true"/>
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:title="@string/byedpi_category">
<SwitchPreference
android:key="byedpi_enable_cmd_settings"
android:title="@string/use_command_line_settings"
android:defaultValue="false" />
<Preference
android:key="byedpi_ui_settings"
android:title="@string/ui_editor"
app:useSimpleSummaryProvider="true"
app:fragment="io.github.dovecoteescapee.byedpi.fragments.ByeDpiUISettingsFragment" />
<Preference
android:key="byedpi_cmd_settings"
android:title="@string/command_line_editor"
app:useSimpleSummaryProvider="true"
app:fragment="io.github.dovecoteescapee.byedpi.fragments.ByeDpiCommandLineSettingsFragment" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:title="@string/about_category">
<Preference
android:key="version"
android:title="@string/version"
app:useSimpleSummaryProvider="true"
tools:summary="1.0.0" />
<Preference
android:key="source_code"
android:title="@string/source_code_link"
android:icon="@drawable/ic_github_36">
<intent
android:action="android.intent.action.VIEW"
android:data="https://github.com/dovecoteescapee/ByeDPIAndroid" />
</Preference>
</androidx.preference.PreferenceCategory>
</PreferenceScreen>