├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── colors.xml │ │ │ │ └── themes.xml │ │ │ ├── drawable │ │ │ │ ├── back.webp │ │ │ │ ├── logo.webp │ │ │ │ ├── greeting.webp │ │ │ │ ├── notification_logo.xml │ │ │ │ ├── ic_mahdisml_emblem.xml │ │ │ │ ├── ic_mahdisml_emblem_splash.xml │ │ │ │ ├── heart.xml │ │ │ │ ├── ic_settings.xml │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ └── ic_mahdisml_logo.xml │ │ │ ├── font │ │ │ │ ├── roboto_medium.ttf │ │ │ │ └── ubuntu_medium.ttf │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── xml │ │ │ │ ├── network_security_config.xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ │ └── dev │ │ │ │ └── mahdisml │ │ │ │ └── webimmortalguards │ │ │ │ ├── Core.kt │ │ │ │ ├── data │ │ │ │ └── SettingsRepository.kt │ │ │ │ ├── net │ │ │ │ ├── VpnAdapter.kt │ │ │ │ ├── VpnController.kt │ │ │ │ └── VpnService.kt │ │ │ │ ├── Strings.kt │ │ │ │ ├── ui │ │ │ │ ├── theme │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Type.kt │ │ │ │ │ └── Theme.kt │ │ │ │ └── MainViewModel.kt │ │ │ │ ├── core │ │ │ │ ├── AppCore.kt │ │ │ │ └── Chayi.kt │ │ │ │ └── MainActivity.kt │ │ ├── AndroidManifest.xml │ │ └── python │ │ │ └── smlwb.py │ ├── test │ │ └── java │ │ │ └── dev │ │ │ └── mahdisml │ │ │ └── webimmortalguards │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── dev │ │ └── mahdisml │ │ └── webimmortalguards │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── .idea ├── .name ├── .gitignore ├── compiler.xml ├── kotlinc.xml ├── vcs.xml ├── misc.xml ├── gradle.xml └── inspectionProfiles │ └── Project_Default.xml ├── showcase.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── LICENSE ├── README.md ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Web Immortal Guards -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/showcase.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Web Immortal Guards 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/back.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/drawable/back.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/drawable/logo.webp -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/greeting.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/drawable/greeting.webp -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/font/roboto_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/ubuntu_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/font/ubuntu_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #16131A 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon May 22 00:26:33 GMT+03:30 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/Core.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards 2 | 3 | import android.content.Context 4 | import dev.mahdisml.webimmortalguards.core.AppCore 5 | 6 | class Core(private val ctx: Context): AppCore(ctx,"prefsKey") { 7 | companion object{} 8 | init {} 9 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "Web Immortal Guards" 16 | include ':app' 17 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 127.0.0.1 10 | 11 | -------------------------------------------------------------------------------- /app/src/test/java/dev/mahdisml/webimmortalguards/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/notification_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/data/SettingsRepository.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards.data 2 | 3 | import android.content.Context 4 | import dev.mahdisml.webimmortalguards.Core 5 | 6 | class SettingsRepository (ctx:Context) { 7 | private val core = Core(ctx) 8 | 9 | suspend fun getGreetingsState():Boolean{ 10 | core.load("greeting_passed").let { 11 | return if (it != null) { 12 | return (it == "true") 13 | } else { 14 | false 15 | } 16 | } 17 | } 18 | suspend fun setGreetingsState(state:Boolean){ 19 | core.save("greeting_passed",state.toString()) 20 | } 21 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mahdisml_emblem.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/androidTest/java/dev/mahdisml/webimmortalguards/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("dev.mahdisml.webimmortalguards", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mahdisml_emblem_splash.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/heart.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mahdi Safarmohammadloo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/net/VpnAdapter.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards.net 2 | 3 | import com.chaquo.python.PyObject 4 | import com.chaquo.python.Python 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.Job 8 | import kotlinx.coroutines.cancel 9 | import kotlinx.coroutines.launch 10 | 11 | class VpnAdapter () { 12 | private var pythonJob: Job? = null 13 | private var smlwb: PyObject? = null 14 | 15 | fun start(){ 16 | stop() 17 | try { 18 | pythonJob = CoroutineScope(Dispatchers.IO).launch { 19 | try { 20 | smlwb = Python.getInstance().getModule("smlwb") 21 | smlwb!!.callAttr("main") 22 | } catch (e: Exception) { 23 | stop() 24 | } 25 | } 26 | } catch (e: Exception) { 27 | stop() 28 | } 29 | } 30 | fun stop(){ 31 | pythonJob?.cancel("stop") 32 | smlwb = null 33 | pythonJob = null 34 | } 35 | 36 | fun isOn():Boolean{ 37 | return (pythonJob != null) && (smlwb != null) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/Strings.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards 2 | 3 | object Strings { 4 | const val version_code = 1 5 | const val version = "1.0.0" 6 | const val app_name = "Web Immortal Guards" 7 | const val greeting_image_des = "Greeting" 8 | const val greeting_text = "Web Immortal Guards is an experimental tool from Persia that protects you from DNS manipulation, It's not a VPN, and It will not hide your IP address. USE IT ONLY WHEN YOU NEED IT, NOT ALL THE TIME." 9 | const val greeting_button_text = "OK, I got it." 10 | const val home_image_des = "Web Immortal Guards" 11 | const val home_setting_des = "Settings" 12 | const val home_text_on = "Service is ON" 13 | const val home_text_off = "Service is OFF" 14 | const val setting_back = "Back" 15 | const val setting_mahdisml = "Mahdisml Logo" 16 | const val setting_1 = "DNS Server (DoH) :" 17 | const val setting_1_1 = "Auto Mode" 18 | 19 | //constraints 20 | 21 | const val package_name = "dev.mahdisml.webimmortalguards" 22 | const val channel_id = "web_guards_channel" 23 | const val vpn_address = "10.0.0.2" 24 | const val start_action = "dev.mahdisml.webimmortalguards.action.startforeground" 25 | const val stop_action = "dev.mahdisml.webimmortalguards.action.stopforeground" 26 | } -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Web Immortal Guards 3 |

4 | 5 | # Web Immortal Guards [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mahdisml/WebImmortalGuards/blob/master/LICENSE) 6 | 7 | Web Immortal Guards is an experimental tool from Persia that protects you from DNS manipulation. 8 | 9 | It's not a VPN, and It will not hide your IP address. 10 | 11 | USE IT ONLY WHEN YOU NEED IT, NOT ALL THE TIME. 12 | 13 | Python infrastructure and DoH : [https://github.com/GFW-knocker/gfw_resist_HTTPS_proxy](https://github.com/GFW-knocker/gfw_resist_HTTPS_proxy) 14 | 15 | # Available in GooglePlay 16 | Google Play 17 | 18 | Get it on Google Play 19 | 20 | another way to download : [https://apkpure.com/web-immortal-guards/dev.mahdisml.webimmortalguards](https://apkpure.com/web-immortal-guards/dev.mahdisml.webimmortalguards) 21 | 22 | # Persian 23 | 24 | این اپلیکیشن فیلترشکن نیست و نمیتونه آی پی شمارو مخفی کنه 25 | -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards.ui.theme 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.ui.graphics.Color 5 | 6 | val Purple80 = Color(0xFFD0BCFF) 7 | val PurpleGrey80 = Color(0xFFCCC2DC) 8 | val Pink80 = Color(0xFFEFB8C8) 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) 12 | val Teal200 = Color(0xFF03DAC5) 13 | val MainColor = Color(0xFF0396FF) 14 | val MainLight = Color(0xFF0357FF) 15 | val Black = Color(0xFF000000) 16 | val Gray = Color(0xFF888888) 17 | val GrayDark = Color(0xFF444444) 18 | val GrayLight = Color(0xFFCCCCCC) 19 | val Milky = Color(0xFFF5F5F7) 20 | val White = Color(0xFFFFFFFF) 21 | val Red = Color(0xFFFF0000) 22 | val Green = Color(0xFF00FF00) 23 | val Blue = Color(0xFF0000FF) 24 | val Yellow = Color(0xFFFFFF00) 25 | val Cyan = Color(0xFF00FFFF) 26 | val Magenta = Color(0xFFFF00FF) 27 | 28 | val Silver = Color(0xFFF5F5F7) 29 | val SilverDark = Color(0xFFCFCFCF) 30 | val SilverDarker = Color(0xFF757575) 31 | val DeepDark = Color(0xFF171717) 32 | val GoodDark = Color(0xFF222222) 33 | val lightDark = Color(0xFF5F5F5F) 34 | val Gold = Color(0xFFFFC800) 35 | val GoldDark = Color(0xFFBF8B29) 36 | val GoldDarker = Color(0x59FFC800) 37 | val TransparentDark = Color(0x93000000) 38 | val TransparentGoodDark = Color(0x1F000000) 39 | val Cinder = Color(0xFF16131A) -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.Font 6 | import androidx.compose.ui.text.font.FontFamily 7 | import androidx.compose.ui.text.font.FontWeight 8 | import androidx.compose.ui.unit.sp 9 | import dev.mahdisml.webimmortalguards.R 10 | 11 | val Ubuntu_Regular = FontFamily( 12 | Font(R.font.ubuntu_medium) 13 | ) 14 | val Roboto_Medium = FontFamily( 15 | Font(R.font.roboto_medium) 16 | ) 17 | 18 | // Set of Material typography styles to start with 19 | val Typography = Typography( 20 | bodyLarge = TextStyle( 21 | fontFamily = Roboto_Medium, 22 | fontWeight = FontWeight.Normal, 23 | fontSize = 16.sp, 24 | lineHeight = 24.sp, 25 | letterSpacing = 0.5.sp 26 | ) 27 | /* Other default text styles to override 28 | titleLarge = TextStyle( 29 | fontFamily = FontFamily.Default, 30 | fontWeight = FontWeight.Normal, 31 | fontSize = 22.sp, 32 | lineHeight = 28.sp, 33 | letterSpacing = 0.sp 34 | ), 35 | labelSmall = TextStyle( 36 | fontFamily = FontFamily.Default, 37 | fontWeight = FontWeight.Medium, 38 | fontSize = 11.sp, 39 | lineHeight = 16.sp, 40 | letterSpacing = 0.5.sp 41 | ) 42 | */ 43 | ) 44 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/net/VpnController.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards.net 2 | 3 | import android.app.Activity 4 | import android.app.NotificationManager 5 | import android.content.Context 6 | import android.content.Intent 7 | import androidx.core.content.ContextCompat 8 | import dev.mahdisml.webimmortalguards.Strings.package_name 9 | import dev.mahdisml.webimmortalguards.Strings.start_action 10 | import dev.mahdisml.webimmortalguards.Strings.stop_action 11 | import kotlinx.coroutines.CoroutineScope 12 | import kotlinx.coroutines.cancel 13 | 14 | class VpnController (private val ctx: Context) { 15 | private var controllerScope: CoroutineScope? = null 16 | 17 | fun startVpn(){ 18 | controllerScope?.cancel("stop") 19 | val startServiceIntent = Intent(ctx, VpnService::class.java) 20 | startServiceIntent.action = start_action 21 | ContextCompat.startForegroundService(ctx, startServiceIntent) 22 | } 23 | fun endVpn(){ 24 | controllerScope?.cancel("stop") 25 | val stopServiceIntent = Intent(ctx, VpnService::class.java) 26 | stopServiceIntent.action = stop_action 27 | ContextCompat.startForegroundService(ctx, stopServiceIntent) 28 | } 29 | fun isOn(): Boolean { 30 | var isFound = false 31 | val notificationManager = (ctx as Activity).getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 32 | val notifications = notificationManager.activeNotifications 33 | notifications?.let { 34 | for (i in notifications){ 35 | if (package_name == i.packageName){ 36 | isFound = true 37 | } 38 | } 39 | } 40 | return isFound 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.dynamicDarkColorScheme 8 | import androidx.compose.material3.dynamicLightColorScheme 9 | import androidx.compose.material3.lightColorScheme 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.SideEffect 12 | import androidx.compose.ui.graphics.toArgb 13 | import androidx.compose.ui.platform.LocalContext 14 | import androidx.compose.ui.platform.LocalView 15 | import androidx.core.view.WindowCompat 16 | 17 | private val LightColorScheme = lightColorScheme( 18 | primary = GoldDark, 19 | secondary = Gold, 20 | tertiary = SilverDark 21 | 22 | /* Other default colors to override 23 | background = Color(0xFFFFFBFE), 24 | surface = Color(0xFFFFFBFE), 25 | onPrimary = Color.White, 26 | onSecondary = Color.White, 27 | onTertiary = Color.White, 28 | onBackground = Color(0xFF1C1B1F), 29 | onSurface = Color(0xFF1C1B1F), 30 | */ 31 | ) 32 | 33 | @Composable 34 | fun WebImmortalGuardsTheme( 35 | darkTheme: Boolean = isSystemInDarkTheme(), 36 | // Dynamic color is available on Android 12+ 37 | dynamicColor: Boolean = false, 38 | content: @Composable () -> Unit 39 | ) { 40 | val colorScheme = when { 41 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 42 | val context = LocalContext.current 43 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 44 | } 45 | 46 | darkTheme -> LightColorScheme 47 | else -> LightColorScheme 48 | } 49 | val view = LocalView.current 50 | if (!view.isInEditMode) { 51 | SideEffect { 52 | val window = (view.context as Activity).window 53 | window.statusBarColor = Cinder.toArgb() 54 | window.navigationBarColor = Cinder.toArgb() 55 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme 56 | } 57 | } 58 | 59 | MaterialTheme( 60 | colorScheme = colorScheme, 61 | typography = Typography, 62 | content = content 63 | ) 64 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/ui/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards.ui 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.setValue 8 | import androidx.lifecycle.ViewModel 9 | import androidx.lifecycle.viewModelScope 10 | import dev.mahdisml.webimmortalguards.data.SettingsRepository 11 | import dev.mahdisml.webimmortalguards.net.VpnController 12 | import kotlinx.coroutines.Job 13 | import kotlinx.coroutines.launch 14 | 15 | class MainViewModel : ViewModel() { 16 | var pageState :Int? by mutableStateOf(null) //0=greeting,1=home,2=settings 17 | private var checkPageStateJob: Job? = null 18 | private var greetingsDoneJob: Job? = null 19 | private var vpnController : VpnController? = null 20 | 21 | var vpnState :Boolean by mutableStateOf(false) 22 | private set 23 | 24 | fun setVpnState(ctx:Context){ 25 | val prepared = android.net.VpnService.prepare(ctx) 26 | if (prepared == null) { 27 | if (vpnController == null) { 28 | vpnController = VpnController(ctx) 29 | } 30 | vpnState = if (vpnState) { 31 | vpnController?.endVpn() 32 | false 33 | } else { 34 | vpnController?.startVpn() 35 | true 36 | } 37 | }else{ 38 | (ctx as Activity).startActivityForResult(prepared,0x0F) 39 | } 40 | } 41 | fun checkVpnState(ctx:Context){ 42 | viewModelScope.launch { 43 | if(vpnController == null){ 44 | vpnController = VpnController(ctx) 45 | } 46 | vpnController?.let { 47 | vpnState = it.isOn() 48 | } 49 | } 50 | } 51 | 52 | fun checkPageState(ctx:Context){ 53 | checkPageStateJob?.cancel() 54 | checkPageStateJob = viewModelScope.launch{ 55 | pageState = if (SettingsRepository(ctx).getGreetingsState()){ 56 | 1 57 | }else{ 58 | 0 59 | } 60 | 61 | } 62 | } 63 | fun greetingsDone(ctx:Context){ 64 | greetingsDoneJob?.cancel() 65 | greetingsDoneJob = viewModelScope.launch{ 66 | SettingsRepository(ctx).setGreetingsState(true) 67 | checkPageState(ctx) 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'com.chaquo.python' 5 | } 6 | 7 | android { 8 | namespace 'dev.mahdisml.webimmortalguards' 9 | compileSdk 33 10 | 11 | defaultConfig { 12 | applicationId "dev.mahdisml.webimmortalguards" 13 | minSdk 29 14 | targetSdk 33 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | vectorDrawables { 20 | useSupportLibrary true 21 | } 22 | ndk { 23 | abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" 24 | } 25 | python { 26 | version "3.8" 27 | pyc { 28 | src false 29 | } 30 | pip { 31 | install "dnspython" 32 | install "requests" 33 | } 34 | } 35 | } 36 | 37 | buildTypes { 38 | release { 39 | minifyEnabled true 40 | shrinkResources true 41 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 42 | } 43 | } 44 | compileOptions { 45 | sourceCompatibility JavaVersion.VERSION_1_8 46 | targetCompatibility JavaVersion.VERSION_1_8 47 | } 48 | kotlinOptions { 49 | jvmTarget = '1.8' 50 | } 51 | buildFeatures { 52 | compose true 53 | } 54 | composeOptions { 55 | kotlinCompilerExtensionVersion '1.4.7' 56 | } 57 | packagingOptions { 58 | resources { 59 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 60 | } 61 | } 62 | } 63 | 64 | dependencies { 65 | 66 | implementation 'androidx.core:core-ktx:1.10.1' 67 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' 68 | implementation 'androidx.activity:activity-compose:1.7.1' 69 | implementation platform('androidx.compose:compose-bom:2023.05.01') 70 | implementation 'androidx.compose.ui:ui' 71 | implementation 'androidx.compose.ui:ui-graphics' 72 | implementation 'androidx.compose.ui:ui-tooling-preview' 73 | implementation 'androidx.compose.material3:material3' 74 | implementation 'androidx.core:core-ktx:1.10.1' 75 | testImplementation 'junit:junit:4.13.2' 76 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 77 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 78 | androidTestImplementation platform('androidx.compose:compose-bom:2023.05.01') 79 | androidTestImplementation 'androidx.compose.ui:ui-test-junit4' 80 | debugImplementation 'androidx.compose.ui:ui-tooling' 81 | debugImplementation 'androidx.compose.ui:ui-test-manifest' 82 | 83 | //core 84 | implementation("androidx.datastore:datastore-preferences:1.0.0") 85 | 86 | implementation 'androidx.core:core-splashscreen:1.0.1' 87 | implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1' 88 | 89 | 90 | 91 | 92 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/net/VpnService.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards.net 2 | 3 | import android.app.NotificationChannel 4 | import android.app.NotificationManager 5 | import android.app.PendingIntent 6 | import android.app.PendingIntent.FLAG_CANCEL_CURRENT 7 | import android.app.PendingIntent.FLAG_IMMUTABLE 8 | import android.app.PendingIntent.FLAG_NO_CREATE 9 | import android.app.PendingIntent.FLAG_UPDATE_CURRENT 10 | import android.content.Intent 11 | import android.graphics.BitmapFactory 12 | import android.net.ProxyInfo 13 | import android.net.VpnService 14 | import android.os.IBinder 15 | import android.os.ParcelFileDescriptor 16 | import android.util.Log 17 | import androidx.core.app.NotificationCompat 18 | import dev.mahdisml.webimmortalguards.MainActivity 19 | import dev.mahdisml.webimmortalguards.R 20 | import dev.mahdisml.webimmortalguards.Strings.app_name 21 | import dev.mahdisml.webimmortalguards.Strings.channel_id 22 | import dev.mahdisml.webimmortalguards.Strings.home_text_on 23 | import dev.mahdisml.webimmortalguards.Strings.package_name 24 | import dev.mahdisml.webimmortalguards.Strings.stop_action 25 | import dev.mahdisml.webimmortalguards.Strings.vpn_address 26 | import java.io.IOException 27 | import kotlin.system.exitProcess 28 | 29 | 30 | class VpnService : VpnService() { 31 | 32 | private val vpnAdapter = VpnAdapter() 33 | private var vpnInterface: ParcelFileDescriptor? = null 34 | 35 | override fun onCreate() { 36 | super.onCreate() 37 | startVpn() 38 | } 39 | 40 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 41 | if (intent != null){ 42 | intent.action?.let { 43 | if (it == stop_action){ 44 | stopVpn() 45 | } 46 | } 47 | } 48 | return super.onStartCommand(intent, flags, startId) 49 | } 50 | override fun onBind(intent: Intent?): IBinder? { 51 | return null 52 | } 53 | 54 | override fun onDestroy() { 55 | super.onDestroy() 56 | stopVpn() 57 | } 58 | 59 | private fun stopVpn(){ 60 | vpnAdapter.stop() 61 | try { 62 | vpnInterface?.close() 63 | } catch (e: IOException) { 64 | Log.e("Sml","parcelFileDescriptor.close()", e) 65 | } 66 | try { 67 | stopForeground(STOP_FOREGROUND_REMOVE) 68 | } catch (_: Exception) { } 69 | try { 70 | stopSelf() 71 | } catch (_: Exception) { } 72 | 73 | } 74 | private fun startVpn(){ 75 | try { 76 | if (vpnInterface == null) { 77 | val channel = NotificationChannel( 78 | channel_id, 79 | app_name, 80 | NotificationManager.IMPORTANCE_DEFAULT 81 | ) 82 | (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel) 83 | val intent = packageManager.getLaunchIntentForPackage(package_name) 84 | val pendingIntent = if (intent != null) { 85 | PendingIntent.getActivity( 86 | this, 87 | 0, 88 | intent, 89 | FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT 90 | ) 91 | }else{ 92 | PendingIntent.getActivity( 93 | this, 0, 94 | Intent(this, MainActivity::class.java), FLAG_IMMUTABLE or FLAG_NO_CREATE or FLAG_UPDATE_CURRENT or FLAG_CANCEL_CURRENT 95 | ) 96 | } 97 | val notification = NotificationCompat.Builder(this, channel_id) 98 | .setContentTitle(app_name) 99 | .setSmallIcon(R.drawable.notification_logo) 100 | .setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.notification_logo)) 101 | .setContentText(home_text_on) 102 | .setContentIntent(pendingIntent).build() 103 | startForeground(1, notification) 104 | vpnAdapter.start() 105 | val builder = Builder() 106 | builder.setUnderlyingNetworks(null) 107 | builder.setMetered(false) 108 | builder.addDisallowedApplication(package_name) 109 | builder.setHttpProxy(ProxyInfo.buildDirectProxy("127.0.0.1",4525)) 110 | builder.addAddress(vpn_address, 32) 111 | builder.addDnsServer("8.8.8.8") 112 | 113 | vpnInterface = builder.setSession(app_name).establish() 114 | } 115 | } catch (e: Exception) { 116 | Log.e("Sml", "error", e) 117 | exitProcess(0) 118 | } 119 | } 120 | fun isOn(): Boolean { 121 | return vpnAdapter.isOn() 122 | } 123 | 124 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/core/AppCore.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards.core 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.graphics.Typeface 8 | import android.net.Uri 9 | import android.view.WindowManager 10 | import android.widget.ImageView 11 | import android.widget.TextView 12 | import androidx.compose.foundation.clickable 13 | import androidx.compose.foundation.interaction.MutableInteractionSource 14 | import androidx.compose.foundation.layout.Box 15 | import androidx.compose.foundation.layout.Row 16 | import androidx.compose.foundation.layout.fillMaxWidth 17 | import androidx.compose.foundation.text.BasicTextField 18 | import androidx.compose.foundation.text.KeyboardActions 19 | import androidx.compose.foundation.text.KeyboardOptions 20 | import androidx.compose.material3.LocalTextStyle 21 | import androidx.compose.material3.Text 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.runtime.DisposableEffect 24 | import androidx.compose.runtime.remember 25 | import androidx.compose.runtime.rememberUpdatedState 26 | import androidx.compose.ui.Alignment 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.composed 29 | import androidx.compose.ui.draw.drawBehind 30 | import androidx.compose.ui.graphics.Brush 31 | import androidx.compose.ui.graphics.Color 32 | import androidx.compose.ui.graphics.Paint 33 | import androidx.compose.ui.graphics.SolidColor 34 | import androidx.compose.ui.graphics.drawscope.drawIntoCanvas 35 | import androidx.compose.ui.graphics.toArgb 36 | import androidx.compose.ui.platform.LocalLifecycleOwner 37 | import androidx.compose.ui.semantics.Role 38 | import androidx.compose.ui.text.TextLayoutResult 39 | import androidx.compose.ui.text.TextStyle 40 | import androidx.compose.ui.text.input.VisualTransformation 41 | import androidx.compose.ui.unit.Dp 42 | import androidx.compose.ui.unit.dp 43 | import androidx.core.content.ContextCompat 44 | import androidx.datastore.core.DataStore 45 | import androidx.datastore.preferences.core.Preferences 46 | import androidx.datastore.preferences.core.edit 47 | import androidx.datastore.preferences.core.stringPreferencesKey 48 | import androidx.datastore.preferences.preferencesDataStore 49 | import androidx.lifecycle.Lifecycle 50 | import androidx.lifecycle.LifecycleEventObserver 51 | import androidx.lifecycle.LifecycleOwner 52 | import kotlinx.coroutines.CoroutineScope 53 | import kotlinx.coroutines.Job 54 | import kotlinx.coroutines.flow.Flow 55 | import kotlinx.coroutines.flow.first 56 | import kotlinx.coroutines.flow.map 57 | import kotlinx.coroutines.launch 58 | import kotlinx.coroutines.sync.Mutex 59 | 60 | val Context.dataStore: DataStore by preferencesDataStore(name = "prefs") 61 | 62 | open class AppCore ( 63 | private val ctx: Context, 64 | private val preferencesKey:String 65 | ) { 66 | companion object { 67 | @SuppressLint("UnnecessaryComposedModifier") 68 | fun Modifier.coloredShadow( 69 | color: Color, 70 | alpha: Float = 0.2f, 71 | borderRadius: Dp = 0.dp, 72 | shadowRadius: Dp = 20.dp, 73 | offsetY: Dp = 0.dp, 74 | offsetX: Dp = 0.dp 75 | ) = composed { 76 | val shadowColor = color.copy(alpha = alpha).toArgb() 77 | val transparent = color.copy(alpha= 0f).toArgb() 78 | this.drawBehind { 79 | this.drawIntoCanvas { 80 | val paint = Paint() 81 | val frameworkPaint = paint.asFrameworkPaint() 82 | frameworkPaint.color = transparent 83 | frameworkPaint.setShadowLayer( 84 | shadowRadius.toPx(), 85 | offsetX.toPx(), 86 | offsetY.toPx(), 87 | shadowColor 88 | ) 89 | it.drawRoundRect( 90 | 0f, 91 | 0f, 92 | this.size.width, 93 | this.size.height, 94 | borderRadius.toPx(), 95 | borderRadius.toPx(), 96 | paint 97 | ) 98 | } 99 | } 100 | } 101 | @Composable 102 | fun OnLifecycleEvent(onEvent: (owner: LifecycleOwner, event: Lifecycle.Event) -> Unit) { 103 | val eventHandler = rememberUpdatedState(onEvent) 104 | val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current) 105 | 106 | DisposableEffect(lifecycleOwner.value) { 107 | val lifecycle = lifecycleOwner.value.lifecycle 108 | val observer = LifecycleEventObserver { owner, event -> 109 | eventHandler.value(owner, event) 110 | } 111 | lifecycle.addObserver(observer) 112 | onDispose { 113 | lifecycle.removeObserver(observer) 114 | } 115 | } 116 | } 117 | 118 | } 119 | 120 | //DataStore 121 | 122 | suspend fun saveIfNotExist(name: String, data: String){ 123 | ctx.dataStore.edit { prefs -> 124 | if (prefs[stringPreferencesKey(name)] == null) { 125 | Chayi.encrypt(data,preferencesKey)?.let { it2 -> 126 | prefs[stringPreferencesKey(name)] = it2 127 | } 128 | } 129 | } 130 | } 131 | suspend fun save(name:String, data:String){ 132 | ctx.dataStore.edit { prefs -> 133 | Chayi.encrypt(data,preferencesKey)?.let { it2 -> 134 | prefs[stringPreferencesKey(name)] = it2 135 | } 136 | } 137 | } 138 | suspend fun load(data:String) : String?{ 139 | return loadFlow(data).first() 140 | } 141 | suspend fun remove(name:String){ 142 | ctx.dataStore.edit { prefs -> 143 | prefs.remove(stringPreferencesKey(name)) 144 | } 145 | } 146 | 147 | private fun loadFlow(name: String) : Flow { 148 | return ctx.dataStore.data.map { prefs -> 149 | prefs[stringPreferencesKey(name)].let { 150 | if (it != null){ 151 | Chayi.decrypt(it,preferencesKey) 152 | }else { 153 | null 154 | } 155 | } 156 | } 157 | } 158 | suspend fun clearAllData(){ 159 | ctx.dataStore.edit { 160 | it.clear() 161 | } 162 | } 163 | 164 | // Display 165 | 166 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/core/Chayi.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards.core 2 | 3 | object Chayi { 4 | 5 | // Mahdi Safarmohammadloo 6 | // www.MahdiSML.dev 7 | 8 | private const val DELTA = -0x61c88647 9 | 10 | fun encrypt(data: String, key: String): String? { 11 | val bytes = encryptToByte(data, key) ?: return null 12 | return Base64.encode(bytes) 13 | } 14 | 15 | fun decrypt(data: String, key: String): String? { 16 | return try { 17 | val bytes = decrypt(Base64.decode(data), key) ?: return null 18 | bytes.decodeToString() 19 | } catch (ex: Exception) { 20 | null 21 | } 22 | } 23 | 24 | private fun mx(sum: Int, y: Int, z: Int, p: Int, e: Int, k: IntArray): Int { 25 | return (z.ushr(5) xor (y shl 2)) + (y.ushr(3) xor (z shl 4)) xor (sum xor y) + (k[p and 3 xor e] xor z) 26 | } 27 | 28 | private fun encrypt(data: ByteArray, key: ByteArray): ByteArray = 29 | data.takeIf { it.isNotEmpty() } 30 | ?.let { 31 | encrypt(data.toIntArray(true), key.fixKey().toIntArray(false)) 32 | .toByteArray(false) 33 | } 34 | ?: data 35 | 36 | private fun encryptToByte(data: String, key: String): ByteArray? = 37 | runCatching { 38 | encrypt( 39 | data.encodeToByteArray(throwOnInvalidSequence = true), 40 | key.encodeToByteArray(throwOnInvalidSequence = true) 41 | ) 42 | }.getOrNull() 43 | 44 | private fun decrypt(data: ByteArray, key: ByteArray): ByteArray = 45 | data.takeIf { it.isNotEmpty() } 46 | ?.let { 47 | decrypt(data.toIntArray(false), key.fixKey().toIntArray(false)) 48 | .toByteArray(true) 49 | } ?: data 50 | 51 | private fun decrypt(data: ByteArray, key: String): ByteArray? = 52 | kotlin.runCatching { decrypt(data, key.encodeToByteArray(throwOnInvalidSequence = true)) }.getOrNull() 53 | 54 | private fun encrypt(v: IntArray, k: IntArray): IntArray { 55 | val n = v.size - 1 56 | if (n < 1) { 57 | return v 58 | } 59 | var p: Int 60 | var q = 6 + 52 / (n + 1) 61 | var z = v[n] 62 | var y: Int 63 | var sum = 0 64 | var e: Int 65 | while (q-- > 0) { 66 | sum += DELTA 67 | e = sum.ushr(2) and 3 68 | p = 0 69 | while (p < n) { 70 | y = v[p + 1] 71 | v[p] += mx(sum, y, z, p, e, k) 72 | z = v[p] 73 | p++ 74 | } 75 | y = v[0] 76 | v[n] += mx(sum, y, z, p, e, k) 77 | z = v[n] 78 | } 79 | return v 80 | } 81 | 82 | private fun decrypt(v: IntArray, k: IntArray): IntArray { 83 | val n = v.size - 1 84 | if (n < 1) { 85 | return v 86 | } 87 | var p: Int 88 | val q = 6 + 52 / (n + 1) 89 | var z: Int 90 | var y = v[0] 91 | var sum = q * DELTA 92 | var e: Int 93 | while (sum != 0) { 94 | e = sum.ushr(2) and 3 95 | p = n 96 | while (p > 0) { 97 | z = v[p - 1] 98 | v[p] -= mx(sum, y, z, p, e, k) 99 | y = v[p] 100 | p-- 101 | } 102 | z = v[n] 103 | v[0] -= mx(sum, y, z, p, e, k) 104 | y = v[0] 105 | sum -= DELTA 106 | } 107 | return v 108 | } 109 | private fun ByteArray.fixKey(): ByteArray { 110 | if (size == 16) return this 111 | val fixedKey = ByteArray(16) 112 | 113 | if (size < 16) { 114 | copyInto(fixedKey) 115 | } else { 116 | copyInto(fixedKey, endIndex = 16) 117 | } 118 | return fixedKey 119 | } 120 | 121 | private fun ByteArray.toIntArray(includeLength: Boolean): IntArray { 122 | var n = if (size and 3 == 0) 123 | size.ushr(2) 124 | else 125 | size.ushr(2) + 1 126 | val result: IntArray 127 | 128 | if (includeLength) { 129 | result = IntArray(n + 1) 130 | result[n] = size 131 | } else { 132 | result = IntArray(n) 133 | } 134 | n = size 135 | for (i in 0 until n) { 136 | result[i.ushr(2)] = result[i.ushr(2)] or (0x000000ff and this[i].toInt() shl (i and 3 shl 3)) 137 | } 138 | return result 139 | } 140 | 141 | private fun IntArray.toByteArray(includeLength: Boolean): ByteArray? { 142 | var n = size shl 2 143 | 144 | if (includeLength) { 145 | val m = this[size - 1] 146 | n -= 4 147 | if (m < n - 3 || m > n) { 148 | return null 149 | } 150 | n = m 151 | } 152 | val result = ByteArray(n) 153 | 154 | for (i in 0 until n) { 155 | result[i] = this[i.ushr(2)].ushr(i and 3 shl 3).toByte() 156 | } 157 | return result 158 | } 159 | private object Base64 { 160 | private val base64EncodeChars = charArrayOf( 161 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 162 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 163 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 164 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 165 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 166 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 167 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', 168 | '4', '5', '6', '7', '8', '9', '+', '/' 169 | ) 170 | private val base64DecodeChars = byteArrayOf( 171 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 172 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 173 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 174 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 175 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 176 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 177 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 178 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 179 | ) 180 | 181 | fun encode(data: ByteArray): String { 182 | val sb = StringBuilder() 183 | val r = data.size % 3 184 | val len = data.size - r 185 | var i = 0 186 | var c: Int 187 | while (i < len) { 188 | c = 0x000000ff and data[i++].toInt() shl 16 or ( 189 | 0x000000ff and data[i++].toInt() shl 8) or 190 | (0x000000ff and data[i++].toInt()) 191 | sb.append(base64EncodeChars[c shr 18]) 192 | sb.append(base64EncodeChars[c shr 12 and 0x3f]) 193 | sb.append(base64EncodeChars[c shr 6 and 0x3f]) 194 | sb.append(base64EncodeChars[c and 0x3f]) 195 | } 196 | if (r == 1) { 197 | c = 0x000000ff and data[i].toInt() 198 | sb.append(base64EncodeChars[c shr 2]) 199 | sb.append(base64EncodeChars[c and 0x03 shl 4]) 200 | sb.append("==") 201 | } else if (r == 2) { 202 | c = 0x000000ff and data[i++].toInt() shl 8 or 203 | (0x000000ff and data[i].toInt()) 204 | sb.append(base64EncodeChars[c shr 10]) 205 | sb.append(base64EncodeChars[c shr 4 and 0x3f]) 206 | sb.append(base64EncodeChars[c and 0x0f shl 2]) 207 | sb.append("=") 208 | } 209 | return sb.toString() 210 | } 211 | 212 | fun decode(str: String): ByteArray { 213 | val data = str.encodeToByteArray() 214 | val len = data.size 215 | val buf = mutableListOf() 216 | var i = 0 217 | var b1: Int 218 | var b2: Int 219 | var b3: Int 220 | var b4: Int 221 | while (i < len) { 222 | 223 | /* b1 */do { 224 | b1 = base64DecodeChars[data[i++].toInt()].toInt() 225 | } while (i < len && b1 == -1) 226 | if (b1 == -1) { 227 | break 228 | } 229 | 230 | /* b2 */do { 231 | b2 = base64DecodeChars[data[i++].toInt()].toInt() 232 | } while (i < len && b2 == -1) 233 | if (b2 == -1) { 234 | break 235 | } 236 | buf.add((b1 shl 2 or (b2 and 0x30 ushr 4)).toByte()) 237 | 238 | /* b3 */do { 239 | b3 = data[i++].toInt() 240 | if (b3 == 61) { 241 | return buf.toByteArray() 242 | } 243 | b3 = base64DecodeChars[b3].toInt() 244 | } while (i < len && b3 == -1) 245 | if (b3 == -1) { 246 | break 247 | } 248 | buf.add((b2 and 0x0f shl 4 or (b3 and 0x3c ushr 2)).toByte()) 249 | 250 | /* b4 */do { 251 | b4 = data[i++].toInt() 252 | if (b4 == 61) { 253 | return buf.toByteArray() 254 | } 255 | b4 = base64DecodeChars[b4].toInt() 256 | } while (i < len && b4 == -1) 257 | if (b4 == -1) { 258 | break 259 | } 260 | buf.add((b3 and 0x03 shl 6 or b4).toByte()) 261 | } 262 | return buf.toByteArray() 263 | } 264 | } 265 | } -------------------------------------------------------------------------------- /app/src/main/python/smlwb.py: -------------------------------------------------------------------------------- 1 | import dns.message 2 | import dns.rdatatype 3 | import requests 4 | import base64 5 | import socket 6 | import threading 7 | import time 8 | import random 9 | 10 | listen_PORT = 4525 11 | 12 | num_fragment = 300 13 | fragment_sleep = 0.001 14 | 15 | log_every_N_sec = 30 16 | 17 | is_logging = False 18 | 19 | DNS_url = 'https://sky.rethinkdns.com/?dns=' 20 | 21 | offline_DNS = { 22 | 'sky.rethinkdns.com': '172.67.162.27' 23 | } 24 | 25 | my_socket_timeout = 21 26 | first_time_sleep = 0.1 27 | accept_time_sleep = 0.01 28 | 29 | DNS_cache = {} 30 | IP_DL_traffic = {} 31 | IP_UL_traffic = {} 32 | 33 | 34 | class DnsOverFragment: 35 | def __init__(self): 36 | self.url = DNS_url 37 | self.req = requests.session() 38 | self.fragment_proxy = { 39 | 'https': 'http://127.0.0.1:' + str(listen_PORT) 40 | } 41 | 42 | def query(self, server_name): 43 | 44 | offline_ip = offline_DNS.get(server_name, None) 45 | if offline_ip is not None: 46 | if is_logging: 47 | print('offline DNS -->', server_name, offline_ip) 48 | return offline_ip 49 | 50 | cache_ip = DNS_cache.get(server_name, None) 51 | if cache_ip is not None: 52 | if is_logging: 53 | print('cached DNS -->', server_name, cache_ip) 54 | return cache_ip 55 | 56 | query_params = { 57 | 'type': 'A', 58 | 'ct': 'application/dns-message', 59 | } 60 | if is_logging: 61 | print(f'online DNS Query', server_name) 62 | try: 63 | query_message = dns.message.make_query(server_name, 'A') 64 | query_wire = query_message.to_wire() 65 | query_base64 = base64.urlsafe_b64encode(query_wire).decode('utf-8') 66 | query_base64 = query_base64.replace('=', '') 67 | 68 | query_url = self.url + query_base64 69 | ans = self.req.get(query_url, params=query_params, headers={'accept': 'application/dns-message'}, 70 | proxies=self.fragment_proxy) 71 | 72 | if ans.status_code == 200 and ans.headers.get('content-type') == 'application/dns-message': 73 | answer_msg = dns.message.from_wire(ans.content) 74 | 75 | resolved_ip = None 76 | for x in answer_msg.answer: 77 | if x.rdtype == dns.rdatatype.A: 78 | resolved_ip = x[0].address 79 | DNS_cache[server_name] = resolved_ip 80 | if is_logging: 81 | print("################# DNS Cache is : ####################") 82 | print(DNS_cache) 83 | # later. 84 | print("#####################################################") 85 | break 86 | if is_logging: 87 | print(f'online DNS --> Resolved {server_name} to {resolved_ip}') 88 | return resolved_ip 89 | else: 90 | if is_logging: 91 | print(f'Error: {ans.status_code} {ans.reason}') 92 | except Exception as e: 93 | if is_logging: 94 | print(repr(e)) 95 | 96 | 97 | def my_downstream(backend_sock, client_sock): 98 | this_ip = backend_sock.getpeername()[0] 99 | if this_ip not in IP_DL_traffic: 100 | IP_DL_traffic[this_ip] = 0 101 | 102 | first_flag = True 103 | while True: 104 | try: 105 | if first_flag: 106 | first_flag = False 107 | data = backend_sock.recv(16384) 108 | if data: 109 | client_sock.sendall(data) 110 | IP_DL_traffic[this_ip] = IP_DL_traffic[this_ip] + len(data) 111 | else: 112 | raise Exception('backend pipe close at first') 113 | 114 | else: 115 | data = backend_sock.recv(16384) 116 | if data: 117 | client_sock.sendall(data) 118 | IP_DL_traffic[this_ip] = IP_DL_traffic[this_ip] + len(data) 119 | else: 120 | raise Exception('backend pipe close') 121 | 122 | except Exception as e: 123 | time.sleep(2) 124 | backend_sock.close() 125 | client_sock.close() 126 | return False 127 | 128 | 129 | def extract_server_name_and_port(data): 130 | host_and_port = str(data).split()[1] 131 | host, port = host_and_port.split(':') 132 | return host, int(port) 133 | 134 | 135 | class ThreadedServer(object): 136 | def __init__(self, host, port): 137 | self.DoH = DnsOverFragment() 138 | self.host = host 139 | self.port = port 140 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 141 | self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 142 | self.sock.bind((self.host, self.port)) 143 | 144 | def listen(self): 145 | self.sock.listen( 146 | 128) 147 | 148 | while True: 149 | client_sock, client_addr = self.sock.accept() 150 | client_sock.settimeout(my_socket_timeout) 151 | 152 | time.sleep(accept_time_sleep) 153 | thread_up = threading.Thread(target=self.my_upstream, args=(client_sock,)) 154 | thread_up.daemon = True 155 | thread_up.start() 156 | 157 | def handle_client_request(self, client_socket): 158 | data = client_socket.recv(16384) 159 | 160 | if data[:7] == b'CONNECT': 161 | server_name, server_port = extract_server_name_and_port(data) 162 | elif (data[:3] == b'GET') or (data[:4] == b'POST'): 163 | q_line = str(data).split('\r\n') 164 | q_url = q_line[0].split()[1] 165 | q_url = q_url.replace('http://', 'https://') 166 | if is_logging: 167 | print('redirect http to HTTPS', q_url) 168 | response_data = 'HTTP/1.1 302 Found\r\nLocation: ' + q_url + '\r\nProxy-agent: MyProxy/1.0\r\n\r\n' 169 | client_socket.sendall(response_data.encode()) 170 | client_socket.close() 171 | return None 172 | else: 173 | if is_logging: 174 | print('Unknown Method', str(data[:10])) 175 | response_data = b'HTTP/1.1 400 Bad Request\r\nProxy-agent: MyProxy/1.0\r\n\r\n' 176 | client_socket.sendall(response_data) 177 | client_socket.close() 178 | return None 179 | 180 | if is_logging: 181 | print(server_name, '-->', server_port) 182 | 183 | try: 184 | server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 185 | server_socket.settimeout(my_socket_timeout) 186 | server_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 187 | try: 188 | socket.inet_aton(server_name) 189 | server_ip = server_name 190 | except socket.error: 191 | server_ip = self.DoH.query(server_name) 192 | 193 | server_socket.connect((server_ip, server_port)) 194 | response_data = b'HTTP/1.1 200 Connection established\r\nProxy-agent: MyProxy/1.0\r\n\r\n' 195 | client_socket.sendall(response_data) 196 | return server_socket 197 | except Exception as e: 198 | if is_logging: 199 | print(repr(e)) 200 | response_data = b'HTTP/1.1 502 Bad Gateway\r\nProxy-agent: MyProxy/1.0\r\n\r\n' 201 | client_socket.sendall(response_data) 202 | client_socket.close() 203 | server_socket.close() 204 | return None 205 | 206 | def my_upstream(self, client_sock): 207 | first_flag = True 208 | backend_sock = self.handle_client_request(client_sock) 209 | 210 | if backend_sock is None: 211 | client_sock.close() 212 | return False 213 | 214 | this_ip = backend_sock.getpeername()[0] 215 | if this_ip not in IP_UL_traffic: 216 | IP_UL_traffic[this_ip] = 0 217 | 218 | while True: 219 | try: 220 | if first_flag: 221 | first_flag = False 222 | 223 | time.sleep(first_time_sleep) 224 | data = client_sock.recv(16384) 225 | 226 | if data: 227 | thread_down = threading.Thread(target=my_downstream, args=(backend_sock, client_sock)) 228 | thread_down.daemon = True 229 | thread_down.start() 230 | send_data_in_fragment(data, backend_sock) 231 | IP_UL_traffic[this_ip] = IP_UL_traffic[this_ip] + len(data) 232 | 233 | else: 234 | raise Exception('cli syn close') 235 | 236 | else: 237 | data = client_sock.recv(16384) 238 | if data: 239 | backend_sock.sendall(data) 240 | IP_UL_traffic[this_ip] = IP_UL_traffic[this_ip] + len(data) 241 | else: 242 | raise Exception('cli pipe close') 243 | 244 | except Exception as e: 245 | time.sleep(2) 246 | client_sock.close() 247 | backend_sock.close() 248 | return False 249 | 250 | 251 | def send_data_in_fragment(data, sock): 252 | l_data = len(data) 253 | indices = random.sample(range(1, l_data - 1), num_fragment - 1) 254 | indices.sort() 255 | 256 | i_pre = 0 257 | for i in indices: 258 | fragment_data = data[i_pre:i] 259 | i_pre = i 260 | sock.sendall(fragment_data) 261 | time.sleep(fragment_sleep) 262 | fragment_data = data[i_pre:l_data] 263 | sock.sendall(fragment_data) 264 | if is_logging: 265 | print('----------finish------------') 266 | 267 | 268 | def main(): 269 | print("Now listening at: 127.0.0.1:" + str(listen_PORT)) 270 | ThreadedServer('', listen_PORT).listen() -------------------------------------------------------------------------------- /app/src/main/java/dev/mahdisml/webimmortalguards/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.mahdisml.webimmortalguards 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.BackHandler 6 | import androidx.activity.compose.setContent 7 | import androidx.compose.foundation.BorderStroke 8 | import androidx.compose.foundation.Image 9 | import androidx.compose.foundation.background 10 | import androidx.compose.foundation.clickable 11 | import androidx.compose.foundation.layout.Arrangement 12 | import androidx.compose.foundation.layout.Box 13 | import androidx.compose.foundation.layout.Column 14 | import androidx.compose.foundation.layout.Spacer 15 | import androidx.compose.foundation.layout.fillMaxHeight 16 | import androidx.compose.foundation.layout.fillMaxSize 17 | import androidx.compose.foundation.layout.fillMaxWidth 18 | import androidx.compose.foundation.layout.height 19 | import androidx.compose.foundation.layout.padding 20 | import androidx.compose.foundation.layout.wrapContentSize 21 | import androidx.compose.foundation.shape.RoundedCornerShape 22 | import androidx.compose.material3.Button 23 | import androidx.compose.material3.ButtonDefaults 24 | import androidx.compose.material3.Surface 25 | import androidx.compose.material3.Switch 26 | import androidx.compose.material3.SwitchDefaults 27 | import androidx.compose.material3.Text 28 | import androidx.compose.runtime.Composable 29 | import androidx.compose.runtime.LaunchedEffect 30 | import androidx.compose.runtime.getValue 31 | import androidx.compose.runtime.mutableStateOf 32 | import androidx.compose.runtime.remember 33 | import androidx.compose.runtime.setValue 34 | import androidx.compose.ui.Alignment 35 | import androidx.compose.ui.Modifier 36 | import androidx.compose.ui.draw.clip 37 | import androidx.compose.ui.draw.scale 38 | import androidx.compose.ui.graphics.Brush 39 | import androidx.compose.ui.graphics.ColorFilter 40 | import androidx.compose.ui.platform.LocalContext 41 | import androidx.compose.ui.res.painterResource 42 | import androidx.compose.ui.text.ExperimentalTextApi 43 | import androidx.compose.ui.text.TextStyle 44 | import androidx.compose.ui.text.style.TextAlign 45 | import androidx.compose.ui.tooling.preview.Preview 46 | import androidx.compose.ui.unit.dp 47 | import androidx.compose.ui.unit.sp 48 | import androidx.lifecycle.Lifecycle 49 | import androidx.lifecycle.viewmodel.compose.viewModel 50 | import dev.mahdisml.webimmortalguards.Strings.setting_1 51 | import dev.mahdisml.webimmortalguards.Strings.setting_1_1 52 | import dev.mahdisml.webimmortalguards.Strings.version 53 | import dev.mahdisml.webimmortalguards.core.AppCore 54 | import dev.mahdisml.webimmortalguards.core.AppCore.Companion.coloredShadow 55 | import dev.mahdisml.webimmortalguards.ui.MainViewModel 56 | import dev.mahdisml.webimmortalguards.ui.theme.Cinder 57 | import dev.mahdisml.webimmortalguards.ui.theme.Gold 58 | import dev.mahdisml.webimmortalguards.ui.theme.GoldDark 59 | import dev.mahdisml.webimmortalguards.ui.theme.GoldDarker 60 | import dev.mahdisml.webimmortalguards.ui.theme.Silver 61 | import dev.mahdisml.webimmortalguards.ui.theme.SilverDarker 62 | import dev.mahdisml.webimmortalguards.ui.theme.Ubuntu_Regular 63 | import dev.mahdisml.webimmortalguards.ui.theme.WebImmortalGuardsTheme 64 | 65 | class MainActivity : ComponentActivity() { 66 | override fun onCreate(savedInstanceState: Bundle?) { 67 | super.onCreate(savedInstanceState) 68 | setContent { 69 | val ctx = LocalContext.current 70 | val mainViewModel :MainViewModel = viewModel() 71 | 72 | LaunchedEffect(true) { 73 | mainViewModel.checkPageState(ctx) 74 | } 75 | 76 | WebImmortalGuardsTheme { 77 | Surface( 78 | modifier = Modifier.fillMaxSize(), 79 | color = Cinder 80 | ) { 81 | when(mainViewModel.pageState){ 82 | 0 -> {Greeting()} 83 | 1 -> {Home()} 84 | 2 -> {Settings()} 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | @OptIn(ExperimentalTextApi::class) 92 | @Composable 93 | fun Greeting() { 94 | val ctx = LocalContext.current 95 | val mainViewModel:MainViewModel = viewModel() 96 | 97 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){ 98 | Column( 99 | modifier = Modifier 100 | .fillMaxWidth() 101 | .fillMaxHeight(), 102 | verticalArrangement = Arrangement.Center, 103 | horizontalAlignment = Alignment.CenterHorizontally 104 | ) { 105 | Image( 106 | painter = painterResource(id = R.drawable.greeting), 107 | contentDescription = Strings.greeting_image_des, 108 | modifier = Modifier 109 | .coloredShadow(color = Gold, alpha = 0.6f) 110 | .fillMaxHeight(0.45f) 111 | .wrapContentSize() 112 | .clip(RoundedCornerShape(33.dp)) 113 | ) 114 | Spacer(modifier = Modifier.height(60.dp)) 115 | Text( 116 | text = Strings.greeting_text, 117 | style = TextStyle( 118 | brush = Brush.linearGradient( 119 | colors = listOf(GoldDark, Gold,GoldDark) 120 | ) 121 | ), 122 | textAlign = TextAlign.Center, 123 | modifier = Modifier.padding(horizontal = 33.dp), 124 | fontSize = 17.sp 125 | ) 126 | Spacer(modifier = Modifier.height(30.dp)) 127 | Button( 128 | onClick = { 129 | mainViewModel.greetingsDone(ctx) 130 | }, 131 | colors = ButtonDefaults.buttonColors( 132 | containerColor = Cinder, 133 | contentColor = Silver 134 | ), 135 | border = BorderStroke(1.dp, Gold) 136 | ) { 137 | Text( 138 | text = Strings.greeting_button_text, 139 | textAlign = TextAlign.Center 140 | ) 141 | } 142 | } 143 | } 144 | 145 | 146 | 147 | } 148 | @OptIn(ExperimentalTextApi::class) 149 | @Composable 150 | fun Home() { 151 | val ctx = LocalContext.current 152 | val mainViewModel:MainViewModel = viewModel() 153 | 154 | LaunchedEffect(true){ 155 | mainViewModel.checkVpnState(ctx) 156 | } 157 | 158 | var firstResume by remember { mutableStateOf(true)} 159 | AppCore.OnLifecycleEvent { _, event -> 160 | when (event) { 161 | Lifecycle.Event.ON_RESUME -> { 162 | if (!firstResume) { 163 | mainViewModel.checkVpnState(ctx) 164 | } else { 165 | firstResume = false 166 | } 167 | } 168 | else -> {} 169 | } 170 | } 171 | 172 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){ 173 | Image( 174 | painter = painterResource(id = R.drawable.ic_settings), 175 | contentDescription = Strings.home_setting_des, 176 | modifier = Modifier 177 | .align(Alignment.TopStart) 178 | .padding(all = 20.dp) 179 | .clickable { 180 | mainViewModel.pageState = 2 181 | }, 182 | colorFilter = ColorFilter.tint(GoldDarker) 183 | ) 184 | Column( 185 | modifier = Modifier 186 | .fillMaxWidth() 187 | .fillMaxHeight() 188 | .padding(top = 70.dp), 189 | verticalArrangement = Arrangement.Top, 190 | horizontalAlignment = Alignment.CenterHorizontally 191 | ) { 192 | Image( 193 | painter = painterResource(id = R.drawable.logo), 194 | contentDescription = Strings.home_image_des, 195 | modifier = Modifier.fillMaxHeight(0.495f) 196 | ) 197 | Text( 198 | text = Strings.app_name, 199 | style = TextStyle( 200 | brush = Brush.linearGradient( 201 | colors = listOf(GoldDark, Gold,GoldDark) 202 | ), 203 | fontFamily = Ubuntu_Regular 204 | ), 205 | textAlign = TextAlign.Center, 206 | modifier = Modifier 207 | .padding(horizontal = 33.dp) 208 | .padding(bottom = (40).dp), 209 | fontSize = 25.sp 210 | ) 211 | Spacer(modifier = Modifier.height(10.dp)) 212 | Switch( 213 | modifier = Modifier 214 | .scale(1.5f) 215 | .coloredShadow( 216 | color = Gold, 217 | alpha = if (mainViewModel.vpnState) 0.4f else 0.1f 218 | ), 219 | checked = mainViewModel.vpnState, 220 | onCheckedChange = { 221 | mainViewModel.setVpnState(ctx) 222 | }, 223 | colors = SwitchDefaults.colors( 224 | uncheckedBorderColor = GoldDark, 225 | checkedBorderColor = Gold, 226 | 227 | uncheckedThumbColor = Gold, 228 | checkedThumbColor = Cinder, 229 | 230 | uncheckedTrackColor = Cinder, 231 | checkedTrackColor = Gold 232 | 233 | ) 234 | ) 235 | Spacer(modifier = Modifier.height(50.dp)) 236 | Text( 237 | text = if (mainViewModel.vpnState) Strings.home_text_on else Strings.home_text_off, 238 | style = TextStyle( 239 | brush = Brush.linearGradient( 240 | colors = if (mainViewModel.vpnState) listOf(GoldDark, Gold,GoldDark) else listOf(GoldDarker,GoldDarker) 241 | ) 242 | ), 243 | textAlign = TextAlign.Center, 244 | modifier = Modifier.padding(horizontal = 33.dp), 245 | fontSize = 17.sp 246 | ) 247 | } 248 | } 249 | } 250 | @Preview(device = "id:pixel_6_pro") 251 | @Composable 252 | fun Settings() { 253 | val mainViewModel:MainViewModel = viewModel() 254 | 255 | var logoState by remember { mutableStateOf(0) } 256 | 257 | BackHandler { 258 | mainViewModel.pageState = 1 259 | } 260 | 261 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 262 | Image( 263 | painter = painterResource(id = R.drawable.back), 264 | contentDescription = Strings.setting_back, 265 | modifier = Modifier 266 | .align(Alignment.TopStart) 267 | .padding(all = 20.dp) 268 | .clickable { 269 | mainViewModel.pageState = 1 270 | }, 271 | colorFilter = ColorFilter.tint(GoldDarker) 272 | ) 273 | Box( 274 | modifier = Modifier 275 | .fillMaxHeight(0.70f) 276 | .fillMaxWidth(0.80f) 277 | .coloredShadow( 278 | color = Gold, 279 | alpha = 0.1f 280 | ) 281 | .clip(RoundedCornerShape(33.dp)) 282 | .background(Cinder), 283 | contentAlignment = Alignment.Center 284 | ){ 285 | Column(modifier = Modifier.align(Alignment.TopCenter),horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top) { 286 | Spacer(modifier = Modifier.height(30.dp)) 287 | Box(modifier = Modifier.fillMaxWidth(0.85f)) { 288 | Text(modifier = Modifier.align(Alignment.CenterStart),text = setting_1, color = Silver, textAlign = TextAlign.Center, fontSize = 14.sp) 289 | Button( 290 | onClick = { 291 | 292 | }, 293 | enabled = false, 294 | modifier=Modifier.align(Alignment.CenterEnd) , 295 | colors = ButtonDefaults.buttonColors( 296 | containerColor = Cinder, 297 | contentColor = Silver, 298 | disabledContainerColor = Cinder, 299 | disabledContentColor = SilverDarker 300 | ), 301 | border = BorderStroke(1.dp, Gold) 302 | ) { 303 | Text( 304 | text = setting_1_1, 305 | textAlign = TextAlign.Center 306 | ) 307 | } 308 | } 309 | } 310 | Column(modifier = Modifier.align(Alignment.BottomCenter),horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { 311 | Image( 312 | modifier= Modifier 313 | .fillMaxWidth(0.25f) 314 | .fillMaxHeight(0.1f) 315 | .wrapContentSize() 316 | .clickable { 317 | logoState++ 318 | }, 319 | painter = if (logoState >= 12) painterResource(id = R.drawable.heart) else painterResource(id = R.drawable.ic_mahdisml_emblem), 320 | contentDescription = Strings.setting_mahdisml 321 | ) 322 | Spacer(modifier = Modifier.height(20.dp)) 323 | Text(text = version, color = Silver, textAlign = TextAlign.Center) 324 | Spacer(modifier = Modifier.height(20.dp)) 325 | } 326 | } 327 | } 328 | 329 | } 330 | 331 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mahdisml_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 13 | 17 | 21 | 25 | 29 | 30 | --------------------------------------------------------------------------------