mirror of
https://github.com/CherretGit/zaprett-app.git
synced 2025-12-12 06:29:39 +05:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5b60b1ee9 | ||
|
|
2f349e5108 | ||
|
|
e8a8fa69db | ||
|
|
c094a072b8 | ||
|
|
786321d852 | ||
|
|
7a17c93622 | ||
|
|
cf38ef7ac2 | ||
|
|
94979d2b2e | ||
|
|
bd1bdf8298 | ||
|
|
adc98db3f1 | ||
|
|
9bbc3b771f | ||
|
|
51356e63eb | ||
|
|
f3f2cf999c | ||
|
|
69f395275e | ||
|
|
39dc2baad3 | ||
|
|
ad5c556241 | ||
|
|
48e7497e79 | ||
|
|
2954cef0a6 | ||
|
|
c49cce1de4 | ||
|
|
4786ef8faf | ||
|
|
0b69d2bf4d | ||
|
|
9208885de4 | ||
|
|
44fbb5fa98 | ||
|
|
7c2fba684d | ||
|
|
3ac0ca73e1 | ||
|
|
abb3a8e4e4 | ||
|
|
73cbd89390 | ||
|
|
8cfe6f4389 | ||
|
|
9a89fcccae | ||
|
|
cd44a09cfc | ||
|
|
14d6ea6caa | ||
|
|
6caff9b611 | ||
|
|
5eba2461dd | ||
|
|
5a6bf9780b | ||
|
|
ce6e1f07b8 | ||
|
|
1e985807d9 | ||
|
|
80d885167e | ||
|
|
01f4333fee |
13
.github/workflows/workflow.yml
vendored
13
.github/workflows/workflow.yml
vendored
@@ -37,6 +37,19 @@ jobs:
|
|||||||
- name: Set up Android SDK
|
- name: Set up Android SDK
|
||||||
uses: android-actions/setup-android@v2
|
uses: android-actions/setup-android@v2
|
||||||
|
|
||||||
|
- name: Setup NDK
|
||||||
|
run: sdkmanager "ndk;27.0.12077973" "cmake;3.22.1"
|
||||||
|
|
||||||
|
- name: Install toolchain
|
||||||
|
run: |
|
||||||
|
rustup default stable
|
||||||
|
rustup update stable
|
||||||
|
cargo install cargo-ndk
|
||||||
|
rustup target add armv7-linux-androideabi
|
||||||
|
rustup target install aarch64-linux-android
|
||||||
|
rustup target add i686-linux-android
|
||||||
|
rustup target add x86_64-linux-android
|
||||||
|
|
||||||
- name: Build APK
|
- name: Build APK
|
||||||
run: ./gradlew assembleRelease
|
run: ./gradlew assembleRelease
|
||||||
|
|
||||||
|
|||||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -1,5 +1,5 @@
|
|||||||
[submodule "app/src/main/cpp/byedpi"]
|
[submodule "rust/byedpi"]
|
||||||
path = app/src/main/cpp/byedpi
|
path = rust/byedpi
|
||||||
url = https://github.com/hufrea/byedpi
|
url = https://github.com/hufrea/byedpi
|
||||||
[submodule "app/src/main/jni/hev-socks5-tunnel"]
|
[submodule "app/src/main/jni/hev-socks5-tunnel"]
|
||||||
path = app/src/main/jni/hev-socks5-tunnel
|
path = app/src/main/jni/hev-socks5-tunnel
|
||||||
|
|||||||
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2025-07-09T09:45:05.074315845Z">
|
<DropdownSelection timestamp="2025-10-03T07:21:08.712998131Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=/home/white/.android/avd/Pixel_8.avd" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=/home/dimap/.android/avd/Medium_Phone.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
13
.idea/deviceManager.xml
generated
Normal file
13
.idea/deviceManager.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DeviceTable">
|
||||||
|
<option name="columnSorters">
|
||||||
|
<list>
|
||||||
|
<ColumnSorterState>
|
||||||
|
<option name="column" value="Name" />
|
||||||
|
<option name="order" value="ASCENDING" />
|
||||||
|
</ColumnSorterState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@@ -2,11 +2,11 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/app/src/main/cpp/byedpi" vcs="Git" />
|
|
||||||
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/src/core" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/src/core" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/hev-task-system" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/hev-task-system" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/lwip" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/lwip" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/yaml" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/yaml" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/rust/byedpi" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -5,6 +5,7 @@ plugins {
|
|||||||
id("com.google.gms.google-services")
|
id("com.google.gms.google-services")
|
||||||
id("com.google.firebase.crashlytics")
|
id("com.google.firebase.crashlytics")
|
||||||
kotlin("plugin.serialization") version "2.1.20"
|
kotlin("plugin.serialization") version "2.1.20"
|
||||||
|
id("org.mozilla.rust-android-gradle.rust-android") version "0.9.6"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -15,19 +16,10 @@ android {
|
|||||||
applicationId = "com.cherret.zaprett"
|
applicationId = "com.cherret.zaprett"
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 21
|
versionCode = 23
|
||||||
versionName = "2.9"
|
versionName = "2.11"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
ndk {
|
|
||||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
path = file("src/main/cpp/CMakeLists.txt")
|
|
||||||
version = "3.22.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
@@ -37,6 +29,12 @@ android {
|
|||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
)
|
)
|
||||||
|
buildConfigField("boolean", "send_firebase_analytics", "true")
|
||||||
|
buildConfigField("boolean", "auto_update", "true")
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
buildConfigField("boolean", "send_firebase_analytics", "false")
|
||||||
|
buildConfigField("boolean", "auto_update", "false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -75,6 +73,27 @@ tasks.preBuild {
|
|||||||
dependsOn("runNdkBuild")
|
dependsOn("runNdkBuild")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cargo {
|
||||||
|
module = "../rust"
|
||||||
|
libname = "byedpi"
|
||||||
|
targets = listOf("arm", "arm64", "x86", "x86_64")
|
||||||
|
profile = "release"
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.preBuild {
|
||||||
|
dependsOn("cargoBuild")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Exec>("cargoClean") {
|
||||||
|
workingDir = file("../rust")
|
||||||
|
commandLine("cargo", "clean")
|
||||||
|
group = "build"
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named("clean") {
|
||||||
|
dependsOn("cargoClean")
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.compose.material3)
|
implementation(libs.compose.material3)
|
||||||
implementation(libs.compose.material3.window.size)
|
implementation(libs.compose.material3.window.size)
|
||||||
|
|||||||
Submodule app/src/main/cpp/byedpi deleted from 22ae84f73d
@@ -1,83 +0,0 @@
|
|||||||
#define VERSION "17.1"
|
|
||||||
union sockaddr_u;
|
|
||||||
|
|
||||||
int get_default_ttl(void);
|
|
||||||
|
|
||||||
int get_addr(const char *str, union sockaddr_u *addr);
|
|
||||||
|
|
||||||
void *add(void **root, int *n, size_t ss);
|
|
||||||
|
|
||||||
void clear_params(void);
|
|
||||||
|
|
||||||
char *ftob(const char *str, ssize_t *sl);
|
|
||||||
|
|
||||||
char *data_from_str(const char *str, ssize_t *size);
|
|
||||||
|
|
||||||
size_t parse_cform(char *buffer, size_t blen, const char *str, size_t slen);
|
|
||||||
|
|
||||||
struct mphdr *parse_hosts(char *buffer, size_t size);
|
|
||||||
|
|
||||||
struct mphdr *parse_ipset(char *buffer, size_t size);
|
|
||||||
|
|
||||||
int parse_offset(struct part *part, const char *str);
|
|
||||||
|
|
||||||
static const char help_text[] = {
|
|
||||||
" -i, --ip, <ip> Listening IP, default 0.0.0.0\n"
|
|
||||||
" -p, --port <num> Listening port, default 1080\n"
|
|
||||||
#ifdef DAEMON
|
|
||||||
" -D, --daemon Daemonize\n"
|
|
||||||
" -w, --pidfile <filename> Write PID to file\n"
|
|
||||||
#endif
|
|
||||||
#ifdef __linux__
|
|
||||||
" -E, --transparent Transparent proxy mode\n"
|
|
||||||
#endif
|
|
||||||
" -c, --max-conn <count> Connection count limit, default 512\n"
|
|
||||||
" -N, --no-domain Deny domain resolving\n"
|
|
||||||
" -U, --no-udp Deny UDP association\n"
|
|
||||||
" -I --conn-ip <ip> Connection binded IP, default ::\n"
|
|
||||||
" -b, --buf-size <size> Buffer size, default 16384\n"
|
|
||||||
" -x, --debug <level> Print logs, 0, 1 or 2\n"
|
|
||||||
" -g, --def-ttl <num> TTL for all outgoing connections\n"
|
|
||||||
// desync options
|
|
||||||
#ifdef TCP_FASTOPEN_CONNECT
|
|
||||||
" -F, --tfo Enable TCP Fast Open\n"
|
|
||||||
#endif
|
|
||||||
" -A, --auto <t,r,s,n> Try desync params after this option\n"
|
|
||||||
" Detect: torst,redirect,ssl_err,none\n"
|
|
||||||
" -L, --auto-mode <0-3> Mode: 1 - post_resp, 2 - sort, 3 - 1+2\n"
|
|
||||||
" -u, --cache-ttl <sec> Lifetime of cached desync params for IP\n"
|
|
||||||
" -y, --cache-dump <file|-> Dump cache to file or stdout\n"
|
|
||||||
#ifdef TIMEOUT_SUPPORT
|
|
||||||
" -T, --timeout <sec> Timeout waiting for response, after which trigger auto\n"
|
|
||||||
#endif
|
|
||||||
" -K, --proto <t,h,u,i> Protocol whitelist: tls,http,udp,ipv4\n"
|
|
||||||
" -H, --hosts <file|:str> Hosts whitelist, filename or :string\n"
|
|
||||||
" -j, --ipset <file|:str> IP whitelist\n"
|
|
||||||
" -V, --pf <port[-portr]> Ports range whitelist\n"
|
|
||||||
" -R, --round <num[-numr]> Number of request to which desync will be applied\n"
|
|
||||||
" -s, --split <pos_t> Position format: offset[:repeats:skip][+flag1[flag2]]\n"
|
|
||||||
" Flags: +s - SNI offset, +h - HTTP host offset, +n - null\n"
|
|
||||||
" Additional flags: +e - end, +m - middle\n"
|
|
||||||
" -d, --disorder <pos_t> Split and send reverse order\n"
|
|
||||||
" -o, --oob <pos_t> Split and send as OOB data\n"
|
|
||||||
" -q, --disoob <pos_t> Split and send reverse order as OOB data\n"
|
|
||||||
#ifdef FAKE_SUPPORT
|
|
||||||
" -f, --fake <pos_t> Split and send fake packet\n"
|
|
||||||
#ifdef __linux__
|
|
||||||
" -S, --md5sig Add MD5 Signature option for fake packets\n"
|
|
||||||
#endif
|
|
||||||
" -n, --fake-sni <str> Change SNI in fake\n"
|
|
||||||
" Replaced: ? - rand let, # - rand num, * - rand let/num\n"
|
|
||||||
#endif
|
|
||||||
" -t, --ttl <num> TTL of fake packets, default 8\n"
|
|
||||||
" -O, --fake-offset <pos_t> Fake data start offset\n"
|
|
||||||
" -l, --fake-data <f|:str> Set custom fake packet\n"
|
|
||||||
" -Q, --fake-tls-mod <r,o> Modify fake TLS CH: rand,orig\n"
|
|
||||||
" -e, --oob-data <char> Set custom OOB data\n"
|
|
||||||
" -M, --mod-http <h,d,r> Modify HTTP: hcsmix,dcsmix,rmspace\n"
|
|
||||||
" -r, --tlsrec <pos_t> Make TLS record at position\n"
|
|
||||||
" -a, --udp-fake <count> UDP fakes count, default 0\n"
|
|
||||||
#ifdef __linux__
|
|
||||||
" -Y, --drop-sack Drop packets with SACK extension\n"
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
#include <android/log.h>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#include "byedpi/error.h"
|
|
||||||
#include "byedpi/proxy.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
static int g_proxy_fd = -1;
|
|
||||||
|
|
||||||
JNIEXPORT jint JNI_OnLoad(
|
|
||||||
__attribute__((unused)) JavaVM *vm,
|
|
||||||
__attribute__((unused)) void *reserved) {
|
|
||||||
default_params = params;
|
|
||||||
return JNI_VERSION_1_6;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_cherret_zaprett_byedpi_NativeBridge_jniCreateSocket(
|
|
||||||
JNIEnv *env,
|
|
||||||
__attribute__((unused)) jobject thiz,
|
|
||||||
jobjectArray args) {
|
|
||||||
|
|
||||||
if (g_proxy_fd != -1) {
|
|
||||||
LOG(LOG_S, "proxy already running, fd: %d", g_proxy_fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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((union sockaddr_u *)¶ms.laddr);
|
|
||||||
|
|
||||||
for (int i = 0; i < argc; i++) {
|
|
||||||
free(argv[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fd < 0) {
|
|
||||||
uniperror("listen_socket");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_proxy_fd = fd;
|
|
||||||
LOG(LOG_S, "listen_socket, fd: %d", fd);
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_cherret_zaprett_byedpi_NativeBridge_jniStartProxy(
|
|
||||||
__attribute__((unused)) JNIEnv *env,
|
|
||||||
__attribute__((unused)) jobject thiz) {
|
|
||||||
|
|
||||||
LOG(LOG_S, "start_proxy, fd: %d", g_proxy_fd);
|
|
||||||
|
|
||||||
if (start_event_loop(g_proxy_fd) < 0) {
|
|
||||||
uniperror("event_loop");
|
|
||||||
return get_e();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_cherret_zaprett_byedpi_NativeBridge_jniStopProxy(
|
|
||||||
__attribute__((unused)) JNIEnv *env,
|
|
||||||
__attribute__((unused)) jobject thiz) {
|
|
||||||
|
|
||||||
LOG(LOG_S, "stop_proxy, fd: %d", g_proxy_fd);
|
|
||||||
|
|
||||||
if (g_proxy_fd < 0) {
|
|
||||||
LOG(LOG_S, "proxy is not running, fd: %d", g_proxy_fd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset_params();
|
|
||||||
int res = shutdown(g_proxy_fd, SHUT_RDWR);
|
|
||||||
g_proxy_fd = -1;
|
|
||||||
|
|
||||||
if (res < 0) {
|
|
||||||
uniperror("shutdown");
|
|
||||||
return get_e();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,594 +0,0 @@
|
|||||||
#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;
|
|
||||||
extern const struct option options[46];
|
|
||||||
|
|
||||||
void reset_params(void) {
|
|
||||||
clear_params();
|
|
||||||
params = default_params;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct desync_params *add_group(struct desync_params *prev)
|
|
||||||
{
|
|
||||||
struct desync_params *dp = calloc(1, sizeof(*prev));
|
|
||||||
if (!dp) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (prev) {
|
|
||||||
dp->prev = prev;
|
|
||||||
prev->next = dp;
|
|
||||||
}
|
|
||||||
params.dp_n++;
|
|
||||||
return dp;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.in.sin_port = htons(1080);
|
|
||||||
if (!ipv6_support()) {
|
|
||||||
params.baddr.sa.sa_family = AF_INET;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *pid_file = 0;
|
|
||||||
bool daemonize = 0;
|
|
||||||
|
|
||||||
int rez;
|
|
||||||
int invalid = 0;
|
|
||||||
|
|
||||||
long val = 0;
|
|
||||||
char *end = 0;
|
|
||||||
bool all_limited = 1;
|
|
||||||
|
|
||||||
int curr_optind = 1;
|
|
||||||
|
|
||||||
struct desync_params *dp = add_group(0);
|
|
||||||
if (!dp) {
|
|
||||||
reset_params();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
params.dp = dp;
|
|
||||||
|
|
||||||
while (!invalid && (rez = getopt_long(
|
|
||||||
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 'G':
|
|
||||||
params.http_connect = 1;
|
|
||||||
break;
|
|
||||||
#ifdef __linux__
|
|
||||||
case 'E':
|
|
||||||
params.transparent = 1;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef DAEMON
|
|
||||||
case 'D':
|
|
||||||
daemonize = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'w':
|
|
||||||
pid_file = optarg;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
case 'h':
|
|
||||||
printf("%s", help_text);
|
|
||||||
reset_params();
|
|
||||||
return 0;
|
|
||||||
case 'v':
|
|
||||||
printf("%s\n", VERSION);
|
|
||||||
reset_params();
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case 'i':
|
|
||||||
if (get_addr(optarg, ¶ms.laddr) < 0)
|
|
||||||
invalid = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'p':
|
|
||||||
val = strtol(optarg, &end, 0);
|
|
||||||
if (val <= 0 || val > 0xffff || *end)
|
|
||||||
invalid = 1;
|
|
||||||
else
|
|
||||||
params.laddr.in.sin_port = htons(val);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'I':
|
|
||||||
if (get_addr(optarg, ¶ms.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;
|
|
||||||
|
|
||||||
case 'y': //
|
|
||||||
params.cache_file = optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// desync options
|
|
||||||
|
|
||||||
case 'F':
|
|
||||||
params.tfo = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'L':
|
|
||||||
end = optarg;
|
|
||||||
while (end && !invalid) {
|
|
||||||
switch (*end) {
|
|
||||||
case '0':
|
|
||||||
break;
|
|
||||||
case '1':
|
|
||||||
case 'p':
|
|
||||||
params.auto_level |= AUTO_POST;
|
|
||||||
break;
|
|
||||||
case '2':
|
|
||||||
case 's':
|
|
||||||
params.auto_level |= AUTO_SORT;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
params.auto_level = 0;
|
|
||||||
break;
|
|
||||||
case '3':
|
|
||||||
params.auto_level |= (AUTO_POST | AUTO_SORT);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
invalid = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
end = strchr(end, ',');
|
|
||||||
if (end) end++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'A':
|
|
||||||
if (optind < curr_optind) {
|
|
||||||
optind = curr_optind;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!(dp->hosts || dp->proto || dp->pf[0] || dp->detect || dp->ipset)) {
|
|
||||||
all_limited = 0;
|
|
||||||
}
|
|
||||||
dp = add_group(dp);
|
|
||||||
if (!dp) {
|
|
||||||
reset_params();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
end = optarg;
|
|
||||||
while (end && !invalid) {
|
|
||||||
switch (*end) {
|
|
||||||
case 't':
|
|
||||||
dp->detect |= DETECT_TORST;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
dp->detect |= DETECT_HTTP_LOCAT;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
case 's':
|
|
||||||
dp->detect |= DETECT_TLS_ERR;
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
invalid = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
end = strchr(end, ',');
|
|
||||||
if (end) end++;
|
|
||||||
}
|
|
||||||
if (dp->detect) {
|
|
||||||
params.auto_level |= AUTO_RECONN;
|
|
||||||
}
|
|
||||||
dp->_optind = optind;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'B':
|
|
||||||
if (optind < curr_optind) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (*optarg == 'i') {
|
|
||||||
dp->pf[0] = htons(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
val = strtol(optarg, &end, 0);
|
|
||||||
struct desync_params *itdp = params.dp;
|
|
||||||
|
|
||||||
while (itdp && itdp->id != val - 1) {
|
|
||||||
itdp = itdp->next;
|
|
||||||
}
|
|
||||||
if (!itdp)
|
|
||||||
invalid = 1;
|
|
||||||
else {
|
|
||||||
curr_optind = optind;
|
|
||||||
optind = itdp->_optind;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'u':
|
|
||||||
val = strtol(optarg, &end, 0);
|
|
||||||
if (val <= 0 || *end)
|
|
||||||
invalid = 1;
|
|
||||||
else
|
|
||||||
params.cache_ttl = val;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'T':;
|
|
||||||
#ifdef __linux__
|
|
||||||
float f = strtof(optarg, &end);
|
|
||||||
val = (long)(f * 1000);
|
|
||||||
#else
|
|
||||||
val = strtol(optarg, &end, 0);
|
|
||||||
#endif
|
|
||||||
if (val <= 0 || (unsigned long)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_TCP | IS_HTTPS;
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
dp->proto |= IS_TCP | IS_HTTP;
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
dp->proto |= IS_UDP;
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
dp->proto |= IS_IPV4;
|
|
||||||
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) {
|
|
||||||
uniperror("parse_hosts");
|
|
||||||
reset_params();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'j':;
|
|
||||||
if (dp->ipset) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ssize_t size;
|
|
||||||
char *data = ftob(optarg, &size);
|
|
||||||
if (!data) {
|
|
||||||
uniperror("read/parse");
|
|
||||||
invalid = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
dp->ipset = parse_ipset(data, size);
|
|
||||||
if (!dp->ipset) {
|
|
||||||
uniperror("parse_ipset");
|
|
||||||
invalid = 1;
|
|
||||||
}
|
|
||||||
free(data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 's':
|
|
||||||
case 'd':
|
|
||||||
case 'o':
|
|
||||||
case 'q':
|
|
||||||
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 'q': part->m = DESYNC_DISOOB;
|
|
||||||
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 'S':
|
|
||||||
dp->md5sig = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'O':
|
|
||||||
if (parse_offset(&dp->fake_offset, optarg)) {
|
|
||||||
invalid = 1;
|
|
||||||
break;
|
|
||||||
} else dp->fake_offset.m = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Q':
|
|
||||||
end = optarg;
|
|
||||||
while (end && !invalid) {
|
|
||||||
switch (*end) {
|
|
||||||
case 'r':
|
|
||||||
dp->fake_mod |= FM_RAND;
|
|
||||||
break;
|
|
||||||
case 'o':
|
|
||||||
dp->fake_mod |= FM_ORIG;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
invalid = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
end = strchr(end, ',');
|
|
||||||
if (end) end++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'n':;
|
|
||||||
const char **p = add((void *)&dp->fake_sni_list,
|
|
||||||
&dp->fake_sni_count, sizeof(optarg));
|
|
||||||
if (!p) {
|
|
||||||
invalid = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
*p = 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':
|
|
||||||
val = parse_cform(dp->oob_char, 1, optarg, strlen(optarg));
|
|
||||||
if (val != 1) {
|
|
||||||
invalid = 1;
|
|
||||||
}
|
|
||||||
else dp->oob_char[1] = 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 'R':
|
|
||||||
val = strtol(optarg, &end, 0);
|
|
||||||
if (val <= 0 || val > INT_MAX)
|
|
||||||
invalid = 1;
|
|
||||||
else {
|
|
||||||
dp->rounds[0] = val;
|
|
||||||
if (*end == '-') {
|
|
||||||
val = strtol(end + 1, &end, 0);
|
|
||||||
if (val <= 0 || val > INT_MAX)
|
|
||||||
invalid = 1;
|
|
||||||
}
|
|
||||||
if (*end)
|
|
||||||
invalid = 1;
|
|
||||||
else
|
|
||||||
dp->rounds[1] = 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 'Y':
|
|
||||||
dp->drop_sack = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Z':
|
|
||||||
params.wait_send = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'W':
|
|
||||||
params.await_int = atoi(optarg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'C':
|
|
||||||
if (get_addr(optarg, &dp->custom_dst_addr) < 0)
|
|
||||||
invalid = 1;
|
|
||||||
else
|
|
||||||
dp->custom_dst = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
case 'P':
|
|
||||||
params.protect_path = optarg;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
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 (all_limited) {
|
|
||||||
dp = add_group(dp);
|
|
||||||
if (!dp) {
|
|
||||||
reset_params();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((size_t )params.dp_n > sizeof(dp->bit) * 8) {
|
|
||||||
LOG(LOG_E, "too many groups!\n");
|
|
||||||
}
|
|
||||||
if (params.baddr.sa.sa_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(MF_EXTRA, CMP_BYTES);
|
|
||||||
if (!params.mempool) {
|
|
||||||
uniperror("mem_pool");
|
|
||||||
reset_params();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
srand((unsigned int)time(0));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
extern struct params default_params;
|
|
||||||
|
|
||||||
void reset_params(void);
|
|
||||||
int parse_args(int argc, char **argv);
|
|
||||||
bool ipv6_support(void);
|
|
||||||
@@ -22,6 +22,7 @@ import androidx.compose.material.icons.filled.Home
|
|||||||
import androidx.compose.material.icons.filled.Lan
|
import androidx.compose.material.icons.filled.Lan
|
||||||
import androidx.compose.material.icons.filled.MultipleStop
|
import androidx.compose.material.icons.filled.MultipleStop
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material.icons.filled.SettingsInputComposite
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.NavigationBar
|
import androidx.compose.material3.NavigationBar
|
||||||
@@ -49,12 +50,15 @@ import androidx.navigation.navArgument
|
|||||||
import com.cherret.zaprett.ui.screen.DebugScreen
|
import com.cherret.zaprett.ui.screen.DebugScreen
|
||||||
import com.cherret.zaprett.ui.screen.HomeScreen
|
import com.cherret.zaprett.ui.screen.HomeScreen
|
||||||
import com.cherret.zaprett.ui.screen.HostsScreen
|
import com.cherret.zaprett.ui.screen.HostsScreen
|
||||||
|
import com.cherret.zaprett.ui.screen.IpsetsScreen
|
||||||
import com.cherret.zaprett.ui.screen.RepoScreen
|
import com.cherret.zaprett.ui.screen.RepoScreen
|
||||||
import com.cherret.zaprett.ui.screen.SettingsScreen
|
import com.cherret.zaprett.ui.screen.SettingsScreen
|
||||||
import com.cherret.zaprett.ui.screen.StrategyScreen
|
import com.cherret.zaprett.ui.screen.StrategyScreen
|
||||||
|
import com.cherret.zaprett.ui.screen.StrategySelectionScreen
|
||||||
import com.cherret.zaprett.ui.theme.ZaprettTheme
|
import com.cherret.zaprett.ui.theme.ZaprettTheme
|
||||||
import com.cherret.zaprett.ui.viewmodel.HomeViewModel
|
import com.cherret.zaprett.ui.viewmodel.HomeViewModel
|
||||||
import com.cherret.zaprett.ui.viewmodel.HostRepoViewModel
|
import com.cherret.zaprett.ui.viewmodel.HostRepoViewModel
|
||||||
|
import com.cherret.zaprett.ui.viewmodel.IpsetRepoViewModel
|
||||||
import com.cherret.zaprett.ui.viewmodel.StrategyRepoViewModel
|
import com.cherret.zaprett.ui.viewmodel.StrategyRepoViewModel
|
||||||
import com.cherret.zaprett.utils.checkModuleInstallation
|
import com.cherret.zaprett.utils.checkModuleInstallation
|
||||||
import com.google.firebase.Firebase
|
import com.google.firebase.Firebase
|
||||||
@@ -65,10 +69,11 @@ sealed class Screen(val route: String, @StringRes val nameResId: Int, val icon:
|
|||||||
object home : Screen("home", R.string.title_home, Icons.Default.Home)
|
object home : Screen("home", R.string.title_home, Icons.Default.Home)
|
||||||
object hosts : Screen("hosts", R.string.title_hosts, Icons.Default.Lan)
|
object hosts : Screen("hosts", R.string.title_hosts, Icons.Default.Lan)
|
||||||
object strategies : Screen("strategies", R.string.title_strategies, Icons.Default.MultipleStop)
|
object strategies : Screen("strategies", R.string.title_strategies, Icons.Default.MultipleStop)
|
||||||
|
object ipsets : Screen("ipsets", R.string.title_ipset, Icons.Default.SettingsInputComposite)
|
||||||
object settings : Screen("settings", R.string.title_settings, Icons.Default.Settings)
|
object settings : Screen("settings", R.string.title_settings, Icons.Default.Settings)
|
||||||
}
|
}
|
||||||
val topLevelRoutes = listOf(Screen.home, Screen.hosts, Screen.strategies, Screen.settings)
|
val topLevelRoutes = listOf(Screen.home, Screen.hosts, Screen.strategies, Screen.ipsets, Screen.settings)
|
||||||
val hideNavBar = listOf("repo?source={source}", "debugScreen")
|
val hideNavBar = listOf("repo?source={source}", "debugScreen", "selectionScreen")
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private val viewModel: HomeViewModel by viewModels()
|
private val viewModel: HomeViewModel by viewModels()
|
||||||
private lateinit var notificationPermissionLauncher: ActivityResultLauncher<String>
|
private lateinit var notificationPermissionLauncher: ActivityResultLauncher<String>
|
||||||
@@ -123,7 +128,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
var showWelcomeDialog by remember { mutableStateOf(sharedPreferences.getBoolean("welcome_dialog", true)) }
|
var showWelcomeDialog by remember { mutableStateOf(sharedPreferences.getBoolean("welcome_dialog", true)) }
|
||||||
firebaseAnalytics.setAnalyticsCollectionEnabled(sharedPreferences.getBoolean("send_firebase_analytics", true))
|
firebaseAnalytics.setAnalyticsCollectionEnabled(sharedPreferences.getBoolean("send_firebase_analytics", BuildConfig.send_firebase_analytics))
|
||||||
BottomBar()
|
BottomBar()
|
||||||
if (showStoragePermissionDialog) {
|
if (showStoragePermissionDialog) {
|
||||||
PermissionDialog(
|
PermissionDialog(
|
||||||
@@ -215,6 +220,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
composable(Screen.home.route) { HomeScreen(viewModel = viewModel, vpnPermissionLauncher) }
|
composable(Screen.home.route) { HomeScreen(viewModel = viewModel, vpnPermissionLauncher) }
|
||||||
composable(Screen.hosts.route) { HostsScreen(navController) }
|
composable(Screen.hosts.route) { HostsScreen(navController) }
|
||||||
composable(Screen.strategies.route) { StrategyScreen(navController) }
|
composable(Screen.strategies.route) { StrategyScreen(navController) }
|
||||||
|
composable(Screen.ipsets.route) { IpsetsScreen(navController) }
|
||||||
composable(Screen.settings.route) { SettingsScreen(navController) }
|
composable(Screen.settings.route) { SettingsScreen(navController) }
|
||||||
composable(route = "repo?source={source}",arguments = listOf(navArgument("source") {})) { backStackEntry ->
|
composable(route = "repo?source={source}",arguments = listOf(navArgument("source") {})) { backStackEntry ->
|
||||||
val source = backStackEntry.arguments?.getString("source")
|
val source = backStackEntry.arguments?.getString("source")
|
||||||
@@ -223,6 +229,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
val viewModel: HostRepoViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
|
val viewModel: HostRepoViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
|
||||||
RepoScreen(navController, viewModel)
|
RepoScreen(navController, viewModel)
|
||||||
}
|
}
|
||||||
|
"ipsets" -> {
|
||||||
|
val viewModel: IpsetRepoViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
|
||||||
|
RepoScreen(navController, viewModel)
|
||||||
|
}
|
||||||
"strategies" -> {
|
"strategies" -> {
|
||||||
val viewModel: StrategyRepoViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
|
val viewModel: StrategyRepoViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
|
||||||
RepoScreen(navController, viewModel)
|
RepoScreen(navController, viewModel)
|
||||||
@@ -230,6 +240,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
composable("debugScreen") { DebugScreen(navController) }
|
composable("debugScreen") { DebugScreen(navController) }
|
||||||
|
composable("selectionScreen") { StrategySelectionScreen(navController, vpnPermissionLauncher) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ import androidx.core.app.NotificationCompat
|
|||||||
import com.cherret.zaprett.MainActivity
|
import com.cherret.zaprett.MainActivity
|
||||||
import com.cherret.zaprett.R
|
import com.cherret.zaprett.R
|
||||||
import com.cherret.zaprett.data.ServiceStatus
|
import com.cherret.zaprett.data.ServiceStatus
|
||||||
|
import com.cherret.zaprett.utils.disableList
|
||||||
|
import com.cherret.zaprett.utils.getActiveByeDPIStrategyContent
|
||||||
|
import com.cherret.zaprett.utils.getActiveExcludeIpsets
|
||||||
import com.cherret.zaprett.utils.getActiveExcludeLists
|
import com.cherret.zaprett.utils.getActiveExcludeLists
|
||||||
|
import com.cherret.zaprett.utils.getActiveIpsets
|
||||||
import com.cherret.zaprett.utils.getActiveLists
|
import com.cherret.zaprett.utils.getActiveLists
|
||||||
import com.cherret.zaprett.utils.getActiveStrategy
|
import com.cherret.zaprett.utils.getActiveStrategy
|
||||||
import com.cherret.zaprett.utils.getAppsListMode
|
import com.cherret.zaprett.utils.getAppsListMode
|
||||||
@@ -174,7 +178,7 @@ class ByeDpiVpnService : VpnService() {
|
|||||||
try {
|
try {
|
||||||
vpnInterface?.close()
|
vpnInterface?.close()
|
||||||
vpnInterface = null
|
vpnInterface = null
|
||||||
NativeBridge().stopProxy()
|
NativeBridge().jniStopProxy()
|
||||||
TProxyService.TProxyStopService()
|
TProxyService.TProxyStopService()
|
||||||
status = ServiceStatus.Disconnected
|
status = ServiceStatus.Disconnected
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -186,9 +190,16 @@ class ByeDpiVpnService : VpnService() {
|
|||||||
val socksIp = sharedPreferences.getString("ip", "127.0.0.1")?: "127.0.0.1"
|
val socksIp = sharedPreferences.getString("ip", "127.0.0.1")?: "127.0.0.1"
|
||||||
val socksPort = sharedPreferences.getString("port", "1080")?: "1080"
|
val socksPort = sharedPreferences.getString("port", "1080")?: "1080"
|
||||||
val listSet = if (getHostListMode(sharedPreferences) == "whitelist") getActiveLists(sharedPreferences) else getActiveExcludeLists(sharedPreferences)
|
val listSet = if (getHostListMode(sharedPreferences) == "whitelist") getActiveLists(sharedPreferences) else getActiveExcludeLists(sharedPreferences)
|
||||||
|
val ipsetSet = if (getHostListMode(sharedPreferences) == "whitelist") getActiveIpsets(sharedPreferences) else getActiveExcludeIpsets(sharedPreferences)
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val args = parseArgs(socksIp, socksPort, getActiveStrategy(sharedPreferences), prepareList(listSet), sharedPreferences)
|
val args = parseArgs(
|
||||||
val result = NativeBridge().startProxy(args)
|
socksIp,
|
||||||
|
socksPort,
|
||||||
|
getActiveByeDPIStrategyContent(sharedPreferences),
|
||||||
|
prepareList(listSet),
|
||||||
|
prepareIpset(ipsetSet),
|
||||||
|
sharedPreferences)
|
||||||
|
val result = NativeBridge().jniStartProxy(args)
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
Log.d("proxy","Failed to start byedpi proxy")
|
Log.d("proxy","Failed to start byedpi proxy")
|
||||||
} else {
|
} else {
|
||||||
@@ -205,10 +216,14 @@ class ByeDpiVpnService : VpnService() {
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
hostlist.printWriter().use { out ->
|
hostlist.printWriter().use { out ->
|
||||||
lists.forEach {
|
lists.forEach {
|
||||||
it.bufferedReader().useLines {
|
if (it.exists()) {
|
||||||
it.forEach {
|
it.bufferedReader().useLines {
|
||||||
out.println(it)
|
it.forEach {
|
||||||
|
out.println(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
disableList(it.name, sharedPreferences)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,7 +233,33 @@ class ByeDpiVpnService : VpnService() {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseArgs(ip: String, port: String, rawArgs: List<String>, list : String, sharedPreferences: SharedPreferences): Array<String> {
|
private suspend fun prepareIpset(actsets: Array<String>): String {
|
||||||
|
if (actsets.isNotEmpty()) {
|
||||||
|
val lists: Array<File> = actsets.map { File(it) }.toTypedArray()
|
||||||
|
val hostlist = withContext(Dispatchers.IO) {
|
||||||
|
File.createTempFile("ipset", ".txt", cacheDir)
|
||||||
|
}.apply { deleteOnExit() }
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
hostlist.printWriter().use { out ->
|
||||||
|
lists.forEach {
|
||||||
|
if (it.exists()) {
|
||||||
|
it.bufferedReader().useLines {
|
||||||
|
it.forEach {
|
||||||
|
out.println(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
disableList(it.name, sharedPreferences)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hostlist.absolutePath
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseArgs(ip: String, port: String, rawArgs: List<String>, list : String, ipset : String, sharedPreferences: SharedPreferences): Array<String> {
|
||||||
val regex = Regex("""--?\S+(?:=(?:[^"'\s]+|"[^"]*"|'[^']*'))?|[^\s]+""")
|
val regex = Regex("""--?\S+(?:=(?:[^"'\s]+|"[^"]*"|'[^']*'))?|[^\s]+""")
|
||||||
val parsedArgs = rawArgs
|
val parsedArgs = rawArgs
|
||||||
.flatMap { args -> regex.findAll(args).map { it.value } }
|
.flatMap { args -> regex.findAll(args).map { it.value } }
|
||||||
@@ -227,6 +268,8 @@ class ByeDpiVpnService : VpnService() {
|
|||||||
when {
|
when {
|
||||||
arg == "\$hostlist" && list.isNotEmpty() -> listOf("-H", list)
|
arg == "\$hostlist" && list.isNotEmpty() -> listOf("-H", list)
|
||||||
arg == "\$hostlist" && list.isEmpty() -> emptyList()
|
arg == "\$hostlist" && list.isEmpty() -> emptyList()
|
||||||
|
arg == "\$ipset" && list.isNotEmpty() -> listOf("-H", list)
|
||||||
|
arg == "\$ipset" && list.isEmpty() -> emptyList()
|
||||||
else -> listOf(arg)
|
else -> listOf(arg)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -235,6 +278,13 @@ class ByeDpiVpnService : VpnService() {
|
|||||||
} else {
|
} else {
|
||||||
listOf("-An", arg).filter { it != "\$hostlist" }
|
listOf("-An", arg).filter { it != "\$hostlist" }
|
||||||
}
|
}
|
||||||
|
if (ipset.isEmpty()) {
|
||||||
|
listOf("-H", list, "-An", arg).filter { it != "\$ipset" }
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
listOf("-An", arg).filter { it != "\$ipset" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.toMutableList()
|
.toMutableList()
|
||||||
|
|||||||
@@ -6,16 +6,7 @@ class NativeBridge {
|
|||||||
System.loadLibrary("byedpi")
|
System.loadLibrary("byedpi")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun startProxy(args: Array<String>): Int {
|
|
||||||
jniCreateSocket(args)
|
|
||||||
return jniStartProxy()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopProxy(): Int {
|
external fun jniStartProxy(args: Array<String>): Int
|
||||||
return jniStopProxy()
|
external fun jniStopProxy(): Int
|
||||||
}
|
|
||||||
|
|
||||||
private external fun jniCreateSocket(args: Array<String>): Int
|
|
||||||
private external fun jniStartProxy(): Int
|
|
||||||
private external fun jniStopProxy(): Int
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
package com.cherret.zaprett.data
|
package com.cherret.zaprett.data
|
||||||
|
|
||||||
enum class ItemType {
|
enum class ItemType {
|
||||||
byedpi, nfqws, list, list_exclude
|
byedpi, nfqws, list, list_exclude, ipset, ipset_exclude
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.cherret.zaprett.data
|
||||||
|
|
||||||
|
data class StrategyCheckResult (
|
||||||
|
val path : String,
|
||||||
|
val progress : Float,
|
||||||
|
var domains: List<String>,
|
||||||
|
val status : StrategyTestingStatus,
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.cherret.zaprett.data
|
||||||
|
|
||||||
|
import com.cherret.zaprett.R
|
||||||
|
|
||||||
|
enum class StrategyTestingStatus(val resId: Int) {
|
||||||
|
Waiting(R.string.strategy_status_waiting), Testing(R.string.strategy_status_testing), Completed(R.string.strategy_status_tested)
|
||||||
|
}
|
||||||
@@ -1,32 +1,58 @@
|
|||||||
package com.cherret.zaprett.ui.component
|
package com.cherret.zaprett.ui.component
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.filled.InstallMobile
|
import androidx.compose.material.icons.filled.InstallMobile
|
||||||
import androidx.compose.material.icons.filled.Update
|
import androidx.compose.material.icons.filled.Update
|
||||||
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
|
import androidx.compose.material3.FilledTonalIconButton
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import com.cherret.zaprett.R
|
import com.cherret.zaprett.R
|
||||||
|
import com.cherret.zaprett.data.StrategyCheckResult
|
||||||
|
import com.cherret.zaprett.data.StrategyTestingStatus
|
||||||
import com.cherret.zaprett.ui.viewmodel.BaseRepoViewModel
|
import com.cherret.zaprett.ui.viewmodel.BaseRepoViewModel
|
||||||
import com.cherret.zaprett.utils.RepoItemInfo
|
import com.cherret.zaprett.utils.RepoItemInfo
|
||||||
|
import com.cherret.zaprett.utils.disableStrategy
|
||||||
|
import com.cherret.zaprett.utils.enableStrategy
|
||||||
|
import com.cherret.zaprett.utils.getActiveStrategy
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ListSwitchItem(item: String, isChecked: Boolean, onCheckedChange: (Boolean) -> Unit, onDeleteClick: () -> Unit) {
|
fun ListSwitchItem(item: String, isChecked: Boolean, onCheckedChange: (Boolean) -> Unit, onDeleteClick: () -> Unit) {
|
||||||
@@ -164,3 +190,119 @@ fun RepoItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StrategySelectionItem(strategy : StrategyCheckResult, prefs : SharedPreferences, context : Context, snackbarHostState : SnackbarHostState) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
ElevatedCard (
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
|
||||||
|
onClick = {
|
||||||
|
if (strategy.status == StrategyTestingStatus.Completed && strategy.domains.isNotEmpty()) {
|
||||||
|
expanded = !expanded
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 10.dp, end = 10.dp, top = 25.dp, bottom = 0.dp)
|
||||||
|
) {
|
||||||
|
Column (
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
text = strategy.path,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
)
|
||||||
|
FilledTonalIconButton(
|
||||||
|
onClick = {
|
||||||
|
if (getActiveStrategy(prefs).isNotEmpty()) disableStrategy(getActiveStrategy(prefs)[0], prefs)
|
||||||
|
enableStrategy(strategy.path, prefs)
|
||||||
|
scope.launch {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = context.getString(R.string.strategy_applied)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = strategy.status == StrategyTestingStatus.Completed
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = "apply"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
text = stringResource(strategy.status.resId),
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
|
||||||
|
progress = {
|
||||||
|
strategy.progress
|
||||||
|
},
|
||||||
|
color = ProgressIndicatorDefaults.linearColor,
|
||||||
|
trackColor = ProgressIndicatorDefaults.linearTrackColor,
|
||||||
|
strokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "${(strategy.progress*100).toInt()}%",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp),
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = expanded,
|
||||||
|
enter = expandVertically(),
|
||||||
|
exit = shrinkVertically()
|
||||||
|
) {
|
||||||
|
Card (
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.selection_available_domains)
|
||||||
|
)
|
||||||
|
LazyColumn(modifier = Modifier.heightIn(max = 300.dp)) {
|
||||||
|
items(strategy.domains) { item ->
|
||||||
|
Card(
|
||||||
|
elevation = CardDefaults.cardElevation(4.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = item,
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,9 +35,9 @@ fun SettingsItem(title: String, checked: Boolean, onToggle: (Boolean) -> Unit, o
|
|||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(80.dp)
|
.height(80.dp),
|
||||||
.clickable { onToggle(!checked) },
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
||||||
|
onClick = { onToggle(!checked) },
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@@ -63,9 +63,9 @@ fun SettingsActionItem(title: String, onClick: () -> Unit) {
|
|||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(80.dp)
|
.height(80.dp),
|
||||||
.clickable { onClick() },
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
||||||
|
onClick = { onClick() },
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -196,9 +196,7 @@ private fun UpdateCard(updateAvailable: MutableState<Boolean>, onClick: () -> Un
|
|||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 10.dp, top = 10.dp, end = 10.dp)
|
.padding(start = 10.dp, top = 10.dp, end = 10.dp),
|
||||||
.width(140.dp)
|
|
||||||
.height(70.dp),
|
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
223
app/src/main/java/com/cherret/zaprett/ui/screen/IpsetsScreen.kt
Normal file
223
app/src/main/java/com/cherret/zaprett/ui/screen/IpsetsScreen.kt
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
package com.cherret.zaprett.ui.screen
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Download
|
||||||
|
import androidx.compose.material.icons.filled.UploadFile
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SegmentedButton
|
||||||
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.Font
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.cherret.zaprett.R
|
||||||
|
import com.cherret.zaprett.ui.component.ListSwitchItem
|
||||||
|
import com.cherret.zaprett.ui.viewmodel.HostsViewModel
|
||||||
|
import com.cherret.zaprett.ui.viewmodel.IpsetViewModel
|
||||||
|
import com.cherret.zaprett.utils.getHostListMode
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun IpsetsScreen(navController: NavController, viewModel: IpsetViewModel = viewModel()) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
val allLists = viewModel.allItems
|
||||||
|
val checked = viewModel.checked
|
||||||
|
val isRefreshing = viewModel.isRefreshing
|
||||||
|
val filePickerLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.OpenDocument()
|
||||||
|
) { uri ->
|
||||||
|
uri?.let {
|
||||||
|
if (getHostListMode(prefs) == "whitelist") viewModel.copySelectedFile(context, "/ipset/include", it)
|
||||||
|
else viewModel.copySelectedFile(context, "/ipset/exclude", it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.title_ipset),
|
||||||
|
fontSize = 40.sp,
|
||||||
|
fontFamily = FontFamily(Font(R.font.unbounded, FontWeight.Normal))
|
||||||
|
)
|
||||||
|
},
|
||||||
|
windowInsets = WindowInsets(0)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = { paddingValues ->
|
||||||
|
PullToRefreshBox(
|
||||||
|
isRefreshing = isRefreshing,
|
||||||
|
onRefresh = {
|
||||||
|
viewModel.refresh()
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
top = paddingValues.calculateTopPadding(),
|
||||||
|
bottom = paddingValues.calculateBottomPadding() + 80.dp
|
||||||
|
),
|
||||||
|
modifier = Modifier.navigationBarsPadding().fillMaxSize()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
IpsetTypeChoose(viewModel, prefs)
|
||||||
|
}
|
||||||
|
when {
|
||||||
|
allLists.isEmpty() -> {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillParentMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.empty_list),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
items(allLists) { item ->
|
||||||
|
ListSwitchItem (
|
||||||
|
item = item,
|
||||||
|
isChecked = checked[item] == true,
|
||||||
|
onCheckedChange = { isChecked ->
|
||||||
|
viewModel.onCheckedChange(item, isChecked, snackbarHostState, scope)
|
||||||
|
},
|
||||||
|
onDeleteClick = {
|
||||||
|
viewModel.deleteItem(item, snackbarHostState, scope)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
FloatingMenu(navController, filePickerLauncher)
|
||||||
|
},
|
||||||
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FloatingMenu(navController: NavController, launcher: ActivityResultLauncher<Array<String>>) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
FloatingActionButton(
|
||||||
|
modifier = Modifier.size(80.dp),
|
||||||
|
onClick = { expanded = !expanded }
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.btn_add_host))
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.btn_download_host)) },
|
||||||
|
onClick = {
|
||||||
|
expanded = false
|
||||||
|
navController.navigate("repo?source=ipsets") { launchSingleTop = true }
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Default.Download, contentDescription = stringResource(R.string.btn_download_host))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.btn_add_host)) },
|
||||||
|
onClick = {
|
||||||
|
expanded = false
|
||||||
|
addHost(launcher)
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Default.UploadFile, contentDescription = stringResource(R.string.btn_add_host))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
|
fun IpsetTypeChoose(viewModel: IpsetViewModel, prefs : SharedPreferences) {
|
||||||
|
val listType = remember { mutableStateOf(getHostListMode(prefs))}
|
||||||
|
val options = listOf(stringResource(R.string.title_whitelist), stringResource(R.string.title_blacklist))
|
||||||
|
val selectedIndex = if (listType.value == "whitelist") 0 else 1
|
||||||
|
|
||||||
|
SingleChoiceSegmentedButtonRow (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
options.forEachIndexed { index, label ->
|
||||||
|
SegmentedButton(
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(
|
||||||
|
index = index,
|
||||||
|
count = options.size
|
||||||
|
),
|
||||||
|
onClick = {
|
||||||
|
listType.value = if (index == 0) "whitelist" else "blacklist"
|
||||||
|
viewModel.setListType(listType.value)
|
||||||
|
},
|
||||||
|
selected = index == selectedIndex,
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
label
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun addHost(launcher: ActivityResultLauncher<Array<String>>) {
|
||||||
|
launcher.launch(arrayOf("text/plain"))
|
||||||
|
}
|
||||||
@@ -97,16 +97,17 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val sharedPreferences = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) }
|
val sharedPreferences = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) }
|
||||||
val editor = remember { sharedPreferences.edit() }
|
val editor = remember { sharedPreferences.edit() }
|
||||||
val useModule = remember { mutableStateOf(sharedPreferences.getBoolean("use_module", false)) }
|
val useModule = viewModel.useModule.collectAsState()
|
||||||
val updateOnBoot = remember { mutableStateOf(sharedPreferences.getBoolean("update_on_boot", true)) }
|
val updateOnBoot = remember { mutableStateOf(sharedPreferences.getBoolean("update_on_boot", true)) }
|
||||||
val autoRestart = remember { mutableStateOf(getStartOnBoot()) }
|
val autoRestart = viewModel.autoRestart.collectAsState()
|
||||||
val autoUpdate = remember { mutableStateOf(sharedPreferences.getBoolean("auto_update", true)) }
|
val autoUpdate = remember { mutableStateOf(sharedPreferences.getBoolean("auto_update", BuildConfig.auto_update)) }
|
||||||
val sendFirebaseAnalytics = remember { mutableStateOf(sharedPreferences.getBoolean("send_firebase_analytics", true)) }
|
val sendFirebaseAnalytics = remember { mutableStateOf(sharedPreferences.getBoolean("send_firebase_analytics", BuildConfig.send_firebase_analytics)) }
|
||||||
val ipv6 = remember { mutableStateOf(sharedPreferences.getBoolean("ipv6",false)) }
|
val ipv6 = remember { mutableStateOf(sharedPreferences.getBoolean("ipv6",false)) }
|
||||||
val openNoRootDialog = remember { mutableStateOf(false) }
|
val openNoRootDialog = remember { mutableStateOf(false) }
|
||||||
val openNoModuleDialog = remember { mutableStateOf(false) }
|
val openNoModuleDialog = remember { mutableStateOf(false) }
|
||||||
val showAboutDialog = remember { mutableStateOf(false) }
|
val showAboutDialog = remember { mutableStateOf(false) }
|
||||||
val showHostsRepoUrlDialog = remember { mutableStateOf(false) }
|
val showHostsRepoUrlDialog = remember { mutableStateOf(false) }
|
||||||
|
val showIpsetRepoUrlDialog = remember { mutableStateOf(false) }
|
||||||
val showStrategyRepoUrlDialog = remember { mutableStateOf(false) }
|
val showStrategyRepoUrlDialog = remember { mutableStateOf(false) }
|
||||||
val showIPDialog = remember { mutableStateOf(false) }
|
val showIPDialog = remember { mutableStateOf(false) }
|
||||||
val showPortDialog = remember { mutableStateOf(false) }
|
val showPortDialog = remember { mutableStateOf(false) }
|
||||||
@@ -116,6 +117,7 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
|||||||
val showBlackDialog = remember { mutableStateOf(false) }
|
val showBlackDialog = remember { mutableStateOf(false) }
|
||||||
val showAppsListsSheet = remember { mutableStateOf(false) }
|
val showAppsListsSheet = remember { mutableStateOf(false) }
|
||||||
val showSystemApps = remember { mutableStateOf(sharedPreferences.getBoolean("show_system_apps", false)) }
|
val showSystemApps = remember { mutableStateOf(sharedPreferences.getBoolean("show_system_apps", false)) }
|
||||||
|
val showChangeProbeTimeout = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val settingsList = listOf(
|
val settingsList = listOf(
|
||||||
Setting.Section(stringResource(R.string.general_section)),
|
Setting.Section(stringResource(R.string.general_section)),
|
||||||
@@ -123,17 +125,11 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
|||||||
title = stringResource(R.string.btn_use_root),
|
title = stringResource(R.string.btn_use_root),
|
||||||
checked = useModule.value,
|
checked = useModule.value,
|
||||||
onToggle = { isChecked ->
|
onToggle = { isChecked ->
|
||||||
useModule(
|
viewModel.useModule(
|
||||||
context = context,
|
context = context,
|
||||||
checked = isChecked,
|
checked = isChecked,
|
||||||
openNoRootDialog = openNoRootDialog,
|
openNoRootDialog = openNoRootDialog,
|
||||||
openNoModuleDialog = openNoModuleDialog
|
openNoModuleDialog = openNoModuleDialog)
|
||||||
) { success ->
|
|
||||||
if (success) {
|
|
||||||
useModule.value = isChecked
|
|
||||||
if (!isChecked) stopService { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Setting.Toggle(
|
Setting.Toggle(
|
||||||
@@ -167,6 +163,13 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
|||||||
showHostsRepoUrlDialog.value = true
|
showHostsRepoUrlDialog.value = true
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
Setting.Action(
|
||||||
|
title = stringResource(R.string.ipset_repo_url),
|
||||||
|
onClick = {
|
||||||
|
textDialogValue.value = sharedPreferences.getString("ipset_repo_url", "https://raw.githubusercontent.com/CherretGit/zaprett-repo/refs/heads/main/ipsets.json") ?: "https://raw.githubusercontent.com/CherretGit/zaprett-repo/refs/heads/main/ipsets.json"
|
||||||
|
showIpsetRepoUrlDialog.value = true
|
||||||
|
}
|
||||||
|
),
|
||||||
Setting.Action(
|
Setting.Action(
|
||||||
title = stringResource(R.string.btn_repository_url_strategies),
|
title = stringResource(R.string.btn_repository_url_strategies),
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -174,9 +177,7 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
|||||||
showStrategyRepoUrlDialog.value = true
|
showStrategyRepoUrlDialog.value = true
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Setting.Section(
|
Setting.Section(title = stringResource(R.string.shared_section)),
|
||||||
title = stringResource(R.string.shared_section)
|
|
||||||
),
|
|
||||||
Setting.Action(
|
Setting.Action(
|
||||||
title = stringResource(R.string.btn_applist),
|
title = stringResource(R.string.btn_applist),
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -195,6 +196,20 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
|||||||
showBlackDialog.value = true
|
showBlackDialog.value = true
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
Setting.Section(stringResource(R.string.title_selection)),
|
||||||
|
Setting.Action(
|
||||||
|
title = stringResource(R.string.begin_selection),
|
||||||
|
onClick = {
|
||||||
|
navController.navigate("selectionScreen")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Setting.Action(
|
||||||
|
title = stringResource(R.string.change_probe_timeout),
|
||||||
|
onClick = {
|
||||||
|
textDialogValue.value = sharedPreferences.getLong("probe_timeout", 1000L).toString()
|
||||||
|
showChangeProbeTimeout.value = true
|
||||||
|
}
|
||||||
|
),
|
||||||
Setting.Section(stringResource(R.string.byedpi_section)),
|
Setting.Section(stringResource(R.string.byedpi_section)),
|
||||||
Setting.Toggle(
|
Setting.Toggle(
|
||||||
title = stringResource(R.string.btn_ipv6),
|
title = stringResource(R.string.btn_ipv6),
|
||||||
@@ -225,14 +240,12 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
|||||||
showDNSDialog.value = true
|
showDNSDialog.value = true
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Setting.Section(
|
Setting.Section(title = stringResource(R.string.zapret_section)),
|
||||||
title = stringResource(R.string.zapret_section)
|
|
||||||
),
|
|
||||||
Setting.Toggle(
|
Setting.Toggle(
|
||||||
title = stringResource(R.string.btn_autorestart),
|
title = stringResource(R.string.btn_autorestart),
|
||||||
checked = autoRestart.value,
|
checked = autoRestart.value,
|
||||||
onToggle = {
|
onToggle = {
|
||||||
if (handleAutoRestart(context, it)) autoRestart.value = it
|
viewModel.handleAutoRestart(context)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -262,6 +275,11 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
|||||||
editor.putString("hosts_repo_url", it).apply()
|
editor.putString("hosts_repo_url", it).apply()
|
||||||
}, onDismiss = { showHostsRepoUrlDialog.value = false })
|
}, onDismiss = { showHostsRepoUrlDialog.value = false })
|
||||||
}
|
}
|
||||||
|
if (showIpsetRepoUrlDialog.value) {
|
||||||
|
TextDialog(stringResource(R.string.btn_repository_url_ipsets), stringResource(R.string.hint_enter_repository_url_ipsets), textDialogValue.value, onConfirm = {
|
||||||
|
editor.putString("ipsets_repo_url", it).apply()
|
||||||
|
}, onDismiss = { showIpsetRepoUrlDialog.value = false })
|
||||||
|
}
|
||||||
|
|
||||||
if (showStrategyRepoUrlDialog.value) {
|
if (showStrategyRepoUrlDialog.value) {
|
||||||
TextDialog(stringResource(R.string.btn_repository_url_strategies), stringResource(R.string.hint_enter_repository_url_strategies), textDialogValue.value, onConfirm = {
|
TextDialog(stringResource(R.string.btn_repository_url_strategies), stringResource(R.string.hint_enter_repository_url_strategies), textDialogValue.value, onConfirm = {
|
||||||
@@ -323,6 +341,12 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showChangeProbeTimeout.value) {
|
||||||
|
TextDialog(stringResource(R.string.probe_timeout), stringResource(R.string.hint_enter_probe_timeout), textDialogValue.value, onConfirm = {
|
||||||
|
editor.putLong("probe_timeout", it.toLong()).apply()
|
||||||
|
}, onDismiss = { showChangeProbeTimeout.value = false })
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
@@ -470,54 +494,6 @@ private fun ListBottomSheet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun useModule(context: Context, checked: Boolean, openNoRootDialog: MutableState<Boolean>, openNoModuleDialog: MutableState<Boolean>, callback: (Boolean) -> Unit) {
|
|
||||||
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
val editor = sharedPreferences.edit()
|
|
||||||
if (checked) {
|
|
||||||
checkRoot { hasRoot ->
|
|
||||||
if (hasRoot) {
|
|
||||||
checkModuleInstallation { hasModule ->
|
|
||||||
if (hasModule) {
|
|
||||||
editor.putBoolean("use_module", true)
|
|
||||||
.putBoolean("update_on_boot", true)
|
|
||||||
.apply()
|
|
||||||
if (ByeDpiVpnService.status == ServiceStatus.Connected) {
|
|
||||||
context.startService(Intent(context, ByeDpiVpnService::class.java).apply {
|
|
||||||
action = "STOP_VPN"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
editor.remove("lists").apply()
|
|
||||||
editor.remove("active_strategy").apply()
|
|
||||||
editor.remove("applist").apply()
|
|
||||||
editor.remove("whitelist").apply()
|
|
||||||
editor.remove("blacklist").apply()
|
|
||||||
callback(true)
|
|
||||||
} else {
|
|
||||||
openNoModuleDialog.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
openNoRootDialog.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
editor.putBoolean("use_module", false)
|
|
||||||
.putBoolean("update_on_boot", false)
|
|
||||||
.apply()
|
|
||||||
callback(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleAutoRestart(context: Context, checked: Boolean): Boolean {
|
|
||||||
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
return if (sharedPreferences.getBoolean("use_module", false)) {
|
|
||||||
setStartOnBoot(checked)
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
package com.cherret.zaprett.ui.screen
|
||||||
|
|
||||||
|
import android.content.Context.MODE_PRIVATE
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.VpnService
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.Font
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Info
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.cherret.zaprett.R
|
||||||
|
import com.cherret.zaprett.ui.component.StrategySelectionItem
|
||||||
|
import com.cherret.zaprett.ui.viewmodel.StrategySelectionViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun StrategySelectionScreen(navController: NavController, vpnLauncher: ActivityResultLauncher<Intent>, viewModel : StrategySelectionViewModel = viewModel()){
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
val strategyStates = viewModel.strategyStates
|
||||||
|
val context = LocalContext.current
|
||||||
|
val prefs = context.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
var showDialog = remember { mutableStateOf(false) }
|
||||||
|
val requestVpnPermission by viewModel.requestVpnPermission.collectAsState()
|
||||||
|
|
||||||
|
if (showDialog.value) {
|
||||||
|
InfoAlert { showDialog.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(requestVpnPermission) {
|
||||||
|
if (requestVpnPermission) {
|
||||||
|
val intent = VpnService.prepare(context)
|
||||||
|
if (intent != null) {
|
||||||
|
vpnLauncher.launch(intent)
|
||||||
|
} else {
|
||||||
|
viewModel.startVpn()
|
||||||
|
viewModel.clearVpnPermissionRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.title_selection),
|
||||||
|
fontSize = 30.sp,
|
||||||
|
fontFamily = FontFamily(Font(R.font.unbounded, FontWeight.Normal))
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = { navController.popBackStack() }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = stringResource(R.string.btn_back)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
showDialog.value = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Info,
|
||||||
|
contentDescription = "info"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
windowInsets = WindowInsets(0)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
content = { paddingValues ->
|
||||||
|
LazyColumn (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
)
|
||||||
|
{
|
||||||
|
NoHostsCard(viewModel.noHostsCard)
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.viewModelScope.launch {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
context.getString(R.string.begin_selection_snack)
|
||||||
|
)
|
||||||
|
viewModel.performTest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.begin_selection))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when {
|
||||||
|
strategyStates.isEmpty() -> {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillParentMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.empty_list),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
items(strategyStates, key = { it.path }) { item ->
|
||||||
|
StrategySelectionItem(item, prefs, context, snackbarHostState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InfoAlert(onDismiss: () -> Unit) {
|
||||||
|
AlertDialog(
|
||||||
|
title = { Text(text = stringResource(R.string.strategy_selection_info_title)) },
|
||||||
|
text = { Text(text = stringResource(R.string.strategy_selection_info_msg)) },
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(R.string.btn_continue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NoHostsCard(noHostsCard: MutableState<Boolean>) {
|
||||||
|
if (noHostsCard.value) {
|
||||||
|
AlertDialog(
|
||||||
|
title = { Text(text = stringResource(R.string.selection_no_hosts_title)) },
|
||||||
|
text = { Text(text = stringResource(R.string.selection_no_hosts_message)) },
|
||||||
|
onDismissRequest = {
|
||||||
|
noHostsCard.value = false
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { noHostsCard.value = false }) {
|
||||||
|
Text(stringResource(R.string.btn_continue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.cherret.zaprett.ui.viewmodel
|
package com.cherret.zaprett.ui.viewmodel
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
@@ -25,7 +26,8 @@ import kotlinx.coroutines.launch
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(application) {
|
abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
val context = application.applicationContext
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
val context: Context = application.applicationContext
|
||||||
val sharedPreferences: SharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val sharedPreferences: SharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
private val _errorFlow = MutableStateFlow<Throwable?>(null)
|
private val _errorFlow = MutableStateFlow<Throwable?>(null)
|
||||||
val errorFlow: StateFlow<Throwable?> = _errorFlow
|
val errorFlow: StateFlow<Throwable?> = _errorFlow
|
||||||
@@ -59,6 +61,8 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
|
|||||||
when (item.type) {
|
when (item.type) {
|
||||||
ItemType.list -> listType == "whitelist"
|
ItemType.list -> listType == "whitelist"
|
||||||
ItemType.list_exclude -> listType == "blacklist"
|
ItemType.list_exclude -> listType == "blacklist"
|
||||||
|
ItemType.ipset -> listType == "whitelist"
|
||||||
|
ItemType.ipset_exclude -> listType == "blacklist"
|
||||||
ItemType.nfqws -> useModule
|
ItemType.nfqws -> useModule
|
||||||
ItemType.byedpi -> !useModule
|
ItemType.byedpi -> !useModule
|
||||||
}
|
}
|
||||||
@@ -101,6 +105,8 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
|
|||||||
ItemType.nfqws -> File(getZaprettPath(), "strategies/nfqws")
|
ItemType.nfqws -> File(getZaprettPath(), "strategies/nfqws")
|
||||||
ItemType.list -> File(getZaprettPath(), "lists/include")
|
ItemType.list -> File(getZaprettPath(), "lists/include")
|
||||||
ItemType.list_exclude -> File(getZaprettPath(), "lists/exclude")
|
ItemType.list_exclude -> File(getZaprettPath(), "lists/exclude")
|
||||||
|
ItemType.ipset -> File(getZaprettPath(), "ipset/include")
|
||||||
|
ItemType.ipset_exclude -> File(getZaprettPath(), "ipset/exclude")
|
||||||
}
|
}
|
||||||
val targetFile = File(targetDir, uri.lastPathSegment!!)
|
val targetFile = File(targetDir, uri.lastPathSegment!!)
|
||||||
sourceFile.copyTo(targetFile, overwrite = true)
|
sourceFile.copyTo(targetFile, overwrite = true)
|
||||||
@@ -131,6 +137,8 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
|
|||||||
ItemType.nfqws -> File(getZaprettPath(), "strategies/nfqws")
|
ItemType.nfqws -> File(getZaprettPath(), "strategies/nfqws")
|
||||||
ItemType.list -> File(getZaprettPath(), "lists/include")
|
ItemType.list -> File(getZaprettPath(), "lists/include")
|
||||||
ItemType.list_exclude -> File(getZaprettPath(), "lists/exclude")
|
ItemType.list_exclude -> File(getZaprettPath(), "lists/exclude")
|
||||||
|
ItemType.ipset -> File(getZaprettPath(), "ipset/include")
|
||||||
|
ItemType.ipset_exclude -> File(getZaprettPath(), "ipset/exclude")
|
||||||
}
|
}
|
||||||
val targetFile = File(targetDir, uri.lastPathSegment!!)
|
val targetFile = File(targetDir, uri.lastPathSegment!!)
|
||||||
sourceFile.copyTo(targetFile, overwrite = true)
|
sourceFile.copyTo(targetFile, overwrite = true)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.compose.runtime.mutableIntStateOf
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import com.cherret.zaprett.BuildConfig
|
||||||
import com.cherret.zaprett.R
|
import com.cherret.zaprett.R
|
||||||
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||||
import com.cherret.zaprett.data.ServiceStatus
|
import com.cherret.zaprett.data.ServiceStatus
|
||||||
@@ -50,7 +51,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
var nfqwsVer = mutableStateOf(context.getString(R.string.unknown_text))
|
var nfqwsVer = mutableStateOf(context.getString(R.string.unknown_text))
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var byedpiVer = mutableStateOf("0.17.2")
|
var byedpiVer = mutableStateOf("0.17.3")
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var serviceMode = mutableIntStateOf(R.string.service_mode_ciadpi)
|
var serviceMode = mutableIntStateOf(R.string.service_mode_ciadpi)
|
||||||
@@ -71,7 +72,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
var showUpdateDialog = mutableStateOf(false)
|
var showUpdateDialog = mutableStateOf(false)
|
||||||
|
|
||||||
fun checkForUpdate() {
|
fun checkForUpdate() {
|
||||||
if (prefs.getBoolean("auto_update", true)) {
|
if (prefs.getBoolean("auto_update", BuildConfig.auto_update)) {
|
||||||
getUpdate(prefs) {
|
getUpdate(prefs) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
downloadUrl.value = it.downloadUrl.toString()
|
downloadUrl.value = it.downloadUrl.toString()
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.cherret.zaprett.ui.viewmodel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.cherret.zaprett.utils.RepoItemInfo
|
||||||
|
import com.cherret.zaprett.utils.getAllIpsets
|
||||||
|
import com.cherret.zaprett.utils.getAllLists
|
||||||
|
import com.cherret.zaprett.utils.getHostList
|
||||||
|
import com.cherret.zaprett.utils.getIpsetList
|
||||||
|
|
||||||
|
class IpsetRepoViewModel(application: Application): BaseRepoViewModel(application) {
|
||||||
|
override fun getInstalledLists(): Array<String> = getAllIpsets()
|
||||||
|
override fun getRepoList(callback: (Result<List<RepoItemInfo>>) -> Unit) =
|
||||||
|
getIpsetList(sharedPreferences, callback)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.cherret.zaprett.ui.viewmodel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import com.cherret.zaprett.utils.disableIpset
|
||||||
|
import com.cherret.zaprett.utils.disableList
|
||||||
|
import com.cherret.zaprett.utils.enableIpset
|
||||||
|
import com.cherret.zaprett.utils.enableList
|
||||||
|
import com.cherret.zaprett.utils.getActiveExcludeIpsets
|
||||||
|
import com.cherret.zaprett.utils.getActiveExcludeLists
|
||||||
|
import com.cherret.zaprett.utils.getActiveIpsets
|
||||||
|
import com.cherret.zaprett.utils.getAllExcludeIpsets
|
||||||
|
import com.cherret.zaprett.utils.getAllIpsets
|
||||||
|
import com.cherret.zaprett.utils.getHostListMode
|
||||||
|
import com.cherret.zaprett.utils.getStatus
|
||||||
|
import com.cherret.zaprett.utils.setHostListMode
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class IpsetViewModel(application: Application): BaseListsViewModel(application) {
|
||||||
|
private val sharedPreferences = application.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
override fun loadAllItems(): Array<String> =
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") getAllIpsets()
|
||||||
|
else getAllExcludeIpsets()
|
||||||
|
override fun loadActiveItems(): Array<String> =
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") getActiveIpsets(sharedPreferences)
|
||||||
|
else getActiveExcludeIpsets(sharedPreferences)
|
||||||
|
|
||||||
|
override fun deleteItem(item: String, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||||
|
val wasChecked = checked[item] == true
|
||||||
|
disableIpset(item, sharedPreferences)
|
||||||
|
val success = File(item).delete()
|
||||||
|
if (success) refresh()
|
||||||
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
|
getStatus { isEnabled ->
|
||||||
|
if (isEnabled && wasChecked) {
|
||||||
|
snackbarHostState.currentSnackbarData?.dismiss()
|
||||||
|
showRestartSnackbar(context, snackbarHostState, scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCheckedChange(item: String, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||||
|
checked[item] = isChecked
|
||||||
|
if (isChecked) enableIpset(item, sharedPreferences) else disableIpset(item, sharedPreferences)
|
||||||
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
|
getStatus { isEnabled ->
|
||||||
|
if (isEnabled) {
|
||||||
|
snackbarHostState.currentSnackbarData?.dismiss()
|
||||||
|
showRestartSnackbar(
|
||||||
|
context,
|
||||||
|
snackbarHostState,
|
||||||
|
scope
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun setListType(type : String) {
|
||||||
|
setHostListMode(sharedPreferences, type)
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,24 @@
|
|||||||
package com.cherret.zaprett.ui.viewmodel
|
package com.cherret.zaprett.ui.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
import android.content.Context.MODE_PRIVATE
|
import android.content.Context.MODE_PRIVATE
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||||
import com.cherret.zaprett.data.AppListType
|
import com.cherret.zaprett.data.AppListType
|
||||||
|
import com.cherret.zaprett.data.ServiceStatus
|
||||||
import com.cherret.zaprett.utils.addPackageToList
|
import com.cherret.zaprett.utils.addPackageToList
|
||||||
|
import com.cherret.zaprett.utils.checkModuleInstallation
|
||||||
|
import com.cherret.zaprett.utils.checkRoot
|
||||||
import com.cherret.zaprett.utils.getAppList
|
import com.cherret.zaprett.utils.getAppList
|
||||||
|
import com.cherret.zaprett.utils.getStartOnBoot
|
||||||
import com.cherret.zaprett.utils.removePackageFromList
|
import com.cherret.zaprett.utils.removePackageFromList
|
||||||
|
import com.cherret.zaprett.utils.setStartOnBoot
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -24,9 +33,18 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
private val _selectedPackages = MutableStateFlow<Set<String>>(emptySet())
|
private val _selectedPackages = MutableStateFlow<Set<String>>(emptySet())
|
||||||
val selectedPackages: StateFlow<Set<String>> = _selectedPackages.asStateFlow()
|
val selectedPackages: StateFlow<Set<String>> = _selectedPackages.asStateFlow()
|
||||||
private val _currentListType = MutableStateFlow(AppListType.Whitelist)
|
private val _currentListType = MutableStateFlow(AppListType.Whitelist)
|
||||||
|
private val _useModule = MutableStateFlow(false)
|
||||||
|
val useModule: StateFlow<Boolean> = _useModule
|
||||||
|
|
||||||
|
private val _autoRestart = MutableStateFlow(false)
|
||||||
|
val autoRestart: StateFlow<Boolean> = _autoRestart
|
||||||
|
|
||||||
init {
|
init {
|
||||||
refreshApplications()
|
refreshApplications()
|
||||||
|
_useModule.value = context.getSharedPreferences("settings", MODE_PRIVATE).getBoolean("use_module", false)
|
||||||
|
getStartOnBoot(prefs) { value ->
|
||||||
|
_autoRestart.value = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getAppIconBitmap(packageName: String): Drawable? = withContext(Dispatchers.IO) {
|
suspend fun getAppIconBitmap(packageName: String): Drawable? = withContext(Dispatchers.IO) {
|
||||||
@@ -100,4 +118,46 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
refreshApplications()
|
refreshApplications()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun useModule(context: Context, checked: Boolean, openNoRootDialog: MutableState<Boolean>, openNoModuleDialog: MutableState<Boolean>) {
|
||||||
|
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
if (checked) {
|
||||||
|
checkRoot { hasRoot ->
|
||||||
|
if (hasRoot) {
|
||||||
|
checkModuleInstallation { hasModule ->
|
||||||
|
if (hasModule) {
|
||||||
|
editor.putBoolean("use_module", true).apply()
|
||||||
|
if (ByeDpiVpnService.status == ServiceStatus.Connected) {
|
||||||
|
context.startService(Intent(context, ByeDpiVpnService::class.java).apply {
|
||||||
|
action = "STOP_VPN"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
editor.remove("lists").apply()
|
||||||
|
editor.remove("active_strategy").apply()
|
||||||
|
editor.remove("applist").apply()
|
||||||
|
editor.remove("whitelist").apply()
|
||||||
|
editor.remove("blacklist").apply()
|
||||||
|
_useModule.value = true
|
||||||
|
} else {
|
||||||
|
openNoModuleDialog.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openNoRootDialog.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
editor.putBoolean("use_module", false).apply()
|
||||||
|
_useModule.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleAutoRestart(context: Context) {
|
||||||
|
val sharedPreferences = context.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
|
setStartOnBoot(prefs) { value ->
|
||||||
|
_autoRestart.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package com.cherret.zaprett.ui.viewmodel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context.MODE_PRIVATE
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import com.cherret.zaprett.R
|
||||||
|
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||||
|
import com.cherret.zaprett.data.ServiceStatus
|
||||||
|
import com.cherret.zaprett.data.StrategyCheckResult
|
||||||
|
import com.cherret.zaprett.data.StrategyTestingStatus
|
||||||
|
import com.cherret.zaprett.utils.disableStrategy
|
||||||
|
import com.cherret.zaprett.utils.enableStrategy
|
||||||
|
import com.cherret.zaprett.utils.getActiveLists
|
||||||
|
import com.cherret.zaprett.utils.getActiveStrategy
|
||||||
|
import com.cherret.zaprett.utils.getAllStrategies
|
||||||
|
import com.cherret.zaprett.utils.getStatus
|
||||||
|
import com.cherret.zaprett.utils.startService
|
||||||
|
import com.cherret.zaprett.utils.stopService
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import java.io.File
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Proxy
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class StrategySelectionViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
val prefs = application.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val context = application
|
||||||
|
private val _requestVpnPermission = MutableStateFlow(false)
|
||||||
|
val requestVpnPermission = _requestVpnPermission.asStateFlow()
|
||||||
|
val strategyStates = mutableStateListOf<StrategyCheckResult>()
|
||||||
|
var noHostsCard = mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
loadStrategies()
|
||||||
|
checkHosts()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildHttpClient(): OkHttpClient {
|
||||||
|
val builder = OkHttpClient.Builder()
|
||||||
|
.callTimeout(prefs.getLong("probe_timeout", 1000L), TimeUnit.MILLISECONDS)
|
||||||
|
.followRedirects(true)
|
||||||
|
.followSslRedirects(true)
|
||||||
|
if (!prefs.getBoolean("use_module", false)) {
|
||||||
|
val ip = prefs.getString("ip", "127.0.0.1") ?: "127.0.0.1"
|
||||||
|
val port = prefs.getString("port", "1080")?.toIntOrNull() ?: 1080
|
||||||
|
val proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress(ip, port))
|
||||||
|
builder.proxy(proxy)
|
||||||
|
}
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadStrategies() {
|
||||||
|
val strategyList = getAllStrategies(prefs)
|
||||||
|
strategyStates.clear()
|
||||||
|
strategyList.forEach { name ->
|
||||||
|
strategyStates += StrategyCheckResult(
|
||||||
|
path = name,
|
||||||
|
status = StrategyTestingStatus.Waiting,
|
||||||
|
progress = 0f,
|
||||||
|
domains = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun testDomain(domain : String) : Boolean = withContext(Dispatchers.IO) {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("https://${domain}")
|
||||||
|
.build()
|
||||||
|
try {
|
||||||
|
buildHttpClient().newCall(request).execute().use { response ->
|
||||||
|
val body = response.body.byteStream().readBytes()
|
||||||
|
val contentLength = response.body.contentLength()
|
||||||
|
contentLength <= 0 || body.size.toLong() >= contentLength
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun countReachable(index: Int, urls: List<String>): Float = coroutineScope {
|
||||||
|
if (urls.isEmpty()) return@coroutineScope 0f
|
||||||
|
val results: List<String> = urls.map { url ->
|
||||||
|
async { if (testDomain(url)) url else null }
|
||||||
|
}.awaitAll().filterNotNull()
|
||||||
|
strategyStates[index].domains = results
|
||||||
|
(results.size.toFloat() / urls.size.toFloat()).coerceIn(0f, 1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun readActiveListsLines(): List<String> = withContext(Dispatchers.IO) {
|
||||||
|
val result = mutableListOf<String>()
|
||||||
|
getActiveLists(prefs).forEach { path ->
|
||||||
|
runCatching {
|
||||||
|
File(path).useLines { lines ->
|
||||||
|
lines.forEach { line ->
|
||||||
|
result += line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
Log.e("Error", "Occured error when creating list for check")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
suspend fun performTest() {
|
||||||
|
val targets = readActiveListsLines()
|
||||||
|
for (index in strategyStates.indices) {
|
||||||
|
val current = strategyStates[index]
|
||||||
|
strategyStates[index] = current.copy(status = StrategyTestingStatus.Testing)
|
||||||
|
enableStrategy(current.path, prefs)
|
||||||
|
if (prefs.getBoolean("use_module", false)) {
|
||||||
|
getStatus { if (it) stopService {} }
|
||||||
|
startService {}
|
||||||
|
try {
|
||||||
|
val progress = countReachable(index, targets)
|
||||||
|
val old = strategyStates[index]
|
||||||
|
strategyStates[index] = old.copy(
|
||||||
|
progress = progress,
|
||||||
|
status = StrategyTestingStatus.Completed
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
stopService {}
|
||||||
|
disableStrategy(current.path, prefs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (ByeDpiVpnService.status == ServiceStatus.Connected) {
|
||||||
|
context.startService(Intent(context, ByeDpiVpnService::class.java).apply {
|
||||||
|
action = "STOP_VPN"
|
||||||
|
})
|
||||||
|
delay(300L)
|
||||||
|
}
|
||||||
|
_requestVpnPermission.value = true
|
||||||
|
/*context.startService(Intent(context, ByeDpiVpnService::class.java).apply {
|
||||||
|
action = "START_VPN"
|
||||||
|
})*/
|
||||||
|
val connected = withTimeoutOrNull(10_000L) {
|
||||||
|
while (ByeDpiVpnService.status != ServiceStatus.Connected) {
|
||||||
|
delay(100L)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} ?: false
|
||||||
|
if (connected) delay(150L)
|
||||||
|
try {
|
||||||
|
val progress = countReachable(index,targets)
|
||||||
|
val old = strategyStates[index]
|
||||||
|
|
||||||
|
strategyStates[index] = old.copy(
|
||||||
|
progress = progress,
|
||||||
|
status = StrategyTestingStatus.Completed
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
context.startService(Intent(context, ByeDpiVpnService::class.java).apply {
|
||||||
|
action = "STOP_VPN"
|
||||||
|
})
|
||||||
|
delay(200L)
|
||||||
|
disableStrategy(current.path, prefs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val sorted = strategyStates.sortedByDescending { it.progress }
|
||||||
|
strategyStates.clear()
|
||||||
|
strategyStates.addAll(sorted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkHosts() {
|
||||||
|
if (getActiveLists(prefs).isEmpty()) noHostsCard.value = true
|
||||||
|
Log.d("getActiveLists.isEmpty", getActiveLists(prefs).isEmpty().toString())
|
||||||
|
}
|
||||||
|
fun startVpn() {
|
||||||
|
ContextCompat.startForegroundService(context, Intent(context, ByeDpiVpnService::class.java).apply { action = "START_VPN" })
|
||||||
|
}
|
||||||
|
fun clearVpnPermissionRequest() {
|
||||||
|
_requestVpnPermission.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,8 @@ import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
|||||||
import com.cherret.zaprett.data.ServiceStatus
|
import com.cherret.zaprett.data.ServiceStatus
|
||||||
import com.cherret.zaprett.utils.disableStrategy
|
import com.cherret.zaprett.utils.disableStrategy
|
||||||
import com.cherret.zaprett.utils.enableStrategy
|
import com.cherret.zaprett.utils.enableStrategy
|
||||||
import com.cherret.zaprett.utils.getActiveByeDPIStrategies
|
import com.cherret.zaprett.utils.getActiveByeDPIStrategy
|
||||||
import com.cherret.zaprett.utils.getActiveNfqwsStrategies
|
import com.cherret.zaprett.utils.getActiveNfqwsStrategy
|
||||||
import com.cherret.zaprett.utils.getAllByeDPIStrategies
|
import com.cherret.zaprett.utils.getAllByeDPIStrategies
|
||||||
import com.cherret.zaprett.utils.getAllNfqwsStrategies
|
import com.cherret.zaprett.utils.getAllNfqwsStrategies
|
||||||
import com.cherret.zaprett.utils.getStatus
|
import com.cherret.zaprett.utils.getStatus
|
||||||
@@ -84,10 +84,10 @@ interface StrategyProvider {
|
|||||||
|
|
||||||
class NfqwsStrategyProvider : StrategyProvider {
|
class NfqwsStrategyProvider : StrategyProvider {
|
||||||
override fun getAll() = getAllNfqwsStrategies()
|
override fun getAll() = getAllNfqwsStrategies()
|
||||||
override fun getActive() = getActiveNfqwsStrategies()
|
override fun getActive() = getActiveNfqwsStrategy()
|
||||||
}
|
}
|
||||||
|
|
||||||
class ByeDPIStrategyProvider(private val sharedPreferences: SharedPreferences) : StrategyProvider {
|
class ByeDPIStrategyProvider(private val sharedPreferences: SharedPreferences) : StrategyProvider {
|
||||||
override fun getAll() = getAllByeDPIStrategies()
|
override fun getAll() = getAllByeDPIStrategies()
|
||||||
override fun getActive() = getActiveByeDPIStrategies(sharedPreferences)
|
override fun getActive() = getActiveByeDPIStrategy(sharedPreferences)
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,16 @@ fun getHostList(sharedPreferences: SharedPreferences, callback: (Result<List<Rep
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getIpsetList(sharedPreferences: SharedPreferences, callback: (Result<List<RepoItemInfo>>) -> Unit) {
|
||||||
|
getRepo(
|
||||||
|
sharedPreferences.getString(
|
||||||
|
"ipset_repo_url",
|
||||||
|
"https://raw.githubusercontent.com/CherretGit/zaprett-repo/refs/heads/main/ipsets.json"
|
||||||
|
) ?: "https://raw.githubusercontent.com/CherretGit/zaprett-repo/refs/heads/main/ipsets.json",
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun getStrategiesList(sharedPreferences: SharedPreferences, callback: (Result<List<RepoItemInfo>>) -> Unit) {
|
fun getStrategiesList(sharedPreferences: SharedPreferences, callback: (Result<List<RepoItemInfo>>) -> Unit) {
|
||||||
getRepo(
|
getRepo(
|
||||||
sharedPreferences.getString(
|
sharedPreferences.getString(
|
||||||
|
|||||||
@@ -64,39 +64,20 @@ fun getConfigFile(): File {
|
|||||||
return File(Environment.getExternalStorageDirectory(), "zaprett/config")
|
return File(Environment.getExternalStorageDirectory(), "zaprett/config")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setStartOnBoot(startOnBoot: Boolean) {
|
fun setStartOnBoot(prefs : SharedPreferences, callback: (Boolean) -> Unit) {
|
||||||
val configFile = getConfigFile()
|
if (prefs.getBoolean("use_module", false)) {
|
||||||
if (configFile.exists()) {
|
Shell.cmd("zaprett autostart").submit { result ->
|
||||||
val props = Properties()
|
if (result.out.isNotEmpty() && result.out.toString().contains("true")) callback(true) else callback(false)
|
||||||
try {
|
|
||||||
FileInputStream(configFile).use { input ->
|
|
||||||
props.load(input)
|
|
||||||
}
|
|
||||||
props.setProperty("start_on_boot", startOnBoot.toString())
|
|
||||||
FileOutputStream(configFile).use { output ->
|
|
||||||
props.store(output, "Don't place '/' in end of directory! Example: /sdcard")
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
throw RuntimeException(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStartOnBoot(): Boolean {
|
fun getStartOnBoot(prefs : SharedPreferences, callback: (Boolean) -> Unit) {
|
||||||
val configFile = getConfigFile()
|
if (prefs.getBoolean("use_module", false)) {
|
||||||
val props = Properties()
|
Shell.cmd("zaprett get-autostart").submit { result ->
|
||||||
return try {
|
if (result.out.isNotEmpty() && result.out.toString().contains("true")) callback(true) else callback(false)
|
||||||
if (configFile.exists()) {
|
|
||||||
FileInputStream(configFile).use { input ->
|
|
||||||
props.load(input)
|
|
||||||
}
|
|
||||||
props.getProperty("start_on_boot", "false").toBoolean()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
} catch (_: IOException) {
|
} else { callback(false) }
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getZaprettPath(): String {
|
fun getZaprettPath(): String {
|
||||||
@@ -117,7 +98,15 @@ fun getZaprettPath(): String {
|
|||||||
|
|
||||||
fun getAllLists(): Array<String> {
|
fun getAllLists(): Array<String> {
|
||||||
val listsDir = File("${getZaprettPath()}/lists/include")
|
val listsDir = File("${getZaprettPath()}/lists/include")
|
||||||
return listsDir.listFiles { file -> file.isFile }
|
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||||
|
?.map { it.absolutePath }
|
||||||
|
?.toTypedArray()
|
||||||
|
?: emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllIpsets(): Array<String> {
|
||||||
|
val listsDir = File("${getZaprettPath()}/ipset/include")
|
||||||
|
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||||
?.map { it.absolutePath }
|
?.map { it.absolutePath }
|
||||||
?.toTypedArray()
|
?.toTypedArray()
|
||||||
?: emptyArray()
|
?: emptyArray()
|
||||||
@@ -125,7 +114,15 @@ fun getAllLists(): Array<String> {
|
|||||||
|
|
||||||
fun getAllExcludeLists(): Array<String> {
|
fun getAllExcludeLists(): Array<String> {
|
||||||
val listsDir = File("${getZaprettPath()}/lists/exclude/")
|
val listsDir = File("${getZaprettPath()}/lists/exclude/")
|
||||||
return listsDir.listFiles { file -> file.isFile }
|
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||||
|
?.map { it.absolutePath }
|
||||||
|
?.toTypedArray()
|
||||||
|
?: emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllExcludeIpsets(): Array<String> {
|
||||||
|
val listsDir = File("${getZaprettPath()}/ipset/exclude/")
|
||||||
|
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||||
?.map { it.absolutePath }
|
?.map { it.absolutePath }
|
||||||
?.toTypedArray()
|
?.toTypedArray()
|
||||||
?: emptyArray()
|
?: emptyArray()
|
||||||
@@ -133,7 +130,7 @@ fun getAllExcludeLists(): Array<String> {
|
|||||||
|
|
||||||
fun getAllNfqwsStrategies(): Array<String> {
|
fun getAllNfqwsStrategies(): Array<String> {
|
||||||
val listsDir = File("${getZaprettPath()}/strategies/nfqws")
|
val listsDir = File("${getZaprettPath()}/strategies/nfqws")
|
||||||
return listsDir.listFiles { file -> file.isFile }
|
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||||
?.map { it.absolutePath }
|
?.map { it.absolutePath }
|
||||||
?.toTypedArray()
|
?.toTypedArray()
|
||||||
?: emptyArray()
|
?: emptyArray()
|
||||||
@@ -141,12 +138,17 @@ fun getAllNfqwsStrategies(): Array<String> {
|
|||||||
|
|
||||||
fun getAllByeDPIStrategies(): Array<String> {
|
fun getAllByeDPIStrategies(): Array<String> {
|
||||||
val listsDir = File("${getZaprettPath()}/strategies/byedpi")
|
val listsDir = File("${getZaprettPath()}/strategies/byedpi")
|
||||||
return listsDir.listFiles { file -> file.isFile }
|
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||||
?.map { it.absolutePath }
|
?.map { it.absolutePath }
|
||||||
?.toTypedArray()
|
?.toTypedArray()
|
||||||
?: emptyArray()
|
?: emptyArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllStrategies(sharedPreferences : SharedPreferences) : Array<String> {
|
||||||
|
return if (sharedPreferences.getBoolean("use_module", false)) getAllNfqwsStrategies()
|
||||||
|
else getAllByeDPIStrategies()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getActiveLists(sharedPreferences: SharedPreferences): Array<String> {
|
fun getActiveLists(sharedPreferences: SharedPreferences): Array<String> {
|
||||||
if (sharedPreferences.getBoolean("use_module", false)) {
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
@@ -171,6 +173,27 @@ fun getActiveLists(sharedPreferences: SharedPreferences): Array<String> {
|
|||||||
return sharedPreferences.getStringSet("lists", emptySet())?.toTypedArray() ?: emptyArray()
|
return sharedPreferences.getStringSet("lists", emptySet())?.toTypedArray() ?: emptyArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun getActiveIpsets(sharedPreferences: SharedPreferences): Array<String> {
|
||||||
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
|
val configFile = getConfigFile()
|
||||||
|
if (configFile.exists()) {
|
||||||
|
val props = Properties()
|
||||||
|
return try {
|
||||||
|
FileInputStream(configFile).use { input ->
|
||||||
|
props.load(input)
|
||||||
|
}
|
||||||
|
val activeLists = props.getProperty("active_ipsets", "")
|
||||||
|
Log.d("Active ipsets", activeLists)
|
||||||
|
if (activeLists.isNotEmpty()) activeLists.split(",")
|
||||||
|
.toTypedArray() else emptyArray()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emptyArray()
|
||||||
|
}
|
||||||
|
else return sharedPreferences.getStringSet("ipsets", emptySet())?.toTypedArray() ?: emptyArray()
|
||||||
|
}
|
||||||
fun getActiveExcludeLists(sharedPreferences: SharedPreferences): Array<String> {
|
fun getActiveExcludeLists(sharedPreferences: SharedPreferences): Array<String> {
|
||||||
if (sharedPreferences.getBoolean("use_module", false)) {
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
val configFile = getConfigFile()
|
val configFile = getConfigFile()
|
||||||
@@ -194,7 +217,29 @@ fun getActiveExcludeLists(sharedPreferences: SharedPreferences): Array<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getActiveNfqwsStrategies(): Array<String> {
|
fun getActiveExcludeIpsets(sharedPreferences: SharedPreferences): Array<String> {
|
||||||
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
|
val configFile = getConfigFile()
|
||||||
|
if (configFile.exists()) {
|
||||||
|
val props = Properties()
|
||||||
|
return try {
|
||||||
|
FileInputStream(configFile).use { input ->
|
||||||
|
props.load(input)
|
||||||
|
}
|
||||||
|
val activeLists = props.getProperty("active_exclude_ipsets", "")
|
||||||
|
Log.d("Active ipsets", activeLists)
|
||||||
|
if (activeLists.isNotEmpty()) activeLists.split(",")
|
||||||
|
.toTypedArray() else emptyArray()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emptyArray()
|
||||||
|
}
|
||||||
|
else return sharedPreferences.getStringSet("exclude_ipsets", emptySet())?.toTypedArray() ?: emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getActiveNfqwsStrategy(): Array<String> {
|
||||||
val configFile = File("${getZaprettPath()}/config")
|
val configFile = File("${getZaprettPath()}/config")
|
||||||
if (configFile.exists()) {
|
if (configFile.exists()) {
|
||||||
val props = Properties()
|
val props = Properties()
|
||||||
@@ -212,7 +257,7 @@ fun getActiveNfqwsStrategies(): Array<String> {
|
|||||||
return emptyArray()
|
return emptyArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getActiveByeDPIStrategies(sharedPreferences: SharedPreferences): Array<String> {
|
fun getActiveByeDPIStrategy(sharedPreferences: SharedPreferences): Array<String> {
|
||||||
val path = sharedPreferences.getString("active_strategy", "")
|
val path = sharedPreferences.getString("active_strategy", "")
|
||||||
if (!path.isNullOrBlank() && File(path).exists()) {
|
if (!path.isNullOrBlank() && File(path).exists()) {
|
||||||
return arrayOf(path)
|
return arrayOf(path)
|
||||||
@@ -220,7 +265,7 @@ fun getActiveByeDPIStrategies(sharedPreferences: SharedPreferences): Array<Strin
|
|||||||
return emptyArray()
|
return emptyArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getActiveStrategy(sharedPreferences: SharedPreferences): List<String> {
|
fun getActiveByeDPIStrategyContent(sharedPreferences: SharedPreferences): List<String> {
|
||||||
val path = sharedPreferences.getString("active_strategy", "")
|
val path = sharedPreferences.getString("active_strategy", "")
|
||||||
if (!path.isNullOrBlank() && File(path).exists()) {
|
if (!path.isNullOrBlank() && File(path).exists()) {
|
||||||
return File(path).readLines()
|
return File(path).readLines()
|
||||||
@@ -228,6 +273,12 @@ fun getActiveStrategy(sharedPreferences: SharedPreferences): List<String> {
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getActiveStrategy(sharedPreferences: SharedPreferences): Array<String> {
|
||||||
|
return if (sharedPreferences.getBoolean("use_module", false)) getActiveNfqwsStrategy()
|
||||||
|
else getActiveByeDPIStrategy(sharedPreferences)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fun enableList(path: String, sharedPreferences: SharedPreferences) {
|
fun enableList(path: String, sharedPreferences: SharedPreferences) {
|
||||||
if (sharedPreferences.getBoolean("use_module", false)) {
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
val configFile = getConfigFile()
|
val configFile = getConfigFile()
|
||||||
@@ -272,7 +323,50 @@ fun enableList(path: String, sharedPreferences: SharedPreferences) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun enableIpset(path: String, sharedPreferences: SharedPreferences) {
|
||||||
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
|
val configFile = getConfigFile()
|
||||||
|
try {
|
||||||
|
val props = Properties()
|
||||||
|
if (configFile.exists()) {
|
||||||
|
FileInputStream(configFile).use { input ->
|
||||||
|
props.load(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val activeLists = props.getProperty(
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") "active_ipsets"
|
||||||
|
else "active_exclude_ipsets",
|
||||||
|
"")
|
||||||
|
.split(",")
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.toMutableList()
|
||||||
|
if (path !in activeLists) {
|
||||||
|
activeLists.add(path)
|
||||||
|
}
|
||||||
|
props.setProperty(
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") "active_ipsets"
|
||||||
|
else "active_exclude_ipsets",
|
||||||
|
activeLists.joinToString(",")
|
||||||
|
)
|
||||||
|
FileOutputStream(configFile).use { output ->
|
||||||
|
props.store(output, "Don't place '/' in end of directory! Example: /sdcard")
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val currentSet = sharedPreferences.getStringSet(
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") "ipsets"
|
||||||
|
else "exclude_ipsets", emptySet())?.toMutableSet() ?: mutableSetOf()
|
||||||
|
if (path !in currentSet) {
|
||||||
|
currentSet.add(path)
|
||||||
|
sharedPreferences.edit { putStringSet(
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") "ipsets"
|
||||||
|
else "exclude_ipsets", currentSet) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fun enableStrategy(path: String, sharedPreferences: SharedPreferences) {
|
fun enableStrategy(path: String, sharedPreferences: SharedPreferences) {
|
||||||
if (sharedPreferences.getBoolean("use_module", false)) {
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
val props = Properties()
|
val props = Properties()
|
||||||
@@ -354,6 +448,57 @@ fun disableList(path: String, sharedPreferences: SharedPreferences) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun disableIpset(path: String, sharedPreferences: SharedPreferences) {
|
||||||
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
|
val props = Properties()
|
||||||
|
val configFile = getConfigFile()
|
||||||
|
try {
|
||||||
|
if (configFile.exists()) {
|
||||||
|
FileInputStream(configFile).use { input ->
|
||||||
|
props.load(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val activeLists = props.getProperty(
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") "active_ipsets"
|
||||||
|
else "active_exclude_ipsets",
|
||||||
|
"")
|
||||||
|
.split(",")
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.toMutableList()
|
||||||
|
if (path in activeLists) {
|
||||||
|
activeLists.remove(path)
|
||||||
|
}
|
||||||
|
props.setProperty(
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") "active_ipsets"
|
||||||
|
else "active_exclude_ipsets",
|
||||||
|
activeLists.joinToString(",")
|
||||||
|
)
|
||||||
|
FileOutputStream(configFile).use { output ->
|
||||||
|
props.store(output, "Don't place '/' in end of directory! Example: /sdcard")
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val currentSet = sharedPreferences.getStringSet(
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") "ipsets"
|
||||||
|
else "exclude_ipsets", emptySet())?.toMutableSet() ?: mutableSetOf()
|
||||||
|
if (path in currentSet) {
|
||||||
|
currentSet.remove(path)
|
||||||
|
sharedPreferences.edit { putStringSet(
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") "ipsets"
|
||||||
|
else "exclude_ipsets", currentSet) }
|
||||||
|
}
|
||||||
|
if (currentSet.isEmpty()) {
|
||||||
|
sharedPreferences.edit { remove(
|
||||||
|
if (getHostListMode(sharedPreferences) == "whitelist") "ipsets"
|
||||||
|
else "exclude_ipsets"
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun disableStrategy(path: String, sharedPreferences: SharedPreferences) {
|
fun disableStrategy(path: String, sharedPreferences: SharedPreferences) {
|
||||||
if (sharedPreferences.getBoolean("use_module", false)) {
|
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||||
val props = Properties()
|
val props = Properties()
|
||||||
@@ -580,7 +725,7 @@ fun getAppsListMode(prefs : SharedPreferences) : String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return prefs.getString("applist", "")!!
|
return prefs.getString("app_list", "none")!!
|
||||||
}
|
}
|
||||||
return "none"
|
return "none"
|
||||||
}
|
}
|
||||||
@@ -608,7 +753,7 @@ fun setAppsListMode(prefs: SharedPreferences, mode: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
prefs.edit { putString("app-list", mode) }
|
prefs.edit { putString("app_list", mode) }
|
||||||
}
|
}
|
||||||
Log.d("App List", "Changed to $mode")
|
Log.d("App List", "Changed to $mode")
|
||||||
}
|
}
|
||||||
@@ -99,4 +99,22 @@
|
|||||||
<string name="btn_copy_log">Скопировать лог</string>
|
<string name="btn_copy_log">Скопировать лог</string>
|
||||||
<string name="log_copied">Лог скопирован</string>
|
<string name="log_copied">Лог скопирован</string>
|
||||||
<string name="download_error">Произошла ошибка скачивания, пожалуйста, сообщите о ней разработчикам</string>
|
<string name="download_error">Произошла ошибка скачивания, пожалуйста, сообщите о ней разработчикам</string>
|
||||||
|
<string name="ipset_repo_url">URL репозитория ipset</string>
|
||||||
|
<string name="title_selection">Подбор стратегии</string>
|
||||||
|
<string name="btn_repository_url_ipsets">URL репозитория ipset</string>
|
||||||
|
<string name="hint_enter_repository_url_ipsets">Введите URL репозитория ipset</string>
|
||||||
|
<string name="strategy_status_waiting">ожидание...</string>
|
||||||
|
<string name="strategy_status_testing">проверка...</string>
|
||||||
|
<string name="strategy_status_tested">проверка завершена</string>
|
||||||
|
<string name="strategy_applied">Стратегия применена</string>
|
||||||
|
<string name="begin_selection">Начать подбор</string>
|
||||||
|
<string name="begin_selection_snack">Начат подбор стратегии. Не закрывайте эту вкладку</string>
|
||||||
|
<string name="change_probe_timeout">Изменить таймаут пробы</string>
|
||||||
|
<string name="probe_timeout">Таймаут пробы</string>
|
||||||
|
<string name="hint_enter_probe_timeout">Введите таймаут пробы</string>
|
||||||
|
<string name="strategy_selection_info_title">Информация</string>
|
||||||
|
<string name="strategy_selection_info_msg">"В этом разделе настроек приложения представлен перебор стратегий\n Подбор проходит среди скачанных стратегий, поэтому заранее скачайте из репозитория или добавьте из файловой системы интересующие вас стратегии для сравнения. \n Перед началом так же выберете один или несколько листов доменов на вкладке \"Листы\", затем нажмите на \"Начать подбор\". Не используйте для перебора списки с большим количеством доменов."</string>
|
||||||
|
<string name="selection_no_hosts_title">Нет активных листов</string>
|
||||||
|
<string name="selection_no_hosts_message">Не обнаружено активных списков хостов, включите один или несколько, иначе подбор не сработает</string>
|
||||||
|
<string name="selection_available_domains">Доступные домены</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -103,4 +103,23 @@
|
|||||||
<string name="btn_copy_log">Copy log</string>
|
<string name="btn_copy_log">Copy log</string>
|
||||||
<string name="log_copied">Log copied</string>
|
<string name="log_copied">Log copied</string>
|
||||||
<string name="download_error">Occurred a file download error, please report to developers</string>
|
<string name="download_error">Occurred a file download error, please report to developers</string>
|
||||||
|
<string name="ipset_repo_url">Ipset repository URL</string>
|
||||||
|
<string name="title_ipset" translatable="false">Ipset</string>
|
||||||
|
<string name="title_selection">Strategy selection</string>
|
||||||
|
<string name="btn_repository_url_ipsets">Ipsets repository url</string>
|
||||||
|
<string name="hint_enter_repository_url_ipsets">Enter ipsets repository URL</string>
|
||||||
|
<string name="strategy_status_waiting">waiting...</string>
|
||||||
|
<string name="strategy_status_testing">testing...</string>
|
||||||
|
<string name="strategy_status_tested">testing ended</string>
|
||||||
|
<string name="strategy_applied">Strategy applied</string>
|
||||||
|
<string name="begin_selection">Begin selection</string>
|
||||||
|
<string name="begin_selection_snack">Strategy selection has begun. Please keep this tab open.</string>
|
||||||
|
<string name="change_probe_timeout">Change probe timeout</string>
|
||||||
|
<string name="probe_timeout">Probe timeout</string>
|
||||||
|
<string name="hint_enter_probe_timeout">Enter probe timeout</string>
|
||||||
|
<string name="strategy_selection_info_title">Tip</string>
|
||||||
|
<string name="strategy_selection_info_msg">This section of the application settings allows you to iterate through strategies.\n The selection is based on downloaded strategies, so download the strategies you\'re interested in from the repository or add them from the file system for comparison.\n Before starting, select one or more domain lists in the \"Lists\" tab, then click \"Start selection\". Avoid using lists with a large number of domains.</string>
|
||||||
|
<string name="selection_no_hosts_title">No active hosts</string>
|
||||||
|
<string name="selection_no_hosts_message">No active host lists found, please enable one or more, otherwise the selection will not work</string>
|
||||||
|
<string name="selection_available_domains">Available domains</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
1. Обработка ошибок связанных с репозиторием(приложение теперь не будет вылетать)
|
1) Добавлена поддержка ipset
|
||||||
2. Исправление ошибок интерфейса
|
2) JNI **полностью переписан** на Rust
|
||||||
3. Прочие мелкие исправления
|
3) Добавлен подбор стратегии
|
||||||
|
4) Исправлены баги
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.12.2"
|
agp = "8.13.0"
|
||||||
kotlin = "2.2.10"
|
kotlin = "2.2.10"
|
||||||
coreKtx = "1.17.0"
|
coreKtx = "1.17.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ project(byedpi_native)
|
|||||||
file(GLOB BYE_DPI_SRC byedpi/*.c)
|
file(GLOB BYE_DPI_SRC byedpi/*.c)
|
||||||
list(REMOVE_ITEM BYE_DPI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/byedpi/win_service.c)
|
list(REMOVE_ITEM BYE_DPI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/byedpi/win_service.c)
|
||||||
|
|
||||||
add_library(byedpi SHARED ${BYE_DPI_SRC} native-lib.c utils.c)
|
add_library(byedpi STATIC ${BYE_DPI_SRC})
|
||||||
target_include_directories(byedpi PRIVATE byedpi)
|
target_include_directories(byedpi PRIVATE byedpi)
|
||||||
|
|
||||||
target_compile_options(byedpi PRIVATE -std=c99 -O2 -Wall -Wno-unused -Wextra -Wno-unused-parameter -pedantic)
|
target_compile_options(byedpi PRIVATE -std=c99 -O2 -Wall -Wno-unused -Wextra -Wno-unused-parameter -pedantic)
|
||||||
349
rust/Cargo.lock
generated
Normal file
349
rust/Cargo.lock
generated
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_log-sys"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_logger"
|
||||||
|
version = "0.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3"
|
||||||
|
dependencies = [
|
||||||
|
"android_log-sys",
|
||||||
|
"env_filter",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byedpi"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"android_logger",
|
||||||
|
"cmake",
|
||||||
|
"jni",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cesu8"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmake"
|
||||||
|
version = "0.1.54"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "combine"
|
||||||
|
version = "4.6.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_filter"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni"
|
||||||
|
version = "0.21.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
|
||||||
|
dependencies = [
|
||||||
|
"cesu8",
|
||||||
|
"cfg-if",
|
||||||
|
"combine",
|
||||||
|
"jni-sys",
|
||||||
|
"log",
|
||||||
|
"thiserror",
|
||||||
|
"walkdir",
|
||||||
|
"windows-sys 0.45.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni-sys"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.176"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.101"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.45.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||||
17
rust/Cargo.toml
Normal file
17
rust/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "byedpi"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
jni = "0.21.1"
|
||||||
|
libc = "0.2.0"
|
||||||
|
android_logger = "0.15.1"
|
||||||
|
log = "0.4"
|
||||||
|
once_cell = "1.21.3"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cmake = "0.1.49"
|
||||||
8
rust/build.rs
Normal file
8
rust/build.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use cmake::Config;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let dst = Config::new(".").build_target("byedpi").build();
|
||||||
|
let lib_path = dst.join("build");
|
||||||
|
println!("cargo:rustc-link-search=native={}", lib_path.display());
|
||||||
|
println!("cargo:rustc-link-lib=static=byedpi");
|
||||||
|
}
|
||||||
1
rust/byedpi
Submodule
1
rust/byedpi
Submodule
Submodule rust/byedpi added at 7efde1b129
74
rust/src/lib.rs
Normal file
74
rust/src/lib.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
use android_logger::Config;
|
||||||
|
use jni::JNIEnv;
|
||||||
|
use jni::objects::{JClass, JObjectArray, JString};
|
||||||
|
use jni::sys::jint;
|
||||||
|
use libc::{SHUT_RDWR, shutdown};
|
||||||
|
use log::{LevelFilter, info};
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
static PROXY_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
#[link(name = "byedpi", kind = "static")]
|
||||||
|
unsafe extern "C" {
|
||||||
|
static mut server_fd: i32;
|
||||||
|
static mut optind: i32;
|
||||||
|
static mut optreset: i32;
|
||||||
|
fn main(argc: libc::c_int, argv: *const *const c_char) -> libc::c_int;
|
||||||
|
fn clear_params();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_logger() {
|
||||||
|
android_logger::init_once(Config::default().with_max_level(LevelFilter::Info));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "system" fn Java_com_cherret_zaprett_byedpi_NativeBridge_jniStartProxy(
|
||||||
|
mut env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
args: JObjectArray,
|
||||||
|
) -> jint {
|
||||||
|
init_logger();
|
||||||
|
if PROXY_RUNNING.swap(true, Ordering::SeqCst) {
|
||||||
|
info!("proxy already running");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
let argc = env.get_array_length(&args).unwrap_or(0) as usize;
|
||||||
|
let mut cstrings: Vec<CString> = Vec::with_capacity(argc);
|
||||||
|
for i in 0..argc {
|
||||||
|
let jstr: JString = env
|
||||||
|
.get_object_array_element(&args, i as i32)
|
||||||
|
.unwrap()
|
||||||
|
.into();
|
||||||
|
let rust_str: String = env.get_string(&jstr).unwrap().into();
|
||||||
|
cstrings.push(CString::new(rust_str).unwrap());
|
||||||
|
}
|
||||||
|
let mut argv: Vec<*const c_char> = cstrings.iter().map(|s| s.as_ptr()).collect();
|
||||||
|
argv.push(std::ptr::null());
|
||||||
|
info!("starting proxy");
|
||||||
|
unsafe {
|
||||||
|
optind = 1;
|
||||||
|
optreset = 1;
|
||||||
|
clear_params();
|
||||||
|
}
|
||||||
|
PROXY_RUNNING.store(true, Ordering::SeqCst);
|
||||||
|
let ret = unsafe { main(argc as i32, argv.as_ptr()) };
|
||||||
|
PROXY_RUNNING.store(false, Ordering::SeqCst);
|
||||||
|
ret as jint
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "system" fn Java_com_cherret_zaprett_byedpi_NativeBridge_jniStopProxy(
|
||||||
|
_env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
) -> jint {
|
||||||
|
init_logger();
|
||||||
|
if !PROXY_RUNNING.load(Ordering::SeqCst) {
|
||||||
|
info!("failed to stop proxy");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
info!("stopping proxy");
|
||||||
|
let ret = unsafe { shutdown(server_fd, SHUT_RDWR) };
|
||||||
|
ret as jint
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "2.8",
|
"version": "2.10",
|
||||||
"versionCode": 20,
|
"versionCode": 22,
|
||||||
"downloadUrl": "https://github.com/CherretGit/zaprett-app/releases/download/2.8.0/app-release.apk",
|
"downloadUrl": "https://github.com/CherretGit/zaprett-app/releases/download/2.10.0/app-release.apk",
|
||||||
"changelogUrl": "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/changelog.md"
|
"changelogUrl": "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/changelog.md"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user