├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── .idea └── copyright │ ├── Geyser.xml │ └── profiles_settings.xml ├── Jenkinsfile ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ ├── java │ │ ├── awt │ │ │ └── image │ │ │ │ ├── BufferedImage.java │ │ │ │ └── RenderedImage.java │ │ └── util │ │ │ └── Base64.java │ ├── javax │ │ └── imageio │ │ │ └── ImageIO.java │ └── org │ │ └── geysermc │ │ └── geyser │ │ └── android │ │ ├── MainActivity.java │ │ ├── proxy │ │ ├── PacketHandler.java │ │ ├── PaletteManger.java │ │ ├── Player.java │ │ ├── ProxyLogger.java │ │ └── ProxyServer.java │ │ ├── service │ │ └── ProxyService.java │ │ ├── ui │ │ ├── about │ │ │ ├── AboutActivity.java │ │ │ └── AboutFragment.java │ │ ├── home │ │ │ └── HomeFragment.java │ │ ├── proxy │ │ │ └── ProxyFragment.java │ │ └── settings │ │ │ ├── SettingsActivity.java │ │ │ └── SettingsFragment.java │ │ └── utils │ │ ├── AndroidDeviceDump.java │ │ ├── AndroidUtils.java │ │ ├── ConfigUtils.java │ │ ├── EventListeners.java │ │ └── UserAuth.java │ └── res │ ├── drawable │ ├── geyser_logo.png │ ├── ic_add.xml │ ├── ic_menu_discord.xml │ ├── ic_menu_home.xml │ ├── ic_menu_proxy.xml │ ├── ic_menu_website.xml │ ├── ic_notification_logs.xml │ ├── nav_view_item_iconcolor.xml │ ├── nav_view_item_shapecolor.xml │ ├── nav_view_item_textcolor.xml │ └── side_nav_bar.xml │ ├── layout │ ├── activity_about.xml │ ├── activity_config_editor_advanced_pretty.xml │ ├── activity_config_editor_advanced_raw.xml │ ├── activity_config_editor_simple.xml │ ├── activity_main.xml │ ├── activity_settings.xml │ ├── activity_user_auths.xml │ ├── app_bar_main.xml │ ├── content_main.xml │ ├── dialog_user_auth.xml │ ├── fragment_home.xml │ ├── fragment_proxy.xml │ └── nav_header_main.xml │ ├── menu │ ├── activity_main_drawer.xml │ ├── config.xml │ └── main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── navigation │ └── mobile_navigation.xml │ ├── values │ ├── arrays.xml │ ├── colors.xml │ ├── dimens.xml │ ├── drawables.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── about_preferences.xml │ └── root_preferences.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── metadata ├── full_description.txt ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ └── 7.jpg ├── short_description.txt └── title.txt └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | **Describe the bug** 15 | 16 | 17 | **To Reproduce** 18 | 19 | 20 | 21 | 22 | 23 | 24 | **Expected behavior** 25 | 26 | 27 | **Screenshots / Videos** 28 | 29 | 30 | **Android OS Version** 31 | 32 | 33 | **Android App Version** 34 | 35 | 36 | **Geyser Version** 37 | 38 | 39 | **Minecraft: Bedrock Edition Version** 40 | 41 | 42 | **Additional Context** 43 | 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What feature do you want?** 11 | Add a description 12 | 13 | **Alternatives?** 14 | Any alternatives you have tried 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/git,java,gradle,eclipse,netbeans,jetbrains+all,android 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=git,java,gradle,eclipse,netbeans,jetbrains+all,android 4 | 5 | ### Android ### 6 | # Built application files 7 | *.apk 8 | *.aar 9 | *.ap_ 10 | *.aab 11 | 12 | # Files for the ART/Dalvik VM 13 | *.dex 14 | 15 | # Java class files 16 | *.class 17 | 18 | # Generated files 19 | bin/ 20 | gen/ 21 | out/ 22 | # Uncomment the following line in case you need and you don't have the release build type files in your app 23 | # release/ 24 | 25 | # Gradle files 26 | .gradle/ 27 | build/ 28 | 29 | # Local configuration file (sdk path, etc) 30 | local.properties 31 | 32 | # Proguard folder generated by Eclipse 33 | proguard/ 34 | 35 | # Log Files 36 | *.log 37 | 38 | # Android Studio Navigation editor temp files 39 | .navigation/ 40 | 41 | # Android Studio captures folder 42 | captures/ 43 | 44 | # IntelliJ 45 | *.iml 46 | .idea/workspace.xml 47 | .idea/tasks.xml 48 | .idea/gradle.xml 49 | .idea/assetWizardSettings.xml 50 | .idea/dictionaries 51 | .idea/libraries 52 | # Android Studio 3 in .gitignore file. 53 | .idea/caches 54 | .idea/modules.xml 55 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 56 | .idea/navEditor.xml 57 | 58 | # Keystore files 59 | # Uncomment the following lines if you do not want to check your keystore files in. 60 | #*.jks 61 | #*.keystore 62 | 63 | # External native build folder generated in Android Studio 2.2 and later 64 | .externalNativeBuild 65 | .cxx/ 66 | 67 | # Google Services (e.g. APIs or Firebase) 68 | # google-services.json 69 | 70 | # Freeline 71 | freeline.py 72 | freeline/ 73 | freeline_project_description.json 74 | 75 | # fastlane 76 | fastlane/report.xml 77 | fastlane/Preview.html 78 | fastlane/screenshots 79 | fastlane/test_output 80 | fastlane/readme.md 81 | 82 | # Version control 83 | vcs.xml 84 | 85 | # lint 86 | lint/intermediates/ 87 | lint/generated/ 88 | lint/outputs/ 89 | lint/tmp/ 90 | # lint/reports/ 91 | 92 | ### Android Patch ### 93 | gen-external-apklibs 94 | output.json 95 | 96 | # Replacement of .externalNativeBuild directories introduced 97 | # with Android Studio 3.5. 98 | 99 | ### Eclipse ### 100 | .metadata 101 | tmp/ 102 | *.tmp 103 | *.bak 104 | *.swp 105 | *~.nib 106 | .settings/ 107 | .loadpath 108 | .recommenders 109 | 110 | # External tool builders 111 | .externalToolBuilders/ 112 | 113 | # Locally stored "Eclipse launch configurations" 114 | *.launch 115 | 116 | # PyDev specific (Python IDE for Eclipse) 117 | *.pydevproject 118 | 119 | # CDT-specific (C/C++ Development Tooling) 120 | .cproject 121 | 122 | # CDT- autotools 123 | .autotools 124 | 125 | # Java annotation processor (APT) 126 | .factorypath 127 | 128 | # PDT-specific (PHP Development Tools) 129 | .buildpath 130 | 131 | # sbteclipse plugin 132 | .target 133 | 134 | # Tern plugin 135 | .tern-project 136 | 137 | # TeXlipse plugin 138 | .texlipse 139 | 140 | # STS (Spring Tool Suite) 141 | .springBeans 142 | 143 | # Code Recommenders 144 | .recommenders/ 145 | 146 | # Annotation Processing 147 | .apt_generated/ 148 | .apt_generated_test/ 149 | 150 | # Scala IDE specific (Scala & Java development for Eclipse) 151 | .cache-main 152 | .scala_dependencies 153 | .worksheet 154 | 155 | # Uncomment this line if you wish to ignore the project description file. 156 | # Typically, this file would be tracked if it contains build/dependency configurations: 157 | #.project 158 | 159 | ### Eclipse Patch ### 160 | # Spring Boot Tooling 161 | .sts4-cache/ 162 | 163 | ### Git ### 164 | # Created by git for backups. To disable backups in Git: 165 | # $ git config --global mergetool.keepBackup false 166 | *.orig 167 | 168 | # Created by git when using merge tools for conflicts 169 | *.BACKUP.* 170 | *.BASE.* 171 | *.LOCAL.* 172 | *.REMOTE.* 173 | *_BACKUP_*.txt 174 | *_BASE_*.txt 175 | *_LOCAL_*.txt 176 | *_REMOTE_*.txt 177 | 178 | ### Java ### 179 | # Compiled class file 180 | 181 | # Log file 182 | 183 | # BlueJ files 184 | *.ctxt 185 | 186 | # Mobile Tools for Java (J2ME) 187 | .mtj.tmp/ 188 | 189 | # Package Files # 190 | *.jar 191 | *.war 192 | *.nar 193 | *.ear 194 | *.zip 195 | *.tar.gz 196 | *.rar 197 | 198 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 199 | hs_err_pid* 200 | 201 | ### JetBrains+all ### 202 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 203 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 204 | 205 | # User-specific stuff 206 | .idea/**/workspace.xml 207 | .idea/**/tasks.xml 208 | .idea/**/usage.statistics.xml 209 | .idea/**/dictionaries 210 | .idea/**/shelf 211 | 212 | # Generated files 213 | .idea/**/contentModel.xml 214 | 215 | # Sensitive or high-churn files 216 | .idea/**/dataSources/ 217 | .idea/**/dataSources.ids 218 | .idea/**/dataSources.local.xml 219 | .idea/**/sqlDataSources.xml 220 | .idea/**/dynamic.xml 221 | .idea/**/uiDesigner.xml 222 | .idea/**/dbnavigator.xml 223 | 224 | # Gradle 225 | .idea/**/gradle.xml 226 | .idea/**/libraries 227 | 228 | # Gradle and Maven with auto-import 229 | # When using Gradle or Maven with auto-import, you should exclude module files, 230 | # since they will be recreated, and may cause churn. Uncomment if using 231 | # auto-import. 232 | # .idea/artifacts 233 | # .idea/compiler.xml 234 | # .idea/jarRepositories.xml 235 | # .idea/modules.xml 236 | # .idea/*.iml 237 | # .idea/modules 238 | # *.iml 239 | # *.ipr 240 | 241 | # CMake 242 | cmake-build-*/ 243 | 244 | # Mongo Explorer plugin 245 | .idea/**/mongoSettings.xml 246 | 247 | # File-based project format 248 | *.iws 249 | 250 | # IntelliJ 251 | 252 | # mpeltonen/sbt-idea plugin 253 | .idea_modules/ 254 | 255 | # JIRA plugin 256 | atlassian-ide-plugin.xml 257 | 258 | # Cursive Clojure plugin 259 | .idea/replstate.xml 260 | 261 | # Crashlytics plugin (for Android Studio and IntelliJ) 262 | com_crashlytics_export_strings.xml 263 | crashlytics.properties 264 | crashlytics-build.properties 265 | fabric.properties 266 | 267 | # Editor-based Rest Client 268 | .idea/httpRequests 269 | 270 | # Android studio 3.1+ serialized cache file 271 | .idea/caches/build_file_checksums.ser 272 | 273 | ### JetBrains+all Patch ### 274 | # Ignores the whole .idea folder and all .iml files 275 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 276 | 277 | # The below was ajusted to add in the copyright file config 278 | .idea/* 279 | !.idea/copyright/ 280 | 281 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 282 | 283 | modules.xml 284 | .idea/misc.xml 285 | *.ipr 286 | 287 | # Sonarlint plugin 288 | .idea/sonarlint 289 | 290 | ### NetBeans ### 291 | **/nbproject/private/ 292 | **/nbproject/Makefile-*.mk 293 | **/nbproject/Package-*.bash 294 | nbbuild/ 295 | dist/ 296 | nbdist/ 297 | .nb-gradle/ 298 | 299 | ### Gradle ### 300 | .gradle 301 | 302 | # Ignore Gradle GUI config 303 | gradle-app.setting 304 | 305 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 306 | !gradle-wrapper.jar 307 | 308 | # Cache of project 309 | .gradletasknamecache 310 | 311 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 312 | # gradle/wrapper/gradle-wrapper.properties 313 | 314 | ### Gradle Patch ### 315 | **/build/ 316 | 317 | # End of https://www.toptal.com/developers/gitignore/api/git,java,gradle,eclipse,netbeans,jetbrains+all,android 318 | 319 | app/release/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "app/src/main/res-locale"] 2 | path = app/src/main/res-locale 3 | url = https://github.com/GeyserMC/languages.git 4 | -------------------------------------------------------------------------------- /.idea/copyright/Geyser.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | label 'master' 4 | } 5 | 6 | tools { 7 | gradle 'Gradle 6' 8 | jdk 'Java 8' 9 | } 10 | 11 | parameters{ 12 | booleanParam(defaultValue: false, description: 'Skip Discord notification', name: 'SKIP_DISCORD') 13 | } 14 | 15 | options { 16 | buildDiscarder(logRotator(artifactNumToKeepStr: '20')) 17 | } 18 | 19 | stages { 20 | stage ('Build') { 21 | steps { 22 | sh 'git submodule update --init --recursive' 23 | sh 'gradle clean assembleRelease --refresh-dependencies' 24 | } 25 | } 26 | stage ('Sign') { 27 | steps { 28 | signAndroidApks ( 29 | apksToSign: '**/*-unsigned.apk', 30 | keyStoreId: 'GeyserAndroidKeys', 31 | keyAlias: 'geyserandroid', 32 | archiveSignedApks: true 33 | ) 34 | } 35 | } 36 | } 37 | 38 | post { 39 | always { 40 | script { 41 | def changeLogSets = currentBuild.changeSets 42 | def message = "**Changes:**" 43 | 44 | if (changeLogSets.size() == 0) { 45 | message += "\n*No changes.*" 46 | } else { 47 | def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "") 48 | def count = 0; 49 | def extra = 0; 50 | for (int i = 0; i < changeLogSets.size(); i++) { 51 | def entries = changeLogSets[i].items 52 | for (int j = 0; j < entries.length; j++) { 53 | if (count <= 10) { 54 | def entry = entries[j] 55 | def commitId = entry.commitId.substring(0, 6) 56 | message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}" 57 | count++ 58 | } else { 59 | extra++; 60 | } 61 | } 62 | } 63 | 64 | if (extra != 0) { 65 | message += "\n - ${extra} more commits" 66 | } 67 | } 68 | 69 | env.changes = message 70 | } 71 | deleteDir() 72 | script { 73 | if(!params.SKIP_DISCORD) { 74 | withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { 75 | discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/GeyserMC/job/GeyserAndroid/job/master/)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 GeyserMC. http://geysermc.org 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Geyser Android 2 | 3 | [![forthebadge made-with-java](https://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) 4 | 5 | [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 6 | [![Build Status](https://ci.opencollab.dev/job/GeyserMC/job/GeyserAndroid/job/master/badge/icon)](https://ci.opencollab.dev/job/GeyserMC/job/GeyserAndroid/job/master/) 7 | [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) 8 | [![HitCount](http://hits.dwyl.io/GeyserMC/GeyserAndroid.svg)](http://hits.dwyl.io/GeyserMC/GeyserAndroid) 9 | [![Trello](https://img.shields.io/badge/trello-geyser--android-blue)](https://trello.com/b/pPJpl9dZ/geyser-android) 10 | 11 | Geyser Android is an Android app version of Geyser. 12 | 13 | ## What is Geyser Android? 14 | Geyser Android is an Android app that supports a proxy server and running Geyser. 15 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | 3 | apply plugin: 'com.android.application' 4 | apply plugin: 'org.anarres.jarjar' 5 | apply plugin: "com.github.johnrengelman.shadow" 6 | 7 | android { 8 | compileSdkVersion 29 9 | buildToolsVersion "29.0.3" 10 | 11 | defaultConfig { 12 | applicationId "org.geysermc.geyser.android" 13 | minSdkVersion 24 14 | targetSdkVersion 29 15 | versionCode 103001 // Format 00.00.000 EG 1.0.1 -> 01.00.001 -> 0100001 -> 100001 16 | versionName "1.3.1" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | 27 | applicationVariants.all { variant -> 28 | variant.outputs.all { output -> 29 | def newName = outputFileName 30 | 31 | newName = newName.replace("app-", "${variant.mergedFlavor.applicationId}-") 32 | newName = newName.replace("-release", "-${variant.versionName}") 33 | newName = newName.replace("-debug", "-${variant.versionName}-debug") 34 | 35 | outputFileName = newName 36 | } 37 | } 38 | } 39 | 40 | compileOptions { 41 | coreLibraryDesugaringEnabled true 42 | 43 | sourceCompatibility JavaVersion.VERSION_1_8 44 | targetCompatibility JavaVersion.VERSION_1_8 45 | } 46 | 47 | packagingOptions { 48 | exclude 'META-INF/INDEX.LIST' 49 | exclude 'META-INF/io.netty.versions.properties' 50 | } 51 | 52 | configurations { 53 | nnio 54 | } 55 | 56 | sourceSets { 57 | main { 58 | res.srcDirs = [ 59 | 'src/main/res', 60 | 'src/main/res-locale' 61 | ] 62 | } 63 | } 64 | } 65 | 66 | repositories { 67 | mavenLocal() 68 | 69 | maven { 70 | url 'https://repo.opencollab.dev/maven-releases/' 71 | } 72 | 73 | maven { 74 | url 'https://repo.opencollab.dev/maven-snapshots/' 75 | } 76 | 77 | maven { 78 | url 'https://jitpack.io' 79 | } 80 | 81 | maven { 82 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 83 | } 84 | 85 | mavenCentral() 86 | } 87 | 88 | task nnioJar (type: ShadowJar) { 89 | getArchiveClassifier().set('nnio') 90 | 91 | relocate 'org.lukhnos.nnio.file', 'java.nio.file' 92 | relocate 'org.lukhnos.nnio.channels', 'java.nio.channels' 93 | 94 | configurations = [project.configurations.nnio] 95 | } 96 | 97 | dependencies { 98 | implementation fileTree(dir: "libs", include: ["*.jar"]) 99 | implementation 'androidx.appcompat:appcompat:1.2.0' 100 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 101 | implementation 'com.google.android.material:material:1.3.0' 102 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 103 | implementation 'androidx.navigation:navigation-fragment:2.3.3' 104 | implementation 'androidx.navigation:navigation-ui:2.3.3' 105 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 106 | implementation 'androidx.preference:preference:1.1.1' 107 | implementation 'com.android.volley:volley:1.2.0' 108 | 109 | testImplementation 'junit:junit:4.12' 110 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 111 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 112 | 113 | coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' 114 | 115 | compileOnly 'org.projectlombok:lombok:1.18.16' 116 | annotationProcessor 'org.projectlombok:lombok:1.18.16' 117 | 118 | implementation "com.fasterxml.jackson.core:jackson-databind:2.10.2" 119 | 120 | // Remove epoll, kqueue and fastutil from the protocol 121 | implementation('com.github.BedrockTogether:Protocol:f883229fa2') { 122 | exclude group: 'com.nukkitx.fastutil', module:'fastutil-common' 123 | exclude group: 'com.nukkitx.network', module:'raknet' 124 | exclude group: 'com.nukkitx.network', module:'common' 125 | exclude group: 'io.netty', module:'netty-transport-native-epoll' 126 | exclude group: 'io.netty', module:'netty-transport-native-kqueue' 127 | } 128 | 129 | // Include the updated common with the android fixes 130 | implementation 'com.nukkitx.network:common:1.6.26-20210217.205834-2' 131 | implementation 'com.nukkitx.network:raknet:1.6.26-20210217.205834-2' 132 | 133 | // Implement the cleaned fastutil manually to prevent the error on release builds 134 | implementation files("${buildDir}/jarjar/fastutil-cleaned.jar") { builtBy 'jarjar-repackage_fastutil-cleaned.jar' } 135 | 136 | // Remove the duplicate classes and include fastutil-common 137 | compileOnly jarjar.repackage('fastutil-cleaned.jar') { 138 | from 'com.nukkitx.fastutil:fastutil-common:8.5.2' 139 | 140 | // classDelete "it.unimi.dsi.fastutil.ints.IntIterator" 141 | // classDelete "it.unimi.dsi.fastutil.longs.LongIterator" 142 | // classDelete "it.unimi.dsi.fastutil.objects.ObjectIterator" 143 | // classDelete "it.unimi.dsi.fastutil.booleans.BooleanIterator" 144 | // classDelete "it.unimi.dsi.fastutil.bytes.ByteIterator" 145 | // classDelete "it.unimi.dsi.fastutil.doubles.DoubleIterator" 146 | // classDelete "it.unimi.dsi.fastutil.floats.FloatIterator" 147 | } 148 | 149 | // Load a remake of nnio to support earlier Android versions 150 | nnio 'com.github.rtm516:nnio:c7b291f4ca' 151 | implementation tasks.nnioJar.outputs.files 152 | 153 | // slf4j port for Android 154 | implementation 'org.slf4j:slf4j-android:1.7.30' 155 | } -------------------------------------------------------------------------------- /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/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 19 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/java/awt/image/BufferedImage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package java.awt.image; 27 | 28 | import android.graphics.Bitmap; 29 | 30 | import lombok.Getter; 31 | 32 | import static android.graphics.Bitmap.Config.ARGB_8888; 33 | 34 | /** 35 | * This class is a facade used by Geyser because 36 | * BufferedImage does not exist on Android. 37 | * So we implement it based on the Bitmap class. 38 | */ 39 | @SuppressWarnings({"unused", "RedundantSuppression"}) 40 | public class BufferedImage extends RenderedImage { 41 | 42 | public static final int TYPE_INT_ARGB = 2; 43 | 44 | @Getter 45 | private Bitmap bitmap; 46 | 47 | public BufferedImage(Bitmap bitmap) { 48 | this.bitmap = bitmap; 49 | } 50 | 51 | public BufferedImage(int width, int height, int format) { 52 | if (format != TYPE_INT_ARGB) { 53 | throw new IllegalArgumentException("BufferedImage format must be TYPE_INT_ARGB!"); 54 | } 55 | 56 | this.bitmap = Bitmap.createBitmap(width, height, ARGB_8888); 57 | } 58 | 59 | public int getWidth() { 60 | return bitmap.getWidth(); 61 | } 62 | 63 | public int getHeight() { 64 | return bitmap.getHeight(); 65 | } 66 | 67 | public void setRGB(int x, int y, int color) { 68 | bitmap.setPixel(x, y, color); 69 | } 70 | 71 | public int getRGB(int x, int y) { 72 | return bitmap.getPixel(x, y); 73 | } 74 | 75 | public void flush() { 76 | bitmap.recycle(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/java/awt/image/RenderedImage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package java.awt.image; 27 | 28 | /** 29 | * This is a dummy class just used for a method declaration 30 | */ 31 | public class RenderedImage { 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/java/util/Base64.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package java.util; 27 | 28 | import lombok.Getter; 29 | 30 | /** 31 | * This class is a facade used for support of earlier android versions. 32 | * java.util.Base64 wasn't included until API level 26, so this 33 | * class redirects the methods used to the android.util.Base64 class. 34 | */ 35 | @SuppressWarnings({"unused", "RedundantSuppression"}) 36 | public class Base64 { 37 | 38 | @Getter 39 | public static Encoder encoder = new Encoder(); 40 | @Getter 41 | public static Decoder decoder = new Decoder(); 42 | 43 | public static class Encoder { 44 | public String encodeToString(byte[] src) { 45 | return android.util.Base64.encodeToString(src, android.util.Base64.NO_WRAP); 46 | } 47 | } 48 | 49 | public static class Decoder { 50 | public byte[] decode(String src) { 51 | return android.util.Base64.decode(src, android.util.Base64.NO_WRAP); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/javax/imageio/ImageIO.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package javax.imageio; 27 | 28 | import android.graphics.Bitmap; 29 | import android.graphics.BitmapFactory; 30 | 31 | import java.awt.image.BufferedImage; 32 | import java.awt.image.RenderedImage; 33 | import java.io.File; 34 | import java.io.FileInputStream; 35 | import java.io.FileOutputStream; 36 | import java.io.IOException; 37 | import java.io.InputStream; 38 | import java.net.URL; 39 | 40 | /** 41 | * This class is a facade used by Geyser because 42 | * ImageIO does not exist on Android. 43 | */ 44 | @SuppressWarnings({"unused", "RedundantSuppression"}) 45 | public class ImageIO { 46 | 47 | public static BufferedImage read(URL input) throws IOException { 48 | if (input == null) { 49 | throw new IllegalArgumentException("input == null!"); 50 | } 51 | 52 | InputStream inputStream; 53 | try { 54 | inputStream = input.openStream(); 55 | } catch (IOException e) { 56 | throw new IOException("Can't get input stream from URL!", e); 57 | } 58 | 59 | return read(inputStream); 60 | } 61 | 62 | public static BufferedImage read(File input) throws IOException { 63 | if (input == null) { 64 | throw new IllegalArgumentException("input == null!"); 65 | } 66 | if (!input.canRead()) { 67 | throw new IOException("Can't read input file!"); 68 | } 69 | 70 | return read(new FileInputStream(input)); 71 | } 72 | 73 | public static BufferedImage read(InputStream input) throws IOException { 74 | if (input == null) { 75 | throw new IllegalArgumentException("input == null!"); 76 | } 77 | 78 | // if (input.available() == 0) { 79 | // return null; 80 | // } 81 | 82 | return new BufferedImage(BitmapFactory.decodeStream(input)); 83 | } 84 | 85 | @SuppressWarnings("SameReturnValue") 86 | public static boolean write(RenderedImage image, String format, File file) throws IOException { 87 | if (!format.equals("png")) { 88 | throw new IllegalArgumentException("ImageIO.write format must be png!"); 89 | } 90 | if (!(image instanceof BufferedImage)) { 91 | throw new IllegalArgumentException("ImageIO.write image must be BufferedImage!"); 92 | } 93 | 94 | FileOutputStream out = new FileOutputStream(file); 95 | ((BufferedImage) image).getBitmap().compress(Bitmap.CompressFormat.PNG, 100, out); 96 | out.close(); 97 | 98 | return true; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android; 27 | 28 | import android.content.Context; 29 | import android.content.Intent; 30 | import android.os.Bundle; 31 | import android.view.Menu; 32 | import android.view.MenuItem; 33 | 34 | import androidx.annotation.NonNull; 35 | import androidx.appcompat.app.AppCompatActivity; 36 | import androidx.appcompat.app.AppCompatDelegate; 37 | import androidx.appcompat.widget.Toolbar; 38 | import androidx.core.view.GravityCompat; 39 | import androidx.drawerlayout.widget.DrawerLayout; 40 | import androidx.navigation.NavController; 41 | import androidx.navigation.Navigation; 42 | import androidx.navigation.ui.AppBarConfiguration; 43 | import androidx.navigation.ui.NavigationUI; 44 | import androidx.preference.PreferenceManager; 45 | 46 | import com.google.android.material.navigation.NavigationView; 47 | 48 | import org.geysermc.geyser.android.ui.about.AboutActivity; 49 | import org.geysermc.geyser.android.ui.settings.SettingsActivity; 50 | import org.geysermc.geyser.android.utils.AndroidUtils; 51 | 52 | import lombok.Getter; 53 | 54 | public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { 55 | 56 | private AppBarConfiguration mAppBarConfiguration; 57 | private DrawerLayout drawer; 58 | private NavController navController; 59 | 60 | @Getter 61 | private static Context context; 62 | 63 | @Override 64 | protected void onCreate(Bundle savedInstanceState) { 65 | super.onCreate(savedInstanceState); 66 | 67 | // Set the static context 68 | context = this; 69 | 70 | setContentView(R.layout.activity_main); 71 | setTitle(getResources().getString(R.string.menu_home)); 72 | 73 | // Setup the theme 74 | String theme = PreferenceManager.getDefaultSharedPreferences(getContext()).getString("theme", "system"); 75 | if (theme != null) { 76 | switch (theme) { 77 | case "dark": 78 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); 79 | break; 80 | 81 | case "light": 82 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); 83 | break; 84 | 85 | default: 86 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); 87 | break; 88 | } 89 | } 90 | 91 | // Setup the action bar 92 | Toolbar toolbar = findViewById(R.id.toolbar); 93 | setSupportActionBar(toolbar); 94 | drawer = findViewById(R.id.drawer_layout); 95 | NavigationView navigationView = findViewById(R.id.nav_view); 96 | // Passing each menu ID as a set of Ids because each 97 | // menu should be considered as top level destinations. 98 | mAppBarConfiguration = new AppBarConfiguration.Builder( 99 | R.id.nav_home, R.id.nav_proxy) 100 | .setOpenableLayout(drawer) 101 | .build(); 102 | navController = Navigation.findNavController(this, R.id.nav_host_fragment); 103 | NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration); 104 | NavigationUI.setupWithNavController(navigationView, navController); 105 | 106 | // Setup the navigation listener 107 | setNavigationViewListener(); 108 | 109 | // Disable epoll kqueue from loading 110 | System.getProperties().put("disableNativeEventLoop", "disableNativeEventLoop"); 111 | 112 | Intent intent = getIntent(); 113 | int navElement = intent.getIntExtra("nav_element", -1); 114 | if (navElement != -1) { 115 | NavigationUI.onNavDestinationSelected(navigationView.getMenu().findItem(navElement), navController); 116 | } 117 | } 118 | 119 | @Override 120 | public boolean onCreateOptionsMenu(Menu menu) { 121 | // Inflate the menu; this adds items to the action bar if it is present. 122 | getMenuInflater().inflate(R.menu.main, menu); 123 | return true; 124 | } 125 | 126 | @Override 127 | public boolean onOptionsItemSelected(@NonNull MenuItem item) { 128 | switch (item.getItemId()) { 129 | case R.id.action_settings: 130 | Intent settingsIntent = new Intent(MainActivity.this, SettingsActivity.class); 131 | MainActivity.this.startActivity(settingsIntent); 132 | return true; 133 | case R.id.action_about: 134 | Intent aboutIntent = new Intent(MainActivity.this, AboutActivity.class); 135 | MainActivity.this.startActivity(aboutIntent); 136 | return true; 137 | default: 138 | break; 139 | } 140 | 141 | return super.onOptionsItemSelected(item); 142 | } 143 | 144 | @Override 145 | public boolean onSupportNavigateUp() { 146 | NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); 147 | return NavigationUI.navigateUp(navController, mAppBarConfiguration) 148 | || super.onSupportNavigateUp(); 149 | } 150 | 151 | private void setNavigationViewListener() { 152 | NavigationView navigationView = findViewById(R.id.nav_view); 153 | navigationView.setNavigationItemSelectedListener(this); 154 | } 155 | 156 | @Override 157 | public boolean onNavigationItemSelected(@NonNull MenuItem item) { 158 | switch (item.getItemId()) { 159 | case R.id.nav_website: 160 | AndroidUtils.showURL(getResources().getString(R.string.app_site)); 161 | drawer.closeDrawer(GravityCompat.START); 162 | return false; 163 | case R.id.nav_discord: 164 | AndroidUtils.showURL(getResources().getString(R.string.app_discord)); 165 | drawer.closeDrawer(GravityCompat.START); 166 | return false; 167 | } 168 | 169 | // Pass the select even onto the nav ui 170 | boolean handled = NavigationUI.onNavDestinationSelected(item, navController); 171 | 172 | // Close the drawer if it was handled 173 | if (handled) { 174 | drawer.closeDrawer(GravityCompat.START); 175 | } 176 | 177 | return handled; 178 | } 179 | } -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/proxy/PacketHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.proxy; 27 | 28 | import com.fasterxml.jackson.databind.JsonNode; 29 | import com.fasterxml.jackson.databind.node.JsonNodeType; 30 | import com.nimbusds.jose.JWSObject; 31 | import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory; 32 | import com.nukkitx.network.util.DisconnectReason; 33 | import com.nukkitx.protocol.bedrock.BedrockServerSession; 34 | import com.nukkitx.protocol.bedrock.handler.BedrockPacketHandler; 35 | import com.nukkitx.protocol.bedrock.packet.LoginPacket; 36 | import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; 37 | import com.nukkitx.protocol.bedrock.packet.ResourcePackClientResponsePacket; 38 | import com.nukkitx.protocol.bedrock.packet.ResourcePackStackPacket; 39 | import com.nukkitx.protocol.bedrock.packet.ResourcePacksInfoPacket; 40 | import com.nukkitx.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket; 41 | import com.nukkitx.protocol.bedrock.util.EncryptionUtils; 42 | 43 | import java.io.IOException; 44 | import java.security.interfaces.ECPublicKey; 45 | 46 | import static org.geysermc.geyser.android.utils.AndroidUtils.OBJECT_MAPPER; 47 | 48 | public class PacketHandler implements BedrockPacketHandler { 49 | 50 | private final BedrockServerSession session; 51 | private final ProxyServer masterServer; 52 | 53 | private Player player; 54 | 55 | public PacketHandler(BedrockServerSession session, ProxyServer masterServer) { 56 | this.session = session; 57 | this.masterServer = masterServer; 58 | 59 | session.addDisconnectHandler(this::disconnect); 60 | } 61 | 62 | public void disconnect(DisconnectReason reason) { 63 | if (player != null) { 64 | masterServer.getProxyLogger().info(player.getDisplayName() + " has disconnected from the master server (" + reason + ")"); 65 | masterServer.getPlayers().remove(player.getXuid()); 66 | } 67 | } 68 | 69 | @Override 70 | public boolean handle(LoginPacket packet) { 71 | // Check the protocol version is correct 72 | int protocol = packet.getProtocolVersion(); 73 | if (protocol != ProxyServer.CODEC.getProtocolVersion()) { 74 | PlayStatusPacket status = new PlayStatusPacket(); 75 | if (protocol > ProxyServer.CODEC.getProtocolVersion()) { 76 | status.setStatus(PlayStatusPacket.Status.LOGIN_FAILED_SERVER_OLD); 77 | } else { 78 | status.setStatus(PlayStatusPacket.Status.LOGIN_FAILED_CLIENT_OLD); 79 | } 80 | session.sendPacket(status); 81 | } 82 | 83 | // Set the session codec 84 | session.setPacketCodec(ProxyServer.CODEC); 85 | 86 | // Read the raw chain data 87 | JsonNode rawChainData; 88 | try { 89 | rawChainData = OBJECT_MAPPER.readTree(packet.getChainData().toByteArray()); 90 | } catch (IOException e) { 91 | throw new AssertionError("Unable to read chain data!"); 92 | } 93 | 94 | // Get the parsed chain data 95 | JsonNode chainData = rawChainData.get("chain"); 96 | if (chainData.getNodeType() != JsonNodeType.ARRAY) { 97 | throw new AssertionError("Invalid chain data!"); 98 | } 99 | 100 | try { 101 | // Parse the signed jws object 102 | JWSObject jwsObject; 103 | jwsObject = JWSObject.parse(chainData.get(chainData.size() - 1).asText()); 104 | 105 | // Read the JWS payload 106 | JsonNode payload = OBJECT_MAPPER.readTree(jwsObject.getPayload().toBytes()); 107 | 108 | // Check the identityPublicKey is there 109 | if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) { 110 | throw new AssertionError("Missing identity public key!"); 111 | } 112 | 113 | // Create an ECPublicKey from the identityPublicKey 114 | ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue()); 115 | 116 | // Get the skin data to validate the JWS token 117 | JWSObject skinData = JWSObject.parse(packet.getSkinData().toString()); 118 | if (skinData.verify(new DefaultJWSVerifierFactory().createJWSVerifier(skinData.getHeader(), identityPublicKey))) { 119 | // Make sure the client sent over the username, xuid and other info 120 | if (payload.get("extraData").getNodeType() != JsonNodeType.OBJECT) { 121 | throw new AssertionError("Missing client data"); 122 | } 123 | 124 | // Fetch the client data 125 | JsonNode extraData = payload.get("extraData"); 126 | 127 | // Create a new player and add it to the players list 128 | player = new Player(extraData, session); 129 | masterServer.getPlayers().put(player.getXuid(), player); 130 | 131 | // Tell the client we have logged in successfully 132 | PlayStatusPacket playStatusPacket = new PlayStatusPacket(); 133 | playStatusPacket.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS); 134 | session.sendPacket(playStatusPacket); 135 | 136 | // Tell the client there are no resourcepacks 137 | ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); 138 | session.sendPacket(resourcePacksInfo); 139 | } else { 140 | throw new AssertionError("Invalid identity public key!"); 141 | } 142 | } catch (Exception e) { 143 | // Disconnect the client 144 | session.disconnect("disconnectionScreen.internalError.cantConnect"); 145 | throw new AssertionError("Failed to login", e); 146 | } 147 | 148 | return false; 149 | } 150 | 151 | @Override 152 | public boolean handle(ResourcePackClientResponsePacket packet) { 153 | switch (packet.getStatus()) { 154 | case COMPLETED: 155 | masterServer.getProxyLogger().info("Logged in " + player.getDisplayName() + " (" + player.getXuid() + ", " + player.getIdentity() + ")"); 156 | player.sendStartGame(); 157 | break; 158 | case HAVE_ALL_PACKS: 159 | ResourcePackStackPacket stack = new ResourcePackStackPacket(); 160 | stack.setExperimentsPreviouslyToggled(false); 161 | stack.setForcedToAccept(false); 162 | stack.setGameVersion("*"); 163 | session.sendPacket(stack); 164 | break; 165 | default: 166 | session.disconnect("disconnectionScreen.resourcePack"); 167 | break; 168 | } 169 | 170 | return true; 171 | } 172 | 173 | @Override 174 | public boolean handle(SetLocalPlayerAsInitializedPacket packet) { 175 | masterServer.getProxyLogger().debug("Player initialized: " + player.getDisplayName()); 176 | 177 | player.connectToServer(ProxyServer.getInstance().getAddress(), ProxyServer.getInstance().getPort()); 178 | 179 | return false; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/proxy/PaletteManger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.proxy; 27 | 28 | import com.nukkitx.nbt.NBTOutputStream; 29 | import com.nukkitx.nbt.NbtList; 30 | import com.nukkitx.nbt.NbtMap; 31 | import com.nukkitx.nbt.NbtMapBuilder; 32 | import com.nukkitx.nbt.NbtUtils; 33 | 34 | import java.io.ByteArrayOutputStream; 35 | import java.io.IOException; 36 | 37 | public class PaletteManger { 38 | 39 | public static final NbtMap BIOMES_PALETTE; 40 | public static final byte[] EMPTY_LEVEL_CHUNK_DATA; 41 | 42 | private static final NbtMap EMPTY_TAG = NbtMap.EMPTY; 43 | 44 | static { 45 | /* Load biomes */ 46 | // Build a fake plains biome entry 47 | NbtMapBuilder plainsBuilder = NbtMap.builder(); 48 | plainsBuilder.putFloat("blue_spores", 0f); 49 | plainsBuilder.putFloat("white_ash", 0f); 50 | plainsBuilder.putFloat("ash", 0f); 51 | plainsBuilder.putFloat("temperature", 0f); 52 | plainsBuilder.putFloat("red_spores", 0f); 53 | plainsBuilder.putFloat("downfall", 0f); 54 | 55 | plainsBuilder.put("minecraft:overworld_generation_rules", NbtMap.EMPTY); 56 | plainsBuilder.put("minecraft:climate", NbtMap.EMPTY); 57 | plainsBuilder.put("tags", NbtList.EMPTY); 58 | 59 | // Add the fake plains to the map 60 | NbtMapBuilder biomesBuilder = NbtMap.builder(); 61 | biomesBuilder.put("plains", plainsBuilder.build()); 62 | 63 | // Build the biomes palette 64 | BIOMES_PALETTE = biomesBuilder.build(); 65 | 66 | /* Create empty chunk data */ 67 | try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { 68 | outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size 69 | 70 | try (NBTOutputStream nbtOutputStream = NbtUtils.createNetworkWriter(outputStream)) { 71 | nbtOutputStream.writeTag(EMPTY_TAG); 72 | } 73 | 74 | EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray(); 75 | } catch (IOException e) { 76 | throw new AssertionError("Unable to generate empty level chunk data"); 77 | } 78 | } 79 | 80 | @SuppressWarnings("EmptyMethod") 81 | public static void init() { 82 | // no-op 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/proxy/Player.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.proxy; 27 | 28 | import com.fasterxml.jackson.databind.JsonNode; 29 | import com.nukkitx.math.vector.Vector2f; 30 | import com.nukkitx.math.vector.Vector3f; 31 | import com.nukkitx.math.vector.Vector3i; 32 | import com.nukkitx.protocol.bedrock.BedrockServerSession; 33 | import com.nukkitx.protocol.bedrock.data.AuthoritativeMovementMode; 34 | import com.nukkitx.protocol.bedrock.data.GamePublishSetting; 35 | import com.nukkitx.protocol.bedrock.data.GameRuleData; 36 | import com.nukkitx.protocol.bedrock.data.GameType; 37 | import com.nukkitx.protocol.bedrock.data.PlayerPermission; 38 | import com.nukkitx.protocol.bedrock.data.SyncedPlayerMovementSettings; 39 | import com.nukkitx.protocol.bedrock.data.inventory.ItemData; 40 | import com.nukkitx.protocol.bedrock.packet.BiomeDefinitionListPacket; 41 | import com.nukkitx.protocol.bedrock.packet.CreativeContentPacket; 42 | import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; 43 | import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; 44 | import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; 45 | import com.nukkitx.protocol.bedrock.packet.StartGamePacket; 46 | import com.nukkitx.protocol.bedrock.packet.TransferPacket; 47 | 48 | import java.net.InetSocketAddress; 49 | import java.util.UUID; 50 | 51 | import lombok.Getter; 52 | 53 | @Getter 54 | public class Player { 55 | 56 | private final String xuid; 57 | private final UUID identity; 58 | private final String displayName; 59 | 60 | private final BedrockServerSession session; 61 | 62 | public Player(JsonNode extraData, BedrockServerSession session) { 63 | this.xuid = extraData.get("XUID").asText(); 64 | this.identity = UUID.fromString(extraData.get("identity").asText()); 65 | this.displayName = extraData.get("displayName").asText(); 66 | 67 | this.session = session; 68 | } 69 | 70 | /** 71 | * Send a few different packets to get the client to load in 72 | */ 73 | public void sendStartGame() { 74 | // A lot of this likely doesn't need to be changed 75 | StartGamePacket startGamePacket = new StartGamePacket(); 76 | startGamePacket.setUniqueEntityId(1); 77 | startGamePacket.setRuntimeEntityId(1); 78 | startGamePacket.setPlayerGameType(GameType.DEFAULT); 79 | startGamePacket.setPlayerPosition(Vector3f.from(0, 64 + 2, 0)); 80 | startGamePacket.setRotation(Vector2f.ONE); 81 | 82 | startGamePacket.setSeed(-1); 83 | startGamePacket.setDimensionId(2); 84 | startGamePacket.setGeneratorId(1); 85 | startGamePacket.setLevelGameType(GameType.DEFAULT); 86 | startGamePacket.setDifficulty(0); 87 | startGamePacket.setDefaultSpawn(Vector3i.ZERO); 88 | startGamePacket.setAchievementsDisabled(true); 89 | startGamePacket.setCurrentTick(-1); 90 | 91 | startGamePacket.setEduEditionOffers(0); 92 | startGamePacket.setEduFeaturesEnabled(false); 93 | startGamePacket.setRainLevel(0); 94 | startGamePacket.setLightningLevel(0); 95 | 96 | startGamePacket.setMultiplayerGame(true); 97 | startGamePacket.setBroadcastingToLan(true); 98 | startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", true)); 99 | startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC); 100 | startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC); 101 | startGamePacket.setCommandsEnabled(true); 102 | startGamePacket.setTexturePacksRequired(false); 103 | startGamePacket.setBonusChestEnabled(false); 104 | startGamePacket.setStartingWithMap(false); 105 | startGamePacket.setTrustingPlayers(true); 106 | startGamePacket.setDefaultPlayerPermission(PlayerPermission.VISITOR); 107 | startGamePacket.setServerChunkTickRange(4); 108 | startGamePacket.setBehaviorPackLocked(false); 109 | startGamePacket.setResourcePackLocked(false); 110 | startGamePacket.setFromLockedWorldTemplate(false); 111 | startGamePacket.setUsingMsaGamertagsOnly(false); 112 | startGamePacket.setFromWorldTemplate(false); 113 | startGamePacket.setWorldTemplateOptionLocked(false); 114 | 115 | SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings(); 116 | settings.setMovementMode(AuthoritativeMovementMode.CLIENT); 117 | settings.setRewindHistorySize(0); 118 | settings.setServerAuthoritativeBlockBreaking(false); 119 | startGamePacket.setPlayerMovementSettings(settings); 120 | startGamePacket.setVanillaVersion("1.17.40"); 121 | 122 | startGamePacket.setLevelId("world"); 123 | startGamePacket.setLevelName("world"); 124 | startGamePacket.setPremiumWorldTemplateId("00000000-0000-0000-0000-000000000000"); 125 | startGamePacket.setCurrentTick(0); 126 | startGamePacket.setEnchantmentSeed(0); 127 | startGamePacket.setMultiplayerCorrelationId(""); 128 | startGamePacket.setServerEngine(""); 129 | 130 | session.sendPacket(startGamePacket); 131 | 132 | // Send an empty chunk 133 | LevelChunkPacket data = new LevelChunkPacket(); 134 | data.setChunkX(0); 135 | data.setChunkZ(0); 136 | data.setSubChunksLength(0); 137 | data.setData(PaletteManger.EMPTY_LEVEL_CHUNK_DATA); 138 | data.setCachingEnabled(false); 139 | session.sendPacket(data); 140 | 141 | // Send a CreativeContentPacket - required for 1.16.100 142 | CreativeContentPacket creativeContentPacket = new CreativeContentPacket(); 143 | creativeContentPacket.setContents(new ItemData[0]); 144 | session.sendPacket(creativeContentPacket); 145 | 146 | // Send the biomes 147 | BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket(); 148 | biomeDefinitionListPacket.setDefinitions(PaletteManger.BIOMES_PALETTE); 149 | session.sendPacket(biomeDefinitionListPacket); 150 | 151 | // Let the client know the player can spawn 152 | PlayStatusPacket playStatusPacket = new PlayStatusPacket(); 153 | playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); 154 | session.sendPacket(playStatusPacket); 155 | 156 | // Freeze the player 157 | SetEntityMotionPacket setEntityMotionPacket = new SetEntityMotionPacket(); 158 | setEntityMotionPacket.setRuntimeEntityId(1); 159 | setEntityMotionPacket.setMotion(Vector3f.ZERO); 160 | session.sendPacket(setEntityMotionPacket); 161 | } 162 | 163 | /** 164 | * Send the player to the another server 165 | */ 166 | public void connectToServer(String address, int port) { 167 | ProxyServer.getInstance().getProxyLogger().info("Sending server transfer packet to " + displayName); 168 | 169 | // Create an InetSocketAddress to reduce issues with hostnames for PS4 170 | // Thanks Extollite 171 | InetSocketAddress socketAddress = new InetSocketAddress(address, port); 172 | 173 | TransferPacket transferPacket = new TransferPacket(); 174 | transferPacket.setAddress(socketAddress.getAddress().getHostAddress()); 175 | transferPacket.setPort(socketAddress.getPort()); 176 | session.sendPacket(transferPacket); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/proxy/ProxyLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.proxy; 27 | 28 | import org.geysermc.geyser.android.utils.EventListeners; 29 | 30 | import lombok.Getter; 31 | import lombok.Setter; 32 | 33 | public class ProxyLogger { 34 | 35 | @Getter 36 | private static String log = ""; 37 | 38 | @Setter 39 | private static EventListeners.LogEventListener listener; 40 | 41 | public void warning(String message) { 42 | log += "WARN - " + message + "\n"; 43 | if (listener != null) listener.onLogLine("WARN - " + message); 44 | // System.out.println("WARN - " + message); 45 | } 46 | 47 | public void info(String message) { 48 | log += "INFO - " + message + "\n"; 49 | if (listener != null) listener.onLogLine("INFO - " + message); 50 | // System.out.println("INFO - " + message); 51 | } 52 | 53 | public void error(String message, Throwable error) { 54 | log += "ERROR - " + message + "\n"; 55 | if (listener != null) listener.onLogLine("ERROR - " + message); 56 | // System.out.println("ERROR - " + message + " - " + error.getMessage()); 57 | // error.printStackTrace(); 58 | } 59 | 60 | public void debug(String message) { 61 | log += "DEBUG - " + message + "\n"; 62 | if (listener != null) listener.onLogLine ("DEBUG - " + message); 63 | // System.out.println("DEBUG - " + message); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/proxy/ProxyServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.proxy; 27 | 28 | import android.content.Context; 29 | 30 | import com.nukkitx.protocol.bedrock.BedrockPacketCodec; 31 | import com.nukkitx.protocol.bedrock.BedrockPong; 32 | import com.nukkitx.protocol.bedrock.BedrockServer; 33 | import com.nukkitx.protocol.bedrock.BedrockServerEventHandler; 34 | import com.nukkitx.protocol.bedrock.BedrockServerSession; 35 | import com.nukkitx.protocol.bedrock.v431.Bedrock_v431; 36 | import com.nukkitx.protocol.bedrock.v440.Bedrock_v440; 37 | import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; 38 | 39 | import org.geysermc.geyser.android.R; 40 | import org.geysermc.geyser.android.utils.EventListeners; 41 | 42 | import java.net.InetSocketAddress; 43 | import java.util.ArrayList; 44 | import java.util.HashMap; 45 | import java.util.List; 46 | import java.util.Map; 47 | import java.util.Timer; 48 | import java.util.TimerTask; 49 | import java.util.concurrent.Executors; 50 | import java.util.concurrent.ScheduledExecutorService; 51 | 52 | import lombok.Getter; 53 | 54 | public class ProxyServer { 55 | 56 | public static final BedrockPacketCodec CODEC = Bedrock_v475.V475_CODEC; 57 | 58 | private BedrockServer bdServer; 59 | private BedrockPong bdPong; 60 | 61 | @Getter 62 | private boolean shuttingDown = false; 63 | 64 | @Getter 65 | private static ProxyServer instance; 66 | 67 | @Getter 68 | private ProxyLogger proxyLogger; 69 | 70 | @Getter 71 | private ScheduledExecutorService generalThreadPool; 72 | 73 | @Getter 74 | private final Map players = new HashMap<>(); 75 | 76 | @Getter 77 | private final String address; 78 | 79 | @Getter 80 | private final int port; 81 | 82 | @Getter 83 | private final Context ctx; 84 | 85 | @Getter 86 | private static final List onDisableListeners = new ArrayList<>(); 87 | 88 | public ProxyServer(String address, int port, Context ctx) { 89 | this.address = address; 90 | this.port = port; 91 | this.ctx = ctx; 92 | } 93 | 94 | public void onEnable() { 95 | instance = this; 96 | 97 | proxyLogger = new ProxyLogger(); 98 | 99 | this.generalThreadPool = Executors.newScheduledThreadPool(32); 100 | 101 | // Start a timer to keep the thread running 102 | Timer timer = new Timer(); 103 | TimerTask task = new TimerTask() { public void run() { } }; 104 | timer.scheduleAtFixedRate(task, 0L, 1000L); 105 | 106 | // Initialise the palettes 107 | PaletteManger.init(); 108 | 109 | start(); 110 | } 111 | 112 | public void onDisable() { 113 | this.shutdown(); 114 | 115 | for (EventListeners.OnDisableEventListener onDisableListener : onDisableListeners) { 116 | if (onDisableListener != null) onDisableListener.onDisable(); 117 | } 118 | } 119 | 120 | private void start() { 121 | proxyLogger.info(ctx.getResources().getString(R.string.proxy_starting) + "..."); 122 | 123 | InetSocketAddress bindAddress = new InetSocketAddress("0.0.0.0", 19132); 124 | bdServer = new BedrockServer(bindAddress); 125 | 126 | bdPong = new BedrockPong(); 127 | bdPong.setEdition("MCPE"); 128 | bdPong.setMotd(ctx.getResources().getString(R.string.menu_proxy)); 129 | bdPong.setSubMotd(ctx.getResources().getString(R.string.menu_proxy)); 130 | bdPong.setPlayerCount(0); 131 | bdPong.setMaximumPlayerCount(1337); 132 | bdPong.setGameType("Survival"); 133 | bdPong.setIpv4Port(19132); 134 | bdPong.setProtocolVersion(ProxyServer.CODEC.getProtocolVersion()); 135 | bdPong.setVersion(ProxyServer.CODEC.getMinecraftVersion()); 136 | 137 | bdServer.setHandler(new BedrockServerEventHandler() { 138 | @Override 139 | public boolean onConnectionRequest(InetSocketAddress address) { 140 | return true; // Connection will be accepted 141 | } 142 | 143 | @Override 144 | public BedrockPong onQuery(InetSocketAddress address) { 145 | return bdPong; 146 | } 147 | 148 | @Override 149 | public void onSessionCreation(BedrockServerSession session) { 150 | session.setPacketHandler(new PacketHandler(session, instance)); 151 | } 152 | }); 153 | 154 | // Start server up 155 | bdServer.bind().join(); 156 | proxyLogger.info(String.format(ctx.getResources().getString(R.string.proxy_started), "0.0.0.0:19132")); 157 | } 158 | 159 | public void shutdown() { 160 | proxyLogger.info(ctx.getResources().getString(R.string.proxy_shutdown)); 161 | shuttingDown = true; 162 | 163 | bdServer.close(); 164 | generalThreadPool.shutdown(); 165 | instance = null; 166 | proxyLogger.info(ctx.getResources().getString(R.string.proxy_shutdown_done)); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/service/ProxyService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.service; 27 | 28 | import android.app.Notification; 29 | import android.app.NotificationChannel; 30 | import android.app.NotificationManager; 31 | import android.app.PendingIntent; 32 | import android.app.Service; 33 | import android.content.Intent; 34 | import android.content.SharedPreferences; 35 | import android.os.Build; 36 | import android.os.IBinder; 37 | 38 | import androidx.annotation.Nullable; 39 | import androidx.core.app.NotificationCompat; 40 | import androidx.preference.PreferenceManager; 41 | 42 | import org.geysermc.geyser.android.MainActivity; 43 | import org.geysermc.geyser.android.R; 44 | import org.geysermc.geyser.android.proxy.ProxyServer; 45 | import org.geysermc.geyser.android.utils.EventListeners; 46 | 47 | import lombok.Getter; 48 | import lombok.Setter; 49 | 50 | public class ProxyService extends Service { 51 | 52 | private final int NOTIFCATION_ID = 1338; 53 | private final String ACTION_STOP_SERVICE = "STOP_PROXY_SERVICE"; 54 | 55 | private ProxyServer proxy; 56 | 57 | @Getter 58 | private static boolean finishedStartup; 59 | 60 | @Setter 61 | private static EventListeners.StartedEventListener listener; 62 | 63 | @Override 64 | public void onCreate() { 65 | super.onCreate(); 66 | 67 | finishedStartup = false; 68 | 69 | Intent openLogs = new Intent(this, MainActivity.class); 70 | openLogs.putExtra("nav_element", R.id.nav_proxy); 71 | 72 | PendingIntent openLogsPendingIntent = PendingIntent.getActivity(this, NOTIFCATION_ID + 1, openLogs, 0); 73 | 74 | Intent stopSelf = new Intent(this, ProxyService.class); 75 | stopSelf.setAction(this.ACTION_STOP_SERVICE); 76 | 77 | PendingIntent stopPendingIntent = PendingIntent.getService(this, NOTIFCATION_ID + 2, stopSelf, PendingIntent.FLAG_CANCEL_CURRENT); 78 | 79 | Notification notification = new NotificationCompat.Builder(this, "proxy_channel") 80 | .setSmallIcon(R.drawable.ic_menu_proxy) 81 | .setContentTitle(getResources().getString(R.string.proxy_background_notification)) 82 | .addAction(R.drawable.ic_notification_logs, getResources().getString(R.string.proxy_logs), openLogsPendingIntent) 83 | .addAction(R.drawable.ic_menu_manage, getResources().getString(R.string.proxy_stop), stopPendingIntent) 84 | .build(); 85 | 86 | createNotificationChannel(); 87 | startForeground(NOTIFCATION_ID, notification); 88 | 89 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 90 | ProxyServer.getOnDisableListeners().add(this::stopSelf); 91 | proxy = new ProxyServer(sharedPreferences.getString("proxy_address", getResources().getString(R.string.default_ip)), Integer.parseInt(sharedPreferences.getString("proxy_port", getResources().getString(R.string.default_port_be))), this); 92 | } 93 | 94 | @Override 95 | public void onDestroy() { 96 | super.onDestroy(); 97 | 98 | ProxyServer.getInstance().onDisable(); 99 | } 100 | 101 | @Override 102 | public int onStartCommand(Intent intent, int flags, int startId) { 103 | if (ACTION_STOP_SERVICE.equals(intent.getAction())) { 104 | if (finishedStartup) { 105 | stopSelf(); 106 | } 107 | } else { 108 | Runnable runnable = () -> { 109 | try { 110 | proxy.onEnable(); 111 | if (listener != null) listener.onStarted(false); 112 | finishedStartup = true; 113 | } catch (Exception e) { 114 | if (listener != null) listener.onStarted(true); 115 | stopForeground(true); 116 | } 117 | }; 118 | Thread proxyThread = new Thread(runnable); 119 | proxyThread.start(); 120 | } 121 | 122 | return super.onStartCommand(intent, flags, startId); 123 | } 124 | 125 | @Nullable 126 | @Override 127 | public IBinder onBind(Intent intent) { 128 | return null; 129 | } 130 | 131 | private void createNotificationChannel() { 132 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 133 | NotificationChannel notificationChannel = new NotificationChannel("proxy_channel", getResources().getString(R.string.menu_proxy), NotificationManager.IMPORTANCE_DEFAULT); 134 | NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 135 | notificationManager.createNotificationChannel(notificationChannel); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/ui/about/AboutActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.ui.about; 27 | 28 | import android.os.Bundle; 29 | import android.view.MenuItem; 30 | 31 | import androidx.appcompat.app.ActionBar; 32 | import androidx.appcompat.app.AppCompatActivity; 33 | 34 | import org.geysermc.geyser.android.R; 35 | 36 | public class AboutActivity extends AppCompatActivity { 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_about); 42 | getSupportFragmentManager() 43 | .beginTransaction() 44 | .replace(R.id.about, new AboutFragment()) 45 | .commit(); 46 | ActionBar actionBar = getSupportActionBar(); 47 | if (actionBar != null) { 48 | actionBar.setDisplayHomeAsUpEnabled(true); 49 | } 50 | } 51 | 52 | @Override 53 | public boolean onOptionsItemSelected(MenuItem item) { 54 | // The back button 55 | if (item.getItemId() == android.R.id.home) { 56 | super.onBackPressed(); 57 | return true; 58 | } 59 | return super.onOptionsItemSelected(item); 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/ui/about/AboutFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.ui.about; 27 | 28 | import android.os.Bundle; 29 | 30 | import androidx.preference.Preference; 31 | import androidx.preference.PreferenceFragmentCompat; 32 | 33 | import org.geysermc.geyser.android.BuildConfig; 34 | import org.geysermc.geyser.android.R; 35 | import org.geysermc.geyser.android.utils.AndroidUtils; 36 | 37 | public class AboutFragment extends PreferenceFragmentCompat { 38 | 39 | @Override 40 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 41 | setPreferencesFromResource(R.xml.about_preferences, rootKey); 42 | 43 | findPreference("version").setSummary(BuildConfig.VERSION_NAME); 44 | } 45 | 46 | @Override 47 | public boolean onPreferenceTreeClick(Preference preference) { 48 | switch (preference.getKey()) { 49 | // About 50 | case "version": 51 | AndroidUtils.setClipboard(requireContext(), BuildConfig.VERSION_NAME); 52 | AndroidUtils.showToast(getContext(), getResources().getString(R.string.about_version_copied)); 53 | return true; 54 | 55 | // Links 56 | case "github": 57 | AndroidUtils.showURL("https://github.com/GeyserMC/GeyserAndroid"); 58 | return true; 59 | case "trello": 60 | AndroidUtils.showURL("https://trello.com/b/pPJpl9dZ/geyser-android"); 61 | return true; 62 | case "ci": 63 | AndroidUtils.showURL("https://ci.opencollab.dev/job/GeyserMC/job/GeyserAndroid/"); 64 | return true; 65 | 66 | // Credits 67 | case "rtm516": 68 | AndroidUtils.showURL("https://rtm516.co.uk/"); 69 | return true; 70 | case "arcratist": 71 | AndroidUtils.showURL("https://github.com/Arcratist/"); 72 | return true; 73 | case "geyser": 74 | AndroidUtils.showURL(getResources().getString(R.string.app_site)); 75 | return true; 76 | 77 | // Licences 78 | case "fontawesome": 79 | AndroidUtils.showURL("https://fontawesome.com/license"); 80 | return true; 81 | default: 82 | break; 83 | } 84 | 85 | return super.onPreferenceTreeClick(preference); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/ui/home/HomeFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.ui.home; 27 | 28 | import android.os.Bundle; 29 | import android.view.LayoutInflater; 30 | import android.view.Menu; 31 | import android.view.View; 32 | import android.view.ViewGroup; 33 | import android.widget.Button; 34 | 35 | import androidx.annotation.NonNull; 36 | import androidx.fragment.app.Fragment; 37 | import androidx.navigation.NavController; 38 | import androidx.navigation.fragment.NavHostFragment; 39 | import androidx.navigation.ui.NavigationUI; 40 | 41 | import com.google.android.material.navigation.NavigationView; 42 | 43 | import org.geysermc.geyser.android.R; 44 | 45 | public class HomeFragment extends Fragment { 46 | 47 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 48 | View root = inflater.inflate(R.layout.fragment_home, container, false); 49 | 50 | Button btnJoinBE = root.findViewById(R.id.btnJoinBE); 51 | 52 | // Get the menu and nav controller 53 | Menu menu = ((NavigationView) requireActivity().findViewById(R.id.nav_view)).getMenu(); 54 | NavController navController = ((NavHostFragment) getParentFragment()).getNavController(); 55 | 56 | // Setup the join BE button 57 | btnJoinBE.setOnClickListener(v -> NavigationUI.onNavDestinationSelected(menu.findItem(R.id.nav_proxy), navController)); 58 | 59 | return root; 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/ui/proxy/ProxyFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.ui.proxy; 27 | 28 | import android.content.Intent; 29 | import android.content.SharedPreferences; 30 | import android.os.Bundle; 31 | import android.text.method.ScrollingMovementMethod; 32 | import android.view.LayoutInflater; 33 | import android.view.View; 34 | import android.view.ViewGroup; 35 | import android.widget.Button; 36 | import android.widget.TextView; 37 | 38 | import androidx.annotation.NonNull; 39 | import androidx.core.content.ContextCompat; 40 | import androidx.fragment.app.Fragment; 41 | import androidx.preference.PreferenceManager; 42 | 43 | import org.geysermc.geyser.android.BuildConfig; 44 | import org.geysermc.geyser.android.R; 45 | import org.geysermc.geyser.android.proxy.ProxyLogger; 46 | import org.geysermc.geyser.android.proxy.ProxyServer; 47 | import org.geysermc.geyser.android.service.ProxyService; 48 | import org.geysermc.geyser.android.utils.AndroidUtils; 49 | 50 | public class ProxyFragment extends Fragment { 51 | 52 | private SharedPreferences sharedPreferences; 53 | 54 | private TextView txtAddress; 55 | private TextView txtPort; 56 | private Button btnStartStop; 57 | private TextView txtLogs; 58 | 59 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 60 | View root = inflater.inflate(R.layout.fragment_proxy, container, false); 61 | 62 | sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); 63 | txtAddress = root.findViewById(R.id.txtAddress); 64 | txtPort = root.findViewById(R.id.txtPort); 65 | btnStartStop = root.findViewById(R.id.btnStartStop); 66 | txtLogs = root.findViewById(R.id.txtLogs); 67 | 68 | // Set the movement method for the logs 69 | txtLogs.setMovementMethod(new ScrollingMovementMethod()); 70 | 71 | // Set the initial text for all the UI elements 72 | txtLogs.setText(ProxyLogger.getLog()); 73 | txtAddress.setText(sharedPreferences.getString("proxy_address", getResources().getString(R.string.default_ip))); 74 | txtPort.setText(sharedPreferences.getString("proxy_port", getResources().getString(R.string.default_port_be))); 75 | 76 | // Check if the server is already running 77 | if (ProxyServer.getInstance() != null && !ProxyServer.getInstance().isShuttingDown()) { 78 | // Check if the server is still starting 79 | if (ProxyService.isFinishedStartup()) { 80 | btnStartStop.setText(container.getResources().getString(R.string.proxy_stop)); 81 | } else { 82 | btnStartStop.setText(container.getResources().getString(R.string.proxy_starting)); 83 | btnStartStop.setEnabled(false); 84 | } 85 | 86 | txtAddress.setEnabled(false); 87 | txtPort.setEnabled(false); 88 | 89 | // Setup the listeners for the current screen 90 | setupListeners(container); 91 | } 92 | 93 | // Update the preference when the user has finished changing 94 | txtAddress.addTextChangedListener(AndroidUtils.generateAfterTextChange((editable) -> sharedPreferences.edit().putString("proxy_address", editable.toString()).apply())); 95 | 96 | // Update the preference when the user has finished changing 97 | txtPort.addTextChangedListener(AndroidUtils.generateAfterTextChange((editable) -> sharedPreferences.edit().putString("proxy_port", editable.toString()).apply())); 98 | 99 | btnStartStop.setOnClickListener(v -> { 100 | Button self = (Button) v; 101 | if (ProxyServer.getInstance() != null && !ProxyServer.getInstance().isShuttingDown()) { 102 | Intent serviceIntent = new Intent(getContext(), ProxyService.class); 103 | getContext().stopService(serviceIntent); 104 | 105 | self.setText(container.getResources().getString(R.string.proxy_start)); 106 | txtAddress.setEnabled(true); 107 | txtPort.setEnabled(true); 108 | } else { 109 | self.setText(container.getResources().getString(R.string.proxy_starting)); 110 | self.setEnabled(false); 111 | txtAddress.setEnabled(false); 112 | txtPort.setEnabled(false); 113 | 114 | // Clear all the current disable listeners to preserve memory usage 115 | ProxyServer.getOnDisableListeners().clear(); 116 | 117 | // Setup the listeners for the current screen 118 | setupListeners(container); 119 | 120 | // Start the proxy service 121 | Intent serviceIntent = new Intent(getContext(), ProxyService.class); 122 | ContextCompat.startForegroundService(getContext(), serviceIntent); 123 | } 124 | }); 125 | 126 | return root; 127 | } 128 | 129 | /** 130 | * Setup the listeners for all the events of the logger and service 131 | * 132 | * @param container The container to use for getting resources 133 | */ 134 | private void setupListeners(ViewGroup container) { 135 | // When we have a new log line add it to txtLogs 136 | ProxyLogger.setListener(line -> { 137 | if (txtLogs != null) { 138 | // If we are in debug then print logs to the console aswell 139 | if (BuildConfig.DEBUG) { 140 | System.out.println(AndroidUtils.purgeColorCodes(line)); 141 | } 142 | 143 | AndroidUtils.runOnUiThread(getActivity(), () -> txtLogs.append(AndroidUtils.purgeColorCodes(line) + "\n")); 144 | } 145 | }); 146 | 147 | // When the server is disabled toggle the button 148 | ProxyServer.getOnDisableListeners().add(() -> AndroidUtils.runOnUiThread(getActivity(), () -> { 149 | btnStartStop.setText(container.getResources().getString(R.string.proxy_start)); 150 | txtAddress.setEnabled(true); 151 | txtPort.setEnabled(true); 152 | })); 153 | 154 | // When the server has started and its failed status 155 | ProxyService.setListener((failed) -> AndroidUtils.runOnUiThread(getActivity(), () -> { 156 | if (failed) { 157 | btnStartStop.setText(container.getResources().getString(R.string.proxy_start)); 158 | btnStartStop.setEnabled(true); 159 | txtAddress.setEnabled(true); 160 | txtPort.setEnabled(true); 161 | } else { 162 | btnStartStop.setText(container.getResources().getString(R.string.proxy_stop)); 163 | btnStartStop.setEnabled(true); 164 | } 165 | })); 166 | } 167 | } -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/ui/settings/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.ui.settings; 27 | 28 | import android.os.Bundle; 29 | import android.view.MenuItem; 30 | 31 | import androidx.appcompat.app.ActionBar; 32 | import androidx.appcompat.app.AppCompatActivity; 33 | 34 | import org.geysermc.geyser.android.R; 35 | 36 | public class SettingsActivity extends AppCompatActivity { 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_settings); 42 | getSupportFragmentManager() 43 | .beginTransaction() 44 | .replace(R.id.settings, new SettingsFragment()) 45 | .commit(); 46 | ActionBar actionBar = getSupportActionBar(); 47 | if (actionBar != null) { 48 | actionBar.setDisplayHomeAsUpEnabled(true); 49 | } 50 | } 51 | 52 | @Override 53 | public boolean onOptionsItemSelected(MenuItem item) { 54 | // The back button 55 | if (item.getItemId() == android.R.id.home) { 56 | super.onBackPressed(); 57 | return true; 58 | } 59 | return super.onOptionsItemSelected(item); 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/ui/settings/SettingsFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.ui.settings; 27 | 28 | import android.annotation.SuppressLint; 29 | import android.content.SharedPreferences; 30 | import android.os.Bundle; 31 | import android.text.InputType; 32 | import android.widget.EditText; 33 | import android.widget.Toast; 34 | 35 | import androidx.appcompat.app.AlertDialog; 36 | import androidx.appcompat.app.AppCompatDelegate; 37 | import androidx.preference.Preference; 38 | import androidx.preference.PreferenceFragmentCompat; 39 | import androidx.preference.PreferenceManager; 40 | 41 | import com.android.volley.ClientError; 42 | import com.android.volley.Request; 43 | import com.android.volley.RequestQueue; 44 | import com.android.volley.toolbox.JsonObjectRequest; 45 | import com.android.volley.toolbox.Volley; 46 | import com.fasterxml.jackson.core.JsonProcessingException; 47 | import com.fasterxml.jackson.databind.ObjectMapper; 48 | 49 | import org.geysermc.geyser.android.R; 50 | import org.geysermc.geyser.android.utils.AndroidDeviceDump; 51 | import org.geysermc.geyser.android.utils.AndroidUtils; 52 | import org.json.JSONException; 53 | import org.json.JSONObject; 54 | 55 | import java.io.File; 56 | 57 | public class SettingsFragment extends PreferenceFragmentCompat { 58 | 59 | private static final ObjectMapper MAPPER = new ObjectMapper(); 60 | private static final String DUMP_URL = "https://dump.geysermc.org/"; 61 | 62 | @SuppressLint("NewApi") 63 | @Override 64 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 65 | setPreferencesFromResource(R.xml.root_preferences, rootKey); 66 | 67 | Preference configReset = findPreference("geyser_reset_config"); 68 | configReset.setOnPreferenceClickListener(preference -> { 69 | File configFile = AndroidUtils.getStoragePath(requireContext()).resolve("config.yml").toFile(); 70 | if (configFile.exists()) { 71 | if (configFile.delete()) { 72 | new AlertDialog.Builder(requireContext()) 73 | .setTitle(getResources().getString(R.string.settings_reset_config_success_title)) 74 | .setMessage(getResources().getString(R.string.settings_reset_config_success_message)) 75 | .setPositiveButton(android.R.string.ok, null) 76 | .show(); 77 | } else { 78 | new AlertDialog.Builder(requireContext()) 79 | .setTitle(getResources().getString(R.string.settings_reset_config_failed_title)) 80 | .setMessage(getResources().getString(R.string.settings_reset_config_failed_message)) 81 | .setPositiveButton(android.R.string.ok, null) 82 | .show(); 83 | } 84 | } else { 85 | new AlertDialog.Builder(requireContext()) 86 | .setTitle(getResources().getString(R.string.settings_reset_config_missing_title)) 87 | .setMessage(getResources().getString(R.string.settings_reset_config_missing_message)) 88 | .setPositiveButton(android.R.string.ok, null) 89 | .show(); 90 | } 91 | return true; 92 | }); 93 | 94 | Preference userAuthsReset = findPreference("geyser_reset_user_auths"); 95 | userAuthsReset.setOnPreferenceClickListener(preference -> { 96 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); 97 | 98 | if (!("{}".equals(sharedPreferences.getString("geyser_user_auths", "{}")))) { 99 | sharedPreferences.edit().putString("geyser_user_auths", "{}").apply(); 100 | 101 | new AlertDialog.Builder(requireContext()) 102 | .setTitle(getResources().getString(R.string.settings_reset_user_auths_success_title)) 103 | .setMessage(getResources().getString(R.string.settings_reset_user_auths_success_message)) 104 | .setPositiveButton(android.R.string.ok, null) 105 | .show(); 106 | } else { 107 | new AlertDialog.Builder(getContext()) 108 | .setTitle(getResources().getString(R.string.settings_reset_user_auths_missing_title)) 109 | .setMessage(getResources().getString(R.string.settings_reset_user_auths_missing_message)) 110 | .setPositiveButton(android.R.string.ok, null) 111 | .show(); 112 | } 113 | return true; 114 | }); 115 | 116 | // Handle the dump creation prefrence 117 | Preference createDeviceDump = findPreference("create_device_dump"); 118 | createDeviceDump.setOnPreferenceClickListener(preference -> { 119 | try { 120 | // Let the user know we started 121 | AndroidUtils.showToast(getContext(), getResources().getString(R.string.settings_create_device_dump_uploading), Toast.LENGTH_LONG); 122 | 123 | // Dump the device info to JSON 124 | JSONObject data = new JSONObject(MAPPER.writeValueAsString(new AndroidDeviceDump(getContext()))); 125 | 126 | // Create the request 127 | JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST, DUMP_URL + "documents", data, response -> { 128 | try { 129 | // Check if we got a response key 130 | if (!response.has("key")) { 131 | new AlertDialog.Builder(getContext()) 132 | .setTitle(getResources().getString(R.string.settings_create_device_dump_failed_title)) 133 | .setMessage(getResources().getString(R.string.settings_create_device_dump_failed_message, (response.has("message") ? response.getString("message") : response.toString()))) 134 | .setPositiveButton(android.R.string.ok, (dialog, which) -> {}) 135 | .show(); 136 | } 137 | 138 | String uploadedDumpUrl = DUMP_URL + response.getString("key"); 139 | 140 | final EditText input = new EditText(getContext()); 141 | input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); 142 | input.setText(uploadedDumpUrl); 143 | 144 | new AlertDialog.Builder(getContext()) 145 | .setTitle(getResources().getString(R.string.settings_create_device_dump_success_title)) 146 | .setView(input) 147 | .setPositiveButton(android.R.string.ok, (dialog, which) -> {}) 148 | .setNegativeButton(getResources().getString(R.string.settings_create_device_dump_open), (dialog, which) -> { 149 | AndroidUtils.showURL(uploadedDumpUrl); 150 | }) 151 | .show(); 152 | } catch (JSONException e) { 153 | new AlertDialog.Builder(getContext()) 154 | .setTitle(getResources().getString(R.string.settings_create_device_dump_failed_title)) 155 | .setMessage(getResources().getString(R.string.settings_create_device_dump_failed_message, e.getMessage())) 156 | .setPositiveButton(android.R.string.ok, (dialog, which) -> {}) 157 | .show(); 158 | } 159 | }, error -> { 160 | String message = error.getMessage(); 161 | try { 162 | JSONObject response = new JSONObject(new String(((ClientError) error).networkResponse.data)); 163 | message = response.getString("message"); 164 | } catch (JSONException ignored) { } 165 | 166 | new AlertDialog.Builder(getContext()) 167 | .setTitle(getResources().getString(R.string.settings_create_device_dump_failed_title)) 168 | .setMessage(getResources().getString(R.string.settings_create_device_dump_failed_message, message)) 169 | .setPositiveButton(android.R.string.ok, (dialog, which) -> {}) 170 | .show(); 171 | }); 172 | 173 | RequestQueue queue = Volley.newRequestQueue(getContext()); 174 | queue.add(jsonObjectRequest); 175 | } catch (JsonProcessingException | JSONException e) { 176 | new AlertDialog.Builder(getContext()) 177 | .setTitle(getResources().getString(R.string.settings_create_device_dump_failed_title)) 178 | .setMessage(getResources().getString(R.string.settings_create_device_dump_failed_message, e.getMessage())) 179 | .setPositiveButton(android.R.string.ok, (dialog, which) -> {}) 180 | .show(); 181 | } 182 | 183 | return true; 184 | }); 185 | 186 | PreferenceManager.getDefaultSharedPreferences(requireContext()).registerOnSharedPreferenceChangeListener((sharedPreferences, key) -> { 187 | if (key.equals("theme")) { 188 | String theme = sharedPreferences.getString(key, "system"); 189 | assert theme != null; 190 | switch (theme) { 191 | case "dark": 192 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); 193 | break; 194 | 195 | case "light": 196 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); 197 | break; 198 | 199 | default: 200 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); 201 | break; 202 | } 203 | } 204 | }); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/utils/AndroidDeviceDump.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2021 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.utils; 27 | 28 | import android.content.Context; 29 | import android.content.pm.PackageInfo; 30 | import android.content.pm.PackageManager; 31 | import android.os.Build; 32 | 33 | import lombok.Getter; 34 | 35 | @Getter 36 | public class AndroidDeviceDump { 37 | 38 | private final AndroidDeviceDump.AndroidInfo androidInfo; 39 | private final AndroidDeviceDump.AppInfo appInfo; 40 | 41 | public AndroidDeviceDump(Context ctx) { 42 | this.androidInfo = new AndroidDeviceDump.AndroidInfo(); 43 | this.appInfo = new AndroidDeviceDump.AppInfo(ctx); 44 | } 45 | 46 | @Getter 47 | public static class AndroidInfo { 48 | 49 | public final String androidVersion; 50 | public final int androidAPIVersion; 51 | public final String deviceManufacturer; 52 | public final String deviceModel; 53 | 54 | public AndroidInfo() { 55 | androidVersion = Build.VERSION.RELEASE; 56 | androidAPIVersion = Build.VERSION.SDK_INT; 57 | deviceManufacturer = Build.MANUFACTURER; 58 | deviceModel = Build.MODEL; 59 | } 60 | } 61 | 62 | @Getter 63 | public static class AppInfo { 64 | 65 | public long versionCode = 0; 66 | public String versionName = "Unknown"; 67 | 68 | public AppInfo(Context ctx) { 69 | try { 70 | PackageInfo packageInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); 71 | versionCode = packageInfo.versionCode; 72 | versionName = packageInfo.versionName; 73 | } catch (PackageManager.NameNotFoundException ignored) { } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/utils/AndroidUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.utils; 27 | 28 | import android.annotation.SuppressLint; 29 | import android.app.Activity; 30 | import android.app.ProgressDialog; 31 | import android.content.ClipData; 32 | import android.content.ClipboardManager; 33 | import android.content.Context; 34 | import android.content.Intent; 35 | import android.content.SharedPreferences; 36 | import android.net.Uri; 37 | import android.text.Editable; 38 | import android.text.TextWatcher; 39 | import android.widget.Toast; 40 | 41 | import androidx.preference.PreferenceManager; 42 | 43 | import com.fasterxml.jackson.databind.DeserializationFeature; 44 | import com.fasterxml.jackson.databind.ObjectMapper; 45 | 46 | import org.geysermc.geyser.android.MainActivity; 47 | import org.geysermc.geyser.android.R; 48 | 49 | import java.io.BufferedReader; 50 | import java.io.File; 51 | import java.io.FileReader; 52 | import java.io.IOException; 53 | import java.nio.file.Path; 54 | import java.nio.file.Paths; 55 | 56 | public class AndroidUtils { 57 | 58 | public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); 59 | 60 | private static ProgressDialog appLoader; 61 | 62 | /** 63 | * Open the default browser at a given URL 64 | * 65 | * @param url The URL to show 66 | */ 67 | public static void showURL(String url) { 68 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 69 | MainActivity.getContext().startActivity(browserIntent); 70 | } 71 | 72 | 73 | /** 74 | * Show a toast message with Toast.LENGTH_SHORT 75 | * 76 | * @param ctx The app context 77 | * @param message The message to show 78 | */ 79 | public static void showToast(Context ctx, String message) { 80 | showToast(ctx, message, Toast.LENGTH_SHORT); 81 | } 82 | 83 | /** 84 | * Show a toast message with the given length 85 | * 86 | * @param ctx The app context 87 | * @param message The message to show 88 | * @param length The length to show the toast for 89 | */ 90 | public static void showToast(Context ctx, String message, int length) { 91 | Toast toast = Toast.makeText(ctx, message, length); 92 | toast.show(); 93 | } 94 | 95 | /** 96 | * Read the given file as a string 97 | * 98 | * @param file The file to read 99 | * @return The string contents of the file 100 | */ 101 | public static String fileToString(File file) { 102 | try { 103 | BufferedReader reader = new BufferedReader(new FileReader(file)); 104 | StringBuilder stringBuilder = new StringBuilder(); 105 | String line; 106 | String ls = System.getProperty("line.separator"); 107 | while ((line = reader.readLine()) != null) { 108 | stringBuilder.append(line); 109 | stringBuilder.append(ls); 110 | } 111 | 112 | stringBuilder.deleteCharAt(stringBuilder.length() - 1); 113 | reader.close(); 114 | 115 | return stringBuilder.toString(); 116 | } catch (IOException e) { 117 | return ""; 118 | } 119 | } 120 | 121 | /** 122 | * Remove all Minecraft color codes from a string 123 | * 124 | * @param line The string to use 125 | * @return The sanitised string 126 | */ 127 | public static CharSequence purgeColorCodes(String line) { 128 | return line.replaceAll("\u00A7[0-9a-fA-F]", ""); 129 | } 130 | 131 | /** 132 | * Get the storage path based on the config 133 | * 134 | * @param ctx The app context 135 | * @return The path of the chosen storage location 136 | */ 137 | @SuppressLint("NewApi") 138 | public static Path getStoragePath(Context ctx) { 139 | File storageDir = ctx.getFilesDir(); 140 | 141 | // Get the current storage preference and change the path accordingly 142 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx); 143 | if ("external".equals(preferences.getString("geyser_storage", "internal"))) { 144 | storageDir = ctx.getExternalFilesDir(""); 145 | } 146 | 147 | assert storageDir != null; 148 | return Paths.get(storageDir.getPath()); 149 | } 150 | 151 | /** 152 | * Create a popup loader for the current context 153 | * 154 | * @param ctx Context to create the loader for 155 | */ 156 | public static void ShowLoader(Context ctx) { 157 | if (appLoader != null && appLoader.isShowing()) { 158 | appLoader.hide(); 159 | } 160 | 161 | appLoader = new ProgressDialog(ctx); 162 | appLoader.setTitle(ctx.getString(R.string.utils_loader)); 163 | appLoader.setIndeterminate(false); 164 | appLoader.setCancelable(true); 165 | 166 | appLoader.show(); 167 | } 168 | 169 | /** 170 | * Hide the loader if there is one 171 | */ 172 | public static void HideLoader() { 173 | if (appLoader != null) { 174 | appLoader.dismiss(); 175 | } 176 | } 177 | 178 | /** 179 | * Run an action on the UI thread. 180 | * Checks if the activity is not null first. 181 | * 182 | * @param activity Activity to run the action on 183 | * @param action The action to run 184 | */ 185 | public static void runOnUiThread(Activity activity, Runnable action) { 186 | if (activity != null) { 187 | activity.runOnUiThread(action); 188 | } 189 | } 190 | 191 | /** 192 | * Set the clipboard text 193 | * 194 | * @param context Context to use 195 | * @param text Text to store 196 | */ 197 | public static void setClipboard(Context context, String text) { 198 | ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 199 | ClipData clip = ClipData.newPlainText(context.getResources().getString(R.string.utils_copy_success), text); 200 | clipboard.setPrimaryClip(clip); 201 | } 202 | 203 | /** 204 | * Generate a {@link TextWatcher} that calls a {@link org.geysermc.geyser.android.utils.EventListeners.AfterTextChangeListener} after a text change 205 | * 206 | * @param listener The listener to call 207 | * @return The built {@link TextWatcher} 208 | */ 209 | public static TextWatcher generateAfterTextChange(EventListeners.AfterTextChangeListener listener) { 210 | return new TextWatcher() { 211 | @Override 212 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 213 | 214 | @Override 215 | public void onTextChanged(CharSequence s, int start, int before, int count) { } 216 | 217 | @Override 218 | public void afterTextChanged(Editable s) { 219 | if (listener != null) listener.afterTextChange(s); 220 | } 221 | }; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/utils/ConfigUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.utils; 27 | 28 | import com.fasterxml.jackson.databind.BeanDescription; 29 | import com.fasterxml.jackson.databind.JavaType; 30 | import com.fasterxml.jackson.databind.ObjectMapper; 31 | import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; 32 | 33 | import java.util.List; 34 | import java.util.Set; 35 | import java.util.stream.Collectors; 36 | 37 | public class ConfigUtils { 38 | 39 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 40 | 41 | /** 42 | * Get the {@link BeanPropertyDefinition}s for the given class 43 | * 44 | * @param clazz The class to get the definitions for 45 | * @return A list of {@link BeanPropertyDefinition} for the given class 46 | */ 47 | public static List getPOJOForClass(Class clazz) { 48 | JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructType(clazz); 49 | 50 | // Introspect the given type 51 | BeanDescription beanDescription = OBJECT_MAPPER.getSerializationConfig().introspect(javaType); 52 | 53 | // Find properties 54 | List properties = beanDescription.findProperties(); 55 | 56 | // Get the ignored properties 57 | Set ignoredProperties = OBJECT_MAPPER.getSerializationConfig().getAnnotationIntrospector() 58 | .findPropertyIgnorals(beanDescription.getClassInfo()).getIgnored(); 59 | 60 | // Filter properties removing the ignored ones 61 | return properties.stream() 62 | .filter(property -> !ignoredProperties.contains(property.getName())) 63 | .collect(Collectors.toList()); 64 | } 65 | 66 | /** 67 | * Get the value of a {@link BeanPropertyDefinition} forces if not directly accessible 68 | * 69 | * @param property The property to get 70 | * @param parentObject The parent to get the property from 71 | * @return The value of the property 72 | */ 73 | public static Object forceGet(BeanPropertyDefinition property, Object parentObject) { 74 | try { 75 | // Try get it normally 76 | return property.getGetter().callOn(parentObject); 77 | } catch (NullPointerException e) { 78 | // Force the get 79 | property.getField().fixAccess(true); 80 | return property.getField().getValue(parentObject); 81 | } catch (Exception ignored) { } 82 | 83 | return null; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/utils/EventListeners.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.utils; 27 | 28 | import android.text.Editable; 29 | 30 | /** 31 | * This class is used to store various interfaces for event listeners 32 | */ 33 | public class EventListeners { 34 | 35 | /** 36 | * This is used for adding a listener to the onDisable method of the {@link org.geysermc.geyser.android.geyser.GeyserAndroidBootstrap} 37 | */ 38 | public interface OnDisableEventListener { 39 | void onDisable(); 40 | } 41 | 42 | /** 43 | * This is used for adding a listener to the log events in both 44 | * {@link org.geysermc.geyser.android.proxy.ProxyLogger} and {@link org.geysermc.geyser.android.geyser.GeyserAndroidLogger} 45 | */ 46 | public interface LogEventListener { 47 | void onLogLine(String line); 48 | } 49 | 50 | /** 51 | * This is used for when the background service has finished starting 52 | */ 53 | public interface StartedEventListener { 54 | void onStarted(boolean failed); 55 | } 56 | 57 | /** 58 | * This is used for running a function after the text has changed in a {@link android.text.TextWatcher} 59 | */ 60 | public interface AfterTextChangeListener { 61 | void afterTextChange(Editable editable); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/org/geysermc/geyser/android/utils/UserAuth.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2020 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/GeyserAndroid 24 | */ 25 | 26 | package org.geysermc.geyser.android.utils; 27 | 28 | import com.fasterxml.jackson.annotation.JsonProperty; 29 | 30 | import lombok.AllArgsConstructor; 31 | import lombok.Getter; 32 | import lombok.NoArgsConstructor; 33 | import lombok.Setter; 34 | 35 | @Getter 36 | @Setter 37 | @AllArgsConstructor 38 | @NoArgsConstructor 39 | public class UserAuth { 40 | private String email; 41 | private String password; 42 | @JsonProperty("microsoft-account") 43 | private boolean microsoftAccount = false; 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/geyser_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeyserMC/GeyserAndroid/ef9167efd3e77a4a15bdc4b1ea403d1b0dd69179/app/src/main/res/drawable/geyser_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_discord.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_home.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_proxy.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_website.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notification_logs.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/nav_view_item_iconcolor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/nav_view_item_shapecolor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/nav_view_item_textcolor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_config_editor_advanced_pretty.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_config_editor_advanced_raw.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_config_editor_simple.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 33 | 34 | 43 | 44 | 58 | 59 | 68 | 69 | 80 | 81 |