From 26a064fbe2fb456f1a9089bd239e3c95f7686262 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Fri, 3 Mar 2023 08:05:41 +0700 Subject: [PATCH] Payload may not be larger than 1048576 bytes # Conflicts: # src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt --- .../mc/otm/core/util/FriendlyStreams.kt | 264 ++++++++++++++++++ .../mc/otm/matter/MatterManager.kt | 6 +- 2 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt new file mode 100644 index 000000000..13d79bef0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt @@ -0,0 +1,264 @@ +package ru.dbotthepony.mc.otm.core.util + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSyntaxException +import io.netty.buffer.ByteBufInputStream +import io.netty.buffer.ByteBufOutputStream +import io.netty.handler.codec.EncoderException +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtAccounter +import net.minecraft.nbt.NbtIo +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.network.chat.Component +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraftforge.registries.ForgeRegistries +import net.minecraftforge.registries.ForgeRegistry +import java.io.* +import java.math.BigDecimal +import java.math.BigInteger +import kotlin.math.absoluteValue + +// But seriously, Mojang, why would you need to derive from ByteBuf directly, when you can implement +// your own InputStream and OutputStream, since ByteBuf is meant to be operated on most time like a stream anyway? + +// netty ByteBuf -> netty ByteBufInputStream -> Minecraft FriendlyInputStream + +fun OutputStream.writeNbt(value: CompoundTag) { + try { + NbtIo.write(value, if (this is DataOutputStream) this else DataOutputStream(this)) + } catch (ioexception: IOException) { + throw EncoderException(ioexception) + } +} + +fun InputStream.readNbt(accounter: NbtAccounter = NbtAccounter(1L shl 18 /* 256 KiB */)): CompoundTag { + return try { + NbtIo.read(if (this is DataInputStream) this else DataInputStream(this), accounter) + } catch (ioexception: IOException) { + throw EncoderException(ioexception) + } +} + +fun OutputStream.writeItem(itemStack: ItemStack, limitedTag: Boolean = true) { + if (itemStack.isEmpty) { + write(0) + } else { + write(1) + val id = (ForgeRegistries.ITEMS as ForgeRegistry).getID(itemStack.item) + + writeInt(id) + writeInt(itemStack.count) + + var compoundtag: CompoundTag? = null + + if (itemStack.item.isDamageable(itemStack) || itemStack.item.shouldOverrideMultiplayerNbt()) { + compoundtag = if (limitedTag) itemStack.shareTag else itemStack.tag + } + + write(if (compoundtag != null) 1 else 0) + + if (compoundtag != null) { + writeNbt(compoundtag) + } + } +} + +fun InputStream.readItem(sizeLimit: NbtAccounter = NbtAccounter(1L shl 18 /* 256 KiB */)): ItemStack { + sizeLimit.accountBytes(1L) + + if (read() == 0) { + return ItemStack.EMPTY + } + + sizeLimit.accountBytes(9L) + val item = (ForgeRegistries.ITEMS as ForgeRegistry).getValue(readInt()) + val itemStack = ItemStack(item, readInt()) + + if (read() != 0) { + itemStack.readShareTag(readNbt(sizeLimit)) + } + + return itemStack +} + +fun OutputStream.writeBigDecimal(value: BigDecimal) { + writeInt(value.scale()) + val bytes = value.unscaledValue().toByteArray() + writeVarIntLE(bytes.size) + write(bytes) +} + +fun InputStream.readBigDecimal(sizeLimit: NbtAccounter = NbtAccounter(512L)): BigDecimal { + val scale = readInt() + val size = readVarIntLE(sizeLimit) + require(size >= 0) { "Negative payload size: $size" } + sizeLimit.accountBytes(size.toLong() + 4L) + val bytes = ByteArray(size) + read(bytes) + return BigDecimal(BigInteger(bytes), scale) +} + +private const val TYPE_NULL = 0x01 +private const val TYPE_DOUBLE = 0x02 +private const val TYPE_BOOLEAN = 0x03 +private const val TYPE_INT = 0x04 +private const val TYPE_STRING = 0x05 +private const val TYPE_ARRAY = 0x06 +private const val TYPE_OBJECT = 0x07 + +private fun fixSignedInt(read: Long): Long { + val sign = read and 0x1L + @Suppress("name_shadowing") + val read = read ushr 1 + + if (sign == 1L) { + return -read - 1L + } else { + return read + } +} + +/** + * Writes binary json to stream in Starbound Object Notation format + * + * just copy pasted this code from my another project because i was lazy + */ +fun OutputStream.writeJson(element: JsonElement) { + if (element is JsonObject) { + write(TYPE_OBJECT) + writeVarIntLE(element.size()) + + for ((k, v) in element.entrySet()) { + writeBinaryString(k) + writeJson(v) + } + } else if (element is JsonArray) { + write(TYPE_ARRAY) + writeVarIntLE(element.size()) + + for (v in element) { + writeJson(v) + } + } else if (element is JsonPrimitive) { + if (element.isNumber) { + val num = element.asNumber + + if (num is Int || num is Long) { + write(TYPE_INT) + var int = num.toLong() + + if (int < 0) { + int = int.absoluteValue.shl(1).or(1) + } else { + int.shl(1) + } + + writeVarLongLE(int) + } else if (num is Float || num is Double) { + write(TYPE_DOUBLE) + writeDouble(num.toDouble()) + } else { + throw IllegalArgumentException("Unknown number type: ${num::class.qualifiedName}") + } + } else if (element.isString) { + write(TYPE_STRING) + writeBinaryString(element.asString) + } else if (element.isBoolean) { + write(TYPE_BOOLEAN) + write(if (element.asBoolean) 1 else 0) + } else { + write(TYPE_NULL) + } + } else { + throw IllegalArgumentException("Unknown element type: ${element::class.qualifiedName}") + } +} + +/** + * Reads binary json from stream in Starbound Object Notation format + * + * just copy pasted this code from my another project because i was lazy + */ +fun InputStream.readJson(sizeLimit: NbtAccounter = NbtAccounter(1L shl 18 /* 256 KiB */)): JsonElement { + sizeLimit.accountBytes(1L) + + return when (val id = read()) { + TYPE_NULL -> JsonNull.INSTANCE + TYPE_DOUBLE -> { + sizeLimit.accountBytes(8L) + JsonPrimitive(readDouble()) + } + TYPE_BOOLEAN -> { + sizeLimit.accountBytes(1L) + JsonPrimitive(read() > 1) + } + TYPE_INT -> JsonPrimitive(fixSignedInt(readVarLongLE(sizeLimit))) + TYPE_STRING -> JsonPrimitive(readBinaryString(sizeLimit)) + TYPE_ARRAY -> { + val values = readVarIntLE(sizeLimit) + + if (values == 0) return JsonArray() + if (values < 0) throw JsonSyntaxException("Tried to read json array with $values elements in it") + + val build = JsonArray(values) + for (i in 0 until values) build.add(readJson(sizeLimit)) + return build + } + TYPE_OBJECT -> { + val values = readVarIntLE(sizeLimit) + if (values == 0) return JsonObject() + if (values < 0) throw JsonSyntaxException("Tried to read json object with $values elements in it") + + val build = JsonObject() + + for (i in 0 until values) { + val key: String + + try { + key = readBinaryString(sizeLimit) + } catch(err: Throwable) { + throw JsonSyntaxException("Reading json object at $i", err) + } + + try { + build.add(key, readJson(sizeLimit)) + } catch(err: Throwable) { + throw JsonSyntaxException("Reading json object at $i with name $key", err) + } + } + + return build + } + else -> throw JsonParseException("Unknown element type $id") + } +} + +fun InputStream.readBinaryComponent(): Component? { + return Component.Serializer.fromJson(readJson()) +} + +fun OutputStream.writeBinaryComponent(component: Component) { + writeJson(Component.Serializer.toJsonTree(component)) +} + +fun FriendlyByteBuf.readJson(sizeLimit: NbtAccounter = NbtAccounter(1L shl 18)): JsonElement { + return ByteBufInputStream(this).readJson(sizeLimit) +} + +fun FriendlyByteBuf.writeJson(value: JsonElement) { + ByteBufOutputStream(this).writeJson(value) +} + +fun FriendlyByteBuf.readBinaryComponent(): Component { + return Component.Serializer.fromJson(readJson()) ?: throw NullPointerException("Received null component") +} + +fun FriendlyByteBuf.writeBinaryComponent(component: Component) { + writeJson(Component.Serializer.toJsonTree(component)) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt index d9d2f187c..aa46ed40d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt @@ -77,6 +77,8 @@ import ru.dbotthepony.mc.otm.core.orNull import ru.dbotthepony.mc.otm.core.readItemType import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.stream +import ru.dbotthepony.mc.otm.core.util.readBinaryComponent +import ru.dbotthepony.mc.otm.core.util.writeBinaryComponent import ru.dbotthepony.mc.otm.core.writeItemType import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel @@ -1397,7 +1399,7 @@ object MatterManager { val result = SyncPacket( buff.readMap(FriendlyByteBuf::readItemType, FriendlyByteBuf::readMatterValue), - buff.readMap(FriendlyByteBuf::readItemType) { self -> self.readCollection(::ArrayList, FriendlyByteBuf::readComponent) } + buff.readMap(FriendlyByteBuf::readItemType) { self -> self.readCollection(::ArrayList, FriendlyByteBuf::readBinaryComponent) } ) LOGGER.debug("Reading matter registry packet took ${time.millis}ms") @@ -1412,7 +1414,7 @@ object MatterManager { override fun write(buff: FriendlyByteBuf) { val time = SystemTime() buff.writeMap(values, FriendlyByteBuf::writeItemType, FriendlyByteBuf::writeMatterValue) - buff.writeMap(comments, FriendlyByteBuf::writeItemType) { self, value -> self.writeCollection(value, FriendlyByteBuf::writeComponent) } + buff.writeMap(comments, FriendlyByteBuf::writeItemType) { self, value -> self.writeCollection(value, FriendlyByteBuf::writeBinaryComponent) } LOGGER.debug("Encoding matter registry packet took ${time.millis}ms, written total ${buff.writerIndex() - 1} bytes") }