commit b3b4acb032f4b7a6de2c470945df2397663c6eff Author: HolographicHat Date: Tue Nov 22 16:08:56 2022 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e969e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +.idea +api-82-sources.jar +restart.cmd \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..ad76b2c --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,64 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + + namespace 'hat.holo.token' + compileSdk 33 + + defaultConfig { + applicationId "hat.holo.token" + minSdk 28 + targetSdk 33 + versionCode 1 + versionName "1.0" + vectorDrawables { + useSupportLibrary true + } + } + + buildTypes { + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion compose_ui_version + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } +} + +dependencies { + + implementation "androidx.core:core-ktx:1.9.0" + implementation "androidx.appcompat:appcompat:1.5.1" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1" + implementation "androidx.compose.ui:ui:1.3.1" + implementation "androidx.compose.material:material:1.3.1" + implementation "androidx.activity:activity-compose:1.6.1" + implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1" + implementation "com.google.accompanist:accompanist-systemuicontroller:0.27.1" + implementation "com.google.code.gson:gson:2.10" + implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2" + implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2" + compileOnly files("libs/api-82.jar") + +} diff --git a/app/libs/api-82.jar b/app/libs/api-82.jar new file mode 100644 index 0000000..eec212f Binary files /dev/null and b/app/libs/api-82.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..0b66a5e --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,29 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +-keepattributes SourceFile,LineNumberTable +-dontwarn org.bouncycastle.jsse.BCSSLParameters +-dontwarn org.bouncycastle.jsse.BCSSLSocket +-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +-dontwarn org.conscrypt.Conscrypt$Version +-dontwarn org.conscrypt.Conscrypt +-dontwarn org.openjsse.javax.net.ssl.SSLParameters +-dontwarn org.openjsse.javax.net.ssl.SSLSocket +-dontwarn org.openjsse.net.ssl.OpenJSSE + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6f713c7 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/xposed_init b/app/src/main/assets/xposed_init new file mode 100644 index 0000000..f023efe --- /dev/null +++ b/app/src/main/assets/xposed_init @@ -0,0 +1 @@ +hat.holo.token.ModuleMain \ No newline at end of file diff --git a/app/src/main/java/hat/holo/token/LoaderActivity.kt b/app/src/main/java/hat/holo/token/LoaderActivity.kt new file mode 100644 index 0000000..4957176 --- /dev/null +++ b/app/src/main/java/hat/holo/token/LoaderActivity.kt @@ -0,0 +1,47 @@ +package hat.holo.token + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.util.ArrayMap +import dalvik.system.DexClassLoader +import hat.holo.token.utils.setAccess +import java.lang.ref.WeakReference + +class LoaderActivity : Activity() { + + private var originClassLoader: ClassLoader? = null + + @Suppress("DEPRECATION") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val dexPath = intent.getStringExtra("dexPath") + val loader = DexClassLoader(dexPath, cacheDir.absolutePath, null, null) + originClassLoader = setClassLoader(loader) + val zActivity = loader.loadClass("hat.holo.token.TokenActivity") + val actIntent = Intent(this, zActivity) + actIntent.putExtra("accountInfo", intent.getSerializableExtra("accountInfo")) + startActivityForResult(actIntent, 1234) + } + + // platform/frameworks/base/+/master/core/java/android/app/Activity.java + // platform/frameworks/base/+/master/core/java/android/app/LoadedApk.java + // platform/frameworks/base/+/master/core/java/android/app/ActivityThread.java + @SuppressLint("DiscouragedPrivateApi") + private fun setClassLoader(classLoader: ClassLoader): ClassLoader { + val mainThread = Activity::class.java.getDeclaredField("mMainThread").setAccess().get(this) + val apks = mainThread.javaClass.getDeclaredField("mPackages").setAccess().get(mainThread) as ArrayMap<*, *> + val apkRef = apks[packageName] as WeakReference<*> + val loadedApk = apkRef.get() ?: throw IllegalStateException("WeakRef is null!") + val fClassLoader = loadedApk.javaClass.getDeclaredField("mClassLoader").setAccess() + val oClassLoader = fClassLoader.get(loadedApk) + fClassLoader.set(loadedApk, classLoader) + return oClassLoader as ClassLoader + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + originClassLoader?.let { setClassLoader(it) } + finishAfterTransition() + } +} diff --git a/app/src/main/java/hat/holo/token/ModuleMain.kt b/app/src/main/java/hat/holo/token/ModuleMain.kt new file mode 100644 index 0000000..dbb6b07 --- /dev/null +++ b/app/src/main/java/hat/holo/token/ModuleMain.kt @@ -0,0 +1,115 @@ +package hat.holo.token + +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.app.Application +import android.content.Context +import android.content.Intent +import android.content.res.XResources +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.annotation.Keep +import dalvik.system.BaseDexClassLoader +import de.robv.android.xposed.IXposedHookLoadPackage +import de.robv.android.xposed.IXposedHookZygoteInit +import de.robv.android.xposed.XC_MethodHook +import de.robv.android.xposed.XposedBridge +import de.robv.android.xposed.XposedHelpers.findAndHookMethod +import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam +import hat.holo.token.utils.* +import java.io.File +import kotlin.math.roundToInt + +@Keep +class ModuleMain : IXposedHookLoadPackage, IXposedHookZygoteInit { + + private var isPatch = false + private var modulePath = "" + private val targetPackageName = "com.mihoyo.hyperion" + + // platform/libcore/+/refs/heads/master/dalvik/src/main/java/dalvik/system/DexPathList.java + // platform/libcore/+/refs/heads/master/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java + @SuppressLint("DiscouragedPrivateApi") + private fun ClassLoader.appendToClassPath(ctx: Context) { + val zBaseDexClassLoader = BaseDexClassLoader::class.java + val fPathList = zBaseDexClassLoader.getDeclaredField("pathList").setAccess() + val oPathList = fPathList.get(this) // DexPathList + val zDexPathList = oPathList.javaClass + val mAddDexPath = zDexPathList.getDeclaredMethod("addDexPath", String::class.java, File::class.java) + mAddDexPath.setAccess().invoke(oPathList, modulePath, ctx.cacheDir) + } + + @SuppressLint("DiscouragedApi") + override fun handleLoadPackage(lpparam: LoadPackageParam) = with(lpparam) pms@{ + if (packageName != targetPackageName) return + with(classLoader) { + val c0 = loadClass("com.mihoyo.hyperion.app.HyperionApplicationHelper") + findAndHookMethod(c0, "initOnMainProcess", Application::class.java, object : XC_MethodHook() { + override fun afterHookedMethod(p: MethodHookParam) { + AppUtils.init(classLoader) + AccountManager.init(classLoader) + val app = p.args[0] as Application + appendToClassPath(app.applicationContext) + } + }) + val c1 = loadClass("com.mihoyo.hyperion.main.user.MainUserInfoPage") + XposedBridge.hookAllConstructors(c1, object : XC_MethodHook() { + override fun afterHookedMethod(p: MethodHookParam) { + if (p.args.size != 3 || p.args.getOrNull(0) !is Context) return + val root1 = p.thisObject as FrameLayout + val root2 = root1.getChildAt(0) as FrameLayout + val root3 = root2.getChildAt(0) as ViewGroup + val ctx = root1.context + val scanId = ctx.resources.getIdentifier("scanIv", "id", targetPackageName) + val scanBtn = root3.findViewById(scanId) + val tokenBtn = ImageView(ctx) + tokenBtn.id = XResources.getFakeResId("getTokenIv") + tokenBtn.setImageDrawable(Res.iconToken) + val size = Dimension.convertDpToPixel(32f, ctx).roundToInt() + tokenBtn.layoutParams = ViewGroup.LayoutParams(size, size) + tokenBtn.setOnClickListener { + if (AccountManager.isLogin) { + if (isPatch) { + val intent = Intent(ctx, LoaderActivity::class.java) + intent.putExtra("accountInfo", AccountManager.accountInfo) + intent.putExtra("dexPath", modulePath) + ctx.startActivity(intent) + } else { + val intent = Intent() + intent.setClassName("hat.holo.token", "hat.holo.token.TokenActivity") + intent.putExtra("accountInfo", AccountManager.accountInfo) + ctx.startActivity(intent) + } + } else { + AppUtils.showToast("未登录") + } + } + root3.addView(tokenBtn) + for (i in 0 until root3.childCount) { + val view = root3.getChildAt(i) + if (view.id == -1) view.id = XResources.getFakeResId("b5AaLhI6WDlkTMIrRA$i") + } + val set = createConstraintSet(ctx) + if (set == null) { + AlertDialog.Builder(ctx).run { + setTitle("Error") + setMessage("Create ConstraintSetWrapper fail.") + }.create().show() + return + } + set.clone(root3) + set.connect(tokenBtn.id, 2, scanBtn.id, 1, Dimension.convertDpToPixel(9f, ctx).roundToInt()) + set.connect(tokenBtn.id, 3, scanBtn.id, 3) + set.applyTo(root3) + } + }) + } + XposedBridge.log("Module initialized!") + } + + override fun initZygote(params: IXposedHookZygoteInit.StartupParam) { + modulePath = params.modulePath + isPatch = modulePath.contains("lspatch") + } +} diff --git a/app/src/main/java/hat/holo/token/TokenActivity.kt b/app/src/main/java/hat/holo/token/TokenActivity.kt new file mode 100644 index 0000000..a530775 --- /dev/null +++ b/app/src/main/java/hat/holo/token/TokenActivity.kt @@ -0,0 +1,344 @@ +package hat.holo.token + +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.content.ClipData +import android.content.ClipboardManager +import android.content.pm.ActivityInfo +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.annotation.Keep +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Done +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.core.content.getSystemService +import com.google.accompanist.systemuicontroller.rememberSystemUiController +import hat.holo.token.models.AccountInfo +import hat.holo.token.models.BaseResponse +import hat.holo.token.models.FetchRsp +import hat.holo.token.utils.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +val textColor = Color(0xFF424242) + +@Keep +class TokenActivity : ComponentActivity() { + + @Suppress("DEPRECATION") + @SuppressLint("DiscouragedPrivateApi", "SourceLockedOrientationActivity") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + setTheme(androidx.appcompat.R.style.Theme_AppCompat_Light_NoActionBar) + val accountInfo = intent.getSerializableExtra("accountInfo") as AccountInfo + setContent { + rememberSystemUiController().setStatusBarColor(Color.White) + MaterialTheme( + colors = lightColors( + primary = Color(0xFF2196F3) + ), + content = { + Surface { + Content(accountInfo) + } + } + ) + } + setResult(2333) + } +} + +private fun TokenActivity.showDialog(msg: String) = runOnUiThread { + AlertDialog.Builder(this).run { + setMessage(msg) + setPositiveButton("确定") { dialog, _ -> dialog.dismiss() } + }.create().show() +} + +private suspend fun TokenActivity.genAuthCode( + acc: AccountInfo, + useSToken: Boolean +): String? = runCatching { + val params = mutableMapOf("app_id" to "4", "device" to "0") + val fetchResult = buildHttpRequest { + url("https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/fetch") + params.post(this) + }.getAsJson() + if (fetchResult.retcode != 0) { + showDialog("请求失败: ${fetchResult.message}") + return null + } + val ticket = fetchResult.data().getUri().getQueryParameter("ticket") + val scanResult = buildHttpRequest { + url("https://api-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/scan") + params.apply { + put("ticket", ticket) + }.post(this) + }.getAsJson() + if (scanResult.retcode != 0) { + showDialog("请求失败: ${scanResult.message}") + return@runCatching null + } + val confirmResult = buildHttpRequest { + url("https://api-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/confirm") + params.apply { + put("payload", buildMap { + put("proto", "Account") + put("raw", buildMap { + put("uid", acc.uid) + put("ltoken", acc.lToken) + if (useSToken) { + put("mid", acc.mid) + put("stoken", acc.sToken) + } + }.toJson()) + }) + }.post(this) + }.getAsJson() + if (confirmResult.retcode != 0) { + showDialog("请求失败: ${confirmResult.message}") + return null + } + return ticket +}.onFailure { + showDialog("网络异常,请稍后重试") +}.getOrNull() + +@Composable +private fun TokenActivity.Content(accountInfo: AccountInfo) = Column( + modifier = Modifier.fillMaxSize() +) { + TopAppBar() + Column( + modifier = Modifier.padding(15.dp) + ) { + var isLoading by remember { mutableStateOf(false) } + var authTicket by remember { mutableStateOf("") } + var grantSToken by remember { mutableStateOf(false) } + CustomCheckBox( + checked = true, + onCheckedChange = {}, + name = "LToken", + permissions = buildAnnotatedString { + appendLine("此令牌可以用于:") + appendLine(" · 获取实时便笺与统计信息") + appendLine(" · 其它可以通过米游社网页完成的操作") + } // TODO: More description + ) + CustomCheckBox( + checked = grantSToken, + onCheckedChange = { v -> grantSToken = v }, + name = "SToken", + permissions = buildAnnotatedString { + appendLine("此令牌可以用于:") + appendLine(" · 登录原神/崩坏3等游戏", true) + appendLine(" · 获取 LToken") + appendLine(" · 其它可以通过米游社APP完成的操作") + } // TODO: More description + ) + Divider() + AnimatedVisibility( + visible = authTicket.isNotEmpty(), + modifier = Modifier.fillMaxWidth() + ) { + AuthTicketView(authTicket) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + if (BuildConfig.DEBUG) { + TextButton( + onClick = { + CoroutineScope(Dispatchers.IO).launch { + val confirmResult = buildHttpRequest { + url("https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/query") + buildMap { + put("app_id", "4") + put("device", "0") + put("ticket", authTicket.removePrefix("ma_")) + }.post(this) + }.getAsJson() + if (confirmResult.retcode != 0) { + showDialog("请求失败: ${confirmResult.message}") + return@launch + } else { + showDialog(confirmResult.data?.toJson() ?: "success, but data is null") + } + } + } + ) { + Text(text = "query") + } + } + TextButton( + onClick = { + CoroutineScope(Dispatchers.IO).launch { + authTicket = "" + isLoading = true + authTicket = genAuthCode(accountInfo, grantSToken)?.let { s -> "ma_${s}" } ?: "" + isLoading = false + } + }, + enabled = !isLoading + ) { + AnimatedVisibility(visible = isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + strokeWidth = 3.dp + ) + } + AnimatedVisibility(visible = !isLoading) { + // 120s available + Text("生成登录代码") + } + } + } + } +} + +@OptIn(ExperimentalAnimationApi::class) +@Composable +private fun TokenActivity.AuthTicketView(authTicket: String) = ConstraintLayout( + modifier = Modifier.fillMaxWidth() +) { + val (divider, code, copyBtn) = createRefs() + Text( + text = authTicket, + modifier = Modifier.constrainAs(code) { + top.linkTo(parent.top, 12.dp) + start.linkTo(parent.start, 12.dp) + }, + fontFamily = FontFamily.Monospace + ) + var showDoneIcon by remember { mutableStateOf(false) } + val iconColor by animateColorAsState(if (showDoneIcon) Color(0xFF4CAF50) else textColor) + IconButton( + modifier = Modifier.constrainAs(copyBtn) { + top.linkTo(parent.top) + end.linkTo(parent.end) + bottom.linkTo(parent.bottom) + }, + onClick = { + runCatching { + val clip = ClipData.newPlainText(null, authTicket) + getSystemService()!!.setPrimaryClip(clip) + }.onFailure { + showDialog("复制失败") + }.onSuccess { + showDoneIcon = true + } + } + ) { + AnimatedContent(targetState = if (showDoneIcon) Icons.Outlined.Done else Res.iconCopy) { targetIcon -> + Image( + imageVector = targetIcon, + contentDescription = "Copy auth ticket", + colorFilter = ColorFilter.tint(iconColor) + ) + } + LaunchedEffect(showDoneIcon) { + if (showDoneIcon) { + delay(1500) + showDoneIcon = false + } + } + } + Divider( + modifier = Modifier.constrainAs(divider) { + top.linkTo(code.bottom, 12.dp) + bottom.linkTo(parent.bottom) + } + ) +} + +@Composable +private fun CustomCheckBox( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + name: String, + permissions: AnnotatedString +) { + val interactionSource = remember { MutableInteractionSource() } + ConstraintLayout( + modifier = Modifier + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { onCheckedChange(!checked) } + ) + .fillMaxWidth() + ) { + val (checkBox, itemTitle, itemDesc) = createRefs() + Checkbox( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = Modifier.constrainAs(checkBox) { + top.linkTo(parent.top) + start.linkTo(parent.start) + }, + interactionSource = interactionSource + ) + Text( + text = name, + modifier = Modifier.constrainAs(itemTitle) { + top.linkTo(parent.top) + start.linkTo(checkBox.end) + bottom.linkTo(itemDesc.top) + } + ) + Text( + text = permissions, + modifier = Modifier.constrainAs(itemDesc) { + top.linkTo(checkBox.bottom) + start.linkTo(itemTitle.start) + bottom.linkTo(parent.bottom, 5.dp) + }, + fontSize = 14.sp + ) + } +} + +@Composable +private fun TopAppBar() = TopAppBar( + modifier = Modifier.fillMaxWidth(), + elevation = 10.dp, + backgroundColor = Color.White +) { + Text( + text = "获取登录信息", + modifier = Modifier.padding(start = 16.dp), + color = textColor + ) +} + +private fun AnnotatedString.Builder.appendLine(str: String, emphasize: Boolean = false) = apply { + if (emphasize) pushStyle(SpanStyle(color = Color.Red, fontWeight = FontWeight.Bold)) + append(str) + append('\n') + if (emphasize) pop() +} diff --git a/app/src/main/java/hat/holo/token/models/AccountInfo.kt b/app/src/main/java/hat/holo/token/models/AccountInfo.kt new file mode 100644 index 0000000..bcf137f --- /dev/null +++ b/app/src/main/java/hat/holo/token/models/AccountInfo.kt @@ -0,0 +1,8 @@ +package hat.holo.token.models + +data class AccountInfo( + val mid: String, + val uid: String, + val lToken: String, + val sToken: String +) : java.io.Serializable diff --git a/app/src/main/java/hat/holo/token/models/BaseResponse.kt b/app/src/main/java/hat/holo/token/models/BaseResponse.kt new file mode 100644 index 0000000..9193a2e --- /dev/null +++ b/app/src/main/java/hat/holo/token/models/BaseResponse.kt @@ -0,0 +1,16 @@ +package hat.holo.token.models + +import androidx.annotation.Keep +import com.google.gson.JsonElement +import hat.holo.token.utils.convertTo + +@Keep +data class BaseResponse( + val retcode: Int = 0, + val message: String = "ok", + val data: JsonElement? = null +) { + inline fun data(): T { + return data!!.convertTo() + } +} diff --git a/app/src/main/java/hat/holo/token/models/FetchRsp.kt b/app/src/main/java/hat/holo/token/models/FetchRsp.kt new file mode 100644 index 0000000..cc6c579 --- /dev/null +++ b/app/src/main/java/hat/holo/token/models/FetchRsp.kt @@ -0,0 +1,11 @@ +package hat.holo.token.models + +import android.net.Uri +import androidx.annotation.Keep + +@Keep +data class FetchRsp( + val url: String +) { + fun getUri(): Uri = Uri.parse(url) +} diff --git a/app/src/main/java/hat/holo/token/utils/AccountManager.kt b/app/src/main/java/hat/holo/token/utils/AccountManager.kt new file mode 100644 index 0000000..b96b7cf --- /dev/null +++ b/app/src/main/java/hat/holo/token/utils/AccountManager.kt @@ -0,0 +1,37 @@ +package hat.holo.token.utils + +import hat.holo.token.models.AccountInfo +import java.lang.reflect.Method + +@Suppress("MemberVisibilityCanBePrivate", "unused") +object AccountManager { + + private lateinit var instance: Any + private val methodTable = arrayListOf() + + fun init(cl: ClassLoader) { + val clz = cl.loadClass("com.mihoyo.hyperion.user.account.AccountManager") + instance = clz.getDeclaredField("INSTANCE").get(null)!! + methodTable.add(clz.getDeclaredMethod("getMid")) + methodTable.add(clz.getDeclaredMethod("getUserId")) + methodTable.add(clz.getDeclaredMethod("getLToken")) + methodTable.add(clz.getDeclaredMethod("getSToken")) + methodTable.add(clz.getDeclaredMethod("getSTokenV1")) + methodTable.add(clz.getDeclaredMethod("userIsLogin")) + methodTable.add(clz.getDeclaredMethod("getLastUserId")) + methodTable.add(clz.getDeclaredMethod("getLoginTicket")) + } + + val isLogin get() = methodTable[5].invoke(instance) as Boolean + + val mid get() = methodTable[0].invoke(instance) as String + val uid get() = methodTable[1].invoke(instance) as String + val lToken get() = methodTable[2].invoke(instance) as String + val sToken get() = methodTable[3].invoke(instance) as String + val sTokenV1 get() = methodTable[4].invoke(instance) as String + val lastUserId get() = methodTable[6].invoke(instance) as String + val loginTicket get() = methodTable[7].invoke(instance) as String + + val accountInfo get() = AccountInfo(mid, uid, lToken, sToken) + +} diff --git a/app/src/main/java/hat/holo/token/utils/AppUtils.kt b/app/src/main/java/hat/holo/token/utils/AppUtils.kt new file mode 100644 index 0000000..700f62b --- /dev/null +++ b/app/src/main/java/hat/holo/token/utils/AppUtils.kt @@ -0,0 +1,19 @@ +package hat.holo.token.utils + +import java.lang.reflect.Method + +object AppUtils { + + private lateinit var instance: Any + private val methodTable = arrayListOf() + + fun init(cl: ClassLoader) { + val clz = cl.loadClass("com.mihoyo.hyperion.utils.AppUtils") + instance = clz.getDeclaredField("INSTANCE").get(null)!! + methodTable.add(clz.getDeclaredMethod("showToast", String::class.java)) + } + + fun showToast(str: String) { + methodTable[0].invoke(instance, str) + } +} diff --git a/app/src/main/java/hat/holo/token/utils/ConstraintSet.kt b/app/src/main/java/hat/holo/token/utils/ConstraintSet.kt new file mode 100644 index 0000000..baa8488 --- /dev/null +++ b/app/src/main/java/hat/holo/token/utils/ConstraintSet.kt @@ -0,0 +1,84 @@ +package hat.holo.token.utils + +import android.content.Context +import de.robv.android.xposed.XC_MethodHook +import de.robv.android.xposed.XposedBridge +import de.robv.android.xposed.XposedHelpers +import java.lang.reflect.Method +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime +import kotlin.time.measureTimedValue + +class ConstraintSetWrapper( + clazz: Class<*>, + private val clone: Method, + private val apply: Method, + private val connect: Method, + private val connectWithMargin: Method +) { + + private val instance = clazz.newInstance() + + fun clone(constraintLayout: Any) { + clone.invoke(instance, constraintLayout) + } + + fun connect(startID: Int, startSide: Int, endID: Int, endSide: Int) { + connect.invoke(instance, startID, startSide, endID, endSide) + } + + fun connect(startID: Int, startSide: Int, endID: Int, endSide: Int, margin: Int) { + connectWithMargin.invoke(instance, startID, startSide, endID, endSide, margin) + } + + fun applyTo(constraintLayout: Any) { + apply.invoke(instance, constraintLayout) + } +} + +@OptIn(ExperimentalTime::class) +fun ClassLoader.createConstraintSet(ctx: Context) = measureTimedValue { + runCatching { + val calledMethods = arrayListOf() + val zRadialViewGroup = loadClass("com.google.android.material.timepicker.RadialViewGroup") + val zConstraints = loadClass("androidx.constraintlayout.widget.Constraints") + val zConstraintLayout = loadClass("androidx.constraintlayout.widget.ConstraintLayout") + val zConstraintSet = zConstraints.getDeclaredMethod("getConstraintSet").returnType + val hooks = zConstraintSet.declaredMethods.filter { method -> + method.parameterCount == 1 && method.parameterTypes[0] == zConstraintLayout + }.map { method -> + XposedHelpers.findAndHookMethod( + zConstraintSet, + method.name, + zConstraintLayout, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + calledMethods += param.method as Method + } + }) + } + val oRadialViewGroup = zRadialViewGroup.getDeclaredConstructor(Context::class.java).apply { + isAccessible = true + }.newInstance(ctx) + zRadialViewGroup.getDeclaredMethod("updateLayoutParams").invoke(oRadialViewGroup) + hooks.forEach { hook -> hook.unhook() } + if (calledMethods.size == 2) { + ConstraintSetWrapper( + clazz = zConstraintSet, + clone = calledMethods[0], + apply = calledMethods[1], + connect = zConstraintSet.declaredMethods.first { method -> + method.parameterCount == 4 && method.parameterTypes.all { p -> p == Int::class.java } + }, + connectWithMargin = zConstraintSet.declaredMethods.first { method -> + method.parameterCount == 5 && method.parameterTypes.all { p -> p == Int::class.java } + } + ) + } else null + }.onFailure { ex -> + XposedBridge.log(ex) + }.getOrNull() +}.let { (result, duration) -> + XposedBridge.log("createConstraintSet cost ${duration.toString(DurationUnit.MILLISECONDS, 2)}") + result +} diff --git a/app/src/main/java/hat/holo/token/utils/Dimension.java b/app/src/main/java/hat/holo/token/utils/Dimension.java new file mode 100644 index 0000000..4ff22a6 --- /dev/null +++ b/app/src/main/java/hat/holo/token/utils/Dimension.java @@ -0,0 +1,29 @@ +package hat.holo.token.utils; + +import android.content.Context; +import android.util.DisplayMetrics; + +public class Dimension { + + /** + * This method converts dp unit to equivalent pixels, depending on device density. + * + * @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels + * @param context Context to get resources and device specific display metrics + * @return A float value to represent px equivalent to dp depending on device density + */ + public static float convertDpToPixel(float dp, Context context){ + return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT); + } + + /** + * This method converts device specific pixels to density independent pixels. + * + * @param px A value in px (pixels) unit. Which we need to convert into db + * @param context Context to get resources and device specific display metrics + * @return A float value to represent dp equivalent to px value + */ + public static float convertPixelsToDp(float px, Context context){ + return px / ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT); + } +} diff --git a/app/src/main/java/hat/holo/token/utils/HttpUtils.kt b/app/src/main/java/hat/holo/token/utils/HttpUtils.kt new file mode 100644 index 0000000..227ec58 --- /dev/null +++ b/app/src/main/java/hat/holo/token/utils/HttpUtils.kt @@ -0,0 +1,70 @@ +package hat.holo.token.utils + +import android.util.Log +import com.google.gson.Gson +import com.google.gson.JsonElement +import hat.holo.token.BuildConfig +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.logging.HttpLoggingInterceptor +import java.io.IOException +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +private val gson by lazy { Gson() } + +fun buildHttpRequest(block: Request.Builder.() -> Unit) = Request.Builder().apply(block).build() + +fun Map.toJson() = gson.toJson(this)!! + +fun JsonElement.toJson() = gson.toJson(this)!! + +fun Map.post(builder: Request.Builder) { + builder.post(toJson().toRequestBody("application/json".toMediaType())) +} + +val defaultOkClient = OkHttpClient.Builder().apply { + if (BuildConfig.DEBUG) { + val i = HttpLoggingInterceptor { + Log.d("LSPosed-Bridge", it) + } + i.level = HttpLoggingInterceptor.Level.BODY + addInterceptor(i) + } +}.build() + +suspend inline fun Request.getAsJson(client: OkHttpClient = defaultOkClient): T { + return Gson().fromJson(this@getAsJson.getAsText(client), T::class.java)!! +} + +suspend fun Request.getAsText(client: OkHttpClient = defaultOkClient) = withContext(Dispatchers.IO) { + client.newCall(this@getAsText).await().use { r-> + checkNotNull(r.body).use { b -> + b.string() + } + } +} + +inline fun JsonElement.convertTo(): T = Gson().fromJson(this, T::class.java) + +suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation -> + enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + continuation.resume(response) + } + override fun onFailure(call: Call, e: IOException) { + if (continuation.isCancelled) return + continuation.resumeWithException(e) + } + }) + continuation.invokeOnCancellation { + try { + cancel() + } catch (_: Throwable) { + } + } +} diff --git a/app/src/main/java/hat/holo/token/utils/ReflectUtils.kt b/app/src/main/java/hat/holo/token/utils/ReflectUtils.kt new file mode 100644 index 0000000..d8404a8 --- /dev/null +++ b/app/src/main/java/hat/holo/token/utils/ReflectUtils.kt @@ -0,0 +1,7 @@ +package hat.holo.token.utils + +import java.lang.reflect.AccessibleObject + +fun T.setAccess() = apply { + isAccessible = true +} diff --git a/app/src/main/java/hat/holo/token/utils/Res.kt b/app/src/main/java/hat/holo/token/utils/Res.kt new file mode 100644 index 0000000..c597edb --- /dev/null +++ b/app/src/main/java/hat/holo/token/utils/Res.kt @@ -0,0 +1,59 @@ +package hat.holo.token.utils + +import android.graphics.drawable.Drawable +import androidx.compose.material.icons.materialIcon +import androidx.compose.material.icons.materialPath +import androidx.compose.ui.graphics.vector.ImageVector +import java.util.* + +@Suppress("SpellCheckingInspection") +object Res { + + val iconToken: Drawable? get() { + val s = "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" + return Base64.getDecoder().decode(s).inputStream().use { stream -> + Drawable.createFromStream(stream, null) + } + } + + val iconCopy: ImageVector + get() { + if (_contentCopy != null) { + return _contentCopy!! + } + _contentCopy = materialIcon(name = "Outlined.ContentCopy") { + materialPath { + moveTo(16.0f, 1.0f) + lineTo(4.0f, 1.0f) + curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f) + verticalLineToRelative(14.0f) + horizontalLineToRelative(2.0f) + lineTo(4.0f, 3.0f) + horizontalLineToRelative(12.0f) + lineTo(16.0f, 1.0f) + close() + moveTo(19.0f, 5.0f) + lineTo(8.0f, 5.0f) + curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f) + verticalLineToRelative(14.0f) + curveToRelative(0.0f, 1.1f, 0.9f, 2.0f, 2.0f, 2.0f) + horizontalLineToRelative(11.0f) + curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f) + lineTo(21.0f, 7.0f) + curveToRelative(0.0f, -1.1f, -0.9f, -2.0f, -2.0f, -2.0f) + close() + moveTo(19.0f, 21.0f) + lineTo(8.0f, 21.0f) + lineTo(8.0f, 7.0f) + horizontalLineToRelative(11.0f) + verticalLineToRelative(14.0f) + close() + } + } + return _contentCopy!! + } + + @Suppress("ObjectPropertyName") + private var _contentCopy: ImageVector? = null + +} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 0000000..060c881 --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,6 @@ + + + + com.mihoyo.hyperion + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..7a245ba --- /dev/null +++ b/build.gradle @@ -0,0 +1,10 @@ +buildscript { + ext { + compose_ui_version = '1.3.1' + } +}// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '7.3.0' apply false + id 'com.android.library' version '7.3.0' apply false + id 'org.jetbrains.kotlin.android' version '1.7.10' apply false +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..9e9426c --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3fcf90b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Nov 18 22:27:22 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..cb12be0 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,17 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + } +} +rootProject.name = "GetToken" +include ':app'