add ProxyList

This commit is contained in:
方块君 2022-05-12 15:47:32 +08:00
parent d371a68382
commit e1f8e971ec
5 changed files with 358 additions and 23 deletions

View File

@ -3,25 +3,35 @@ package xfk233.GenshinProxy
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog
import android.app.Application
import android.content.ClipboardManager
import android.content.Context
import android.content.res.XModuleResources
import android.graphics.Color
import android.graphics.PixelFormat
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.Switch
import android.widget.Toast
import android.util.TypedValue
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.widget.*
import com.github.kyuubiran.ezxhelper.init.EzXHelperInit
import com.github.kyuubiran.ezxhelper.utils.findConstructor
import com.github.kyuubiran.ezxhelper.utils.findMethod
import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull
import com.github.kyuubiran.ezxhelper.utils.hookBefore
import com.github.kyuubiran.ezxhelper.utils.*
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.callbacks.XC_LoadPackage
import org.json.JSONObject
import xfk233.GenshinProxy.Utils.dp2px
import xfk233.GenshinProxy.Utils.isInit
import java.io.BufferedReader
import java.io.ByteArrayOutputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import java.util.regex.Pattern
@ -31,33 +41,75 @@ class MainHook : IXposedHookLoadPackage, IXposedHookZygoteInit {
private var forceUrl = false
private lateinit var modulePath: String
private lateinit var moduleRes: XModuleResources
private lateinit var windowManager: WindowManager
private lateinit var activity: Activity
private var proxyList = false
private val proxyListRegex = arrayListOf(
"api-os-takumi.mihoyo.com",
"hk4e-api-os-static.mihoyo.com",
"hk4e-sdk-os.mihoyo.com",
"dispatchosglobal.yuanshen.com",
"osusadispatch.yuanshen.com",
"account.mihoyo.com",
"log-upload-os.mihoyo.com",
"dispatchcntest.yuanshen.com",
"devlog-upload.mihoyo.com",
"webstatic.mihoyo.com",
"log-upload.mihoyo.com",
"hk4e-sdk.mihoyo.com",
"api-beta-sdk.mihoyo.com",
"api-beta-sdk-os.mihoyo.com",
"cnbeta01dispatch.yuanshen.com",
"dispatchcnglobal.yuanshen.com",
"cnbeta02dispatch.yuanshen.com",
"sdk-os-static.mihoyo.com",
"webstatic-sea.mihoyo.com",
"webstatic-sea.hoyoverse.com",
"hk4e-sdk-os-static.hoyoverse.com",
"sdk-os-static.hoyoverse.com",
"api-account-os.hoyoverse.com",
"hk4e-sdk-os.hoyoverse.com",
"overseauspider.yuanshen.com",
"gameapi-account.mihoyo.com",
"minor-api.mihoyo.com",
"public-data-api.mihoyo.com",
"uspider.yuanshen.com",
"sdk-static.mihoyo.com",
"minor-api-os.hoyoverse.com",
"log-upload-os.hoyoverse.com"
)
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
modulePath = startupParam.modulePath
moduleRes = XModuleResources.createInstance(modulePath, null)
}
@SuppressLint("WrongConstant", "ClickableViewAccessibility")
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
if (lpparam.packageName != "com.miHoYo.GenshinImpact") return
EzXHelperInit.initHandleLoadPackage(lpparam)
findMethod(Application::class.java, true) { name == "attachBaseContext" }.hookBefore {
findMethod("com.combosdk.openapi.ComboApplication") { name == "attachBaseContext" }.hookBefore {
val context = it.args[0] as Context
val sp = context.getSharedPreferences("serverConfig", 0)
forceUrl = sp.getBoolean("forceUrl", false)
server = sp.getString("serverip", "") ?: ""
proxyList = sp.getBoolean("ProxyList", false)
}
sslHook(lpparam)
hook()
findMethod(Activity::class.java, true) { name == "onCreate" }.hookBefore { param ->
activity = param.thisObject as Activity
}
findMethod("com.miHoYo.GetMobileInfo.MainActivity") { name == "onCreate" }.hookBefore { param ->
val context = param.thisObject as Activity
val sp = context.getSharedPreferences("serverConfig", 0)
AlertDialog.Builder(context).apply {
activity = param.thisObject as Activity
val sp = activity.getSharedPreferences("serverConfig", 0)
AlertDialog.Builder(activity).apply {
setCancelable(false)
setTitle(moduleRes.getString(R.string.SelectServer))
setMessage(moduleRes.getString(R.string.Tips))
setView(LinearLayout(context).apply {
setView(LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
addView(EditText(context).apply {
addView(EditText(activity).apply {
hint = "http(s)://server.com:1234"
val str = sp.getString("serverip", "") ?: ""
setText(str.toCharArray(), 0, str.length)
@ -74,7 +126,7 @@ class MainHook : IXposedHookLoadPackage, IXposedHookZygoteInit {
}
})
})
addView(Switch(context).apply {
addView(Switch(activity).apply {
text = moduleRes.getString(R.string.ForcedMode)
isChecked = sp.getBoolean("forceUrl", false)
setOnClickListener {
@ -85,15 +137,28 @@ class MainHook : IXposedHookLoadPackage, IXposedHookZygoteInit {
forceUrl = (it as Switch).isChecked
}
})
addView(Switch(activity).apply {
text = moduleRes.getString(R.string.ProxyList)
isChecked = sp.getBoolean("ProxyList", false)
setOnClickListener {
sp.edit().run {
putBoolean("ProxyList", (it as Switch).isChecked)
apply()
}
proxyList = (it as Switch).isChecked
}
})
})
setNegativeButton(moduleRes.getString(R.string.CustomServer)) { _, _ ->
setPositiveButton(moduleRes.getString(R.string.CustomServer)) { _, _ ->
val ip = sp.getString("serverip", "") ?: ""
if (ip == "") {
Toast.makeText(context, moduleRes.getString(R.string.ServerAddressError), Toast.LENGTH_LONG).show()
context.finish()
Toast.makeText(activity, moduleRes.getString(R.string.ServerAddressError), Toast.LENGTH_LONG).show()
activity.finish()
} else {
server = ip
forceUrl = true
// TODO: The input box cannot enter content
// gmTool()
}
}
setNeutralButton(moduleRes.getString(R.string.OfficialServer)) { _, _ ->
@ -104,6 +169,249 @@ class MainHook : IXposedHookLoadPackage, IXposedHookZygoteInit {
}
}
inner class MoveOnTouchListener : View.OnTouchListener {
private var originalXPos = 0
private var originalYPos = 0
private var offsetX = 0f
private var offsetY = 0f
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
val x = event.rawX
val y = event.rawY
val location = IntArray(2)
v.getLocationOnScreen(location)
originalXPos = location[0]
originalYPos = location[1]
offsetX = x - originalXPos
offsetY = y - originalYPos
}
MotionEvent.ACTION_MOVE -> {
val onScreen = IntArray(2)
v.getLocationOnScreen(onScreen)
val x = event.rawX
val y = event.rawY
val params: WindowManager.LayoutParams =
v.layoutParams as WindowManager.LayoutParams
val newX = (x - offsetX).toInt()
val newY = (y - offsetY).toInt()
if (newX == originalXPos && newY == originalYPos) {
return false
}
params.x = newX
params.y = newY
windowManager.updateViewLayout(v, params)
}
}
return false
}
}
private lateinit var imageView: ImageView
private lateinit var mainView: ScrollView
private fun gmTool() {
if (this::mainView.isInitialized) return
if (this::imageView.isInitialized) return
if (isInit) return
isInit = true
mainView = ScrollView(activity).apply {
visibility = View.GONE
addView(LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)
setBackgroundColor(Color.parseColor("#5F000000"))
addView(LinearLayout(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
setBackgroundColor(Color.parseColor("#8F000000"))
addView(TextView(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
setTextColor(Color.BLUE)
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
text = "Tools"
})
addView(TextView(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
setTextColor(Color.BLUE)
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
text = "X"
setOnClickListener {
mainView.visibility = View.GONE
imageView.visibility = View.VISIBLE
}
})
})
addView(LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)
var userEdit: EditText
var passEdit: EditText
addView(LinearLayout(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
addView(TextView(activity).apply {
setTextColor(Color.BLUE)
text = "(Check server stats)"
setOnClickListener {
Thread() {
try {
XposedBridge.log("$server/authentication/type")
(URL("$server/authentication/type").openConnection() as HttpURLConnection).apply {
requestMethod = "GET"
readTimeout = 8000
connectTimeout = 8000
val reader = BufferedReader(InputStreamReader(inputStream))
if (responseCode == 200) {
val response = StringBuilder()
var line = ""
while (reader.readLine()?.also { line = it } != null) {
response.append(line)
}
runOnMainThread {
text = if (response.toString() == "me.exzork.gcauth.handler.GCAuthAuthenticationHandler") "Server stats: GcAuth" else "Server stats: GcAuth not install"
}
} else {
runOnMainThread {
text = "Server stats: Get server stats error. "
}
}
}
} catch (e: Throwable) {
runOnMainThread {
text = "Server stats: Get server stats error. $e"
}
}
}.start()
}
})
})
addView(LinearLayout(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
addView(TextView(activity).apply {
setTextColor(Color.BLUE)
text = "User:"
})
addView(EditText(activity).apply {
userEdit = this
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
})
})
addView(LinearLayout(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
addView(TextView(activity).apply {
setTextColor(Color.BLUE)
text = "Password:"
})
addView(EditText(activity).apply {
passEdit = this
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
})
})
addView(LinearLayout(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
addView(Button(activity).apply {
text = "Login"
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
setOnClickListener {
Thread() {
try {
(URL("$server/authentication/login").openConnection() as HttpURLConnection).apply {
requestMethod = "POST"
readTimeout = 8000
connectTimeout = 8000
doOutput = true
doInput = true
useCaches = false
outputStream.apply {
write(URLEncoder.encode("{\"username\":\"${userEdit.text}\",\"password\":\"${passEdit.text}\"}", "UTF-8").toByteArray())
flush()
}
if (responseCode == 200) {
val input = inputStream
val message = ByteArrayOutputStream()
var len: Int
val buffer = ByteArray(1024)
while (input.read(buffer).also { len = it } != -1) {
message.write(buffer, 0, len)
}
input.close()
message.close()
val json = JSONObject(String(message.toByteArray()))
if (json.optBoolean("success", false)) {
val token = json.optString("jwt", "")
runOnMainThread {
Toast.makeText(activity, "Login success\n${token}", Toast.LENGTH_LONG).show()
(activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).text = token
}
} else {
runOnMainThread {
Toast.makeText(activity, "Login failed, ${json.optString("message", "")}", Toast.LENGTH_LONG).show()
}
}
}
}
} catch (e: Throwable) {
runOnMainThread {
Toast.makeText(activity, "Login error, $e", Toast.LENGTH_LONG).show()
}
}
}.start()
}
})
})
})
})
}
windowManager = activity.windowManager
windowManager.addView(mainView, WindowManager.LayoutParams(
dp2px(activity, 200f),
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
).apply {
gravity = Gravity.START or Gravity.TOP
x = 0
y = 0
})
val layoutParams = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
).apply {
gravity = Gravity.START or Gravity.TOP
x = 0
y = 0
}
imageView = ImageView(activity).apply {
background = moduleRes.getDrawable(R.drawable.ic_android_black_24dp).also { it.alpha = 50 }
this.layoutParams = layoutParams
setOnTouchListener(MoveOnTouchListener())
setOnClickListener {
mainView.visibility = View.VISIBLE
it.visibility = View.GONE
}
}
windowManager.addView(imageView, layoutParams)
}
private fun sslHook(lpparam: XC_LoadPackage.LoadPackageParam) {
findMethodOrNull("com.combosdk.lib.third.okhttp3.internal.tls.OkHostnameVerifier") { name == "verify" }?.hookBefore {
it.result = true
@ -141,14 +449,22 @@ class MainHook : IXposedHookLoadPackage, IXposedHookZygoteInit {
}
private fun replaceUrl(method: XC_MethodHook.MethodHookParam, args: Int) {
if (!forceUrl) return
if (!forceUrl && !proxyList) return
if (!this::server.isInitialized) return
if (server == "") return
if (BuildConfig.DEBUG) XposedBridge.log("old: " + method.args[args].toString())
val m = regex.matcher(method.args[args].toString())
if (m.find()) {
method.args[args] = m.replaceAll(server)
if (proxyList) {
for (list in proxyListRegex) {
for (head in arrayListOf("http://", "https://")) {
method.args[args] = method.args[args].toString().replace(head + list, server)
}
}
} else {
val m = regex.matcher(method.args[args].toString())
if (m.find()) {
method.args[args] = m.replaceAll(server)
}
}
if (BuildConfig.DEBUG) XposedBridge.log("new: " + method.args[args].toString())
}

View File

@ -0,0 +1,12 @@
package xfk233.GenshinProxy
import android.content.Context
object Utils {
var isInit = false
fun dp2px(context: Context, dpValue: Float): Int {
val scale: Float = context.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#213150"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z"/>
</vector>

View File

@ -7,4 +7,5 @@
<string name="Tips">第一次使用请输入好服务器地址后进入官方服务器下载资源,之后便可进入私服。\n强制模式: 强制替换全部域名\n\n请输入服务器地址: </string>
<string name="ForcedMode">强制模式 (需重启)</string>
<string name="ServerAddressError">服务器地址错误</string>
<string name="ProxyList">只代理列表内的地址 (需重启)</string>
</resources>

View File

@ -6,4 +6,5 @@
<string name="Tips">For the first time, please enter the server address and enter the official server to download resources. Then you can enter the private server.\nForced mode: Forced replacement of all domain names\n\nInput server address: </string>
<string name="ForcedMode">Forced mode (restart required)</string>
<string name="ServerAddressError">Server address error.</string>
<string name="ProxyList">Only addresses in the proxy list (restart required)</string>
</resources>