Skip to content

Commit ae925a1

Browse files
committed
Packetlogger with dynamic reflection based serializer
1 parent a8b5c4b commit ae925a1

File tree

9 files changed

+297
-4
lines changed

9 files changed

+297
-4
lines changed

common/src/main/kotlin/com/lambda/config/Configuration.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.google.gson.JsonObject
55
import com.google.gson.JsonParser
66
import com.lambda.Lambda.LOG
77
import com.lambda.Lambda.gson
8+
import com.lambda.config.configurations.ModuleConfig
89
import com.lambda.event.EventFlow.lambdaScope
910
import com.lambda.event.events.ClientEvent
1011
import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener
@@ -14,7 +15,6 @@ import com.lambda.util.StringUtils.capitalize
1415
import kotlinx.coroutines.Dispatchers
1516
import kotlinx.coroutines.launch
1617
import java.io.File
17-
import java.util.*
1818

1919
/**
2020
* Represents a compound of [Configurable] objects whose [AbstractSetting]s

common/src/main/kotlin/com/lambda/config/configurations/LambdaConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ import com.lambda.util.FolderRegister
55

66
object LambdaConfig : Configuration() {
77
override val configName = "lambda"
8-
override val primary = FolderRegister.lambda.resolve("$configName.json")
8+
override val primary = FolderRegister.config.resolve("$configName.json")
99
}

common/src/main/kotlin/com/lambda/config/configurations/ModuleConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ import java.io.File
1717
*/
1818
object ModuleConfig : Configuration() {
1919
override val configName = "modules"
20-
override val primary = File(FolderRegister.config, "$configName.json")
20+
override val primary = FolderRegister.config.resolve("$configName.json")
2121
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package com.lambda.module.modules
2+
3+
import com.lambda.Lambda.mc
4+
import com.lambda.event.EventFlow.lambdaScope
5+
import com.lambda.event.events.PacketEvent
6+
import com.lambda.event.listener.UnsafeListener.Companion.unsafeConcurrentListener
7+
import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener
8+
import com.lambda.module.Module
9+
import com.lambda.util.Communication.info
10+
import com.lambda.util.DynamicReflectionSerializer.dynamicString
11+
import com.lambda.util.FolderRegister
12+
import com.lambda.util.Formatting.getTime
13+
import kotlinx.coroutines.Dispatchers
14+
import kotlinx.coroutines.channels.BufferOverflow
15+
import kotlinx.coroutines.flow.MutableSharedFlow
16+
import kotlinx.coroutines.launch
17+
import net.minecraft.network.packet.Packet
18+
import java.io.File
19+
import java.time.format.DateTimeFormatter
20+
21+
object Packetlogger : Module(
22+
name = "Packetlogger",
23+
description = "Logs packets",
24+
defaultTags = setOf()
25+
) {
26+
private val logConcurrent by setting("Build String Concurrent", false, "Whether to serialize packets concurrently. Will not save packets in chronological order but wont lag the game.")
27+
28+
private var file: File? = null
29+
private val entryFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSSS")
30+
private val fileFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss.SSSS")
31+
32+
private val storageFlow = MutableSharedFlow<String>(
33+
extraBufferCapacity = 1000,
34+
onBufferOverflow = BufferOverflow.DROP_OLDEST
35+
)
36+
37+
init {
38+
lambdaScope.launch(Dispatchers.IO) {
39+
storageFlow.collect { entry ->
40+
file?.appendText(entry)
41+
}
42+
}
43+
44+
onEnableUnsafe {
45+
val fileName = "packet-log-${getTime(fileFormatter)}.txt"
46+
file = FolderRegister.packetLogs.resolve(fileName).apply {
47+
if (!parentFile.exists()) {
48+
parentFile.mkdirs()
49+
}
50+
if (!exists()) {
51+
createNewFile()
52+
}
53+
this@Packetlogger.info("Logging packets to $absolutePath")
54+
}.apply {
55+
// ToDo: Add more rich and accurate data to the header
56+
StringBuilder().apply {
57+
val playerName = mc.player?.name?.string ?: "Unknown"
58+
val serverInfo = mc.networkHandler?.serverInfo?.toNbt() ?: "Singleplayer"
59+
appendLine("Packet logger started at ${getTime()} by $playerName\n")
60+
appendLine("Connected to $serverInfo\n")
61+
}.toString().let { header ->
62+
storageFlow.tryEmit(header)
63+
}
64+
}
65+
}
66+
67+
onDisableUnsafe {
68+
this@Packetlogger.info("Stopped logging packets. Saved to ${file?.absolutePath}")
69+
file = null
70+
}
71+
72+
unsafeListener<PacketEvent.Receive.Pre> {
73+
if (logConcurrent) return@unsafeListener
74+
75+
it.packet.logReceived()
76+
}
77+
78+
unsafeListener<PacketEvent.Send.Pre> {
79+
if (logConcurrent) return@unsafeListener
80+
81+
it.packet.logSent()
82+
}
83+
84+
unsafeConcurrentListener<PacketEvent.Receive.Pre> {
85+
if (!logConcurrent) return@unsafeConcurrentListener
86+
87+
it.packet.logReceived()
88+
}
89+
90+
unsafeConcurrentListener<PacketEvent.Send.Pre> {
91+
if (!logConcurrent) return@unsafeConcurrentListener
92+
93+
it.packet.logSent()
94+
}
95+
}
96+
97+
private fun Packet<*>.logReceived() {
98+
storageFlow.tryEmit("Received at ${getTime(entryFormatter)}\n${dynamicString()}\n")
99+
}
100+
101+
private fun Packet<*>.logSent() {
102+
storageFlow.tryEmit("Sent at ${getTime(entryFormatter)}\n${dynamicString()}\n")
103+
}
104+
}

common/src/main/kotlin/com/lambda/module/modules/client/Rubberband.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import com.lambda.util.text.literal
1515
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket
1616
import net.minecraft.util.math.Vec3d
1717

18-
// ToDo: Should also include last packet info as HUD element and connection state. We may find a better name.
18+
// ToDo: Should also include last packet info as HUD element and connection state.
19+
// We should find a better name.
20+
// Also should pause baritone on lag.
1921
object Rubberband : Module(
2022
name = "Rubberband",
2123
description = "Info about rubberbands",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.lambda.module.modules.render
2+
3+
import com.lambda.Lambda.LOG
4+
import com.lambda.event.events.PacketEvent
5+
import com.lambda.event.listener.SafeListener.Companion.listener
6+
import com.lambda.module.Module
7+
import com.lambda.util.Communication.info
8+
import com.lambda.util.DynamicReflectionSerializer.dynamicString
9+
import net.minecraft.network.packet.c2s.play.*
10+
import net.minecraft.network.packet.s2c.play.InventoryS2CPacket
11+
import net.minecraft.network.packet.s2c.play.UpdateSelectedSlotS2CPacket
12+
13+
object InventoryDebug : Module(
14+
name = "InventoryDebug",
15+
description = "Debugs the inventory",
16+
defaultTags = setOf()
17+
) {
18+
init {
19+
listener<PacketEvent.Receive.Pre> {
20+
when (val packet = it.packet) {
21+
is UpdateSelectedSlotS2CPacket, is InventoryS2CPacket -> {
22+
LOG.info(packet.dynamicString())
23+
}
24+
}
25+
}
26+
27+
listener<PacketEvent.Send.Pre> {
28+
when (it.packet) {
29+
is SlotChangedStateC2SPacket,
30+
is ClickSlotC2SPacket,
31+
is CloseHandledScreenC2SPacket,
32+
is CraftRequestC2SPacket,
33+
is CreativeInventoryActionC2SPacket,
34+
is PickFromInventoryC2SPacket,
35+
is UpdateSelectedSlotC2SPacket -> {
36+
LOG.info(it.packet.dynamicString())
37+
}
38+
}
39+
}
40+
}
41+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package com.lambda.util
2+
3+
import com.mojang.serialization.Codec
4+
import net.minecraft.block.BlockState
5+
import net.minecraft.client.resource.language.TranslationStorage
6+
import net.minecraft.item.ItemStack
7+
import net.minecraft.nbt.NbtCompound
8+
import net.minecraft.registry.RegistryKey
9+
import net.minecraft.registry.entry.RegistryEntry
10+
import net.minecraft.screen.ScreenHandlerType
11+
import net.minecraft.text.Text
12+
import net.minecraft.util.Identifier
13+
import net.minecraft.util.math.BlockPos
14+
import net.minecraft.util.math.ChunkPos
15+
import org.apache.logging.log4j.Logger
16+
import java.lang.reflect.Field
17+
import java.lang.reflect.InaccessibleObjectException
18+
import java.util.*
19+
20+
object DynamicReflectionSerializer {
21+
// Classes that should not be recursively serialized
22+
private val skipables = setOf(
23+
Codec::class.java,
24+
Logger::class.java,
25+
BlockPos::class.java,
26+
BlockState::class.java,
27+
ItemStack::class.java,
28+
Identifier::class.java,
29+
NbtCompound::class.java,
30+
Map::class.java,
31+
BitSet::class.java,
32+
Collection::class.java,
33+
RegistryEntry::class.java,
34+
RegistryKey::class.java,
35+
ScreenHandlerType::class.java,
36+
TranslationStorage::class.java,
37+
ChunkPos::class.java,
38+
Text::class.java,
39+
org.slf4j.Logger::class.java,
40+
String::class.java,
41+
)
42+
private val skipFields = setOf(
43+
Codec::class.java,
44+
)
45+
46+
private const val INDENT = 2
47+
48+
// ToDo: To make this work in production, every field could be remapped.
49+
fun Any.dynamicString(
50+
maxRecursionDepth: Int = 6,
51+
currentDepth: Int = 0,
52+
indent: String = "",
53+
visitedObjects: MutableSet<Any> = HashSet(),
54+
builder: StringBuilder = StringBuilder(),
55+
): String {
56+
if (visitedObjects.contains(this)) {
57+
builder.appendLine("$indent${javaClass.simpleName} (Circular Reference)")
58+
return builder.toString()
59+
}
60+
61+
visitedObjects.add(this)
62+
builder.appendLine("$indent${javaClass.simpleName}")
63+
64+
val fields = javaClass.declaredFields + javaClass.superclass?.declaredFields.orEmpty()
65+
fields.forEach { field ->
66+
processField(field, indent, builder, currentDepth, maxRecursionDepth, visitedObjects)
67+
}
68+
69+
return builder.toString()
70+
}
71+
72+
private fun Any.processField(
73+
field: Field,
74+
indent: String,
75+
builder: StringBuilder,
76+
currentDepth: Int,
77+
maxRecursionDepth: Int,
78+
visitedObjects: MutableSet<Any>,
79+
) {
80+
if (skipFields.any { it.isAssignableFrom(field.type) }) return
81+
82+
try {
83+
field.isAccessible = true
84+
} catch (e: InaccessibleObjectException) {
85+
return
86+
}
87+
val fieldValue = field.get(this)
88+
val fieldIndent = indent + " ".repeat(INDENT)
89+
builder.appendLine("$fieldIndent${field.name}: ${formatFieldValue(fieldValue)}")
90+
91+
if (currentDepth < maxRecursionDepth
92+
&& fieldValue != null
93+
&& !field.type.isPrimitive
94+
&& !field.type.isArray &&
95+
!field.type.isEnum &&
96+
skipables.none { it.isAssignableFrom(field.type) }
97+
) {
98+
fieldValue.dynamicString(
99+
maxRecursionDepth,
100+
currentDepth + 1,
101+
fieldIndent + " ".repeat(INDENT),
102+
visitedObjects,
103+
builder,
104+
)
105+
}
106+
}
107+
108+
private fun formatFieldValue(value: Any?): String {
109+
return when (value) {
110+
is String -> "\"$value\""
111+
is Collection<*> -> "[${value.joinToString(", ") { formatFieldValue(it) }}]"
112+
is Array<*> -> "[${value.joinToString(", ") { formatFieldValue(it) }}]"
113+
is Map<*, *> -> "{${
114+
value.entries.joinToString(", ") { (k, v) ->
115+
"${formatFieldValue(k)}: ${formatFieldValue(v)}"
116+
}
117+
}}"
118+
is Text -> value.string
119+
is Identifier -> "${value.namespace}:${value.path}"
120+
is NbtCompound -> value.asString()
121+
is RegistryEntry<*> -> "${value.value()}"
122+
else -> value?.toString() ?: "null"
123+
}
124+
}
125+
}

common/src/main/kotlin/com/lambda/util/FolderRegister.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ object FolderRegister {
1717
val minecraft: File = mc.runDirectory
1818
val lambda: File = File(minecraft, "lambda")
1919
val config: File = File(lambda, "config")
20+
val packetLogs: File = File(lambda, "packet-log")
2021
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.lambda.util
2+
3+
import net.minecraft.util.math.Vec3d
4+
import java.time.LocalDateTime
5+
import java.time.ZoneId
6+
import java.time.ZonedDateTime
7+
import java.time.format.DateTimeFormatter
8+
9+
object Formatting {
10+
val Vec3d.asString: String
11+
get() = "(%.2f, %.2f, %.2f)".format(x, y, z)
12+
13+
fun getTime(formatter: DateTimeFormatter = DateTimeFormatter.RFC_1123_DATE_TIME): String {
14+
val localDateTime = LocalDateTime.now()
15+
val zoneId = ZoneId.systemDefault()
16+
val zonedDateTime = ZonedDateTime.of(localDateTime, zoneId)
17+
18+
return zonedDateTime.format(formatter)
19+
}
20+
}

0 commit comments

Comments
 (0)