diff --git a/.gitignore b/.gitignore index 0e9e244..77bc300 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ bin/ ### Mac OS ### .DS_Store /.idea/ +/bl-client/src/commonTest/resources/cookie.txt diff --git a/bl-client/build.gradle.kts b/bl-client/build.gradle.kts index e231022..935f321 100644 --- a/bl-client/build.gradle.kts +++ b/bl-client/build.gradle.kts @@ -19,12 +19,24 @@ kotlin { commonMain.dependencies { api(project(":bl-core")) implementation("io.ktor:ktor-client-core:$ktorVersion") - implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-websockets:$ktorVersion") } + appleMain.dependencies { + implementation("io.ktor:ktor-client-darwin:${ktorVersion}") + } + + linuxMain.dependencies { + implementation("io.ktor:ktor-client-curl:${ktorVersion}") + } + + mingwMain.dependencies { + implementation("io.ktor:ktor-client-winhttp:${ktorVersion}") + } + commonTest.dependencies { - implementation(project(":bl-core")) + implementation(kotlin("test")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0") } } } \ No newline at end of file diff --git a/bl-client/src/commonMain/kotlin/cn/rtast/bldm/client/bldm.kt b/bl-client/src/commonMain/kotlin/cn/rtast/bldm/client/bldm.kt new file mode 100644 index 0000000..be8772c --- /dev/null +++ b/bl-client/src/commonMain/kotlin/cn/rtast/bldm/client/bldm.kt @@ -0,0 +1,91 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/8/30 + */ + + +@file:OptIn(InternalBldmApi::class) + +package cn.rtast.bldm.client + +import cn.rtast.bldm.core.BLDMConstants +import cn.rtast.bldm.core.annotations.InternalBldmApi +import cn.rtast.bldm.core.data.DMServerConf +import cn.rtast.bldm.core.data.RealRoomId +import cn.rtast.bldm.core.data.UserNavData +import cn.rtast.bldm.core.protocol.Packet +import cn.rtast.bldm.core.protocol.PacketDecoder +import cn.rtast.bldm.core.protocol.PacketEncoder +import cn.rtast.bldm.core.protocol.event.PacketEvents +import cn.rtast.bldm.core.util._fromJson +import cn.rtast.bldm.core.util.parseBuvidAndUid +import io.ktor.client.plugins.websocket.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.websocket.* +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.seconds + +private val HEARTBEAT_INTERVAL = 30.seconds + +public suspend fun connectToBlDM( + roomId: Long, + cookie: String?, + events: PacketEvents.() -> Unit, +) { + val parsedCookie = parseBuvidAndUid(cookie ?: "") + val packetEncoder = PacketEncoder() + val packetDecoder = PacketDecoder(events) + val realRoomId = httpClient.get(BLDMConstants.REAL_ROOM_ID_URL + roomId) + .bodyAsText()._fromJson() + val wbi = httpClient.get(BLDMConstants.USER_NAV_URL) + .bodyAsText()._fromJson().signWbi(realRoomId.data.roomId) + val serverConf = httpClient.get(BLDMConstants.DM_SERVER_CONF_URL + "?$wbi") { + cookie?.let { header(HttpHeaders.Cookie, it) } + }.bodyAsText()._fromJson().data + val wsUrl = "ws://${serverConf.hostList.first().host}:${serverConf.hostList.first().wsPort}/sub" + + httpClient.webSocket(wsUrl, { +// header(HttpHeaders.Host, "${serverConf.hostList.first().host}:${serverConf.hostList.first().wssPort}") +// header(HttpHeaders.Origin, "https://www.bilibili.com") + }) { + val authPacket = packetEncoder.authPacket( + parsedCookie.second ?: 0, + realRoomId.data.roomId, + parsedCookie.first ?: "", + serverConf.token + ) + sendPacket(authPacket) + val heartbeatJob = launch { + val heartbeatPacket = packetEncoder.heartbeatPacket() + while (isActive) { + delay(HEARTBEAT_INTERVAL) + runCatching { + sendPacket(heartbeatPacket) + }.onFailure { + println("heartbeat failed ${it.message}") + } + } + } + try { + for (frame in incoming) { + if (frame !is Frame.Binary) continue + val data = frame.readBytes() + packetDecoder.decode(data) + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + heartbeatJob.cancel() + println("Closed") + } + } + httpClient.close() +} + +internal suspend fun DefaultWebSocketSession.sendPacket(packet: Packet) = + send(Frame.Binary(true, packet.toByteArray())) \ No newline at end of file diff --git a/bl-client/src/commonMain/kotlin/cn/rtast/bldm/client/constant.kt b/bl-client/src/commonMain/kotlin/cn/rtast/bldm/client/constant.kt new file mode 100644 index 0000000..0dd02ea --- /dev/null +++ b/bl-client/src/commonMain/kotlin/cn/rtast/bldm/client/constant.kt @@ -0,0 +1,25 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/8/30 + */ + + +package cn.rtast.bldm.client + +import io.ktor.client.* +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.websocket.* +import io.ktor.client.request.header +import io.ktor.http.HttpHeaders + +internal val httpClient = HttpClient { + install(WebSockets) + + defaultRequest { + header(HttpHeaders.UserAgent, USER_AGENT) + } +} + +internal const val USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36" \ No newline at end of file diff --git a/bl-client/src/commonTest/kotlin/test/TestClient.kt b/bl-client/src/commonTest/kotlin/test/TestClient.kt new file mode 100644 index 0000000..984e99d --- /dev/null +++ b/bl-client/src/commonTest/kotlin/test/TestClient.kt @@ -0,0 +1,39 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/8/30 + */ + + +package test + +import cn.rtast.bldm.client.connectToBlDM +import kotlinx.coroutines.test.runTest +import kotlinx.io.buffered +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem +import kotlinx.io.readString +import kotlin.test.Test + +class TestClient { + + private val cookie = SystemFileSystem.source(Path("src/commonTest/resources/cookie.txt")) + .buffered().readString() + + @Test + fun `test bldm client`() = runTest { + connectToBlDM(6, cookie) { + onHeartbeat { + println("Heartbeat") + } + + onMessage { + println(it.content) + } + + onAuthReply { + println("auth reply") + } + } + } +} \ No newline at end of file diff --git a/bl-client/src/commonTest/resources/cookie.example.txt b/bl-client/src/commonTest/resources/cookie.example.txt new file mode 100644 index 0000000..69ba71e --- /dev/null +++ b/bl-client/src/commonTest/resources/cookie.example.txt @@ -0,0 +1 @@ +Rename this file to cookie.txt and paste cookie here \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/annotations/InternalBldmApi.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/annotations/InternalBldmApi.kt new file mode 100644 index 0000000..f044f1f --- /dev/null +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/annotations/InternalBldmApi.kt @@ -0,0 +1,13 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/8/30 + */ + + +package cn.rtast.bldm.core.annotations + +@RequiresOptIn(level = RequiresOptIn.Level.ERROR) +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) +public annotation class InternalBldmApi \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/constant.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/constant.kt index 68dba40..41cc8d8 100644 --- a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/constant.kt +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/constant.kt @@ -6,6 +6,9 @@ package cn.rtast.bldm.core -public const val REAL_ROOM_ID_URL: String = "https://api.live.bilibili.com/room/v1/Room/room_init?id=" -public const val DM_SERVER_CONF_URL: String = "https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=" -public const val USER_NAV_URL: String = "https://api.bilibili.com/x/web-interface/nav" \ No newline at end of file +public object BLDMConstants { + public const val REAL_ROOM_ID_URL: String = "https://api.live.bilibili.com/room/v1/Room/room_init?id=" + public const val USER_NAV_URL: String = "https://api.bilibili.com/x/web-interface/nav" + public const val DM_SERVER_CONF_URL: String = + "https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo" +} \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/UserNavData.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/UserNavData.kt index 0c9aa00..32162ca 100644 --- a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/UserNavData.kt +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/UserNavData.kt @@ -16,9 +16,15 @@ import kotlin.time.Clock @Serializable public data class UserNavData( - @SerialName("wbi_img") - val wbiImg: WbiImg, + val data: NavData, ) { + + @Serializable + public data class NavData( + @SerialName("wbi_img") + val wbiImg: WbiImg, + ) + @Serializable public data class WbiImg( @SerialName("img_url") @@ -67,8 +73,8 @@ public data class UserNavData( * get resorted mixin key */ private fun getMixinKey(): String = - (wbiImg.imgUrl.substringAfterLast('/').removeSuffix(".png") + - wbiImg.subUrl.substringAfterLast('/').removeSuffix(".png")).let { s -> + (data.wbiImg.imgUrl.substringAfterLast('/').removeSuffix(".png") + + data.wbiImg.subUrl.substringAfterLast('/').removeSuffix(".png")).let { s -> buildString { repeat(32) { append(s[MIXIN_KEY_ENC_TAB[it]]) } } } diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/_wbi_sign_payload.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/_wbi_sign_payload.kt deleted file mode 100644 index e472133..0000000 --- a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/_wbi_sign_payload.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright © 2026 RTAkland - * Author: RTAkland - * Date: 2026/8/30 - */ - - -package cn.rtast.bldm.core.data - -import kotlinx.serialization.Serializable - -@Serializable -@Suppress("ClassName") -internal data class _wbi_sign_payload( - /** - * room id - */ - val id: Long, - /** - * always be 0 - */ - val type: Int, - /** - * current unix timestamp (seconds) - */ - val wts: Long -) \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/protocol/AuthPayload.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/protocol/AuthPayload.kt index c92bd0d..9292e5f 100644 --- a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/protocol/AuthPayload.kt +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/protocol/AuthPayload.kt @@ -7,7 +7,6 @@ package cn.rtast.bldm.core.data.protocol -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable @@ -15,7 +14,7 @@ internal data class AuthPayload( /** * user id */ - val uid: Int, + val uid: Long, /** * room id */ diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/protocol/DanmuMessage.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/protocol/DanmuMessage.kt new file mode 100644 index 0000000..625e278 --- /dev/null +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/protocol/DanmuMessage.kt @@ -0,0 +1,15 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/8/30 + */ + + +package cn.rtast.bldm.core.data.protocol + +import kotlinx.serialization.Serializable + +@Serializable +public data class DanmuMessage( + val content: String +) \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/protocol/PacketMetadata.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/protocol/PacketMetadata.kt new file mode 100644 index 0000000..1cb9fa9 --- /dev/null +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/data/protocol/PacketMetadata.kt @@ -0,0 +1,17 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/8/30 + */ + + +package cn.rtast.bldm.core.data.protocol + +import kotlinx.serialization.Serializable + +@Serializable +public data class PacketMetadata( + val packetLength: Int, + val protocolType: Int, + val opCode: Int +) \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/Packet.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/Packet.kt index 40ac4f5..af9e924 100644 --- a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/Packet.kt +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/Packet.kt @@ -78,6 +78,6 @@ public data class Packet( writeShort(protocolVersion) writeInt(packetType) writeInt(sequence) - write(body, body.size) + write(body, 0, body.size) }.readByteArray() } \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketDecoder.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketDecoder.kt index 195ba84..bcf59d1 100644 --- a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketDecoder.kt +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketDecoder.kt @@ -4,8 +4,63 @@ * Date: 2026/8/30 */ - package cn.rtast.bldm.core.protocol -class PacketDecoder { +import cn.rtast.bldm.core.data.protocol.DanmuMessage +import cn.rtast.bldm.core.protocol.ProtocolVersion.Raw +import cn.rtast.bldm.core.protocol.ProtocolVersion.Zlib +import cn.rtast.bldm.core.protocol.event.PacketEvents +import cn.rtast.bldm.core.util.zlibDecompress +import kotlinx.io.Buffer +import kotlinx.io.readByteArray + +public class PacketDecoder(eventsBuilder: PacketEvents.() -> Unit) { + private val events = PacketEvents().apply(eventsBuilder) + private val persistentBuffer = Buffer() + + public fun decode(bytes: ByteArray) { + persistentBuffer.write(bytes) + this.decodeBuffer(persistentBuffer) + } + + private fun decodeBuffer(buffer: Buffer) { + while (buffer.size >= 16) { + val pk = buffer.peek() + val packetLength = pk.readInt() + val headerLength = pk.readShort() + val protocolVersion = pk.readShort() + val opCode = pk.readInt() + + if (buffer.size < packetLength) break + + val currentPacket = buffer.readByteArray(packetLength) + val bodySize = packetLength - headerLength + if (bodySize <= 0) continue + + val body = currentPacket.copyOfRange(headerLength.toInt(), packetLength) + + when (ProtocolVersion.fromCode(protocolVersion)) { + Raw -> handlePayload(opCode, body) + Zlib -> { + val decompressed = body.zlibDecompress() + val decompressedBuffer = Buffer().apply { write(decompressed) } + decodeBuffer(decompressedBuffer) + } + null -> {} + } + } + } + + private fun handlePayload(opCode: Int, body: ByteArray) { + when (PacketType.fromCode(opCode)) { + PacketType.AUTH_REPLY -> events.authReplyHandler?.invoke() + PacketType.HEARTBEAT_REPLY -> events.heartbeatHandler?.invoke() + PacketType.Message -> events.messageHandler?.invoke(decodeDanmuMessage(body)) + else -> {} + } + } + + private fun decodeDanmuMessage(bytes: ByteArray): DanmuMessage { + return DanmuMessage(bytes.decodeToString()) + } } \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketEncoder.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketEncoder.kt index 66815cc..8e926df 100644 --- a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketEncoder.kt +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketEncoder.kt @@ -5,11 +5,14 @@ */ +@file:OptIn(InternalBldmApi::class) + package cn.rtast.bldm.core.protocol +import cn.rtast.bldm.core.annotations.InternalBldmApi import cn.rtast.bldm.core.data.protocol.AuthPayload import cn.rtast.bldm.core.util.AutoIncrementInt -import cn.rtast.bldm.core.util.encodeJson +import cn.rtast.bldm.core.util._encodeJson public class PacketEncoder { public companion object { @@ -18,10 +21,10 @@ public class PacketEncoder { private val _sequence by AutoIncrementInt() - public fun authPacket(uid: Int, roomId: Long, buvid: String, token: String): Packet { + public fun authPacket(uid: Long, roomId: Long, buvid: String, token: String): Packet { val payload = AuthPayload(uid, roomId, 2, buvid, "web", 2, token) - .encodeJson().encodeToByteArray() - return Packet(packetType = PacketType.AUTH, sequence = _sequence, body = payload) + ._encodeJson().encodeToByteArray() + return Packet(packetType = PacketType.AUTH.pkCode, sequence = _sequence, body = payload) } public fun heartbeatPacket(): Packet = Packet(packetType = 2, sequence = _sequence, body = HEARTBEAT_BODY) diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketType.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketType.kt index 021cf2d..e2baa3a 100644 --- a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketType.kt +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/PacketType.kt @@ -7,12 +7,15 @@ package cn.rtast.bldm.core.protocol -public object PacketType { - public const val AUTH: Int = 7 - public const val AUTH_REPLY: Int = 8 - public const val HEARTBEAT: Int = 2 - public const val HANDSHAKE_REPLY: Int = 1 - public const val HEARTBEAT_REPLY: Int = 3 - public const val SEND_MSG_REPLY: Int = 5 - // TODO +public enum class PacketType(public val pkCode: Int) { + AUTH(7), + AUTH_REPLY(8), + HEARTBEAT(2), + HEARTBEAT_REPLY(3), + Message(5); + + public companion object { + private val map = PacketType.entries.associateBy { it.pkCode } + public fun fromCode(code: Int): PacketType? = map[code] + } } \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/ProtocolVersion.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/ProtocolVersion.kt new file mode 100644 index 0000000..6927aff --- /dev/null +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/ProtocolVersion.kt @@ -0,0 +1,17 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/8/30 + */ + + +package cn.rtast.bldm.core.protocol + +internal enum class ProtocolVersion(val proto: Short) { + Raw(0), Zlib(2); + + companion object { + private val map = entries.associateBy { it.proto } + fun fromCode(code: Short): ProtocolVersion? = map[code] + } +} \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/event/PacketEvents.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/event/PacketEvents.kt new file mode 100644 index 0000000..c849008 --- /dev/null +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/protocol/event/PacketEvents.kt @@ -0,0 +1,28 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/8/30 + */ + + +package cn.rtast.bldm.core.protocol.event + +import cn.rtast.bldm.core.data.protocol.DanmuMessage + +public class PacketEvents { + internal var messageHandler: ((DanmuMessage) -> Unit)? = null + internal var heartbeatHandler: (() -> Unit)? = null + internal var authReplyHandler: (() -> Unit)? = null + + public fun onMessage(block: (DanmuMessage) -> Unit) { + messageHandler = block + } + + public fun onHeartbeat(block: () -> Unit) { + heartbeatHandler = block + } + + public fun onAuthReply(block: () -> Unit) { + authReplyHandler = block + } +} \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/util/_cookie_parser.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/util/_cookie_parser.kt new file mode 100644 index 0000000..90d968e --- /dev/null +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/util/_cookie_parser.kt @@ -0,0 +1,29 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/8/30 + */ + + +package cn.rtast.bldm.core.util + +/** + * get buvid and uid from a cookie + * first -> buvid + * second -> uid + */ +public fun parseBuvidAndUid(cookie: String): Pair { + if (cookie.isBlank()) return Pair(null, null) + var buvid: String? = null + var uid: Long? = null + cookie.split(';').forEach { entry -> + val parts = entry.split('=', limit = 2) + if (parts.size == 2) { + when (parts[0].trim()) { + "buvid3" -> buvid = parts[1].trim() + "DedeUserID" -> uid = parts[1].trim().toLongOrNull() + } + } + } + return buvid to uid +} \ No newline at end of file diff --git a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/util/json.kt b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/util/json.kt index f6578f4..7e8184f 100644 --- a/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/util/json.kt +++ b/bl-core/src/commonMain/kotlin/cn/rtast/bldm/core/util/json.kt @@ -4,11 +4,21 @@ * Date: 2026/8/30 */ +@file:Suppress("FunctionName") package cn.rtast.bldm.core.util +import cn.rtast.bldm.core.annotations.InternalBldmApi import kotlinx.serialization.json.Json -internal inline fun String.fromJson(): T = Json.decodeFromString(this) +@InternalBldmApi +@Suppress("ObjectPropertyName") +public val _json: Json = Json { + ignoreUnknownKeys = true +} -internal inline fun T.encodeJson(): String = Json.encodeToString(this) \ No newline at end of file +@InternalBldmApi +public inline fun String._fromJson(): T = _json.decodeFromString(this) + +@InternalBldmApi +public inline fun T._encodeJson(): String = _json.encodeToString(this) \ No newline at end of file diff --git a/bl-core/src/commonTest/kotlin/test/TestWbiSign.kt b/bl-core/src/commonTest/kotlin/test/TestWbiSign.kt index 64d3ae6..22333fa 100644 --- a/bl-core/src/commonTest/kotlin/test/TestWbiSign.kt +++ b/bl-core/src/commonTest/kotlin/test/TestWbiSign.kt @@ -16,7 +16,7 @@ class TestWbiSign { fun `test wbi sign`() { val imgUrl = "https://i0.hdslb.com/bfs/wbi/7cd084941338484aae1ad9425b84077c.png" val subUrl = "https://i0.hdslb.com/bfs/wbi/4932caff0ff746eab6f01bf08b70ac45.png" - val signWbi = UserNavData(UserNavData.WbiImg(imgUrl, subUrl)).signWbi(27245513) + val signWbi = UserNavData(UserNavData.NavData(UserNavData.WbiImg(imgUrl, subUrl))).signWbi(27245513) println(signWbi) } } \ No newline at end of file diff --git a/bl-core/src/jvmMain/kotlin/cn/rtast/bldm/core/util/zlib.jvm.kt b/bl-core/src/jvmMain/kotlin/cn/rtast/bldm/core/util/zlib.jvm.kt index 8e6fafe..9b08830 100644 --- a/bl-core/src/jvmMain/kotlin/cn/rtast/bldm/core/util/zlib.jvm.kt +++ b/bl-core/src/jvmMain/kotlin/cn/rtast/bldm/core/util/zlib.jvm.kt @@ -11,7 +11,7 @@ import java.io.ByteArrayOutputStream import java.util.zip.Inflater public actual fun ByteArray.zlibDecompress(): ByteArray { - val inflater = Inflater(true) + val inflater = Inflater(false) val outputStream = ByteArrayOutputStream(this.size) val buffer = ByteArray(1024) inflater.setInput(this) diff --git a/bl-core/src/nativeMain/kotlin/cn/rtast/bldm/core/util/zlib.native.kt b/bl-core/src/nativeMain/kotlin/cn/rtast/bldm/core/util/zlib.native.kt index bd76e56..03f7810 100644 --- a/bl-core/src/nativeMain/kotlin/cn/rtast/bldm/core/util/zlib.native.kt +++ b/bl-core/src/nativeMain/kotlin/cn/rtast/bldm/core/util/zlib.native.kt @@ -4,7 +4,6 @@ * Date: 2026/8/30 */ - @file:OptIn(ExperimentalForeignApi::class) package cn.rtast.bldm.core.util @@ -12,7 +11,7 @@ package cn.rtast.bldm.core.util import kotlinx.cinterop.* import platform.zlib.* -private const val MAX_WBITS = 15 +private const val ENABLE_ZLIB_GZIP_HEADER = 15 + 32 public actual fun ByteArray.zlibDecompress(): ByteArray { if (isEmpty()) return ByteArray(0) @@ -21,7 +20,14 @@ public actual fun ByteArray.zlibDecompress(): ByteArray { stream.zalloc = null stream.zfree = null stream.opaque = null - check(inflateInit2_(stream.ptr, MAX_WBITS, ZLIB_VERSION, sizeOf().toInt()) == Z_OK) { "inflateInit2_ failed" } + + val initResult = inflateInit2_( + stream.ptr, + ENABLE_ZLIB_GZIP_HEADER, + ZLIB_VERSION, + sizeOf().toInt() + ) + check(initResult == Z_OK) { "inflateInit2_ failed with code: $initResult" } val inputPinned = this@zlibDecompress.pin() try { @@ -31,14 +37,15 @@ public actual fun ByteArray.zlibDecompress(): ByteArray { val bufferSize = 4096 val tempBuffer = ByteArray(bufferSize) val tempPinned = tempBuffer.pin() - val output = ArrayList(this@zlibDecompress.size * 2) + val output = ArrayList(this@zlibDecompress.size * 3) try { + var result: Int do { stream.next_out = tempPinned.addressOf(0).reinterpret() stream.avail_out = bufferSize.toUInt() - val result = inflate(stream.ptr, Z_NO_FLUSH) + result = inflate(stream.ptr, Z_NO_FLUSH) check(result == Z_OK || result == Z_STREAM_END) { "inflate error: $result" } val bytesDecompressed = bufferSize - stream.avail_out.toInt() @@ -47,7 +54,7 @@ public actual fun ByteArray.zlibDecompress(): ByteArray { } if (result == Z_STREAM_END) break - } while (stream.avail_out == 0u) + } while (stream.avail_in > 0u || stream.avail_out == 0u) } finally { tempPinned.unpin() }