Commit 62b3740a authored by Him188's avatar Him188

Android stuff

parent 7f338edc
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 {
}
}
\ No newline at end of file
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)
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network.io
abstract class JceStruct {
abstract fun writeTo(p0: JceOutput)
}
\ No newline at end of file
package net.mamoe.mirai.qqandroid.network.packet
package net.mamoe.mirai.qqandroid.network.protocol.packet
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.io.core.BytePacketBuilder
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(
......@@ -52,7 +70,7 @@ private fun BytePacketBuilder.writeHead(
command: Short,
uin: Long,
encryptType: Int, //
sequenceId: Int = SequenceIdCounter.nextSequenceId(),
sequenceId: UShort = PacketFactory.atomicNextSequenceId(),
const8_always_0: Byte = 0,
appClientVersion: Int,
constp_always_0: Int = 0,
......@@ -62,7 +80,7 @@ private fun BytePacketBuilder.writeHead(
writeShort((27 + 2 + bodyLength).toShort())
writeShort(always_8001)
writeShort(command)
writeShort(sequenceId.toShort())
writeUShort(sequenceId)
writeInt(uin.toInt())
writeByte(3)
writeByte(encryptType.toByte())
......@@ -72,24 +90,45 @@ private fun BytePacketBuilder.writeHead(
writeInt(constp_always_0)
}
fun buildOutgoingPacket(
command: Short
///uin: Long,
) {
@UseExperimental(ExperimentalUnsignedTypes::class)
inline class EncryptMethod(val value: UByte) {
companion object {
val BySessionToken = EncryptMethod(69u)
val ByECDH7 = EncryptMethod(7u)
// 登录都使用 135
val ByECDH135 = EncryptMethod(135u)
}
}
//private b
@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
private object SequenceIdCounter {
private val sequenceId: AtomicInt = atomic(0)
// Body
writePacket(body)
fun nextSequenceId(): Int {
val id = sequenceId.getAndAdd(1)
if (id > Short.MAX_VALUE.toInt() * 2) {
sequenceId.value = 0
return nextSequenceId()
}
return id
}
// Tail
writeByte(0x03) // tail
})
}
\ No newline at end of file
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()
}
}
}
\ No newline at end of file
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")
......
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")
}
package net.mamoe.mirai.qqandroid.network.packet.tlv
package net.mamoe.mirai.qqandroid.network.protocol.packet.tlv
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes
import kotlinx.io.core.writeFully
import kotlinx.io.core.*
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) {
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(0x0001)
writeShort(0x1)
writeShortLVPacket {
writeShort(1) // ip_ver
writeShort(1) // _ip_ver
writeInt(Random.nextInt())
writeInt(qq.toInt())
writeInt(uin.toInt())
writeTime()
writeFully(ip)
writeShort(0)
}
} shouldEqualsTo 20
}
fun BytePacketBuilder.t2(captchaCode: String, captchaToken: ByteArray, sigVer: Short = 0) {
writeShort(0x0002)
writeShort(0x2)
writeShortLVPacket {
writeShort(sigVer)
writeShortLVString(captchaCode)
......@@ -43,14 +61,14 @@ object Tlv {
fun BytePacketBuilder.t18(appId: Long, appClientVersion: Int, uin: Long, constant1_always_0: Int) {
writeShort(0x18)
writeShortLVPacket {
writeShort(1) //ping_version
writeInt(1536) //sso_version
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(
......@@ -66,7 +84,7 @@ object Tlv {
tgtgtKey: ByteArray,
n7: Int,
array_6_may_be_null: ByteArray?,
ret_is_0_or_4: Int
loginType: LoginType
) {
writeShort(0x106)
......@@ -105,9 +123,26 @@ object Tlv {
writeFully(array_6_may_be_null)
}
writeInt(subAppId.toInt())
writeInt(ret_is_0_or_4)
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())
}
}
}
......@@ -142,8 +177,210 @@ object Tlv {
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)
......@@ -151,8 +388,14 @@ 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"
\ No newline at end of file
fun getMacAddr(): String = "02:00:00:00:00:00"
// AndroidDevInfo: oicq.wlogin_sdk.tools.util#get_android_dev_info
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