diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/BinaryJson.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/BinaryJson.kt new file mode 100644 index 000000000..04c27ee5e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/BinaryJson.kt @@ -0,0 +1,129 @@ +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 net.minecraft.nbt.NbtAccounter +import java.io.InputStream +import java.io.OutputStream + +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 + +/** + * 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.writeBinaryJson(element: JsonElement) { + if (element is JsonObject) { + write(TYPE_OBJECT) + writeVarIntLE(element.size()) + + for ((k, v) in element.entrySet()) { + writeBinaryString(k) + writeBinaryJson(v) + } + } else if (element is JsonArray) { + write(TYPE_ARRAY) + writeVarIntLE(element.size()) + + for (v in element) { + writeBinaryJson(v) + } + } else if (element is JsonPrimitive) { + if (element.isNumber) { + val num = element.asNumber + + if (num is Int || num is Long || num is Short || num is Byte) { + write(TYPE_INT) + writeVarLongLE(num.toLong()) + } 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 if (element is JsonNull) { + 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.readBinaryJson(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(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(readBinaryJson(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, readBinaryJson(sizeLimit)) + } catch(err: Throwable) { + throw JsonSyntaxException("Reading json object at $i with name $key", err) + } + } + + return build + } + else -> throw JsonParseException("Unknown element type $id") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt new file mode 100644 index 000000000..5de89cfe1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt @@ -0,0 +1,43 @@ +package ru.dbotthepony.mc.otm.core.util + +import com.google.gson.JsonElement +import com.mojang.serialization.Codec +import com.mojang.serialization.JsonOps +import io.netty.buffer.ByteBufInputStream +import io.netty.buffer.ByteBufOutputStream +import io.netty.handler.codec.DecoderException +import io.netty.handler.codec.EncoderException +import net.minecraft.nbt.NbtAccounter +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.network.chat.Component + +fun FriendlyByteBuf.readBinaryJson(sizeLimit: NbtAccounter = NbtAccounter(1L shl 18)): JsonElement { + return ByteBufInputStream(this).readBinaryJson(sizeLimit) +} + +fun FriendlyByteBuf.writeBinaryJson(value: JsonElement) { + ByteBufOutputStream(this).writeBinaryJson(value) +} + +fun FriendlyByteBuf.writeBinaryJsonWithCodec(codec: Codec, value: S) { + writeBinaryJson(codec.encode(value, JsonOps.INSTANCE, JsonOps.INSTANCE.empty()) + .get().map({ it }, { throw EncoderException("Failed to encode input data: ${it.message()}") })) +} + +fun FriendlyByteBuf.readBinaryJsonWithCodec(codec: Codec, sizeLimit: NbtAccounter = NbtAccounter(1L shl 18)): S { + return codec.decode(JsonOps.INSTANCE, readBinaryJson(sizeLimit)) + .get().map({ it.first }, { throw DecoderException("Failed to decode data from network: ${it.message()}") }) +} + +fun FriendlyByteBuf.readBinaryComponent(): Component { + return Component.Serializer.fromJson(readBinaryJson()) ?: throw NullPointerException("Received null component") +} + +fun FriendlyByteBuf.writeBinaryComponent(component: Component) { + writeBinaryJson(Component.Serializer.toJsonTree(component)) +} + +// обратный порядок аргументов у лямбда выражения +fun FriendlyByteBuf.writeCollection(collection: Collection, writer: (T, FriendlyByteBuf) -> Unit) { + writeCollection(collection) { a, b -> writer.invoke(b, a) } +} 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 index 6b6f332af..e86462576 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt @@ -1,22 +1,9 @@ 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 com.mojang.serialization.Codec -import com.mojang.serialization.JsonOps -import io.netty.buffer.ByteBufInputStream -import io.netty.buffer.ByteBufOutputStream -import io.netty.handler.codec.DecoderException 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 @@ -143,123 +130,6 @@ fun InputStream.readBigDecimal(sizeLimit: NbtAccounter = NbtAccounter(512L)): Bi 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 - -/** - * 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.writeBinaryJson(element: JsonElement) { - if (element is JsonObject) { - write(TYPE_OBJECT) - writeVarIntLE(element.size()) - - for ((k, v) in element.entrySet()) { - writeBinaryString(k) - writeBinaryJson(v) - } - } else if (element is JsonArray) { - write(TYPE_ARRAY) - writeVarIntLE(element.size()) - - for (v in element) { - writeBinaryJson(v) - } - } else if (element is JsonPrimitive) { - if (element.isNumber) { - val num = element.asNumber - - if (num is Int || num is Long || num is Short || num is Byte) { - write(TYPE_INT) - writeVarLongLE(num.toLong()) - } 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 if (element is JsonNull) { - 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.readBinaryJson(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(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(readBinaryJson(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, readBinaryJson(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(readBinaryJson()) } @@ -268,37 +138,6 @@ fun OutputStream.writeBinaryComponent(component: Component) { writeBinaryJson(Component.Serializer.toJsonTree(component)) } -fun FriendlyByteBuf.readBinaryJson(sizeLimit: NbtAccounter = NbtAccounter(1L shl 18)): JsonElement { - return ByteBufInputStream(this).readBinaryJson(sizeLimit) -} - -fun FriendlyByteBuf.writeBinaryJson(value: JsonElement) { - ByteBufOutputStream(this).writeBinaryJson(value) -} - -fun FriendlyByteBuf.writeBinaryJsonWithCodec(codec: Codec, value: S) { - writeBinaryJson(codec.encode(value, JsonOps.INSTANCE, JsonOps.INSTANCE.empty()) - .get().map({ it }, { throw EncoderException("Failed to encode input data: ${it.message()}") })) -} - -fun FriendlyByteBuf.readBinaryJsonWithCodec(codec: Codec, sizeLimit: NbtAccounter = NbtAccounter(1L shl 18)): S { - return codec.decode(JsonOps.INSTANCE, readBinaryJson(sizeLimit)) - .get().map({ it.first }, { throw DecoderException("Failed to decode data from network: ${it.message()}") }) -} - -fun FriendlyByteBuf.readBinaryComponent(): Component { - return Component.Serializer.fromJson(readBinaryJson()) ?: throw NullPointerException("Received null component") -} - -fun FriendlyByteBuf.writeBinaryComponent(component: Component) { - writeBinaryJson(Component.Serializer.toJsonTree(component)) -} - -// обратный порядок аргументов у лямбда выражения -fun FriendlyByteBuf.writeCollection(collection: Collection, writer: (T, FriendlyByteBuf) -> Unit) { - writeCollection(collection) { a, b -> writer.invoke(b, a) } -} - fun S.writeCollection(collection: Collection, writer: S.(V) -> Unit) { writeVarIntLE(collection.size)