wip: sing-box module

This commit is contained in:
luvletter2333 2023-03-16 01:18:00 +08:00
parent d00e4efb30
commit b00eadcdbb
No known key found for this signature in database
GPG Key ID: 9EB7723F3A0ACF92
29 changed files with 1158 additions and 0 deletions

3
.gitmodules vendored
View File

@ -40,3 +40,6 @@
[submodule "v2ray"]
path = v2ray
url = https://github.com/nekohasekai/AndroidLibV2rayLite
[submodule "nekox-sing-box/sing-box"]
path = nekox-sing-box/sing-box
url = https://github.com/SagerNet/sing-box.git

View File

@ -0,0 +1,53 @@
package tw.nekomimi.nekogram.proxynext
import android.widget.Toast
import org.json.JSONObject
import org.telegram.messenger.ApplicationLoader
import org.telegram.messenger.FileLog
import org.telegram.messenger.LocaleController
import org.telegram.messenger.R
import java.lang.Exception
object ProxyConfig {
@JvmStatic
fun parseSingBoxConfig(url: String): BoxProxy? {
try {
if (url.startsWith(VMESS_PROTOCOL) || url.startsWith(VMESS1_PROTOCOL)) {
return VMessBean().also { it.parseFromLink(url) }
} else if (url.startsWith(SS_PROTOCOL)) {
return ShadowsocksBean().also { it.parseFromLink(url) }
} else if (url.startsWith(SSR_PROTOCOL)) {
return ShadowsocksRBean().also { it.parseFromLink(url) }
} else if (url.startsWith(TROJAN_PROTOCOL)) {
return TrojanBean().also { it.parseFromLink(url) }
}
return null
} catch (ex: Exception) {
FileLog.e(ex);
Toast.makeText(ApplicationLoader.applicationContext,
LocaleController.getString("UnsupportedProxy", R.string.UnsupportedProxy),
Toast.LENGTH_LONG).show()
return null
}
}
const val VMESS_PROTOCOL: String = "vmess://"
const val VMESS1_PROTOCOL = "vmess1://"
const val SS_PROTOCOL: String = "ss://"
const val SSR_PROTOCOL: String = "ssr://"
const val TROJAN_PROTOCOL: String = "trojan://"
const val WS_PROTOCOL: String = "ws://"
const val WSS_PROTOCOL: String = "wss://"
val SUPPORTED_PROTOCOLS = listOf(VMESS_PROTOCOL, VMESS1_PROTOCOL, SS_PROTOCOL, SSR_PROTOCOL, TROJAN_PROTOCOL)
abstract class BoxProxy {
var socks5Port: Int = 1080
abstract fun parseFromLink(link: String)
abstract fun parseFromBoxConf(json: JSONObject)
abstract fun generateBoxConf(): JSONObject
abstract fun generateLink(): String
abstract fun getAddress(): String
}
}

View File

@ -0,0 +1,127 @@
package tw.nekomimi.nekogram.proxynext
import android.annotation.SuppressLint
import android.os.Build
import androidx.annotation.RequiresApi
import cn.hutool.core.codec.Base64
import com.github.shadowsocks.plugin.PluginConfiguration
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.json.JSONObject
data class ShadowsocksBean(
var host: String = "",
var port: Int = 443,
var password: String = "",
var method: String = "aes-256-cfb",
var plugin: String = "",
var remarks: String = "",
val pluginOptions: MutableMap<String, String> = HashMap()
) : ProxyConfig.BoxProxy() {
companion object {
val methods = arrayOf(
"none",
"rc4-md5",
"aes-128-cfb",
"aes-192-cfb",
"aes-256-cfb",
"aes-128-ctr",
"aes-192-ctr",
"aes-256-ctr",
"bf-cfb",
"camellia-128-cfb",
"camellia-192-cfb",
"camellia-256-cfb",
"salsa20",
"chacha20",
"chacha20-ietf",
"aes-128-gcm",
"aes-192-gcm",
"aes-256-gcm",
"chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305"
)
}
@SuppressLint("NewApi")
override fun parseFromLink(link: String) {
if (link.contains("@")) {
// ss-android style
val link = link.replace(ProxyConfig.SS_PROTOCOL, "https://").toHttpUrlOrNull()
?: error("invalid ss-android link $link")
if (link.password.isNotBlank()) {
host = link.host
port = link.port
password = link.password
method = link.username
plugin = link.queryParameter("plugin") ?: ""
remarks = link.fragment ?: ""
} else {
val methodAndPswd = Base64.decodeStr(link.username)
host = link.host
port = link.port
password = methodAndPswd.substringAfter(":")
method = methodAndPswd.substringBefore(":")
plugin = link.queryParameter("plugin") ?: ""
remarks = link.fragment ?: ""
}
} else {
// v2rayNG style
var v2Url = link
if (v2Url.contains("#")) v2Url = v2Url.substringBefore("#")
val link = ("https://" + Base64.decodeStr(v2Url.substringAfter(ProxyConfig.SS_PROTOCOL))).toHttpUrlOrNull()
?: error("invalid v2rayNG link $link")
host = link.host
port = link.port
password = link.password
method = link.username
plugin = ""
remarks = link.fragment ?: ""
}
// init
if (method == "plain") method = "none"
// resole plugin
val pl = PluginConfiguration(plugin)
if (pl.selected.contains("v2ray") && pl.selected != "v2ray-plugin") {
// v2ray plugin
// pl.pluginsOptions["v2ray-plugin"] = pl.getOptions().apply { id = "v2ray-plugin" }
// pl.pluginsOptions.remove(pl.selected)
this.plugin = "v2ray-plugin"
pl.pluginsOptions["v2ray-plugin"] = pl.getOptions().apply { id = "v2ray-plugin" }
pl.getOptions().forEach { key, value ->
run {
if (value != null)
this.pluginOptions[key] = value
}
}
} else if (pl.selected == "obfs") {
this.plugin = "obfs-local"
pl.pluginsOptions["obfs-local"] = pl.getOptions().apply { id = "obfs-local" }
pl.getOptions().forEach { key, value ->
run {
if (value != null)
this.pluginOptions[key] = value
}
}
}
}
override fun parseFromBoxConf(json: JSONObject) {
TODO("Not yet implemented")
}
override fun generateBoxConf(): JSONObject {
TODO("Not yet implemented")
}
override fun generateLink(): String {
TODO("Not yet implemented")
}
override fun getAddress(): String {
return this.host
}
}

View File

@ -0,0 +1,110 @@
package tw.nekomimi.nekogram.proxynext
import cn.hutool.core.codec.Base64
import com.google.gson.JsonObject
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject
import tw.nekomimi.nekogram.proxynext.Utils.parseInt
data class ShadowsocksRBean(
var host: String = "",
var port: Int = 443,
var method: String = "aes-256-cfb",
var password: String = "",
var protocol: String = "origin",
var protocol_param: String = "",
var obfs: String = "plain",
var obfs_param: String = "",
var remarks: String = "shadowsocksr"
) : ProxyConfig.BoxProxy() {
override fun parseFromLink(link: String) {
val params = Base64.decodeStr(link.substringAfter(ProxyConfig.SSR_PROTOCOL)).split(":")
host = params[0]
port = params[1].parseInt()
protocol = params[2]
method = params[3]
obfs = params[4]
password = Base64.decodeStr(params[5].substringBefore("/"))
val httpUrl = ("https://localhost" + params[5].substringAfter("/")).toHttpUrl()
runCatching {
obfs_param = Base64.decodeStr(httpUrl.queryParameter("obfsparam")!!)
}
runCatching {
protocol_param = Base64.decodeStr(httpUrl.queryParameter("protoparam")!!)
}
runCatching {
val remarks = httpUrl.queryParameter("remarks")
if (remarks?.isNotBlank() == true) {
this.remarks = Base64.decodeStr(remarks)
}
}
}
override fun parseFromBoxConf(json: JSONObject) {
TODO("Not yet implemented")
}
override fun generateBoxConf(): JSONObject {
TODO("Not yet implemented")
}
override fun generateLink(): String {
TODO("Not yet implemented")
}
override fun getAddress(): String {
return this.host
}
companion object {
val methods = arrayOf(
"none",
"table",
"rc4",
"rc4-md5",
"rc4-md5-6",
"aes-128-cfb",
"aes-192-cfb",
"aes-256-cfb",
"aes-128-ctr",
"aes-192-ctr",
"aes-256-ctr",
"bf-cfb",
"camellia-128-cfb",
"camellia-192-cfb",
"camellia-256-cfb",
"salsa20",
"chacha20",
"chacha20-ietf"
)
val protocols = arrayOf(
"origin",
"verify_simple",
"verify_sha1",
"auth_sha1",
"auth_sha1_v2",
"auth_sha1_v4",
"auth_aes128_sha1",
"auth_aes128_md5",
"auth_chain_a",
"auth_chain_b"
)
val obfses = arrayOf(
"plain",
"http_simple",
"http_post",
"tls_simple",
"tls1.2_ticket_auth"
)
}
}

View File

@ -0,0 +1,98 @@
package tw.nekomimi.nekogram.proxynext
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Build
import org.telegram.messenger.ApplicationLoader
import org.telegram.messenger.FileLog
import tw.nekomimi.nekogram.proxy.GuardedProcessPool
import tw.nekomimi.nekogram.proxy.ProxyManager
import tw.nekomimi.nekogram.utils.ProxyUtil
import java.io.File
class SingProxyManager private constructor() {
companion object {
@JvmField
val INSTANCE = SingProxyManager()
@JvmField
val TEST_INSTANCE = SingProxyManager()
}
val proxies = mutableListOf<ProxyConfig.BoxProxy>()
private val allocatedPorts = mutableSetOf<Int>()
val isSingExist: Boolean by lazy {
resolveProviders(ApplicationLoader.applicationContext).size == 1
}
private val singPath: String? by lazy {
val providers = resolveProviders(ApplicationLoader.applicationContext)
val provider = providers.single().providerInfo
provider?.metaData?.getString("nekox.messenger.sing.executable_path")
?.let { relativePath ->
File(provider.applicationInfo.nativeLibraryDir).resolve(relativePath).apply {
}.absolutePath
}
}
private val singRunner: GuardedProcessPool = GuardedProcessPool {
FileLog.e(it.toString())
}
@Volatile
var isStarted: Boolean = false
fun start() {
synchronized(this) {
if (isStarted) return
isStarted = true
}
}
fun stop() {
}
fun restart() {
}
private fun resolveProviders(context: Context): List<ResolveInfo> {
val uri = Uri.Builder()
.scheme("plugin")
.authority("nekox.messenger.sing")
.path("/sing-box")
.build()
val flags = PackageManager.GET_META_DATA
return context.packageManager.queryIntentContentProviders(
Intent("nekox.messenger.sing.ACTION_SING_PLUGIN", uri), flags
).filter { it.providerInfo.exported }
}
fun setProxyRemarks(proxy: ProxyConfig.BoxProxy, remarks: String) {
}
fun allocatePort(proxy: ProxyConfig.BoxProxy): Int {
var port = ProxyManager.mkPort()
while (allocatedPorts.contains(port)) port = ProxyManager.mkPort()
allocatedPorts.add(port)
proxy.socks5Port = port
return port
}
fun addProxy(boxProxy: ProxyConfig.BoxProxy) {
this.proxies.add(boxProxy)
}
fun clearProxies() {
check(!isStarted)
this.proxies.clear()
}
}

View File

@ -0,0 +1,18 @@
package tw.nekomimi.nekogram.proxynext
import java.net.URLDecoder
object Utils {
fun String.parseInt(): Int {
return Integer.parseInt(this)
}
fun String.urlDecode(): String {
return try {
URLDecoder.decode(this, "UTF-8")
} catch (e: Exception) {
e.printStackTrace()
this
}
}
}

View File

@ -0,0 +1,291 @@
package tw.nekomimi.nekogram.proxynext
import cn.hutool.core.codec.Base64
import com.google.gson.Gson
import com.google.gson.JsonObject
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject
import tw.nekomimi.nekogram.proxynext.Utils.parseInt
import tw.nekomimi.nekogram.proxynext.Utils.urlDecode
/*
VMess / Trojan
*/
data class VMessQRCode(var v: String = "",
var ps: String = "",
var add: String = "",
var port: String = "",
var id: String = "",
var aid: String = "",
var net: String = "",
var type: String = "",
var host: String = "",
var path: String = "",
var tls: String = "") {}
data class VMessBean(var uuid: String = "123456",
var address: String = "",
var port: Int = 443,
var id: String = "",
var alterId: Int = 0,
var security: String = "auto",
var network: String = "tcp",
var remarks: String = "",
var headerType: String = "none",
var requestHost: String = "",
var path: String = "",
var streamSecurity: String = "",
var configVersion: Int = 2,
var testResult: String = "") : ProxyConfig.BoxProxy() {
override fun parseFromLink(link: String) {
check(link.startsWith("vmess://") || link.startsWith("vmess1://"))
try {
if (link.isBlank()) error("empty link")
if (link.startsWith(ProxyConfig.VMESS_PROTOCOL)) {
val indexSplit = link.indexOf("?")
if (indexSplit > 0) {
resolveSimpleVmess1(link)
} else {
var result = link.replace(ProxyConfig.VMESS_PROTOCOL, "")
result = Base64.decodeStr(result)
if (result.isBlank()) {
error("invalid url format")
}
if (result.contains("= vmess")) {
resolveSomeIOSAppShitCsvLink(result)
} else {
val vmessQRCode = Gson().fromJson(result, VMessQRCode::class.java)
if (vmessQRCode.add.isBlank() || vmessQRCode.port.isBlank()
|| vmessQRCode.id.isBlank() || vmessQRCode.aid.isBlank()
|| vmessQRCode.net.isBlank())
error("invalid vmess protocol")
security = "auto"
network = "tcp"
headerType = "none"
configVersion = vmessQRCode.v.parseInt()
remarks = vmessQRCode.ps
address = vmessQRCode.add
port = vmessQRCode.port.parseInt()
id = vmessQRCode.id
alterId = vmessQRCode.aid.parseInt()
network = vmessQRCode.net
headerType = vmessQRCode.type
requestHost = vmessQRCode.host
path = vmessQRCode.path
streamSecurity = vmessQRCode.tls
}
}
upgradeServerVersion()
} else if (link.startsWith(ProxyConfig.VMESS1_PROTOCOL)) {
parseVmess1Link(link)
} else {
error("invalid protocol")
}
} catch (e: Exception) {
throw IllegalArgumentException(e)
}
}
private fun resolveSimpleVmess1(link: String) {
var result = link.replace(ProxyConfig.VMESS_PROTOCOL, "")
val indexSplit = result.indexOf("?")
if (indexSplit > 0) {
result = result.substring(0, indexSplit)
}
result = Base64.decodeStr(result)
val arr1 = result.split('@')
if (arr1.count() != 2) {
error("unexpected VMess1 link format")
}
val arr21 = arr1[0].split(':')
val arr22 = arr1[1].split(':')
if (arr21.count() != 2 || arr21.count() != 2) {
error("unexpected VMess1 link format")
}
address = arr22[0]
port = arr22[1].parseInt()
security = arr21[0]
id = arr21[1]
security = "chacha20-poly1305"
network = "tcp"
headerType = "none"
remarks = ""
alterId = 0
}
private fun parseVmess1Link(link: String) {
val lnk = ("https://" + link.substringAfter(ProxyConfig.VMESS1_PROTOCOL)).toHttpUrl()
address = lnk.host
port = lnk.port
id = lnk.username
remarks = lnk.fragment ?: ""
lnk.queryParameterNames.forEach {
when (it) {
"tls" -> if (lnk.queryParameter(it) == "true") streamSecurity = "tls"
"network" -> {
network = lnk.queryParameter(it)!!
if (network in arrayOf("http", "ws")) {
path = lnk.encodedPath.urlDecode()
}
}
"header" -> {
headerType = lnk.queryParameter(it)!!
}
}
}
}
private fun resolveSomeIOSAppShitCsvLink(csv: String) {
val args = csv.split(",")
address = args[1]
port = args[2].toInt()
security = args[3]
id = args[4].replace("\"", "")
args.subList(5, args.size).forEach {
when {
it == "over-tls=true" -> {
streamSecurity = "tls"
}
it.startsWith("tls-host=") -> {
requestHost = it.substringAfter("=")
}
it.startsWith("obfs=") -> {
network = it.substringAfter("=")
}
it.startsWith("obfs-path=") || it.contains("Host:") -> {
runCatching {
path = it
.substringAfter("obfs-path=\"")
.substringBefore("\"obfs")
}
runCatching {
requestHost = it
.substringAfter("Host:")
.substringBefore("[")
}
}
}
}
}
private fun upgradeServerVersion(): Int {
try {
if (configVersion == 2) {
return 0
}
when (network) {
"ws" -> {
var path = ""
var host = ""
val lstParameter = requestHost.split(";")
if (lstParameter.isNotEmpty()) {
path = lstParameter[0].trim()
}
if (lstParameter.size > 1) {
path = lstParameter[0].trim()
host = lstParameter[1].trim()
}
this.path = path
this.requestHost = host
}
"h2" -> {
var path = ""
var host = ""
val lstParameter = requestHost.split(";")
if (lstParameter.isNotEmpty()) {
path = lstParameter[0].trim()
}
if (lstParameter.size > 1) {
path = lstParameter[0].trim()
host = lstParameter[1].trim()
}
this.path = path
this.requestHost = host
}
}
configVersion = 2
return 0
} catch (e: Exception) {
e.printStackTrace()
return -1
}
}
override fun parseFromBoxConf(json: JSONObject) {
TODO("Not yet implemented")
}
override fun generateBoxConf(): JSONObject {
TODO("Not yet implemented")
}
override fun generateLink(): String {
val qr = VMessQRCode().also {
it.v = configVersion.toString()
it.ps = remarks
it.add = address
it.port = port.toString()
it.id = id
it.aid = alterId.toString()
it.net = network
it.type = headerType
it.host = requestHost
it.path = path
it.tls = streamSecurity
}
return ProxyConfig.VMESS_PROTOCOL + Base64.encode(Gson().toJson(qr))
}
override fun getAddress(): String {
return this.address;
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as VMessBean
if (address != other.address) return false
if (port != other.port) return false
if (id != other.id) return false
if (alterId != other.alterId) return false
if (security != other.security) return false
if (network != other.network) return false
if (headerType != other.headerType) return false
if (requestHost != other.requestHost) return false
if (path != other.path) return false
if (streamSecurity != other.streamSecurity) return false
if (remarks != other.remarks) return false
return true
}
override fun hashCode(): Int {
var result = address.hashCode()
result = 31 * result + port
result = 31 * result + id.hashCode()
result = 31 * result + alterId
result = 31 * result + security.hashCode()
result = 31 * result + network.hashCode()
result = 31 * result + headerType.hashCode()
result = 31 * result + requestHost.hashCode()
result = 31 * result + path.hashCode()
result = 31 * result + streamSecurity.hashCode()
result = 31 * result + remarks.hashCode()
return result
}
}

1
nekox-sing-box/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

43
nekox-sing-box/build-sing-box Executable file
View File

@ -0,0 +1,43 @@
#!/bin/bash
COMMIT_HASH=$1
[[ -z "${ANDROID_NDK_HOME}" ]] && ANDROID_NDK_HOME="${ANDROID_HOME}/ndk/23.1.7779620"
echo "$COMMIT_HASH"
git submodule init sing-box
git submodule update sing-box
pushd sing-box || exit 1
git fetch origin
git reset --hard
git clean -fdx
git checkout "$COMMIT_HASH"
export PATH=$PATH:/usr/lib/go-1.19/bin/
go version || exit 1
TOOLCHAIN="$(find "${ANDROID_NDK_HOME}"/toolchains/llvm/prebuilt/* -maxdepth 1 -type d -print -quit)/bin"
ABIS=(armeabi-v7a arm64-v8a x86 x86_64)
GO_ARCHS=('arm GOARM=7' arm64 386 amd64)
CLANG_ARCHS=(armv7a-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android)
MIN_API="21"
OUT_DIR="$(dirname "$(pwd)")/src/main/jniLibs"
mkdir -p "$OUT_DIR"
BIN="sing-box"
for i in "${!ABIS[@]}"; do
ABI="${ABIS[$i]}"
[[ -f "${OUT_DIR}/${ABI}/${BIN}" ]] && continue
echo "Build ${BIN} for ${ABI}"
mkdir -p ${OUT_DIR}/${ABI}
env \
CGO_ENABLED=1 CC="${TOOLCHAIN}/${CLANG_ARCHS[$i]}${MIN_API}-clang" \
GOOS=android GOARCH=${GO_ARCHS[$i]} \
go build -v -trimpath -tags "-with_shadowsocksr " -ldflags "-s -w" ./cmd/sing-box || exit 1
"${TOOLCHAIN}/llvm-strip" "sing-box" -o "${OUT_DIR}/${ABI}/${BIN}.so" || exit 1
rm "sing-box" || exit 1
done
popd || exit 1

View File

@ -0,0 +1,102 @@
import java.io.ByteArrayInputStream
import java.util.Base64
import java.util.Properties
import org.apache.tools.ant.taskdefs.condition.Os
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.failure
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
fun setupSigningConfig() = Properties().apply {
val base64: String? = System.getenv("LOCAL_PROPERTIES")
if (!base64.isNullOrBlank()) {
load(ByteArrayInputStream(Base64.getDecoder().decode(base64)))
} else if (project.rootProject.file("local.properties").exists()) {
load(project.rootProject.file("local.properties").inputStream())
}
if (getProperty("KEYSTORE_PASS")?.isBlank() == true) {
setProperty("KEYSTORE_PASS", System.getenv("KEYSTORE_PASS"))
setProperty("ALIAS_NAME", System.getenv("ALIAS_NAME"))
setProperty("ALIAS_PASS", System.getenv("ALIAS_PASS"))
}
}
android {
namespace = "nekox.messenger.sing"
compileSdk = 33
defaultConfig {
applicationId = "nekox.messenger.sing"
minSdk = 21
targetSdk = 33
versionCode = 100
versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
val releaseSigningKey = setupSigningConfig()
if (!releaseSigningKey.getProperty("KEYSTORE_PASS").isNullOrBlank()){
create("release") {
storeFile = file("../TMessagesProj/release.keystore")
storePassword = releaseSigningKey.getProperty("KEYSTORE_PASS")
keyAlias = releaseSigningKey.getProperty("ALIAS_NAME")
keyPassword = releaseSigningKey.getProperty("ALIAS_PASS")
}
}
}
buildTypes {
release {
// sourceSets["main"].jniLibs.srcDirs.plus(File(project.path, "libs"))
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
signingConfig = if(signingConfigs.names.contains("release")) signingConfigs.getByName("release") else signingConfigs.getByName("debug")
}
}
splits {
abi {
isEnable = true
isUniversalApk = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10")
// implementation(fileTree("libs"))
}
val singboxCommit = "ef73c6f2a9e5b40028d9bdc0c7e7023c32010fb1" // Release 1.1.6
val nativeBuild = task("native-build-sing-box") {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
println("Build in Windows is not supported")
} else {
exec {
workingDir(projectDir)
executable("/bin/bash")
args("build-sing-box", singboxCommit, android.defaultConfig.minSdkVersion)
environment("ANDROID_HOME", android.sdkDirectory)
environment("ANDROID_NDK_HOME", android.ndkDirectory)
}
}
}
tasks.whenTaskAdded {
if (name.contains("javaPreCompile")) {
dependsOn(nativeBuild)
}
}

21
nekox-sing-box/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1 @@
Subproject commit ef73c6f2a9e5b40028d9bdc0c7e7023c32010fb1

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/nekox_singbox_plugin"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">
<provider
android:authorities="nekox.messenger.sing.PluginProvider"
android:name=".PluginProvider"
android:exported="true">
<intent-filter>
<action android:name="nekox.messenger.sing.ACTION_SING_PLUGIN"/>
</intent-filter>
<intent-filter>
<action android:name="nekox.messenger.sing.ACTION_SING_PLUGIN"/>
<data android:scheme="plugin"
android:host="nekox.messenger.sing"
android:pathPrefix="/sing-box"/>
</intent-filter>
<meta-data android:name="nekox.messenger.sing.executable_path"
android:value="sing-box.so"/>
</provider>
</application>
</manifest>

View File

@ -0,0 +1,45 @@
package nekox.messenger.sing
import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.os.ParcelFileDescriptor
import java.io.File
class PluginProvider : ContentProvider() {
override fun onCreate(): Boolean {
return true
}
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
val result = MatrixCursor(projection)
result.newRow().add("path", "sing-box")
return result
}
override fun getType(uri: Uri): String {
return "application/x-elf"
}
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
check(mode == "r")
var libraryPath = context?.applicationInfo?.nativeLibraryDir
check(libraryPath != null && libraryPath != "")
libraryPath += "/sing-box.so"
return ParcelFileDescriptor.open(File(libraryPath), ParcelFileDescriptor.MODE_READ_ONLY)
}
override fun insert(p0: Uri, p1: ContentValues?): Uri? {
throw UnsupportedOperationException()
}
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int {
throw UnsupportedOperationException()
}
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {
throw UnsupportedOperationException()
}
}

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,4 @@
<resources>
<string name="app_name">NekoX sing-box Plugin</string>
<string name="nekox_singbox_plugin">NekoX sing-box Plugin</string>
</resources>