mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-07 16:40:43 +08:00
Android stuff
This commit is contained in:
parent
7f338edcba
commit
62b3740a72
@ -0,0 +1,54 @@
|
||||
package net.mamoe.mirai.qqandroid.network
|
||||
|
||||
import net.mamoe.mirai.BotAccount
|
||||
import net.mamoe.mirai.utils.io.chunkedHexToBytes
|
||||
|
||||
/**
|
||||
* From QQAndroid 8.2.0
|
||||
* `oicq.wlogin_sdk.tools.EcdhCrypt`
|
||||
*
|
||||
* Constant to avoid calculations
|
||||
*/
|
||||
interface ECDH {
|
||||
object Default : ECDH {
|
||||
override val publicKey: ByteArray = "020b03cf3d99541f29ffec281bebbd4ea211292ac1f53d7128".chunkedHexToBytes()
|
||||
override val shareKey: ByteArray = "4da0f614fc9f29c2054c77048a6566d7".chunkedHexToBytes()
|
||||
override val privateKey: ByteArray = ByteArray(16)
|
||||
}
|
||||
|
||||
val publicKey: ByteArray
|
||||
|
||||
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 {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
package net.mamoe.mirai.qqandroid.network.io
|
||||
|
||||
import kotlinx.io.charsets.Charset
|
||||
import kotlinx.io.core.BytePacketBuilder
|
||||
import kotlinx.io.core.ExperimentalIoApi
|
||||
import kotlinx.io.core.toByteArray
|
||||
import kotlinx.io.core.writeFully
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
*
|
||||
* From: com.qq.taf.jce.JceOutputStream
|
||||
*/
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
@UseExperimental(ExperimentalIoApi::class)
|
||||
class JceOutput(
|
||||
private val stringCharset: Charset = Charset.forName("GBK")
|
||||
) {
|
||||
private val output: BytePacketBuilder = BytePacketBuilder()
|
||||
|
||||
fun close() = output.close()
|
||||
fun flush() = output.flush()
|
||||
|
||||
fun writeByte(v: Byte, tag: Int) {
|
||||
if (v.toInt() == 0) {
|
||||
writeHead(ZERO_TAG, tag)
|
||||
} else {
|
||||
writeHead(BYTE, tag)
|
||||
output.writeByte(v)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeDouble(v: Double, tag: Int) {
|
||||
writeHead(DOUBLE, tag)
|
||||
output.writeDouble(v)
|
||||
}
|
||||
|
||||
fun writeFloat(v: Float, tag: Int) {
|
||||
writeHead(FLOAT, tag)
|
||||
output.writeFloat(v)
|
||||
}
|
||||
|
||||
fun writeFully(src: ByteArray, tag: Int) {
|
||||
writeHead(SIMPLE_LIST, tag)
|
||||
writeHead(BYTE, 0)
|
||||
writeInt(src.size, 0)
|
||||
output.writeFully(src)
|
||||
}
|
||||
|
||||
fun writeFully(src: DoubleArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeDouble(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFully(src: FloatArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeFloat(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFully(src: IntArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeInt(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFully(src: LongArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeLong(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFully(src: ShortArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeShort(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFully(src: BooleanArray, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeBoolean(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> writeFully(src: Array<T>, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
writeInt(src.size, 0)
|
||||
src.forEach {
|
||||
writeObject(it, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeInt(v: Int, tag: Int) {
|
||||
if (v in Short.MIN_VALUE..Short.MAX_VALUE) {
|
||||
writeShort(v.toShort(), tag)
|
||||
} else {
|
||||
writeHead(INT, tag)
|
||||
output.writeInt(v)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeLong(v: Long, tag: Int) {
|
||||
if (v in Int.MIN_VALUE..Int.MAX_VALUE) {
|
||||
writeInt(v.toInt(), tag)
|
||||
} else {
|
||||
writeHead(LONG, tag)
|
||||
output.writeLong(v)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeShort(v: Short, tag: Int) {
|
||||
if (v in Byte.MIN_VALUE..Byte.MAX_VALUE) {
|
||||
writeByte(v.toByte(), tag)
|
||||
} else {
|
||||
writeHead(BYTE, tag)
|
||||
output.writeShort(v)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeBoolean(v: Boolean, tag: Int) {
|
||||
this.writeByte(if (v) 1 else 0, tag)
|
||||
}
|
||||
|
||||
fun writeString(v: String, tag: Int) {
|
||||
val array = v.toByteArray(stringCharset)
|
||||
if (array.size > 255) {
|
||||
writeHead(STRING4, tag)
|
||||
output.writeInt(array.size)
|
||||
output.writeFully(array)
|
||||
} else {
|
||||
writeHead(STRING1, tag)
|
||||
output.writeByte(array.size.toByte())
|
||||
output.writeFully(array)
|
||||
}
|
||||
}
|
||||
|
||||
fun <K, V> writeMap(map: Map<K, V>, tag: Int) {
|
||||
writeHead(MAP, tag)
|
||||
if (map.isEmpty()) {
|
||||
writeInt(0, 0)
|
||||
} else {
|
||||
writeInt(map.size, 0)
|
||||
map.forEach { (key, value) ->
|
||||
writeObject(key, 0)
|
||||
writeObject(value, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun writeCollection(collection: Collection<*>?, tag: Int) {
|
||||
writeHead(LIST, tag)
|
||||
if (collection == null || collection.isEmpty()) {
|
||||
writeInt(0, 0)
|
||||
} else {
|
||||
writeInt(collection.size, 0)
|
||||
collection.forEach {
|
||||
writeObject(it, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun writeJceStruct(v: JceStruct, tag: Int) {
|
||||
writeHead(STRUCT_BEGIN, tag)
|
||||
v.writeTo(this)
|
||||
writeHead(STRUCT_END, 0)
|
||||
}
|
||||
|
||||
fun <T> writeObject(v: T, tag: Int) {
|
||||
when (v) {
|
||||
is Byte -> writeByte(v, tag)
|
||||
is Boolean -> writeBoolean(v, tag)
|
||||
is Short -> writeShort(v, tag)
|
||||
is Int -> writeInt(v, tag)
|
||||
is Long -> writeLong(v, tag)
|
||||
is Float -> writeFloat(v, tag)
|
||||
is Double -> writeDouble(v, tag)
|
||||
is Map<*, *> -> writeMap(v, tag)
|
||||
is Collection<*> -> writeCollection(v, tag)
|
||||
is JceStruct -> writeJceStruct(v, tag)
|
||||
is ByteArray -> writeFully(v, tag)
|
||||
is IntArray -> writeFully(v, tag)
|
||||
is ShortArray -> writeFully(v, tag)
|
||||
is BooleanArray -> writeFully(v, tag)
|
||||
is LongArray -> writeFully(v, tag)
|
||||
is FloatArray -> writeFully(v, tag)
|
||||
is DoubleArray -> writeFully(v, tag)
|
||||
is Array<*> -> writeFully(v, tag)
|
||||
else -> error("unsupported type: ${v.getClassName()}")
|
||||
}
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal companion object {
|
||||
const val BYTE: Int = 0
|
||||
const val DOUBLE: Int = 5
|
||||
const val FLOAT: Int = 4
|
||||
const val INT: Int = 2
|
||||
const val JCE_MAX_STRING_LENGTH = 104857600
|
||||
const val LIST: Int = 9
|
||||
const val LONG: Int = 3
|
||||
const val MAP: Int = 8
|
||||
const val SHORT: Int = 1
|
||||
const val SIMPLE_LIST: Int = 13
|
||||
const val STRING1: Int = 6
|
||||
const val STRING4: Int = 7
|
||||
const val STRUCT_BEGIN: Int = 10
|
||||
const val STRUCT_END: Int = 11
|
||||
const val ZERO_TAG: Int = 12
|
||||
|
||||
private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun writeHead(type: Int, tag: Int) {
|
||||
if (tag < 15) {
|
||||
this.output.writeByte((tag shl 4 or type).toByte())
|
||||
return
|
||||
}
|
||||
if (tag < 256) {
|
||||
this.output.writeByte((type or 0xF0).toByte())
|
||||
this.output.writeByte(tag.toByte())
|
||||
return
|
||||
}
|
||||
throw JceEncodeException("tag is too large: $tag")
|
||||
}
|
||||
}
|
||||
|
||||
class JceEncodeException(message: String) : RuntimeException(message)
|
@ -0,0 +1,5 @@
|
||||
package net.mamoe.mirai.qqandroid.network.io
|
||||
|
||||
abstract class JceStruct {
|
||||
abstract fun writeTo(p0: JceOutput)
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
package net.mamoe.mirai.qqandroid.network.packet
|
||||
|
||||
import kotlinx.atomicfu.AtomicInt
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.io.core.BytePacketBuilder
|
||||
|
||||
|
||||
/*
|
||||
private open fun writeHead(
|
||||
always_8001: Int,
|
||||
command: Int,
|
||||
uin: Long,
|
||||
encryptType: Int,
|
||||
const8_always_0: Int,
|
||||
appClientVersion: Int,
|
||||
constp_always_0: Int,
|
||||
bodyLength: Int
|
||||
) {
|
||||
val j: Int = this.j + 1
|
||||
this.j = j
|
||||
this.pos = 0
|
||||
util.int8_to_buf(this.buffer, this.pos, 2)
|
||||
++this.pos
|
||||
util.int16_to_buf(this.buffer, this.pos, this.d + 2 + bodyLength)
|
||||
this.pos += 2
|
||||
util.int16_to_buf(this.buffer, this.pos, always_8001)
|
||||
this.pos += 2
|
||||
util.int16_to_buf(this.buffer, this.pos, command)
|
||||
this.pos += 2
|
||||
util.int16_to_buf(this.buffer, this.pos, j)
|
||||
this.pos += 2
|
||||
util.int32_to_buf(this.buffer, this.pos, uin.toInt())
|
||||
this.pos += 4
|
||||
util.int8_to_buf(this.buffer, this.pos, 3)
|
||||
++this.pos
|
||||
util.int8_to_buf(this.buffer, this.pos, encryptType)
|
||||
++this.pos
|
||||
util.int8_to_buf(this.buffer, this.pos, const8_always_0)
|
||||
++this.pos
|
||||
util.int32_to_buf(this.buffer, this.pos, 2)
|
||||
this.pos += 4
|
||||
util.int32_to_buf(this.buffer, this.pos, appClientVersion)
|
||||
this.pos += 4
|
||||
util.int32_to_buf(this.buffer, this.pos, constp_always_0)
|
||||
this.pos += 4
|
||||
}
|
||||
*/
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
private fun BytePacketBuilder.writeHead(
|
||||
always_8001: Short = 8001,
|
||||
command: Short,
|
||||
uin: Long,
|
||||
encryptType: Int, //
|
||||
sequenceId: Int = SequenceIdCounter.nextSequenceId(),
|
||||
const8_always_0: Byte = 0,
|
||||
appClientVersion: Int,
|
||||
constp_always_0: Int = 0,
|
||||
bodyLength: Int
|
||||
) {
|
||||
writeByte(2)
|
||||
writeShort((27 + 2 + bodyLength).toShort())
|
||||
writeShort(always_8001)
|
||||
writeShort(command)
|
||||
writeShort(sequenceId.toShort())
|
||||
writeInt(uin.toInt())
|
||||
writeByte(3)
|
||||
writeByte(encryptType.toByte())
|
||||
writeByte(const8_always_0)
|
||||
writeInt(2)
|
||||
writeInt(appClientVersion)
|
||||
writeInt(constp_always_0)
|
||||
}
|
||||
|
||||
fun buildOutgoingPacket(
|
||||
command: Short
|
||||
///uin: Long,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
//private b
|
||||
|
||||
private object SequenceIdCounter {
|
||||
private val sequenceId: AtomicInt = atomic(0)
|
||||
|
||||
fun nextSequenceId(): Int {
|
||||
val id = sequenceId.getAndAdd(1)
|
||||
if (id > Short.MAX_VALUE.toInt() * 2) {
|
||||
sequenceId.value = 0
|
||||
return nextSequenceId()
|
||||
}
|
||||
return id
|
||||
}
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
package net.mamoe.mirai.qqandroid.network.packet.tlv
|
||||
|
||||
import kotlinx.io.core.BytePacketBuilder
|
||||
import kotlinx.io.core.buildPacket
|
||||
import kotlinx.io.core.readBytes
|
||||
import kotlinx.io.core.writeFully
|
||||
import net.mamoe.mirai.utils.io.*
|
||||
import net.mamoe.mirai.utils.md5
|
||||
import kotlin.random.Random
|
||||
|
||||
object Tlv {
|
||||
fun BytePacketBuilder.t1(qq: Long, ip: ByteArray) {
|
||||
require(ip.size == 4)
|
||||
writeShort(0x0001)
|
||||
writeShortLVPacket {
|
||||
writeShort(1) // ip_ver
|
||||
writeInt(Random.nextInt())
|
||||
writeInt(qq.toInt())
|
||||
writeTime()
|
||||
writeFully(ip)
|
||||
writeShort(0)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t2(captchaCode: String, captchaToken: ByteArray, sigVer: Short = 0) {
|
||||
writeShort(0x0002)
|
||||
writeShortLVPacket {
|
||||
writeShort(sigVer)
|
||||
writeShortLVString(captchaCode)
|
||||
writeShortLVByteArray(captchaToken)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t8() {
|
||||
writeShort(0x0008)
|
||||
writeShortLVPacket {
|
||||
writeShort(0)
|
||||
writeInt(2052) // localId
|
||||
writeShort(0)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t18(appId: Long, appClientVersion: Int, uin: Long, constant1_always_0: Int) {
|
||||
writeShort(0x18)
|
||||
writeShortLVPacket {
|
||||
writeShort(1) //ping_version
|
||||
writeInt(1536) //sso_version
|
||||
writeInt(appId.toInt())
|
||||
writeInt(appClientVersion)
|
||||
writeInt(uin.toInt())
|
||||
writeShort(constant1_always_0.toShort())
|
||||
writeShort(0)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t106(
|
||||
appId: Long,
|
||||
subAppId: Long,
|
||||
appClientVersion: Int,
|
||||
uin: Long,
|
||||
ipAddress: ByteArray,
|
||||
n5_always_1: Int = 1,
|
||||
temp_pwd: ByteArray,
|
||||
salt: Long,
|
||||
uinAccount: ByteArray,
|
||||
tgtgtKey: ByteArray,
|
||||
n7: Int,
|
||||
array_6_may_be_null: ByteArray?,
|
||||
ret_is_0_or_4: Int
|
||||
) {
|
||||
writeShort(0x106)
|
||||
|
||||
writeShortLVPacket {
|
||||
encryptAndWrite(
|
||||
if (salt == 0L) {
|
||||
md5(buildPacket { writeFully(temp_pwd); writeInt(uin.toInt()) }.readBytes())
|
||||
} else {
|
||||
md5(buildPacket { writeFully(temp_pwd); writeInt(salt.toInt()) }.readBytes())
|
||||
}
|
||||
) {
|
||||
writeShort(4)//TGTGTVer
|
||||
writeInt(Random.nextInt())
|
||||
writeInt(5)//ssoVer
|
||||
writeInt(appId.toInt())
|
||||
writeInt(appClientVersion)
|
||||
|
||||
if (uin == 0L) {
|
||||
writeLong(salt)
|
||||
} else {
|
||||
writeLong(uin)
|
||||
}
|
||||
|
||||
writeTime()
|
||||
writeFully(ipAddress)
|
||||
writeByte(n5_always_1.toByte())
|
||||
writeFully(temp_pwd)
|
||||
writeFully(tgtgtKey)
|
||||
writeInt(0)
|
||||
writeByte(n7.toByte())
|
||||
if (array_6_may_be_null == null) {
|
||||
repeat(4) {
|
||||
writeInt(Random.nextInt())
|
||||
}
|
||||
} else {
|
||||
writeFully(array_6_may_be_null)
|
||||
}
|
||||
writeInt(subAppId.toInt())
|
||||
writeInt(ret_is_0_or_4)
|
||||
writeShortLVByteArray(uinAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t100(
|
||||
appId: Long,
|
||||
subAppId: Long,
|
||||
appClientVersion: Int,
|
||||
mainSigMap: Int
|
||||
) {
|
||||
writeShort(0x100)
|
||||
writeShortLVPacket {
|
||||
writeShort(1)//db_buf_ver
|
||||
writeInt(5)//sso_ver
|
||||
writeInt(appId.toInt())
|
||||
writeInt(subAppId.toInt())
|
||||
writeInt(appClientVersion)
|
||||
writeInt(mainSigMap)
|
||||
} shouldEqualsTo 22
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t107(
|
||||
picType: Int,
|
||||
const1_always_0: Int = 0,
|
||||
const2_always_0: Int = 0,
|
||||
const3_always_1: Int = 1
|
||||
) {
|
||||
writeShort(0x107)
|
||||
writeShortLVPacket {
|
||||
writeShort(picType.toShort())
|
||||
writeByte(const1_always_0.toByte())
|
||||
writeShort(const2_always_0.toShort())
|
||||
writeByte(const3_always_1.toByte())
|
||||
} shouldEqualsTo 6
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private infix fun Int.shouldEqualsTo(int: Int) = require(this == int)
|
||||
|
||||
fun randomAndroidId(): String = buildString(15) {
|
||||
repeat(15) { append(Random.nextInt(10)) }
|
||||
}
|
||||
|
||||
fun generateGuid(androidId: String, macAddress: String): ByteArray {
|
||||
return md5(androidId + macAddress)
|
||||
}
|
||||
|
||||
fun getMacAddr(): String = "02:00:00:00:00:00"
|
@ -0,0 +1,134 @@
|
||||
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.protocol.packet.login.PacketId
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.io.writeQQ
|
||||
|
||||
/**
|
||||
* 待发送给服务器的数据包. 它代表着一个 [ByteReadPacket],
|
||||
*/
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
class OutgoingPacket constructor(
|
||||
name: String?,
|
||||
val packetId: PacketId,
|
||||
val sequenceId: UShort,
|
||||
val delegate: ByteReadPacket
|
||||
) : Packet {
|
||||
val name: String by lazy {
|
||||
name ?: packetId.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
private open fun writeHead(
|
||||
always_8001: Int,
|
||||
command: Int,
|
||||
uin: Long,
|
||||
encryptType: Int,
|
||||
const8_always_0: Int,
|
||||
appClientVersion: Int,
|
||||
constp_always_0: Int,
|
||||
bodyLength: Int
|
||||
) {
|
||||
val j: Int = this.j + 1
|
||||
this.j = j
|
||||
this.pos = 0
|
||||
util.int8_to_buf(this.buffer, this.pos, 2)
|
||||
++this.pos
|
||||
util.int16_to_buf(this.buffer, this.pos, this.d + 2 + bodyLength)
|
||||
this.pos += 2
|
||||
util.int16_to_buf(this.buffer, this.pos, always_8001)
|
||||
this.pos += 2
|
||||
util.int16_to_buf(this.buffer, this.pos, command)
|
||||
this.pos += 2
|
||||
util.int16_to_buf(this.buffer, this.pos, j)
|
||||
this.pos += 2
|
||||
util.int32_to_buf(this.buffer, this.pos, uin.toInt())
|
||||
this.pos += 4
|
||||
util.int8_to_buf(this.buffer, this.pos, 3)
|
||||
++this.pos
|
||||
util.int8_to_buf(this.buffer, this.pos, encryptType)
|
||||
++this.pos
|
||||
util.int8_to_buf(this.buffer, this.pos, const8_always_0)
|
||||
++this.pos
|
||||
util.int32_to_buf(this.buffer, this.pos, 2)
|
||||
this.pos += 4
|
||||
util.int32_to_buf(this.buffer, this.pos, appClientVersion)
|
||||
this.pos += 4
|
||||
util.int32_to_buf(this.buffer, this.pos, constp_always_0)
|
||||
this.pos += 4
|
||||
}
|
||||
*/
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
private fun BytePacketBuilder.writeHead(
|
||||
always_8001: Short = 8001,
|
||||
command: Short,
|
||||
uin: Long,
|
||||
encryptType: Int, //
|
||||
sequenceId: UShort = PacketFactory.atomicNextSequenceId(),
|
||||
const8_always_0: Byte = 0,
|
||||
appClientVersion: Int,
|
||||
constp_always_0: Int = 0,
|
||||
bodyLength: Int
|
||||
) {
|
||||
writeByte(2)
|
||||
writeShort((27 + 2 + bodyLength).toShort())
|
||||
writeShort(always_8001)
|
||||
writeShort(command)
|
||||
writeUShort(sequenceId)
|
||||
writeInt(uin.toInt())
|
||||
writeByte(3)
|
||||
writeByte(encryptType.toByte())
|
||||
writeByte(const8_always_0)
|
||||
writeInt(2)
|
||||
writeInt(appClientVersion)
|
||||
writeInt(constp_always_0)
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
inline class EncryptMethod(val value: UByte) {
|
||||
companion object {
|
||||
val BySessionToken = EncryptMethod(69u)
|
||||
val ByECDH7 = EncryptMethod(7u)
|
||||
// 登录都使用 135
|
||||
val ByECDH135 = EncryptMethod(135u)
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||
inline fun PacketFactory<*, *>.buildOutgoingPacket(
|
||||
device: QQAndroidDevice,
|
||||
encryptMethod: EncryptMethod,
|
||||
name: String? = null,
|
||||
id: PacketId = this.id,
|
||||
sequenceId: UShort = PacketFactory.atomicNextSequenceId(),
|
||||
bodyBlock: BytePacketBuilder.() -> Unit
|
||||
): OutgoingPacket {
|
||||
val body = buildPacket { bodyBlock() }
|
||||
return OutgoingPacket(name, id, sequenceId, buildPacket {
|
||||
// Head
|
||||
writeByte(0x02) // head
|
||||
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
|
||||
writeShort(device.protocolVersion)
|
||||
writeShort(id.commandId.toShort())
|
||||
writeShort(sequenceId.toShort())
|
||||
writeQQ(device.uin)
|
||||
writeByte(3) // originally const
|
||||
writeUByte(encryptMethod.value)
|
||||
writeByte(0) // const8_always_0
|
||||
writeInt(2) // originally const
|
||||
writeInt(device.appClientVersion)
|
||||
writeInt(0) // constp_always_0
|
||||
|
||||
// Body
|
||||
writePacket(body)
|
||||
|
||||
// Tail
|
||||
writeByte(0x03) // tail
|
||||
})
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet
|
||||
|
||||
import kotlinx.atomicfu.AtomicInt
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.discardExact
|
||||
import kotlinx.io.core.readBytes
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.NullPacketId
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId
|
||||
import net.mamoe.mirai.utils.cryptor.Decrypter
|
||||
import net.mamoe.mirai.utils.cryptor.DecrypterType
|
||||
import net.mamoe.mirai.utils.cryptor.readProtoMap
|
||||
import net.mamoe.mirai.utils.io.debugPrint
|
||||
import net.mamoe.mirai.utils.io.read
|
||||
|
||||
/**
|
||||
* 一种数据包的处理工厂. 它可以解密解码服务器发来的这个包, 也可以编码加密要发送给服务器的这个包
|
||||
* 应由一个 `object` 实现, 且实现 `operator fun invoke`
|
||||
*
|
||||
* @param TPacket 服务器回复包解析结果
|
||||
* @param TDecrypter 服务器回复包解密器
|
||||
*/
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(val decrypterType: DecrypterType<TDecrypter>) {
|
||||
|
||||
@Suppress("PropertyName")
|
||||
internal var _id: PacketId = NullPacketId
|
||||
|
||||
/**
|
||||
* 包 ID.
|
||||
*/
|
||||
open val id: PacketId get() = _id
|
||||
|
||||
/**
|
||||
* **解码**服务器的回复数据包
|
||||
*/
|
||||
abstract suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): TPacket
|
||||
|
||||
fun <T> ByteReadPacket.decodeProtoPacket(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
debuggingTag: String? = null
|
||||
): T {
|
||||
val headLength = readInt()
|
||||
val protoLength = readInt()
|
||||
if (debuggingTag != null) {
|
||||
readBytes(headLength).debugPrint("$debuggingTag head")
|
||||
} else {
|
||||
discardExact(headLength)
|
||||
}
|
||||
val bytes = readBytes(protoLength)
|
||||
// println(ByteReadPacket(bytes).readProtoMap())
|
||||
|
||||
if (debuggingTag != null) {
|
||||
bytes.read { readProtoMap() }.toString().debugPrint("$debuggingTag proto")
|
||||
}
|
||||
|
||||
return ProtoBuf.load(deserializer, bytes)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val sequenceId: AtomicInt = atomic(1)
|
||||
|
||||
fun atomicNextSequenceId(): UShort {
|
||||
val id = sequenceId.getAndAdd(1)
|
||||
if (id > Short.MAX_VALUE.toInt() * 2) {
|
||||
sequenceId.value = 0
|
||||
return atomicNextSequenceId()
|
||||
}
|
||||
return id.toUShort()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,22 @@
|
||||
package net.mamoe.mirai.qqandroid.network.packet
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.packet.DecrypterByteArray
|
||||
import net.mamoe.mirai.network.packet.DecrypterType
|
||||
import net.mamoe.mirai.network.packet.PacketFactory
|
||||
import net.mamoe.mirai.network.packet.PacketId
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId
|
||||
import net.mamoe.mirai.utils.cryptor.DecrypterByteArray
|
||||
import net.mamoe.mirai.utils.cryptor.DecrypterType
|
||||
|
||||
|
||||
object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
|
||||
object TouchKey : DecrypterByteArray,
|
||||
DecrypterType<TouchKey> {
|
||||
override val value: ByteArray
|
||||
get() = TODO("not implemented")
|
||||
}
|
||||
|
||||
object TouchPacket : PacketFactory<TouchPacketResponse, TouchKey>(TouchKey) {
|
||||
object TouchPacket : PacketFactory<TouchPacketResponse, TouchKey>(
|
||||
TouchKey
|
||||
) {
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): TouchPacketResponse {
|
||||
TODO("not implemented")
|
@ -0,0 +1,52 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
|
||||
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import net.mamoe.mirai.data.Packet
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidDevice
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.EncryptMethod
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.tlv.writeTLVList
|
||||
import net.mamoe.mirai.utils.cryptor.DecrypterByteArray
|
||||
import net.mamoe.mirai.utils.cryptor.DecrypterType
|
||||
|
||||
class LoginPacketDecrypter(override val value: ByteArray) : DecrypterByteArray {
|
||||
companion object : DecrypterType<LoginPacketDecrypter> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, LoginPacketDecrypter>(LoginPacketDecrypter) {
|
||||
|
||||
fun invoke(
|
||||
device: QQAndroidDevice
|
||||
): OutgoingPacket = buildOutgoingPacket(device, EncryptMethod.ByECDH135) {
|
||||
writeTLVList {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LoginPacketResponse : Packet
|
||||
|
||||
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): LoginPacketResponse {
|
||||
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
interface PacketId {
|
||||
val commandId: Int // ushort actually
|
||||
val subCommandId: Int // ushort actually
|
||||
}
|
||||
|
||||
object NullPacketId : PacketId {
|
||||
override val commandId: Int
|
||||
get() = error("uninitialized")
|
||||
override val subCommandId: Int
|
||||
get() = error("uninitialized")
|
||||
}
|
@ -0,0 +1,401 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.tlv
|
||||
|
||||
import kotlinx.io.core.*
|
||||
import net.mamoe.mirai.utils.io.*
|
||||
import net.mamoe.mirai.utils.md5
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
fun BytePacketBuilder.writeTLVList(block: TlvBuilder.() -> Unit) {
|
||||
var tlvCount = 0
|
||||
val tlvList = buildPacket { block(TlvBuilder { tlvCount++ }) }
|
||||
writeShort(tlvCount.toShort())
|
||||
writePacket(tlvList)
|
||||
}
|
||||
|
||||
|
||||
inline class LoginType(
|
||||
val value: Int
|
||||
) {
|
||||
companion object {
|
||||
val SMS = LoginType(3)
|
||||
val PASSWORD = LoginType(1)
|
||||
val WE_CHAT = LoginType(4)
|
||||
}
|
||||
}
|
||||
|
||||
inline class TlvBuilder(
|
||||
val counter: () -> Unit
|
||||
) {
|
||||
fun BytePacketBuilder.t1(uin: Long, ip: ByteArray) {
|
||||
require(ip.size == 4)
|
||||
writeShort(0x1)
|
||||
writeShortLVPacket {
|
||||
writeShort(1) // _ip_ver
|
||||
writeInt(Random.nextInt())
|
||||
writeInt(uin.toInt())
|
||||
writeTime()
|
||||
writeFully(ip)
|
||||
writeShort(0)
|
||||
} shouldEqualsTo 20
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t2(captchaCode: String, captchaToken: ByteArray, sigVer: Short = 0) {
|
||||
writeShort(0x2)
|
||||
writeShortLVPacket {
|
||||
writeShort(sigVer)
|
||||
writeShortLVString(captchaCode)
|
||||
writeShortLVByteArray(captchaToken)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t8() {
|
||||
writeShort(0x0008)
|
||||
writeShortLVPacket {
|
||||
writeShort(0)
|
||||
writeInt(2052) // localId
|
||||
writeShort(0)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t18(appId: Long, appClientVersion: Int, uin: Long, constant1_always_0: Int) {
|
||||
writeShort(0x18)
|
||||
writeShortLVPacket {
|
||||
writeShort(1) //_ping_version
|
||||
writeInt(1536) //_sso_version
|
||||
writeInt(appId.toInt())
|
||||
writeInt(appClientVersion)
|
||||
writeInt(uin.toInt())
|
||||
writeShort(constant1_always_0.toShort())
|
||||
writeShort(0)
|
||||
} shouldEqualsTo 22
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t106(
|
||||
appId: Long,
|
||||
subAppId: Long,
|
||||
appClientVersion: Int,
|
||||
uin: Long,
|
||||
ipAddress: ByteArray,
|
||||
n5_always_1: Int = 1,
|
||||
temp_pwd: ByteArray,
|
||||
salt: Long,
|
||||
uinAccount: ByteArray,
|
||||
tgtgtKey: ByteArray,
|
||||
n7: Int,
|
||||
array_6_may_be_null: ByteArray?,
|
||||
loginType: LoginType
|
||||
) {
|
||||
writeShort(0x106)
|
||||
|
||||
writeShortLVPacket {
|
||||
encryptAndWrite(
|
||||
if (salt == 0L) {
|
||||
md5(buildPacket { writeFully(temp_pwd); writeInt(uin.toInt()) }.readBytes())
|
||||
} else {
|
||||
md5(buildPacket { writeFully(temp_pwd); writeInt(salt.toInt()) }.readBytes())
|
||||
}
|
||||
) {
|
||||
writeShort(4)//TGTGTVer
|
||||
writeInt(Random.nextInt())
|
||||
writeInt(5)//ssoVer
|
||||
writeInt(appId.toInt())
|
||||
writeInt(appClientVersion)
|
||||
|
||||
if (uin == 0L) {
|
||||
writeLong(salt)
|
||||
} else {
|
||||
writeLong(uin)
|
||||
}
|
||||
|
||||
writeTime()
|
||||
writeFully(ipAddress)
|
||||
writeByte(n5_always_1.toByte())
|
||||
writeFully(temp_pwd)
|
||||
writeFully(tgtgtKey)
|
||||
writeInt(0)
|
||||
writeByte(n7.toByte())
|
||||
if (array_6_may_be_null == null) {
|
||||
repeat(4) {
|
||||
writeInt(Random.nextInt())
|
||||
}
|
||||
} else {
|
||||
writeFully(array_6_may_be_null)
|
||||
}
|
||||
writeInt(subAppId.toInt())
|
||||
writeInt(loginType.value)
|
||||
writeShortLVByteArray(uinAccount)
|
||||
}
|
||||
} shouldEqualsTo 98
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t116(
|
||||
miscBitmap: Int,
|
||||
subSigMap: Int,
|
||||
appIdList: LongArray
|
||||
) {
|
||||
writeShort(0x116)
|
||||
writeShortLVPacket {
|
||||
writeByte(0) // _ver
|
||||
writeInt(miscBitmap)
|
||||
writeInt(subSigMap)
|
||||
writeByte(appIdList.size.toByte())
|
||||
appIdList.forEach {
|
||||
writeInt(it.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t100(
|
||||
appId: Long,
|
||||
subAppId: Long,
|
||||
appClientVersion: Int,
|
||||
mainSigMap: Int
|
||||
) {
|
||||
writeShort(0x100)
|
||||
writeShortLVPacket {
|
||||
writeShort(1)//db_buf_ver
|
||||
writeInt(5)//sso_ver
|
||||
writeInt(appId.toInt())
|
||||
writeInt(subAppId.toInt())
|
||||
writeInt(appClientVersion)
|
||||
writeInt(mainSigMap)
|
||||
} shouldEqualsTo 22
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t107(
|
||||
picType: Int,
|
||||
const1_always_0: Int = 0,
|
||||
const2_always_0: Int = 0,
|
||||
const3_always_1: Int = 1
|
||||
) {
|
||||
writeShort(0x107)
|
||||
writeShortLVPacket {
|
||||
writeShort(picType.toShort())
|
||||
writeByte(const1_always_0.toByte())
|
||||
writeShort(const2_always_0.toShort())
|
||||
writeByte(const3_always_1.toByte())
|
||||
} shouldEqualsTo 6
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t108(
|
||||
to_verify_passwd_img: ByteArray
|
||||
) {
|
||||
writeShort(0x108)
|
||||
writeShortLVPacket {
|
||||
writeFully(to_verify_passwd_img)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t104(
|
||||
t104Data: ByteArray
|
||||
) {
|
||||
writeShort(0x104)
|
||||
writeShortLVPacket {
|
||||
writeFully(t104Data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param apkId application.getPackageName().getBytes()
|
||||
*/
|
||||
fun BytePacketBuilder.t142(
|
||||
apkId: ByteArray
|
||||
) {
|
||||
writeShort(0x142)
|
||||
writeShortLVPacket {
|
||||
writeShort(0) //_version
|
||||
writeShortLVByteArrayLimitedLength(apkId, 32)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t112(
|
||||
nonNumberUin: ByteArray
|
||||
) {
|
||||
writeShort(0x112)
|
||||
writeShortLVPacket {
|
||||
writeFully(nonNumberUin)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t144(
|
||||
// t109
|
||||
androidId: ByteArray,
|
||||
|
||||
// t52d
|
||||
androidDevInfo: ByteArray,
|
||||
|
||||
// t124
|
||||
osType: ByteArray = "android".toByteArray(),
|
||||
osVersion: ByteArray,
|
||||
ipv6NetType: Int,
|
||||
simInfo: ByteArray,
|
||||
unknown: ByteArray,
|
||||
apn: ByteArray = "wifi".toByteArray(),
|
||||
|
||||
// t128
|
||||
isGuidFromFileNull: Boolean = false,
|
||||
isGuidAvailable: Boolean = true,
|
||||
isGuidChanged: Boolean = false,
|
||||
guidFlag: Int,
|
||||
buildModel: ByteArray,
|
||||
guid: ByteArray,
|
||||
buildBrand: ByteArray,
|
||||
|
||||
// encrypt
|
||||
tgtgtKey: ByteArray
|
||||
) {
|
||||
writeShort(0x144)
|
||||
writeShortLVPacket {
|
||||
encryptAndWrite(tgtgtKey) {
|
||||
t109(androidId)
|
||||
t52d(androidDevInfo)
|
||||
t124(osType, osVersion, ipv6NetType, simInfo, unknown, apn)
|
||||
t128(isGuidFromFileNull, isGuidAvailable, isGuidChanged, guidFlag, buildModel, guid, buildBrand)
|
||||
t16e(buildModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t109(
|
||||
androidId: ByteArray
|
||||
) {
|
||||
writeShort(0x109)
|
||||
writeShortLVPacket {
|
||||
writeFully(androidId)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t52d(
|
||||
androidDevInfo: ByteArray // oicq.wlogin_sdk.tools.util#get_android_dev_info
|
||||
) {
|
||||
writeShort(0x52d)
|
||||
writeShortLVPacket {
|
||||
writeFully(androidDevInfo)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t124(
|
||||
osType: ByteArray = "android".toByteArray(),
|
||||
osVersion: ByteArray, // Build.VERSION.RELEASE.toByteArray()
|
||||
ipv6NetType: Int, //oicq.wlogin_sdk.tools.util#get_network_type
|
||||
simInfo: ByteArray, // oicq.wlogin_sdk.tools.util#get_sim_operator_name
|
||||
unknown: ByteArray,
|
||||
apn: ByteArray = "wifi".toByteArray() // oicq.wlogin_sdk.tools.util#get_apn_string
|
||||
) {
|
||||
writeShort(0x124)
|
||||
writeShortLVPacket {
|
||||
writeShortLVByteArrayLimitedLength(osType, 16)
|
||||
writeShortLVByteArrayLimitedLength(osVersion, 16)
|
||||
writeShort(ipv6NetType.toShort())
|
||||
writeShortLVByteArrayLimitedLength(simInfo, 16)
|
||||
writeShortLVByteArrayLimitedLength(unknown, 32)
|
||||
writeShortLVByteArrayLimitedLength(apn, 16)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t128(
|
||||
isGuidFromFileNull: Boolean = false, // 保存到文件的 GUID 是否为 null
|
||||
isGuidAvailable: Boolean = true, // GUID 是否可用(计算/读取成功)
|
||||
isGuidChanged: Boolean = false, // GUID 是否有变动
|
||||
/**
|
||||
* guidFlag:
|
||||
* ```java
|
||||
* 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;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
guidFlag: Int,
|
||||
buildModel: ByteArray, // android.os.Build.MODEL
|
||||
/**
|
||||
* [generateGuid] or `"%4;7t>;28<fc.5*6".toByteArray()`
|
||||
*/
|
||||
guid: ByteArray,
|
||||
buildBrand: ByteArray // android.os.Build.BRAND
|
||||
) {
|
||||
writeShort(0x128)
|
||||
writeShortLVPacket {
|
||||
writeShort(0)
|
||||
writeByte(isGuidFromFileNull.toByte())
|
||||
writeByte(isGuidAvailable.toByte())
|
||||
writeByte(isGuidChanged.toByte())
|
||||
writeInt(guidFlag)
|
||||
writeShortLVByteArrayLimitedLength(buildModel, 32)
|
||||
writeShortLVByteArrayLimitedLength(guid, 16)
|
||||
writeShortLVByteArrayLimitedLength(buildBrand, 16)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t16e(
|
||||
buildModel: ByteArray
|
||||
) {
|
||||
writeShort(0x16e)
|
||||
writeShortLVPacket {
|
||||
writeFully(buildModel)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t145(
|
||||
guid: ByteArray
|
||||
) {
|
||||
writeShort(0x145)
|
||||
writeShortLVPacket {
|
||||
writeFully(guid)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t147(
|
||||
appId: Long,
|
||||
apkVersionName: ByteArray,
|
||||
apkSignatureMd5: ByteArray
|
||||
) {
|
||||
writeShort(0x147)
|
||||
writeShortLVPacket {
|
||||
writeLong(appId)
|
||||
writeShortLVByteArrayLimitedLength(apkVersionName, 32)
|
||||
writeShortLVByteArrayLimitedLength(apkSignatureMd5, 32)
|
||||
}
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t166(
|
||||
imageType: Int
|
||||
) {
|
||||
writeShort(0x166)
|
||||
writeShortLVPacket {
|
||||
writeByte(imageType.toByte())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Boolean.toByte(): Byte = if (this) 1 else 0
|
||||
|
||||
private infix fun Int.shouldEqualsTo(int: Int) = require(this == int)
|
||||
|
||||
fun randomAndroidId(): String = buildString(15) {
|
||||
repeat(15) { append(Random.nextInt(10)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults "%4;7t>;28<fc.5*6".toByteArray()
|
||||
*/
|
||||
fun generateGuid(androidId: String, macAddress: String): ByteArray {
|
||||
return md5(androidId + macAddress)
|
||||
}
|
||||
|
||||
fun getMacAddr(): String = "02:00:00:00:00:00"
|
||||
|
||||
|
||||
// AndroidDevInfo: oicq.wlogin_sdk.tools.util#get_android_dev_info
|
Loading…
Reference in New Issue
Block a user