Commit d61a2df1 authored by Him188's avatar Him188

Utilities for QQAndroid

parent 7bdaa9b1
......@@ -76,6 +76,7 @@ kotlin {
dependencies {
api(kotlin("test-annotations-common"))
api(kotlin("test-common"))
implementation(kotlin("script-runtime"))
}
}
......
package net.mamoe.mirai.qqandroid.utils
actual typealias Context = android.content.Context
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils
import android.annotation.SuppressLint
import android.os.Build
import android.telephony.TelephonyManager
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.utils.localIpAddress
import net.mamoe.mirai.utils.md5
import java.io.File
/**
* Delegated by [Build]
*/
actual class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(context) {
override val display: ByteArray get() = Build.DISPLAY.toByteArray()
override val product: ByteArray get() = Build.PRODUCT.toByteArray()
override val device: ByteArray get() = Build.DEVICE.toByteArray()
override val board: ByteArray get() = Build.BOARD.toByteArray()
override val brand: ByteArray get() = Build.BRAND.toByteArray()
override val model: ByteArray get() = Build.MODEL.toByteArray()
override val bootloader: ByteArray get() = Build.BOOTLOADER.toByteArray()
override val baseBand: ByteArray
@SuppressLint("PrivateApi")
@Suppress("SpellCheckingInspection")
get() = kotlin.runCatching {
Class.forName("android.os.SystemProperties").let { clazz ->
clazz.getMethod("get", String::class.java, String::class.java)
.invoke(clazz.newInstance(), "gsm.version.baseband", "no message")
?.toString() ?: ""
}
}.getOrElse { "" }.toByteArray()
override val fingerprint: ByteArray get() = Build.FINGERPRINT.toByteArray()
override val procVersion: ByteArray
get() = File("/proc/version").useLines { it.firstOrNull() ?: "" }.toByteArray()
override val bootId: ByteArray
get() = File("/proc/sys/kernel/random/boot_id").useLines { it.firstOrNull() ?: "" }.toByteArray()
override val version: DeviceInfo.Version get() = Version
override val simInfo: ByteArray
@SuppressLint("WrongConstant")
get() {
return kotlin.runCatching {
val telephonyManager = context.getSystemService("phone") as TelephonyManager
if (telephonyManager.simState == 5) {
telephonyManager.simOperatorName.toByteArray()
} else byteArrayOf()
}.getOrElse { byteArrayOf() }
}
override val osType: ByteArray = "android".toByteArray()
override val macAddress: ByteArray get() = "02:00:00:00:00:00".toByteArray()
override val wifiBSSID: ByteArray?
get() = TODO("not implemented")
override val wifiSSID: ByteArray?
get() = TODO("not implemented")
override val imsiMd5: ByteArray // null due to permission READ_PHONE_STATE required
get() = md5(byteArrayOf())/*{
val telephonyManager = context.getSystemService("phone") as TelephonyManager
if (telephonyManager != null) {
val subscriberId = telephonyManager.subscriberId
if (subscriberId != null) {
return subscriberId.toByteArray()
}
}
return kotlin.runCatching {
val telephonyManager = context.getSystemService("phone") as TelephonyManager
if (telephonyManager != null) {
telephonyManager.subscriberId.toByteArray()
} else byteArrayOf()
}.getOrElse { byteArrayOf() }
}*/
override val ipAddress: ByteArray get() = localIpAddress().toByteArray()
override val androidId: ByteArray get() = Build.ID.toByteArray()
override val apn: ByteArray get() = "wifi".toByteArray()
object Version : DeviceInfo.Version {
override val incremental: ByteArray get() = Build.VERSION.INCREMENTAL.toByteArray()
override val release: ByteArray get() = Build.VERSION.RELEASE.toByteArray()
override val codename: ByteArray get() = Build.VERSION.CODENAME.toByteArray()
}
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.qqandroid.utils.*
/*
APP ID:
GetStViaSMSVerifyLogin = 16
GetStWithoutPasswd = 16
TICKET ID
Pskey = 0x10_0000, from oicq/wlogin_sdk/request/WtloginHelper.java:2980
Skey = 0x1000 from oicq/wlogin_sdk/request/WtloginHelper.java:2986
DOMAINS
Pskey: "openmobile.qq.com"
*/
@PublishedApi
internal open class QQAndroidClient(
val context: Context,
val account: BotAccount,
val ecdh: ECDH = ECDH.Default,
val device: DeviceInfo = SystemDeviceInfo(context)
) {
val tgtgtKey: ByteArray = generateTgtgtKey(device.guid)
var miscBitMap: Int = 150470524
var mainSigMap: Int = 16724722
var subSigMap: Int = 0x10400 //=66,560
var ssoSequenceId: Int = 0
var openAppId: Long = 715019303L
var ipv6NetType: Int = TODO()
val apkVersionName: ByteArray = "8.2.0".toByteArray()
val appClientVersion: Int = TODO()
val apkSignatureMd5: ByteArray = TODO()
/**
* 协议版本?, 8.2.0 的为 8001
*/
val protocolVersion: Short = 8001
@Suppress("SpellCheckingInspection")
@PublishedApi
internal val apkId: ByteArray = "com.tencent.mobileqq".toByteArray()
}
......@@ -3,10 +3,13 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet
import kotlinx.io.core.*
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidDevice
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId
import net.mamoe.mirai.qqandroid.utils.ECDH
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeQQ
import net.mamoe.mirai.utils.io.writeShortLVByteArray
/**
* 待发送给服务器的数据包. 它代表着一个 [ByteReadPacket],
......@@ -101,8 +104,8 @@ inline class EncryptMethod(val value: UByte) {
}
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
inline fun PacketFactory<*, *>.buildOutgoingPacket(
device: QQAndroidDevice,
internal inline fun PacketFactory<*, *>.buildOutgoingPacket(
client: QQAndroidClient,
encryptMethod: EncryptMethod,
name: String? = null,
id: PacketId = this.id,
......@@ -114,15 +117,15 @@ inline fun PacketFactory<*, *>.buildOutgoingPacket(
// Head
writeByte(0x02) // head
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
writeShort(device.protocolVersion)
writeShort(client.protocolVersion)
writeShort(id.commandId.toShort())
writeShort(sequenceId.toShort())
writeQQ(device.uin)
writeQQ(client.account.id)
writeByte(3) // originally const
writeUByte(encryptMethod.value)
writeByte(0) // const8_always_0
writeInt(2) // originally const
writeInt(device.appClientVersion)
writeInt(client.appClientVersion)
writeInt(0) // constp_always_0
// Body
......@@ -131,4 +134,55 @@ inline fun PacketFactory<*, *>.buildOutgoingPacket(
// Tail
writeByte(0x03) // tail
})
}
/**
* buildPacket{
* byte 1
* byte 1
* fully privateKey
* short 258
* short publicKey.length
* fully publicKey
* encryptAndWrite(shareKey, body)
* }
*/
inline fun BytePacketBuilder.writeECDHEncryptedPacket(
ecdh: ECDH,
body: BytePacketBuilder.() -> Unit
) = ecdh.run {
writeByte(1) // const
writeByte(1) // const
writeFully(privateKey)
writeShort(258) // const
writeShortLVByteArray(publicKey)
encryptAndWrite(shareKey, body)
}
/**
* buildPacket{
* byte 1
* if loginState == 2:
* byte 3
* else:
* byte 2
* fully key
* short 258
* short 0
* fully encrypted
* }
*/
inline fun BytePacketBuilder.writeTEAEncryptedPacket(
loginState: Int,
key: ByteArray,
body: BytePacketBuilder.() -> Unit
) {
require(loginState == 2 || loginState == 3)
writeByte(1) // const
writeByte(if (loginState == 2) 3 else 2) // const
writeFully(key)
writeShort(258) // const
writeShort(0) // const, length of publicKey
encryptAndWrite(key, body)
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils
/**
* On Android, typealias to `android.content.Context`
* On JVM, empty class.
*/
expect abstract class Context
package net.mamoe.mirai.qqandroid.utils
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoBuf
abstract class DeviceInfo(
val context: Context
) {
abstract val display: ByteArray
abstract val product: ByteArray
abstract val device: ByteArray
abstract val board: ByteArray
abstract val brand: ByteArray
abstract val model: ByteArray
abstract val bootloader: ByteArray
abstract val fingerprint: ByteArray
abstract val bootId: ByteArray
abstract val procVersion: ByteArray
abstract val baseBand: ByteArray
abstract val version: Version
abstract val simInfo: ByteArray
abstract val osType: ByteArray
abstract val macAddress: ByteArray
abstract val wifiBSSID: ByteArray?
abstract val wifiSSID: ByteArray?
abstract val imsiMd5: ByteArray
abstract val ipAddress: ByteArray
abstract val androidId: ByteArray
abstract val apn: ByteArray
val guid: ByteArray get() = generateGuid(androidId, macAddress)
fun generateDeviceInfoData(): ByteArray {
@Serializable
class DevInfo(
@SerialId(1) val bootloader: ByteArray,
@SerialId(2) val procVersion: ByteArray,
@SerialId(3) val codename: ByteArray,
@SerialId(4) val incremental: ByteArray,
@SerialId(5) val fingerprint: ByteArray,
@SerialId(6) val bootId: ByteArray,
@SerialId(7) val androidId: ByteArray,
@SerialId(8) val baseBand: ByteArray,
@SerialId(9) val innerVersion: ByteArray
)
return ProtoBuf.dump(
DevInfo.serializer(), DevInfo(
bootloader,
procVersion,
version.codename,
version.incremental,
fingerprint,
bootId,
androidId,
baseBand,
version.incremental
)
)
}
interface Version {
val incremental: ByteArray
val release: ByteArray
val codename: ByteArray
}
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network
package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.utils.io.chunkedHexToBytes
/**
......@@ -21,34 +20,4 @@ interface ECDH {
val shareKey: ByteArray
val privateKey: ByteArray
}
/*
APP ID:
GetStViaSMSVerifyLogin = 16
GetStWithoutPasswd = 16
*/
class QQAndroidDevice(
private val account: BotAccount,
/**
* 协议版本?, 8.2.0 的为 8001
*/
@PublishedApi
internal val protocolVersion: Short = 8001,
@PublishedApi
internal val ecdh: ECDH = ECDH.Default,
@PublishedApi
internal val appClientVersion: Int
) {
val uin: Long get() = account.id
val password: String get() = account.password
object Debugging {
}
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils
inline class MacOrAndroidIdChangeFlag(val value: Long = 0) {
fun macChanged(): MacOrAndroidIdChangeFlag =
MacOrAndroidIdChangeFlag(this.value or 0x1)
fun androidIdChanged(): MacOrAndroidIdChangeFlag =
MacOrAndroidIdChangeFlag(this.value or 0x2)
fun guidChanged(): MacOrAndroidIdChangeFlag =
MacOrAndroidIdChangeFlag(this.value or 0x3)
companion object {
val NoChange: MacOrAndroidIdChangeFlag get() = MacOrAndroidIdChangeFlag()
}
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.utils.md5
import kotlin.jvm.JvmStatic
/**
* GUID 来源
*
* 0: 初始值;
* 1: 以前保存的文件;
* 20: 以前没保存且现在生成失败;
* 17: 以前没保存但现在生成成功;
*/
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
inline class GuidSource private constructor(val id: Long) { // uint actually
companion object {
/**
* 初始值
*/
@JvmStatic
val STUB = GuidSource(0)
/**
*
*/
@JvmStatic
val NEWLY_GENERATED = GuidSource(17)
/**
* 以前没保存但现在生成成功
*/
@JvmStatic
val FROM_STORAGE = GuidSource(1)
/**
* 以前没保存且现在生成失败
*/
@JvmStatic
val UNAVAILABLE = GuidSource(20)
}
}
/**
* ```java
* GUID_FLAG = 0;
* GUID_FLAG |= GUID_SRC << 24 & 0xFF000000;
* GUID_FLAG |= FLAG_MAC_ANDROIDID_GUID_CHANGE << 8 & 0xFF00;
* ```
*
* FLAG_MAC_ANDROIDID_GUID_CHANGE:
* ```java
* if (!Arrays.equals(currentMac, get_last_mac)) {
* oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x1;
* }
* if (!Arrays.equals(currentAndroidId, get_last_android_id)) {
* oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x2;
* }
* if (!Arrays.equals(currentGuid, get_last_guid)) {
* oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x4;
* }
* ```
*/
internal fun guidFlag(
guidSource: GuidSource,
macOrAndroidIdChangeFlag: MacOrAndroidIdChangeFlag
): Long {
var flag = 0L
flag = flag or (guidSource.id shl 24 and 0xFF000000)
flag = flag or (macOrAndroidIdChangeFlag.value shl 8 and 0xFF00)
return flag
}
/**
* Defaults "%4;7t>;28<fc.5*6".toByteArray()
*/
fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray = md5(androidId + macAddress)
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils
/**
* System default values
*/
expect class SystemDeviceInfo(context: Context) : DeviceInfo
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.utils.io.getRandomByteArray
import net.mamoe.mirai.utils.md5
fun generateTgtgtKey(guid: ByteArray): ByteArray =
md5(getRandomByteArray(16) + guid)
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils
/**
* Inline the block
*/
@PublishedApi
internal inline fun <R> inline(block: () -> R): R = block()
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.utils.md5
actual class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(context) {
override val display: ByteArray get() = TODO("not implemented")
override val product: ByteArray get() = TODO("not implemented")
override val device: ByteArray get() = TODO("not implemented")
override val board: ByteArray get() = TODO("not implemented")
override val brand: ByteArray get() = TODO("not implemented")
override val model: ByteArray get() = TODO("not implemented")
override val bootloader: ByteArray get() = TODO("not implemented")
override val fingerprint: ByteArray get() = TODO("not implemented")
override val bootId: ByteArray get() = TODO("not implemented")
override val procVersion: ByteArray get() = "Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com)".toByteArray()
override val baseBand: ByteArray get() = TODO()
override val version: DeviceInfo.Version get() = TODO("not implemented")
override val simInfo: ByteArray get() = TODO("not implemented")
override val osType: ByteArray get() = TODO("not implemented")
override val macAddress: ByteArray get() = TODO("not implemented")
override val wifiBSSID: ByteArray?
get() = null
override val wifiSSID: ByteArray?
get() = null
override val imsiMd5: ByteArray get() = md5(byteArrayOf())
override val ipAddress: ByteArray get() = TODO("not implemented")
override val androidId: ByteArray get() = TODO("not implemented")
override val apn: ByteArray get() = TODO("not implemented")
object Version : DeviceInfo.Version {
override val incremental: ByteArray get() = TODO("not implemented")
override val release: ByteArray get() = TODO("not implemented")
override val codename: ByteArray get() = TODO("not implemented")
}
}
actual abstract class Context
open class ContextImpl : Context()
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package test
import net.mamoe.mirai.utils.cryptor.protoFieldNumber
intArrayOf(10, 18, 26, 34, 42, 50, 58, 66, 74).forEach {
println(protoFieldNumber(it.toUInt()))
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment