Skip to content
This repository has been archived by the owner on Jan 25, 2024. It is now read-only.

Commit

Permalink
http
Browse files Browse the repository at this point in the history
MrXiaoM committed Jan 7, 2024
1 parent 3bf73a5 commit e3b897a
Showing 17 changed files with 656 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ jobs:
- name: Build Package
uses: gradle/gradle-build-action@v2
with:
arguments: deploy -Pdev.sha=${{ env.SHORT_SHA }}
arguments: deploy distZip -Pdev.sha=${{ env.SHORT_SHA }}
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -10,6 +10,38 @@

本插件**自带**协议信息,在加载时将会应用与签名服务版本相同的设备信息文件。

# 非未来使用方式

1.3.0 起,恢复了原 http 接口。

[瑞丽斯](https://github.com/MrXiaoM/qsign/releases) 下载 `http-x.x.x.zip`,解压后文件夹结构大致情况如下
```
|-bin
|-lib
|-txlib
|-8.9.58
|-8.9.63
|-android_phone.json
|-android_pad.json
|-config.json
|-dtconfig.json
|-libfekit.so
|-libQSec.so
|-8.9.68
|-8.9.70
|-8.9.71
|-8.9.73
|-8.9.76
|-8.9.78
|-8.9.80
|-8.9.83
```
打开`cmd``终端`,执行以下命令即可启动快信号服务
```shell
java -cp lib/* MainKt --basePath=txlib/8.9.80
```
如需更换协议版本,请自行替换命令中的`8.9.80`

# 不支持特缪斯

特缪斯请使用 [bak-fpv](https://github.com/MrXiaoM/qsign/tree/bak-fpv),并在电脑上搭建签名服务,通过局域网连接签名服务。
@@ -158,10 +190,16 @@ dependencies {
# 编译

编译插件
```
```shell
./gradlew buildPlugin
```
打包发布 (将会在项目目录生成 `qsign-x.x.x-all.zip`)
```
```shell
./gradlew deploy
```
打包http服务 (将会在 `http/build/distributions/` 生成 `http-x.x.x.zip`)
```shell
./gradlew distZip
```

为这[一切](https://wiki.mrxiaom.top/mirai/sign.html)画上一个逗号,
87 changes: 87 additions & 0 deletions http/src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import com.tencent.mobileqq.dt.model.FEBound
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.json.Json
import moe.fuqiuluo.api.*
import moe.fuqiuluo.comm.CommonConfigExt
import moe.fuqiuluo.comm.QSignConfig
import moe.fuqiuluo.comm.checkIllegal
import moe.fuqiuluo.comm.invoke
import java.io.File

private val API_LIST = arrayOf(
Routing::index,
Routing::sign,
Routing::energy,
Routing::submit,
Routing::requestToken,
Routing::register,
)

private val json1 = Json {
encodeDefaults = true
prettyPrint = true
ignoreUnknownKeys = true
}

fun main(args: Array<String>) {
val file = File("qsign.json")
val s = if (file.exists()) file.readText() else "{}"
val cfg = json1.decodeFromString(CommonConfigExt.serializer(), s)
cfg.apply()
file.writeText(json1.encodeToString(CommonConfigExt.serializer(), cfg))

args().also {
val baseDir = File(it["basePath", "Lack of basePath."]).also {
BASE_PATH = it
}
if (!baseDir.exists() ||
!baseDir.isDirectory ||
!baseDir.resolve("libfekit.so").exists() ||
!baseDir.resolve("config.json").exists()
|| !baseDir.resolve("dtconfig.json").exists()
) {
error("The base path is invalid, perhaps it is not a directory or something is missing inside.")
} else {
val json = Json { ignoreUnknownKeys = true }
FEBound.initAssertConfig(baseDir)
println("FEBond sum = ${FEBound.checkCurrent()}")
CONFIG = json.decodeFromString<QSignConfig>(baseDir.resolve("config.json").readText())
.apply { checkIllegal() }
println("Load Package = ${CONFIG.protocol}")
}
}
CONFIG.server.also {
embeddedServer(Netty, host = it.host, port = it.port, module = Application::init)
.start(wait = true)
}

}

fun Application.init() {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
install(StatusPages) {
exception<Throwable> { call, cause ->
if (CONFIG.unidbg.debug) {
cause.printStackTrace()
}
call.respond(APIResult(1, cause.message ?: cause.javaClass.name, call.request.uri))
}
}
routing {
API_LIST.forEach { it(this) }
}
}
52 changes: 52 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/api/energy.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package moe.fuqiuluo.api

import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import moe.fuqiuluo.ext.fetchGet
import moe.fuqiuluo.ext.hex2ByteArray
import moe.fuqiuluo.ext.toHexString

fun Routing.energy() {
get("/custom_energy") {
val uin = fetchGet("uin")!!.toLong()
val data = fetchGet("data")!!
val salt = fetchGet("salt")!!.hex2ByteArray()

val androidId = fetchGet("android_id", def = "")
val guid = fetchGet("guid", def = "")
val sign = kotlin.runCatching {
UnidbgFetchQSign.customEnergy(uin, data, salt, androidId, guid)
}.onFailure {
it.printStackTrace()
}.getOrNull()

if (sign == null) {
call.respond(APIResult(-1, "failed", null))
} else {
call.respond(APIResult(0, "success", sign.toHexString()))
}
}

get("/energy") {
val uin = fetchGet("uin")!!.toLong()

val data = fetchGet("data")!!
val androidId = fetchGet("android_id", def = "")
val guid = fetchGet("guid", def = null)?.hex2ByteArray()
val version = fetchGet("version", def = null)
val phone = fetchGet("phone", def = null)?.toByteArray() // 86-xxx
val receipt = fetchGet("receipt", def = null)?.toByteArray()
val sign = kotlin.runCatching {
UnidbgFetchQSign.energy(uin, data, null, version, guid, androidId, phone, receipt)
}.onFailure {
it.printStackTrace()
}.getOrNull()

if (sign == null) {
call.respond(APIResult(-1, "failed", null))
} else {
call.respond(APIResult(0, "success", sign.toHexString()))
}
}
}
9 changes: 9 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/api/exceptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package moe.fuqiuluo.api

object SessionNotFoundError : RuntimeException("Uin is not registered.")

object WrongKeyError : RuntimeException("Wrong API key.")

object MissingKeyError : RuntimeException("First use must be submitted with android_id and guid.")

object BlackListError : RuntimeException("Blacklist uin.")
41 changes: 41 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/api/main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package moe.fuqiuluo.api

import CONFIG
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import moe.fuqiuluo.comm.Protocol
import top.mrxiaom.qsign.BuildConstants
import java.lang.management.ManagementFactory

@Serializable
data class APIResult<T>(
val code: Int,
val msg: String = "",
@Contextual
val data: T? = null
)

@Serializable
data class APIInfo(
val version: String,
val protocol: Protocol,
val pid: Int
)

fun Routing.index() {
get("/") {
call.respond(
APIResult(
0, "IAA 云天明 章北海 猫", APIInfo(
version = BuildConstants.VERSION,
protocol = CONFIG.protocol,
pid = runCatching { ManagementFactory.getRuntimeMXBean().name.split("@")[0].toInt() }.getOrNull()
?: -1
)
)
)
}
}
85 changes: 85 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/api/qsign.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package moe.fuqiuluo.api

import CONFIG
import com.tencent.mobileqq.channel.SsoPacket
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.pipeline.*
import kotlinx.serialization.Serializable
import moe.fuqiuluo.ext.fetchGet
import moe.fuqiuluo.ext.fetchPost
import moe.fuqiuluo.ext.hex2ByteArray
import moe.fuqiuluo.ext.toHexString

fun Routing.sign() {
get("/sign") {
val uin = fetchGet("uin")!!
val qua = fetchGet("qua", CONFIG.protocol.qua)!!
val cmd = fetchGet("cmd")!!
val seq = fetchGet("seq")!!.toInt()
val buffer = fetchGet("buffer")!!.hex2ByteArray()
val qimei36 = fetchGet("qimei36", def = "")!!

val androidId = fetchGet("android_id", def = "")!!
val guid = fetchGet("guid", def = "")!!

requestSign(cmd, uin, qua, seq, buffer, qimei36, androidId, guid)
}

post("/sign") {
val param = call.receiveParameters()
val uin = fetchPost(param, "uin")!!
val qua = fetchPost(param, "qua", CONFIG.protocol.qua)!!
val cmd = fetchPost(param, "cmd")!!
val seq = fetchPost(param, "seq")!!.toInt()
val buffer = fetchPost(param, "buffer")!!.hex2ByteArray()
val qimei36 = fetchPost(param, "qimei36", def = "")!!

val androidId = param["android_id"] ?: ""
val guid = param["guid"] ?: ""

requestSign(cmd, uin, qua, seq, buffer, qimei36, androidId, guid)
}
}

@Serializable
private data class Sign(
val token: String,
val extra: String,
val sign: String,
val o3did: String,
val requestCallback: List<SsoPacket>
)

private suspend fun PipelineContext<Unit, ApplicationCall>.requestSign(
cmd: String,
uin: String,
qua: String,
seq: Int,
buffer: ByteArray,
qimei36: String,
androidId: String,
guid: String
) {
val sign = runCatching {
UnidbgFetchQSign.sign(cmd, uin.toLong(), seq, buffer, qua, qimei36, androidId, guid)
}.onFailure {
it.printStackTrace()
}.getOrNull()

if (sign == null) {
call.respond(APIResult(-1, "failed", null))
} else {
call.respond(
APIResult(
0, "success", Sign(
sign.token.toHexString(),
sign.extra.toHexString(),
sign.sign.toHexString(), sign.o3did, sign.requestCallback
)
)
)
}
}
54 changes: 54 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/api/register.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package moe.fuqiuluo.api

import CONFIG
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import moe.fuqiuluo.ext.failure
import moe.fuqiuluo.ext.fetchGet

fun Routing.register() {
get("/register") {
val uin = fetchGet("uin")!!.toLong()
val androidId = fetchGet("android_id")!!
val guid = fetchGet("guid")!!.lowercase()
val qimei36 = fetchGet("qimei36")!!.lowercase()

val key = fetchGet("key")!!

val qua = fetchGet("qua", CONFIG.protocol.qua)!!
val version = fetchGet("version", CONFIG.protocol.version)!!
val code = fetchGet("code", CONFIG.protocol.code)!!

if (key == CONFIG.key) {
if (UnidbgFetchQSign.register(uin, androidId, guid, qimei36, qua, version, code)) {
call.respond(
APIResult(
0,
"The QQ has already loaded an instance, so this time it is deleting the existing instance and creating a new one.",
""
)
)
} else {
call.respond(APIResult(0, "Instance loaded successfully.", ""))
}
} else {
throw WrongKeyError
}
}

get("/destroy") {
val uin = fetchGet("uin")!!.toLong()
val key = fetchGet("key")!!

if (key == CONFIG.key) {
if (UnidbgFetchQSign.destroy(uin)) {
call.respond(APIResult(0, "Instance destroyed successfully.", ""))
} else {
failure(1, "Instance does not exist.")
}
} else {
throw WrongKeyError
}
}
}
32 changes: 32 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/api/request_token.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")

package moe.fuqiuluo.api

import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import moe.fuqiuluo.ext.fetchGet

fun Routing.requestToken() {
get("/request_token") {
val uin = fetchGet("uin")!!.toLong()
val isForced = fetchGet("force", def = "false")

val pair = runCatching {
UnidbgFetchQSign.requestToken(uin, isForced == "true")
}.onFailure {
call.respond(APIResult(-1, it.message.toString(), ""))
}.getOrNull() ?: return@get

val isSuccessful = pair.first
val list = pair.second

call.respond(
APIResult(
if (!isSuccessful) -1 else 0,
if (!isSuccessful) "request_token timeout" else "submit success",
list
)
)
}
}
24 changes: 24 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/api/submit.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package moe.fuqiuluo.api

import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import moe.fuqiuluo.ext.fetchGet
import moe.fuqiuluo.ext.hex2ByteArray


fun Routing.submit() {
get("/submit") {
val uin = fetchGet("uin")!!.toLong()
val cmd = fetchGet("cmd")!!
val callbackId = fetchGet("callback_id")!!.toLong()
val buffer = fetchGet("buffer")!!.hex2ByteArray()
val androidId = fetchGet("android_id", def = "")!!
val guid = fetchGet("guid", def = "")!!.lowercase()
val qimei36 = fetchGet("qimei36", def = "")!!.lowercase()

UnidbgFetchQSign.submit(uin, cmd, callbackId, buffer, androidId, guid, qimei36)

call.respond(APIResult(0, "submit success", ""))
}
}
42 changes: 42 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/comm/ArgsParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package moe.fuqiuluo.comm

import moe.fuqiuluo.ext.StringArray

class ArgsParser(
args: StringArray
) {
private val map = hashMapOf<String, String>()

init {
args.forEach {
it.substring(
if (it.startsWith("--")) 2
else if (it.startsWith("-")) 1
else error("Not support the expr.")
).split("=").also {
map[it[0]] = it.slice(1 until it.size).joinToString("")
}
}
}

operator fun get(key: String): String? {
return map[key]
}

fun getOrDefault(key: String, def: String?): String? {
return get(key) ?: def
}

operator fun get(key: String, err: String): String {
require(key in this) { err }
return this[key]!!
}

operator fun contains(key: String): Boolean {
return key in map
}
}

operator fun StringArray.invoke(): ArgsParser {
return ArgsParser(this)
}
32 changes: 32 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/comm/CommonConfigExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package moe.fuqiuluo.comm

import kotlinx.serialization.Serializable
import top.mrxiaom.qsign.CommonConfig
import java.io.File

@Serializable
data class CommonConfigExt(
var virtualRootPath: String = "virtualRoot",
var appInstallFolder: String = "/data/app/~~nNzv5koU9DgkrbtCpa02wQ==/\${packageName}-fR9VqAFGIZNVZ8MgZYh0Ow==",
var screenSizeWidth: Int = 1080,
var screenSizeHeight: Int = 2400,
var density: String = "2.75",
var serialNumber: String = "0x0000043be8571339",
var androidVersion: String = "13",
var androidSdkVersion: Int = 33,
var targetSdkVersion: Int = 29,
var storageSize: String = "137438953471"
) {
fun apply() = CommonConfig.also {
it.virtualRootPath = File(virtualRootPath)
it.appInstallFolder = appInstallFolder
it.screenSizeWidth = screenSizeWidth
it.screenSizeHeight = screenSizeHeight
it.density = density
it.serialNumber = serialNumber
it.androidVersion = androidVersion
it.androidSdkVersion = androidSdkVersion
it.targetSdkVersion = targetSdkVersion
it.storageSize = storageSize
}
}
103 changes: 103 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/ext/BytePacket.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package moe.fuqiuluo.ext

import com.tencent.crypt.Crypt
import io.ktor.utils.io.core.*
import moe.fuqiuluo.utils.BytesUtil
import kotlin.text.Charsets.UTF_8
import kotlin.text.toByteArray

inline fun newBuilder() = BytePacketBuilder()

fun BytePacketBuilder.writeBytes(bytes: ByteArray) = this.writeFully(bytes)

fun BytePacketBuilder.toByteArray(): ByteArray = use { it.build().readBytes() }

fun ByteReadPacket.toByteArray(): ByteArray = use { it.readBytes() }

/**
* 补充功能代码
* @receiver BytePacketBuilder
* @param packet BytePacketBuilder
*/
fun BytePacketBuilder.writePacket(packet: BytePacketBuilder) = this.writePacket(packet.build())

/**
* 写布尔型
* @receiver BytePacketBuilder
* @param z Boolean
*/
fun BytePacketBuilder.writeBoolean(z: Boolean) = this.writeByte(if (z) 1 else 0)

/**
* 自动转换类型
* @receiver BytePacketBuilder
* @param i Int
*/
fun BytePacketBuilder.writeShort(i: Int) = this.writeShort(i.toShort())

fun BytePacketBuilder.writeLongToBuf32(v: Long) {
this.writeBytes(BytesUtil.int64ToBuf32(v))
}

fun BytePacketBuilder.writeStringWithIntLen(str: String) {
writeBytesWithIntLen(str.toByteArray())
}

fun BytePacketBuilder.writeStringWithShortLen(str: String) {
writeBytesWithShortLen(str.toByteArray())
}

fun BytePacketBuilder.writeBytesWithIntLen(bytes: ByteArray) {
writeInt(bytes.size)
writeBytes(bytes)
}

fun BytePacketBuilder.writeBytesWithShortLen(bytes: ByteArray) {
check(bytes.size <= Short.MAX_VALUE) { "byteArray length is too long" }
writeShort(bytes.size.toShort())
writeBytes(bytes)
}

inline fun BytePacketBuilder.writeBlockWithIntLen(len: (Int) -> Int = { it }, block: BytePacketBuilder.() -> Unit) {
val builder = newBuilder()
builder.block()
this.writeInt(len(builder.size))
this.writePacket(builder)
builder.close()
}

inline fun BytePacketBuilder.writeBlockWithShortLen(len: (Int) -> Int = { it }, block: BytePacketBuilder.() -> Unit) {
val builder = newBuilder()
builder.block()
this.writeShort(len(builder.size))
this.writePacket(builder)
builder.close()
}


inline fun BytePacketBuilder.writeTeaEncrypt(key: ByteArray, block: BytePacketBuilder.() -> Unit) {
val body = newBuilder()
body.block()
this.writeBytes(Crypt().encrypt(body.toByteArray(), key))
body.close()
}

fun BytePacketBuilder.writeString(str: String) {
this.writeBytes(str.toByteArray(UTF_8))
}

fun BytePacketBuilder.writeHex(uHex: String) {
writeBytes(uHex.hex2ByteArray())
}

fun ByteReadPacket.readString(length: Int) = readBytes(length).decodeToString()

fun ByteArray.toByteReadPacket() = ByteReadPacket(this)

inline fun ByteArray.reader(block: ByteReadPacket.() -> Unit) {
this.toByteReadPacket().use(block)
}

fun ByteReadPacket.readByteReadPacket(length: Int): ByteReadPacket {
return readBytes(length).toByteReadPacket()
}
36 changes: 36 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/ext/Ktor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package moe.fuqiuluo.ext

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.util.pipeline.*
import moe.fuqiuluo.api.APIResult

suspend fun PipelineContext<Unit, ApplicationCall>.fetchGet(
key: String,
def: String? = null,
err: String? = "Parameter '$key' is missing."
): String? {
val data = call.parameters[key] ?: def
if (data == null && err != null) {
failure(1, err)
}
return data
}

suspend fun PipelineContext<Unit, ApplicationCall>.fetchPost(
params: Parameters,
key: String,
def: String? = null,
err: String? = "Parameter '$key' is missing."
): String? {
val data = params[key] ?: def
if (data == null && err != null) {
failure(1, err)
}
return data
}

suspend fun PipelineContext<Unit, ApplicationCall>.failure(code: Int, msg: String) {
call.respond(APIResult(code, msg, "failed"))
}
7 changes: 7 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/ext/Numbers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package moe.fuqiuluo.ext

fun String.toInt(range: IntRange, lazyMessage: () -> Any = { "Failed requirement." }): Int {
val i = toInt()
require(i in range, lazyMessage)
return i
}
3 changes: 3 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/ext/Types.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package moe.fuqiuluo.ext

typealias StringArray = Array<String>
8 changes: 8 additions & 0 deletions http/src/main/kotlin/moe/fuqiuluo/ext/Unidbg.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package moe.fuqiuluo.ext

import com.github.unidbg.linux.android.dvm.BaseVM
import com.github.unidbg.linux.android.dvm.DvmObject

fun BaseVM.newInstance(className: String, arg: Any? = null): DvmObject<*> {
return resolveClass(className).newObject(arg)
}

0 comments on commit e3b897a

Please sign in to comment.