Files
ReplayUtils/src/main/kotlin/de/chaoscaot/replay/parser/PacketReader.kt
Chaoscaot 7803e05431 Add packet parsing, CLI commands, and serialization support
Introduce new packet parsing functionality with a PacketDecoder and serializer-based system. Add CLI commands for listing packets, displaying replay info, and extracting schematics. Enhance project dependencies and build configuration to support serialization and CLI tools.
2025-03-06 21:45:45 +01:00

110 lines
5.1 KiB
Kotlin

package de.chaoscaot.replay.parser
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.AbstractDecoder
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
import java.io.DataInputStream
@OptIn(ExperimentalSerializationApi::class)
fun readPacket(type: Int, stream: DataInputStream): Any = PacketType.entries.find { it.id == type }.let {
if (it == null) {
println("next bytes: ${stream.readNBytes(40).map { it.toUByte().toString(16) }.joinToString("")}")
throw IllegalArgumentException("Unknown packet type: $type")
}
it
}.let { PacketDecoder(stream).decodeSerializableValue(it.serializer) }
fun readPackets(stream: DataInputStream): Sequence<Any> = sequence {
while (stream.available() > 0) {
yield(readPacket(stream.readByte().toUByte().toInt(), stream))
}
}
@ExperimentalSerializationApi
class PacketDecoder(val input: DataInputStream, var elementsCount: Int = 0): AbstractDecoder() {
private var elementIndex = 0
override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0
override fun decodeByte(): Byte = input.readByte()
override fun decodeShort(): Short = input.readShort()
override fun decodeInt(): Int = input.readInt()
override fun decodeLong(): Long = input.readLong()
override fun decodeFloat(): Float = input.readFloat()
override fun decodeDouble(): Double = input.readDouble()
override fun decodeChar(): Char = input.readChar()
override fun decodeString(): String = input.readUTF()
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = input.readInt()
override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE
return elementIndex++
}
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
PacketDecoder(input, descriptor.elementsCount)
override fun decodeSequentially(): Boolean = true
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int =
decodeInt().also { elementsCount = it }
override fun decodeNotNullMark(): Boolean = decodeBoolean()
}
enum class PacketType(val id: Int, val serializer: KSerializer<out Any>) {
USER_JOIN_PACKET(0x00, UserJoinPacket.serializer()),
ENTITY_MOVE_PACKET(0x01, EntityMovePacket.serializer()),
ENTITY_DESPAWNS_PACKET(0x02, EntityDespawnsPacket.serializer()),
PLAYER_SNEAK_PACKET(0x03, PlayerSneakPacket.serializer()),
ENTITY_ANIMATION_PACKET(0x04, EntityAnimationPacket.serializer()),
TNT_SPAWN_PACKET(0x05, TNTSpawnPacket.serializer()),
ENTITY_SPEED_PACKET(0x06, EntitySpeedPacket.serializer()),
PLAYER_ITEM_PACKET(0x07, PlayerItemPacket.serializer()),
ARROW_SPAWN_PACKET(0x08, ArrowSpawnPacket.serializer()),
FIREBALL_SPAWN_PACKET(0x09, FireballSpawnPacket.serializer()),
BOW_SPAN_PACKET(0x0a, BowSpanPacket.serializer()),
PLAYER_DAMAGE_PACKET(0x0b, PlayerDamagePacket.serializer()),
SET_ON_FIRE_PACKET(0x0c, SetOnFirePacket.serializer()),
LEGACY_ARENA_INFO(0x20, LegacyArenaInfo.serializer()),
ARENA_INFO(0x21, ArenaInfo.serializer()),
LEGACY_BLOCK_PACKET(0x30, LegacyBlockPacket.serializer()),
PARTICLE_PACKET(0x31, ParticlePacket.serializer()),
SOUND_PACKET(0x32, SoundPacket.serializer()),
LEGACY_SHORT_BLOCK_PACKET(0x33, LegacyShortBlockPacket.serializer()),
SOUND_AT_PLAYER_PACKET(0x34, SoundAtPlayerPacket.serializer()),
SHORT_BLOCK_PACKET(0x035, ShortBlockPacket.serializer()),
BLOCK_PACKET(0x36, BlockPacket.serializer()),
LEGACY_CHAT_PACKET(0xa0, LegacyChatPacket.serializer()),
LEGACY_ACTION_BAR_PACKET(0xa1, LegacyActionBarPacket.serializer()),
LEGACY_SYSTEM_PACKET(0xa2, LegacySystemPacket.serializer()),
COUNTDOWN_PACKET(0xa3, CountdownPacket.serializer()),
CHAT_PACKET(0xa4, ChatPacket.serializer()),
SYSTEM_PACKET(0xa5, SystemPacket.serializer()),
BLUE_SCHEM_PACKET(0xb0, BlueSchemPacket.serializer()),
RED_SCHEM_PACKET(0xb1, RedSchemPacket.serializer()),
TEAM_ID_PACKET(0xb2, TeamIDPacket.serializer()),
BLUE_EMBEDDED_SCHEM_PACKET(0xb3, BlueEmbeddedSchemPacket.serializer()),
RED_EMBEDDED_SCHEM_PACKET(0xb4, RedEmbeddedSchemPacket.serializer()),
LEGACY_SCOREBOARD_TITLE_PACKET(0xc0, LegacyScoreboardTitlePacket.serializer()),
LEGACY_SCOREBOARD_DATA_PACKET(0xc1, LegacyScoreboardDataPacket.serializer()),
LEGACY_BOSS_BAR_PACKET(0xc2, LegacyBossBarPacket.serializer()),
LEGACY_SUBTITLE_PACKET(0xc3, LegacySubtitlePacket.serializer()),
LEGACY_PRINT_WIN_PACKET(0xc4, LegacyPrintWinPacket.serializer()),
SUBTITLE_PACKET(0xc5, SubtitlePacket.serializer()),
WIN_PACKET(0xc6, WinPacket.serializer()),
BOSS_BAR_PACKET(0xc7, BossBarPacket.serializer()),
COMMENT_PACKET(0xfe, CommentPacket.serializer()),
TICK_PACKET(0xff, TickPacket.serializer()),
}