diff --git a/Cargo.lock b/Cargo.lock
index 4b21695..fc42871 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3961,7 +3961,8 @@ dependencies = [
[[package]]
name = "tun2proxy"
version = "0.7.21"
-source = "git+https://github.com/yyoyoian-pixel/tun2proxy?branch=feat%2Fudpgw-jni-param#dfc24ed12cdee69987bdd321ea55c6b940f2d0f0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d336ad07beb04a9e219972fcdc54a71d2586cdfd35ac03551a629e4ca328db3c"
dependencies = [
"android_logger",
"async-trait",
diff --git a/Cargo.toml b/Cargo.toml
index 2ef3b1f..003d362 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -100,12 +100,6 @@ tun2proxy = { version = "0.7", default-features = false, features = ["udpgw"] }
# Used in mitm tests to sanity-check the cert extensions we emit.
x509-parser = "0.16"
-# Temporary patch: adds udpgw_server parameter to the Android JNI run()
-# function. Upstream PR: https://github.com/tun2proxy/tun2proxy/pull/247
-# Remove this section once tun2proxy >= 0.8 ships with the change.
-[patch.crates-io]
-tun2proxy = { git = "https://github.com/yyoyoian-pixel/tun2proxy", branch = "feat/udpgw-jni-param" }
-
[profile.release]
panic = "abort"
codegen-units = 1
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 6f64f46..4362ad1 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -136,6 +136,10 @@ dependencies {
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material-icons-extended")
+ // QR code generation + scanning (self-contained, no ML Kit needed).
+ implementation("com.google.zxing:core:3.5.3")
+ implementation("com.journeyapps:zxing-android-embedded:4.3.0")
+
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index dd2e94e..4d74ca5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -53,8 +53,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
%1$d lines
+
+ Paste config from clipboard
+ Export config
+ Show QR code
+ Scan QR code
+ Copy to clipboard
+ Config imported
+ Config copied to clipboard
+ Invalid config in clipboard
+ Export config
+ This includes your auth_key. Only share with people you trust.
+ Import config?
+ This will replace your current settings.
+ Camera permission needed to scan QR codes
+
google_ip updated to %1$s
google_ip already current (%1$s)
diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..1e63d10
--- /dev/null
+++ b/android/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/android_jni.rs b/src/android_jni.rs
index 6f467be..6bb5a97 100644
--- a/src/android_jni.rs
+++ b/src/android_jni.rs
@@ -482,3 +482,53 @@ pub extern "system" fn Java_com_therealaleph_mhrv_Native_statsJson<'a>(
}));
env.new_string(out).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
}
+
+// ---------------------------------------------------------------------------
+// tun2proxy CLI API wrapper (dlsym — no fork or patch needed)
+// ---------------------------------------------------------------------------
+
+/// `Native.runTun2proxy(cliArgs, tunMtu)` -> int
+///
+/// Calls `tun2proxy_run_with_cli_args` from libtun2proxy.so via dlsym.
+/// This is the C API the tun2proxy maintainer recommends for callers that
+/// need full CLI flexibility (e.g. --udpgw-server). BLOCKS until shutdown.
+#[no_mangle]
+pub extern "system" fn Java_com_therealaleph_mhrv_Native_runTun2proxy<'a>(
+ mut env: JNIEnv<'a>,
+ _class: JClass,
+ cli_args: JString,
+ tun_mtu: jni::sys::jint,
+) -> jni::sys::jint {
+ safe(-1, AssertUnwindSafe(|| {
+ let args_str = jstring_to_string(&mut env, &cli_args);
+ tracing::info!("runTun2proxy: cli={}", args_str);
+
+ unsafe {
+ use std::ffi::{CStr, CString};
+
+ let lib = CString::new("libtun2proxy.so").unwrap();
+ let handle = libc::dlopen(lib.as_ptr(), libc::RTLD_NOW);
+ if handle.is_null() {
+ let err = CStr::from_ptr(libc::dlerror());
+ tracing::error!("dlopen libtun2proxy.so failed: {:?}", err);
+ return -10;
+ }
+
+ let sym = CString::new("tun2proxy_run_with_cli_args").unwrap();
+ let func = libc::dlsym(handle, sym.as_ptr());
+ if func.is_null() {
+ let err = CStr::from_ptr(libc::dlerror());
+ tracing::error!("dlsym tun2proxy_run_with_cli_args: {:?}", err);
+ libc::dlclose(handle);
+ return -11;
+ }
+
+ type RunFn = unsafe extern "C" fn(*const std::ffi::c_char, u16, bool) -> i32;
+ let run: RunFn = std::mem::transmute(func);
+ let c_args = CString::new(args_str).unwrap();
+ let rc = run(c_args.as_ptr(), tun_mtu as u16, false);
+ libc::dlclose(handle);
+ rc
+ }
+ }))
+}