diff --git a/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/3.json b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/3.json new file mode 100644 index 0000000..491eb6f --- /dev/null +++ b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/3.json @@ -0,0 +1,360 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "cff00d0142d9e53d2ca24a6a55cd213c", + "entities": [ + { + "tableName": "proxy_groups", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL, `isSelector` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL, `landingProxy` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userOrder", + "columnName": "userOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ungrouped", + "columnName": "ungrouped", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscription", + "columnName": "subscription", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSelector", + "columnName": "isSelector", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "frontProxy", + "columnName": "frontProxy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "landingProxy", + "columnName": "landingProxy", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "proxy_entities", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `mieruBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "groupId", + "columnName": "groupId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userOrder", + "columnName": "userOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tx", + "columnName": "tx", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "rx", + "columnName": "rx", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ping", + "columnName": "ping", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "socksBean", + "columnName": "socksBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "httpBean", + "columnName": "httpBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "ssBean", + "columnName": "ssBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "vmessBean", + "columnName": "vmessBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "trojanBean", + "columnName": "trojanBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "trojanGoBean", + "columnName": "trojanGoBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "mieruBean", + "columnName": "mieruBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "naiveBean", + "columnName": "naiveBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "hysteriaBean", + "columnName": "hysteriaBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "tuicBean", + "columnName": "tuicBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "sshBean", + "columnName": "sshBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "wgBean", + "columnName": "wgBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "shadowTLSBean", + "columnName": "shadowTLSBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "chainBean", + "columnName": "chainBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "nekoBean", + "columnName": "nekoBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "configBean", + "columnName": "configBean", + "affinity": "BLOB", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "groupId", + "unique": false, + "columnNames": [ + "groupId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "rules", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userOrder", + "columnName": "userOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "domains", + "columnName": "domains", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ip", + "columnName": "ip", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sourcePort", + "columnName": "sourcePort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "network", + "columnName": "network", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "protocol", + "columnName": "protocol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "outbound", + "columnName": "outbound", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packages", + "columnName": "packages", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cff00d0142d9e53d2ca24a6a55cd213c')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c132174..bd284c0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -164,6 +164,9 @@ + diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt index feaca9b..0f20337 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt @@ -10,6 +10,8 @@ import io.nekohasekai.sagernet.fmt.ConfigBuildResult import io.nekohasekai.sagernet.fmt.buildConfig import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean import io.nekohasekai.sagernet.fmt.hysteria.buildHysteria1Config +import io.nekohasekai.sagernet.fmt.mieru.MieruBean +import io.nekohasekai.sagernet.fmt.mieru.buildMieruConfig import io.nekohasekai.sagernet.fmt.naive.NaiveBean import io.nekohasekai.sagernet.fmt.naive.buildNaiveConfig import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean @@ -65,6 +67,11 @@ abstract class BoxInstance( pluginConfigs[port] = profile.type to bean.buildTrojanGoConfig(port) } + is MieruBean -> { + initPlugin("mieru-plugin") + pluginConfigs[port] = profile.type to bean.buildMieruConfig(port) + } + is NaiveBean -> { initPlugin("naive-plugin") pluginConfigs[port] = profile.type to bean.buildNaiveConfig(port) @@ -129,6 +136,25 @@ abstract class BoxInstance( processes.start(commands) } + bean is MieruBean -> { + val configFile = File( + cacheDir, "mieru_" + SystemClock.elapsedRealtime() + ".json" + ) + + configFile.parentFile?.mkdirs() + configFile.writeText(config) + cacheFiles.add(configFile) + + val envMap = mutableMapOf() + envMap["MIERU_CONFIG_JSON_FILE"] = configFile.absolutePath + + val commands = mutableListOf( + initPlugin("mieru-plugin").path, "run", + ) + + processes.start(commands, envMap) + } + bean is NaiveBean -> { val configFile = File( cacheDir, "naive_" + SystemClock.elapsedRealtime() + ".json" diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt index 401b468..45f0a31 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt @@ -11,6 +11,8 @@ import io.nekohasekai.sagernet.fmt.http.HttpBean import io.nekohasekai.sagernet.fmt.http.toUri import io.nekohasekai.sagernet.fmt.hysteria.* import io.nekohasekai.sagernet.fmt.internal.ChainBean +import io.nekohasekai.sagernet.fmt.mieru.MieruBean +import io.nekohasekai.sagernet.fmt.mieru.buildMieruConfig import io.nekohasekai.sagernet.fmt.naive.NaiveBean import io.nekohasekai.sagernet.fmt.naive.buildNaiveConfig import io.nekohasekai.sagernet.fmt.naive.toUri @@ -56,6 +58,7 @@ data class ProxyEntity( var vmessBean: VMessBean? = null, var trojanBean: TrojanBean? = null, var trojanGoBean: TrojanGoBean? = null, + var mieruBean: MieruBean? = null, var naiveBean: NaiveBean? = null, var hysteriaBean: HysteriaBean? = null, var tuicBean: TuicBean? = null, @@ -75,6 +78,7 @@ data class ProxyEntity( const val TYPE_TROJAN = 6 const val TYPE_TROJAN_GO = 7 + const val TYPE_MIERU = 21 const val TYPE_NAIVE = 9 const val TYPE_HYSTERIA = 15 const val TYPE_TUIC = 20 @@ -161,6 +165,7 @@ data class ProxyEntity( TYPE_VMESS -> vmessBean = KryoConverters.vmessDeserialize(byteArray) TYPE_TROJAN -> trojanBean = KryoConverters.trojanDeserialize(byteArray) TYPE_TROJAN_GO -> trojanGoBean = KryoConverters.trojanGoDeserialize(byteArray) + TYPE_MIERU -> mieruBean = KryoConverters.mieruDeserialize(byteArray) TYPE_NAIVE -> naiveBean = KryoConverters.naiveDeserialize(byteArray) TYPE_HYSTERIA -> hysteriaBean = KryoConverters.hysteriaDeserialize(byteArray) TYPE_SSH -> sshBean = KryoConverters.sshDeserialize(byteArray) @@ -180,6 +185,7 @@ data class ProxyEntity( TYPE_VMESS -> if (vmessBean!!.isVLESS) "VLESS" else "VMess" TYPE_TROJAN -> "Trojan" TYPE_TROJAN_GO -> "Trojan-Go" + TYPE_MIERU -> "Mieru" TYPE_NAIVE -> "Naïve" TYPE_HYSTERIA -> "Hysteria" + hysteriaBean!!.protocolVersion TYPE_SSH -> "SSH" @@ -203,6 +209,7 @@ data class ProxyEntity( TYPE_VMESS -> vmessBean TYPE_TROJAN -> trojanBean TYPE_TROJAN_GO -> trojanGoBean + TYPE_MIERU -> mieruBean TYPE_NAIVE -> naiveBean TYPE_HYSTERIA -> hysteriaBean TYPE_SSH -> sshBean @@ -270,6 +277,11 @@ data class ProxyEntity( append(bean.buildTrojanGoConfig(port)) } + is MieruBean -> { + append("\n\n") + append(bean.buildMieruConfig(port)) + } + is NaiveBean -> { append("\n\n") append(bean.buildNaiveConfig(port)) @@ -289,6 +301,7 @@ data class ProxyEntity( fun needExternal(): Boolean { return when (type) { TYPE_TROJAN_GO -> true + TYPE_MIERU -> true TYPE_NAIVE -> true TYPE_HYSTERIA -> !hysteriaBean!!.canUseSingBox() TYPE_NEKO -> true @@ -319,6 +332,7 @@ data class ProxyEntity( vmessBean = null trojanBean = null trojanGoBean = null + mieruBean = null naiveBean = null hysteriaBean = null sshBean = null @@ -360,6 +374,11 @@ data class ProxyEntity( trojanGoBean = bean } + is MieruBean -> { + type = TYPE_MIERU + mieruBean = bean + } + is NaiveBean -> { type = TYPE_NAIVE naiveBean = bean @@ -419,6 +438,7 @@ data class ProxyEntity( TYPE_VMESS -> VMessSettingsActivity::class.java TYPE_TROJAN -> TrojanSettingsActivity::class.java TYPE_TROJAN_GO -> TrojanGoSettingsActivity::class.java + TYPE_MIERU -> MieruSettingsActivity::class.java TYPE_NAIVE -> NaiveSettingsActivity::class.java TYPE_HYSTERIA -> HysteriaSettingsActivity::class.java TYPE_SSH -> SSHSettingsActivity::class.java diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt b/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt index 4d57ed3..9f63fbb 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt @@ -15,7 +15,7 @@ import kotlinx.coroutines.launch @Database( entities = [ProxyGroup::class, ProxyEntity::class, RuleEntity::class], - version = 2 + version = 3 ) @TypeConverters(value = [KryoConverters::class, GsonConverters::class]) @GenerateRoomMigrations diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java index b9a6338..627ea35 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java @@ -13,6 +13,7 @@ import io.nekohasekai.sagernet.database.SubscriptionBean; import io.nekohasekai.sagernet.fmt.http.HttpBean; import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean; import io.nekohasekai.sagernet.fmt.internal.ChainBean; +import io.nekohasekai.sagernet.fmt.mieru.MieruBean; import io.nekohasekai.sagernet.fmt.naive.NaiveBean; import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean; import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean; @@ -99,6 +100,12 @@ public class KryoConverters { return deserialize(new TrojanGoBean(), bytes); } + @TypeConverter + public static MieruBean mieruDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new MieruBean(), bytes); + } + @TypeConverter public static NaiveBean naiveDeserialize(byte[] bytes) { if (JavaUtil.isEmpty(bytes)) return null; diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt index ee26953..42769e2 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt @@ -14,6 +14,16 @@ enum class PluginEntry( SagerNet.application.getString(R.string.action_trojan_go), "io.nekohasekai.sagernet.plugin.trojan_go" ), + MieruProxy( + "mieru-plugin", + SagerNet.application.getString(R.string.action_mieru), + "moe.matsuri.exe.mieru", + DownloadSource( + playStore = false, + fdroid = false, + downloadLink = "https://github.com/MatsuriDayo/plugins/releases?q=mieru" + ) + ), NaiveProxy( "naive-plugin", SagerNet.application.getString(R.string.action_naive), diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt index c55c58b..12554a1 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt @@ -10,6 +10,7 @@ object TypeMap : HashMap() { this["vmess"] = ProxyEntity.TYPE_VMESS this["trojan"] = ProxyEntity.TYPE_TROJAN this["trojan-go"] = ProxyEntity.TYPE_TROJAN_GO + this["mieru"] = ProxyEntity.TYPE_MIERU this["naive"] = ProxyEntity.TYPE_NAIVE this["hysteria"] = ProxyEntity.TYPE_HYSTERIA this["ssh"] = ProxyEntity.TYPE_SSH diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruBean.java new file mode 100644 index 0000000..0a7c21f --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruBean.java @@ -0,0 +1,89 @@ +/****************************************************************************** + * Copyright (C) 2022 by nekohasekai * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + * * + ******************************************************************************/ + +package io.nekohasekai.sagernet.fmt.mieru; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; + +public class MieruBean extends AbstractBean { + + public String protocol; + public String username; + public String password; + public Integer mtu; + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + if (protocol == null) protocol = "TCP"; + if (username == null) username = ""; + if (password == null) password = ""; + if (mtu == null) mtu = 1400; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(0); + super.serialize(output); + output.writeString(protocol); + output.writeString(username); + output.writeString(password); + if (protocol.equals("UDP")) { + output.writeInt(mtu); + } + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + protocol = input.readString(); + username = input.readString(); + password = input.readString(); + if (protocol.equals("UDP")) { + mtu = input.readInt(); + } + } + + @NotNull + @Override + public MieruBean clone() { + return KryoConverters.deserialize(new MieruBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public MieruBean newInstance() { + return new MieruBean(); + } + + @Override + public MieruBean[] newArray(int size) { + return new MieruBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruFmt.kt new file mode 100644 index 0000000..4085216 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruFmt.kt @@ -0,0 +1,53 @@ +/****************************************************************************** + * Copyright (C) 2022 by nekohasekai * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + * * + ******************************************************************************/ + +package io.nekohasekai.sagernet.fmt.mieru + +import io.nekohasekai.sagernet.ktx.toStringPretty +import org.json.JSONArray +import org.json.JSONObject + +fun MieruBean.buildMieruConfig(port: Int): String { + val serverInfo = JSONArray().apply { + put(JSONObject().apply { + put("ipAddress", finalAddress) + put("portBindings", JSONArray().apply { + put(JSONObject().apply { + put("port", finalPort) + put("protocol", protocol) + }) + }) + }) + } + return JSONObject().apply { + put("activeProfile", "default") + put("socks5Port", port) + // TODO: follow NekoBox logging level. + put("loggingLevel", "INFO") + put("profiles", JSONArray().apply { + put(JSONObject().apply { + put("profileName", "default") + put("user", JSONObject().apply { + put("name", username) + put("password", password) + }) + put("servers", serverInfo) + }) + }) + }.toStringPretty() +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt index 301f157..5b1c50f 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt @@ -362,6 +362,10 @@ class ConfigurationFragment @JvmOverloads constructor( startActivity(Intent(requireActivity(), TrojanGoSettingsActivity::class.java)) } + R.id.action_new_mieru -> { + startActivity(Intent(requireActivity(), MieruSettingsActivity::class.java)) + } + R.id.action_new_naive -> { startActivity(Intent(requireActivity(), NaiveSettingsActivity::class.java)) } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/MieruSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/MieruSettingsActivity.kt new file mode 100644 index 0000000..f9195fb --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/MieruSettingsActivity.kt @@ -0,0 +1,77 @@ +/****************************************************************************** + * * + * Copyright (C) 2021 by nekohasekai * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + * * + ******************************************************************************/ + +package io.nekohasekai.sagernet.ui.profile + +import android.os.Bundle +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceFragmentCompat +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.fmt.mieru.MieruBean +import io.nekohasekai.sagernet.ktx.applyDefaultValues +import moe.matsuri.nb4a.ui.SimpleMenuPreference + +class MieruSettingsActivity : ProfileSettingsActivity() { + + override fun createEntity() = MieruBean().applyDefaultValues() + + override fun MieruBean.init() { + DataStore.profileName = name + DataStore.serverAddress = serverAddress + DataStore.serverPort = serverPort + DataStore.serverProtocol = protocol + DataStore.serverUsername = username + DataStore.serverPassword = password + DataStore.mtu = mtu + } + + override fun MieruBean.serialize() { + name = DataStore.profileName + serverAddress = DataStore.serverAddress + serverPort = DataStore.serverPort + protocol = DataStore.serverProtocol + username = DataStore.serverUsername + password = DataStore.serverPassword + mtu = DataStore.mtu + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.mieru_preferences) + findPreference(Key.SERVER_PORT)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + } + findPreference(Key.SERVER_PASSWORD)!!.apply { + summaryProvider = PasswordSummaryProvider + } + val protocol = findPreference(Key.SERVER_PROTOCOL)!! + val mtu = findPreference(Key.MTU)!! + mtu.isVisible = protocol.value.equals("UDP") + protocol.setOnPreferenceChangeListener { _, newValue -> + mtu.isVisible = newValue.equals("UDP") + true + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/menu/add_profile_menu.xml b/app/src/main/res/menu/add_profile_menu.xml index 072cf8d..129d320 100644 --- a/app/src/main/res/menu/add_profile_menu.xml +++ b/app/src/main/res/menu/add_profile_menu.xml @@ -45,6 +45,9 @@ + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 1e58c64..0be3670 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -332,6 +332,10 @@ TPROXY + + TCP + UDP + HTTPS diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ab6bc3d..c9e09ed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -214,6 +214,7 @@ VMess Trojan Trojan Go + Mieru Naïve Ping Tunnel Hysteria diff --git a/app/src/main/res/xml/mieru_preferences.xml b/app/src/main/res/xml/mieru_preferences.xml new file mode 100644 index 0000000..19a3dab --- /dev/null +++ b/app/src/main/res/xml/mieru_preferences.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + \ No newline at end of file