diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 9d62f93..2a65317 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,10 +1,16 @@ + diff --git a/.idea/misc.xml b/.idea/misc.xml index 815fb9a..8362da6 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -12,6 +12,9 @@ + + + diff --git a/build.gradle.kts b/build.gradle.kts index 2d8ba64..217b673 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,7 @@ plugins { kotlin("jvm") version "2.1.10" + kotlin("plugin.serialization") version "2.1.0" + application } group = "de.chaoscaot" @@ -11,6 +13,13 @@ repositories { dependencies { testImplementation(kotlin("test")) + implementation("com.github.ajalt.clikt:clikt:5.0.3") + implementation("com.github.ajalt.clikt:clikt-markdown:5.0.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0") + implementation("com.github.ajalt.mordant:mordant:3.0.2") + implementation("com.github.ajalt.mordant:mordant-coroutines:3.0.2") + implementation("com.github.ajalt.mordant:mordant-markdown:3.0.2") + implementation("dev.dewy:nbt:1.5.1") } tasks.test { diff --git a/src/main/kotlin/de/chaoscaot/replay/Main.kt b/src/main/kotlin/de/chaoscaot/replay/Main.kt index ffa8d11..f183cc4 100644 --- a/src/main/kotlin/de/chaoscaot/replay/Main.kt +++ b/src/main/kotlin/de/chaoscaot/replay/Main.kt @@ -1,2 +1,33 @@ package de.chaoscaot.replay +import com.github.ajalt.clikt.core.* +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.arguments.optional +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import de.chaoscaot.replay.commands.* +import java.io.File + +data class RunConfig(var replay: File? = null) + +object ReplayUtils : CliktCommand() { + override val printHelpOnEmptyArgs = true + + private val config by findOrSetObject { RunConfig() } + private val replay by argument(help = "Replay ID or Path").optional() + private val path by option("-p", "--path", help = "Replay is a Path").flag() + + override fun run() { + if (replay != null) { + if (path) { + config.replay = File(replay!!) + } else { + config.replay = File("/mnt/storage/replays/$replay.replay") + } + } else { + config.replay = null + } + } +} + +fun main(args: Array) = ReplayUtils.subcommands(Info, ListPackets, ExtractSchematics).main(readln().split(" ")) \ No newline at end of file diff --git a/src/main/kotlin/de/chaoscaot/replay/commands/ExtractSchematics.kt b/src/main/kotlin/de/chaoscaot/replay/commands/ExtractSchematics.kt index 2ccb6e7..13039e8 100644 --- a/src/main/kotlin/de/chaoscaot/replay/commands/ExtractSchematics.kt +++ b/src/main/kotlin/de/chaoscaot/replay/commands/ExtractSchematics.kt @@ -1,4 +1,43 @@ package de.chaoscaot.replay.commands -object ExtractSchematics { +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.requireObject +import de.chaoscaot.replay.RunConfig +import de.chaoscaot.replay.parser.BlueEmbeddedSchemPacket +import de.chaoscaot.replay.parser.RedEmbeddedSchemPacket +import de.chaoscaot.replay.parser.ReplayParser +import com.github.ajalt.mordant.rendering.TextColors.* +import com.github.ajalt.mordant.rendering.TextStyles +import dev.dewy.nbt.Nbt +import dev.dewy.nbt.io.CompressionType +import java.io.File + +object ExtractSchematics: CliktCommand() { + private val config by requireObject() + + override fun run() { + if (config.replay == null) { + error("No Replay specified") + } + + val nbt by lazy { Nbt() } + + val packets = ReplayParser.read(config.replay!!).toList() + + val blueSchem = packets.filterIsInstance().firstOrNull() + + if (blueSchem == null) { + echo((brightRed + TextStyles.bold)("No Blue Embedded Schem found")) + } else { + nbt.toFile(blueSchem.schematic.data, File("blue.dump"), CompressionType.GZIP) + } + + val redSchem = packets.filterIsInstance().firstOrNull() + + if (redSchem == null) { + echo((brightRed + TextStyles.bold)("No Red Embedded Schem found")) + } else { + nbt.toFile(redSchem.schematic.data, File("red.dump"), CompressionType.GZIP) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/de/chaoscaot/replay/commands/Info.kt b/src/main/kotlin/de/chaoscaot/replay/commands/Info.kt index 9f617c2..1a8d3cd 100644 --- a/src/main/kotlin/de/chaoscaot/replay/commands/Info.kt +++ b/src/main/kotlin/de/chaoscaot/replay/commands/Info.kt @@ -1,4 +1,53 @@ package de.chaoscaot.replay.commands -object Info { +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.requireObject +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.multiple +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.mordant.table.table +import de.chaoscaot.replay.RunConfig +import de.chaoscaot.replay.parser.ReplayParser +import de.chaoscaot.replay.parser.UserJoinPacket + +object Info : CliktCommand() { + private val config by requireObject() + + private val players by option("-p", "--players").flag() + private val toCount by option("-c", "--count").multiple(required = false) + + override fun run() { + if (config.replay == null) { + error("No Replay specified") + } + + val packets = ReplayParser.read(config.replay!!).toList() + + if (players) { + echo("Players:") + packets.filterIsInstance().map { it.userId }.forEach { echo("\t$it") } + echo() + } + + if (toCount.isNotEmpty()) { + echo(table { + header { + row("PacketType", "Count") + } + + body { + toCount.forEach { name -> + val klass = packets.firstOrNull { it.javaClass.simpleName.lowercase() == "${name}packet".lowercase() }?.javaClass + + if (klass == null) { + row(name, 0) + } else { + row(klass.simpleName, packets.count { it.javaClass == klass }) + } + } + } + + }) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/de/chaoscaot/replay/commands/ListPackets.kt b/src/main/kotlin/de/chaoscaot/replay/commands/ListPackets.kt index 0d65c15..6a9d4f2 100644 --- a/src/main/kotlin/de/chaoscaot/replay/commands/ListPackets.kt +++ b/src/main/kotlin/de/chaoscaot/replay/commands/ListPackets.kt @@ -1,4 +1,11 @@ package de.chaoscaot.replay.commands -class ListPackets { +import com.github.ajalt.clikt.core.CliktCommand +import de.chaoscaot.replay.parser.PacketType + +object ListPackets : CliktCommand() { + override fun run() { + echo("Packets:") + PacketType.entries.forEach { echo("\t${it.name.lowercase().replace("_packet", "").replace("_", "").padEnd(25, ' ')}: 0x${it.id.toString(16).padStart(2, '0')}") } + } } \ No newline at end of file diff --git a/src/main/kotlin/de/chaoscaot/replay/parser/EmbeddedSchematic.kt b/src/main/kotlin/de/chaoscaot/replay/parser/EmbeddedSchematic.kt index 95f6d53..104a9af 100644 --- a/src/main/kotlin/de/chaoscaot/replay/parser/EmbeddedSchematic.kt +++ b/src/main/kotlin/de/chaoscaot/replay/parser/EmbeddedSchematic.kt @@ -1,4 +1,32 @@ package de.chaoscaot.replay.parser -class EmbeddedSchematic { +import dev.dewy.nbt.Nbt +import dev.dewy.nbt.tags.collection.CompoundTag +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ByteArraySerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.io.DataInputStream +import java.io.InputStream + +@Serializable(with = EmbeddedSchematicSerializer::class) +data class EmbeddedSchematic(val data: CompoundTag) + +object EmbeddedSchematicSerializer: KSerializer { + private val delegateSerializer = ByteArraySerializer() + private val nbt = Nbt() + + override val descriptor: SerialDescriptor = SerialDescriptor("de.chaoscaot.EmbeddedSchematic", delegateSerializer.descriptor) + + override fun deserialize(decoder: Decoder): EmbeddedSchematic = EmbeddedSchematic(nbt.fromStream(DataInputStream(DecoderStream(decoder)))) + + override fun serialize(encoder: Encoder, value: EmbeddedSchematic) { + TODO("Not yet implemented") + } +} + +class DecoderStream(private val decoder: Decoder): InputStream() { + override fun read(): Int = decoder.decodeByte().toUByte().toInt() } \ No newline at end of file diff --git a/src/main/kotlin/de/chaoscaot/replay/parser/Message.kt b/src/main/kotlin/de/chaoscaot/replay/parser/Message.kt index 7b812e5..dc8990c 100644 --- a/src/main/kotlin/de/chaoscaot/replay/parser/Message.kt +++ b/src/main/kotlin/de/chaoscaot/replay/parser/Message.kt @@ -1,3 +1,57 @@ package de.chaoscaot.replay.parser -data class Message() +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/* + * Message-Format + * String message + byte prefixed Object-Params + byte 0x00 + * 0x00: End of message + * 0x01: boolean following + * 0x02: byte following + * 0x03: short following + * 0x04: int following + * 0x05: float following + * 0x06: double following + * 0x07: String following + * 0x08: Message following + */ + +@Serializable(with = MessageSerializer::class) +data class Message(val message: String, val params: List) + +object MessageSerializer: KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("de.chaoscaot.Message", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Message { + val msg = decoder.decodeString() + val params = mutableListOf() + + var type: Byte + + while (decoder.decodeByte().also { type = it } != 0.toByte()) { + when (type.toInt()) { + 1 -> params.add(decoder.decodeBoolean()) + 2 -> params.add(decoder.decodeByte()) + 3 -> params.add(decoder.decodeShort()) + 4 -> params.add(decoder.decodeInt()) + 5 -> params.add(decoder.decodeFloat()) + 6 -> params.add(decoder.decodeDouble()) + 7 -> params.add(decoder.decodeString()) + 8 -> params.add(decoder.decodeSerializableValue(this)) + } + } + + return Message(msg, params) + } + + override fun serialize(encoder: Encoder, value: Message) { + TODO("Not yet implemented") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/de/chaoscaot/replay/parser/PacketReader.kt b/src/main/kotlin/de/chaoscaot/replay/parser/PacketReader.kt index 5157893..35e9fc9 100644 --- a/src/main/kotlin/de/chaoscaot/replay/parser/PacketReader.kt +++ b/src/main/kotlin/de/chaoscaot/replay/parser/PacketReader.kt @@ -1,18 +1,110 @@ -package de.chaoscaot.replay.parser.packets +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 -abstract class Packet(val type: PacketType) +@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") + } -interface PacketReader { - fun readPacket(stream: DataInputStream): T + it +}.let { PacketDecoder(stream).decodeSerializableValue(it.serializer) } - companion object { - fun readPacket(type: Byte, stream: DataInputStream): Packet = - PacketType.entries.find { it.id == type }?.reader?.readPacket(stream) ?: throw IllegalArgumentException("Unknown Packet") +fun readPackets(stream: DataInputStream): Sequence = sequence { + while (stream.available() > 0) { + yield(readPacket(stream.readByte().toUByte().toInt(), stream)) } } -enum class PacketType(val id: Byte, val reader: PacketReader) { - PLAYER_JOIN_PACKET(0x00, UserJoinPacketReader), +@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) { + 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()), } \ No newline at end of file diff --git a/src/main/kotlin/de/chaoscaot/replay/parser/Packets.kt b/src/main/kotlin/de/chaoscaot/replay/parser/Packets.kt index c489d2d..54db49c 100644 --- a/src/main/kotlin/de/chaoscaot/replay/parser/Packets.kt +++ b/src/main/kotlin/de/chaoscaot/replay/parser/Packets.kt @@ -1,2 +1,327 @@ package de.chaoscaot.replay.parser +import kotlinx.serialization.Serializable + + +/* + * PlayerJoinPacket (0x00) + int EntityId + int SWUserId + * EntityMovePacket (0x01) + int EntityId + double x, y, z + float pitch, yaw + byte headyaw + * EntityDespawnsPacket (0x02) + int EntityId + * PlayerSneakPacket (0x03) + int EntityId + boolean sneaks + * EntityAnimationPacket (0x04) + int EntityId + byte animation + * TNTSpawnPacket (0x05) + int EntityId + * EntitySpeedPacket (0x06) + int EntityId + double dx, dy, dz + * PlayerItemPacket (0x07) + int EntityId + String item + boolean enchanted + String slot + * ArrowSpawnPacket (0x08) + int EntityId + * FireballSpawnPacket (0x09) + int EntityId + * BowSpanPacket (0x0a) + int EntityId + boolean start + hand + * PlayerDamagePacket (0x0b) + int EntityId + * SetOnFire (0x0c) + int EntityId + boolean perma + * + * DEPRECATED ArenaInfo (0x20) + bool blueNegZ + byte arenaY + int arenaMinX + int arenaMinZ + * ArenaInfo (0x21) + bool blueNegZ + int arenaMinX + int arenaMinY + int arenaMinZ + * + * DEPRECATED BlockPacket (0x30) + pos int, byte, int + int BlockState + * ParticlePacket (0x31) + double x, y, z + string particleType + * SoundPacket (0x32) + int x, y, z + string soundType + string soundCategory + float volume, pitch + * DEPRECATED ShortBlockPacket (0x33) + pos relative to ArenaMinX,ArenaMinZ byte, byte, byte + short BlockState + * SoundAtPlayerPacket (0x34) + string (soundType, soundCategory) + float volume, pitch + * ShortBlockPacket (0x35) + pos relative to ArenaMinX,BluePasteY,ArenaMinZ byte, byte, byte + short BlockState + * BlockPacket (0x36) + pos int, short, int + int BlockState + * + * + * DEPRECATED ChatPacket (0xa0) + String message + * DEPRECATED ActionBarPacket (0xa1) + String message + * DEPRECATED SystemPacket (0xa2) + String message + * CountdownPacket (0xa3) + String message, int displaytime, Message appendix + * ChatPacket (0xa4) + Message + * SystemPacket (0xa5) + Message + * + * BlueSchemPacket (0xb0) + int blueSchemId + * RedSchemPacket (0xb1) + int redSchemId + * TeamIDPacket (0xb2) + int blueTeamId, redTeamId + * BlueEmbeddedSchemPacket (0xb3) + int blueSchemId + gzipt NBT blob + * RedEmbeddedSchemPacket (0xb4) + int redSchemId + gzipt NBT blob + * + * DEPRECATED ScoreboardTitlePacket (0xc0) + String scoreboardTitle + * DEPRECATED ScoreboardDataPacket (0xc1) + String key + int value + * DEPRECATED BossBarPacket (0xc2) + double leftBlueProgress, leftRedProgress + String leftBlueText, leftRedText + * DEPRECATED SubtitlePacket (0xc3) + String subtitle + * DEPRECATED PrintWinPacket (0xc4) + String title, subtitle + * SubtitlePacket (0xc5) + Message + * WinPacket (0xc6) + byte team + Message subtitle + * BossBarPacket (0xc7) + double leftBlueProgress, leftRedProgress + Message leftBlueText, leftRedText + * + * CommentPacket (0xfe) + String comment + * TickPacket (0xff) + * + * Message-Format + * String message + byte prefixed Object-Params + byte 0x00 + * 0x00: End of message + * 0x01: boolean following + * 0x02: byte following + * 0x03: short following + * 0x04: int following + * 0x05: float following + * 0x06: double following + * 0x07: String following + * 0x08: Message following + * */ + +@Serializable +data class UserJoinPacket(val entityId: Int, val userId: Int) + +@Serializable +data class EntityMovePacket(val entityId: Int, val x: Double, val y: Double, val z: Double, val pitch: Float, val yaw: Float, val headYaw: Byte) + +@Serializable +data class EntityDespawnsPacket(val entityId: Int) + +@Serializable +data class EntitySpeedPacket(val entityId: Int, val dx: Double, val dy: Double, val dz: Double) + +@Serializable +data class PlayerSneakPacket( + val entityId: Int, + val sneaks: Boolean +) + +@Serializable +data class EntityAnimationPacket( + val entityId: Int, + val animation: Byte +) + +@Serializable +data class TNTSpawnPacket( + val entityId: Int +) + +@Serializable +data class PlayerItemPacket( + val entityId: Int, + val item: String, + val enchanted: Boolean, + val slot: String +) + +@Serializable +data class ArrowSpawnPacket( + val entityId: Int +) + +@Serializable +data class FireballSpawnPacket( + val entityId: Int +) + +@Serializable +data class BowSpanPacket( + val entityId: Int, + val start: Boolean, + val hand: String +) + +@Serializable +data class PlayerDamagePacket( + val entityId: Int +) + +@Serializable +data class SetOnFirePacket( + val entityId: Int, + val perma: Boolean +) + +@Serializable +data class ArenaInfo( + val blueNegZ: Boolean, + val arenaMinX: Int, + val arenaMinY: Int, + val arenaMinZ: Int +) + +@Serializable +data class ParticlePacket( + val x: Double, + val y: Double, + val z: Double, + val particleType: String +) + +@Serializable +data class SoundPacket( + val x: Int, + val y: Int, + val z: Int, + val soundType: String, + val soundCategory: String, + val volume: Float, + val pitch: Float +) + +@Serializable +data class SoundAtPlayerPacket( + val soundType: String, + val volume: Float, + val pitch: Float +) + +@Serializable +data class ShortBlockPacket( + val x: Byte, + val y: Byte, + val z: Byte, + val blockState: Short +) + +@Serializable +data class BlockPacket( + val x: Int, + val y: Short, + val z: Int, + val blockState: Int +) + +// Verbleibende aktive Pakete +@Serializable +data class BlueEmbeddedSchemPacket( + val blueSchemId: Int, + val schematic: EmbeddedSchematic +) + +@Serializable +data class RedEmbeddedSchemPacket( + val redSchemId: Int, + val schematic: EmbeddedSchematic +) + +@Serializable +data class BlueSchemPacket( + val blueSchemId: Int +) + +@Serializable +data class RedSchemPacket( + val redSchemId: Int +) + +@Serializable +data class TeamIDPacket( + val blueTeamId: Int, + val redTeamId: Int +) + +@Serializable +data class CountdownPacket( + val message: String, + val displayTime: Int, + val appendix: Message +) + +@Serializable +data class ChatPacket( + val message: Message +) + +@Serializable +data class SystemPacket( + val message: Message +) + +@Serializable +data class SubtitlePacket( + val message: Message +) + +@Serializable +data class WinPacket( + val team: Byte, + val subtitle: Message +) + +@Serializable +data class BossBarPacket( + val leftBlueProgress: Double, + val leftRedProgress: Double, + val leftBlueText: Message, + val leftRedText: Message +) + +@Serializable +data class CommentPacket( + val comment: String +) + +@Serializable +class TickPacket + +// Legacy (Deprecated) Pakete +@Serializable +data class LegacyArenaInfo( + val blueNegZ: Boolean, + val arenaY: Byte, + val arenaMinX: Int, + val arenaMinZ: Int +) + +@Serializable +data class LegacyBlockPacket( + val x: Int, + val y: Byte, + val z: Int, + val blockState: Int +) + +@Serializable +data class LegacyShortBlockPacket( + val x: Byte, + val y: Byte, + val z: Byte, + val blockState: Short +) + +@Serializable +data class LegacyChatPacket( + val message: String +) + +@Serializable +data class LegacyActionBarPacket( + val message: String +) + +@Serializable +data class LegacySystemPacket( + val message: String +) + +@Serializable +data class LegacyScoreboardTitlePacket( + val scoreboardTitle: String +) + +@Serializable +data class LegacyScoreboardDataPacket( + val key: String, + val value: Int +) + +@Serializable +data class LegacyBossBarPacket( + val leftBlueProgress: Double, + val leftRedProgress: Double, + val leftBlueText: String, + val leftRedText: String +) + +@Serializable +data class LegacySubtitlePacket( + val subtitle: String +) + +@Serializable +data class LegacyPrintWinPacket( + val title: String, + val subtitle: String +) diff --git a/src/main/kotlin/de/chaoscaot/replay/parser/ReplayParser.kt b/src/main/kotlin/de/chaoscaot/replay/parser/ReplayParser.kt index 2565542..9ce58c8 100644 --- a/src/main/kotlin/de/chaoscaot/replay/parser/ReplayParser.kt +++ b/src/main/kotlin/de/chaoscaot/replay/parser/ReplayParser.kt @@ -1,4 +1,12 @@ package de.chaoscaot.replay.parser +import java.io.DataInputStream +import java.io.File +import java.io.InputStream +import java.util.zip.GZIPInputStream + object ReplayParser { + fun read(file: File): Sequence = read(file.inputStream()) + + fun read(inputStream: InputStream, gzipped: Boolean = true): Sequence = readPackets(DataInputStream(if (gzipped) GZIPInputStream(inputStream) else inputStream)) } \ No newline at end of file