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.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.app.Application import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.res.XModuleResources import android.content.res.XModuleResources
import android.graphics.Color
import android.graphics.PixelFormat
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.widget.EditText import android.util.TypedValue
import android.widget.LinearLayout import android.view.Gravity
import android.widget.Switch import android.view.MotionEvent
import android.widget.Toast import android.view.View
import android.view.WindowManager
import android.widget.*
import com.github.kyuubiran.ezxhelper.init.EzXHelperInit import com.github.kyuubiran.ezxhelper.init.EzXHelperInit
import com.github.kyuubiran.ezxhelper.utils.findConstructor import com.github.kyuubiran.ezxhelper.utils.*
import com.github.kyuubiran.ezxhelper.utils.findMethod
import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull
import com.github.kyuubiran.ezxhelper.utils.hookBefore
import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.IXposedHookZygoteInit import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.callbacks.XC_LoadPackage 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 import java.util.regex.Pattern
@ -31,33 +41,75 @@ class MainHook : IXposedHookLoadPackage, IXposedHookZygoteInit {
private var forceUrl = false private var forceUrl = false
private lateinit var modulePath: String private lateinit var modulePath: String
private lateinit var moduleRes: XModuleResources 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) { override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
modulePath = startupParam.modulePath modulePath = startupParam.modulePath
moduleRes = XModuleResources.createInstance(modulePath, null) moduleRes = XModuleResources.createInstance(modulePath, null)
} }
@SuppressLint("WrongConstant", "ClickableViewAccessibility")
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
if (lpparam.packageName != "com.miHoYo.GenshinImpact") return if (lpparam.packageName != "com.miHoYo.GenshinImpact") return
EzXHelperInit.initHandleLoadPackage(lpparam) 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 context = it.args[0] as Context
val sp = context.getSharedPreferences("serverConfig", 0) val sp = context.getSharedPreferences("serverConfig", 0)
forceUrl = sp.getBoolean("forceUrl", false) forceUrl = sp.getBoolean("forceUrl", false)
server = sp.getString("serverip", "") ?: "" server = sp.getString("serverip", "") ?: ""
proxyList = sp.getBoolean("ProxyList", false)
} }
sslHook(lpparam) sslHook(lpparam)
hook() hook()
findMethod(Activity::class.java, true) { name == "onCreate" }.hookBefore { param ->
activity = param.thisObject as Activity
}
findMethod("com.miHoYo.GetMobileInfo.MainActivity") { name == "onCreate" }.hookBefore { param -> findMethod("com.miHoYo.GetMobileInfo.MainActivity") { name == "onCreate" }.hookBefore { param ->
val context = param.thisObject as Activity activity = param.thisObject as Activity
val sp = context.getSharedPreferences("serverConfig", 0) val sp = activity.getSharedPreferences("serverConfig", 0)
AlertDialog.Builder(context).apply { AlertDialog.Builder(activity).apply {
setCancelable(false) setCancelable(false)
setTitle(moduleRes.getString(R.string.SelectServer)) setTitle(moduleRes.getString(R.string.SelectServer))
setMessage(moduleRes.getString(R.string.Tips)) setMessage(moduleRes.getString(R.string.Tips))
setView(LinearLayout(context).apply { setView(LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL orientation = LinearLayout.VERTICAL
addView(EditText(context).apply { addView(EditText(activity).apply {
hint = "http(s)://server.com:1234" hint = "http(s)://server.com:1234"
val str = sp.getString("serverip", "") ?: "" val str = sp.getString("serverip", "") ?: ""
setText(str.toCharArray(), 0, str.length) 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) text = moduleRes.getString(R.string.ForcedMode)
isChecked = sp.getBoolean("forceUrl", false) isChecked = sp.getBoolean("forceUrl", false)
setOnClickListener { setOnClickListener {
@ -85,15 +137,28 @@ class MainHook : IXposedHookLoadPackage, IXposedHookZygoteInit {
forceUrl = (it as Switch).isChecked 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", "") ?: "" val ip = sp.getString("serverip", "") ?: ""
if (ip == "") { if (ip == "") {
Toast.makeText(context, moduleRes.getString(R.string.ServerAddressError), Toast.LENGTH_LONG).show() Toast.makeText(activity, moduleRes.getString(R.string.ServerAddressError), Toast.LENGTH_LONG).show()
context.finish() activity.finish()
} else { } else {
server = ip server = ip
forceUrl = true forceUrl = true
// TODO: The input box cannot enter content
// gmTool()
} }
} }
setNeutralButton(moduleRes.getString(R.string.OfficialServer)) { _, _ -> 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) { private fun sslHook(lpparam: XC_LoadPackage.LoadPackageParam) {
findMethodOrNull("com.combosdk.lib.third.okhttp3.internal.tls.OkHostnameVerifier") { name == "verify" }?.hookBefore { findMethodOrNull("com.combosdk.lib.third.okhttp3.internal.tls.OkHostnameVerifier") { name == "verify" }?.hookBefore {
it.result = true it.result = true
@ -141,15 +449,23 @@ class MainHook : IXposedHookLoadPackage, IXposedHookZygoteInit {
} }
private fun replaceUrl(method: XC_MethodHook.MethodHookParam, args: Int) { private fun replaceUrl(method: XC_MethodHook.MethodHookParam, args: Int) {
if (!forceUrl) return if (!forceUrl && !proxyList) return
if (!this::server.isInitialized) return if (!this::server.isInitialized) return
if (server == "") return if (server == "") return
if (BuildConfig.DEBUG) XposedBridge.log("old: " + method.args[args].toString()) if (BuildConfig.DEBUG) XposedBridge.log("old: " + method.args[args].toString())
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()) val m = regex.matcher(method.args[args].toString())
if (m.find()) { if (m.find()) {
method.args[args] = m.replaceAll(server) method.args[args] = m.replaceAll(server)
} }
}
if (BuildConfig.DEBUG) XposedBridge.log("new: " + method.args[args].toString()) 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="Tips">第一次使用请输入好服务器地址后进入官方服务器下载资源,之后便可进入私服。\n强制模式: 强制替换全部域名\n\n请输入服务器地址: </string>
<string name="ForcedMode">强制模式 (需重启)</string> <string name="ForcedMode">强制模式 (需重启)</string>
<string name="ServerAddressError">服务器地址错误</string> <string name="ServerAddressError">服务器地址错误</string>
<string name="ProxyList">只代理列表内的地址 (需重启)</string>
</resources> </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="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="ForcedMode">Forced mode (restart required)</string>
<string name="ServerAddressError">Server address error.</string> <string name="ServerAddressError">Server address error.</string>
<string name="ProxyList">Only addresses in the proxy list (restart required)</string>
</resources> </resources>