diff --git a/src/main/kotlin/nbt/Nbt.kt b/src/main/kotlin/nbt/Nbt.kt index 8aa5ce359..a3c9a4df8 100644 --- a/src/main/kotlin/nbt/Nbt.kt +++ b/src/main/kotlin/nbt/Nbt.kt @@ -21,36 +21,99 @@ package com.demonwav.mcdev.nbt import com.demonwav.mcdev.asset.MCDevBundle -import com.demonwav.mcdev.nbt.tags.NbtTag -import com.demonwav.mcdev.nbt.tags.NbtTypeId -import com.demonwav.mcdev.nbt.tags.RootCompound -import com.demonwav.mcdev.nbt.tags.TagByte -import com.demonwav.mcdev.nbt.tags.TagByteArray -import com.demonwav.mcdev.nbt.tags.TagCompound -import com.demonwav.mcdev.nbt.tags.TagDouble -import com.demonwav.mcdev.nbt.tags.TagEnd -import com.demonwav.mcdev.nbt.tags.TagFloat -import com.demonwav.mcdev.nbt.tags.TagInt -import com.demonwav.mcdev.nbt.tags.TagIntArray -import com.demonwav.mcdev.nbt.tags.TagList -import com.demonwav.mcdev.nbt.tags.TagLong -import com.demonwav.mcdev.nbt.tags.TagLongArray -import com.demonwav.mcdev.nbt.tags.TagShort -import com.demonwav.mcdev.nbt.tags.TagString -import java.io.DataInputStream -import java.io.InputStream +import com.demonwav.mcdev.nbt.editor.NbtFormat +import com.demonwav.mcdev.nbt.tags.* +import com.demonwav.mcdev.nbt.util.LittleEndianDataInputStream +import com.demonwav.mcdev.nbt.util.NetworkDataInputStream +import io.netty.buffer.ByteBuf +import io.netty.buffer.ByteBufInputStream +import io.netty.buffer.Unpooled +import java.io.* +import java.util.* import java.util.zip.GZIPInputStream import java.util.zip.ZipException object Nbt { + private fun judgeDataInput(dataIn: DataInput): Boolean { + try { + val firstByte = dataIn.readUnsignedByte()//type must be compound_tag + val fb = NbtTypeId.getById(firstByte.toByte()) + if (fb == null || fb != NbtTypeId.COMPOUND) { + return false + } + dataIn.readUTF()//root compound name + val secondByte = dataIn.readUnsignedByte()//tag type + val sb = NbtTypeId.getById(secondByte.toByte()) ?: return false + if (sb == NbtTypeId.END) return false + dataIn.readUTF()//tag name + dataIn.readTag(sb, System.currentTimeMillis(), 20) + return true + } catch (e: Throwable) { + return false + } + } + + private fun isBedrockLevelDat(dataIn: DataInput): Boolean { + val header = ByteArray(4) + dataIn.readFully(header) + dataIn.skipBytes(4) + return header[0].toInt() >= 8 && header[1].toInt() == 0 && header[2].toInt() == 0 && header[3].toInt() == 0 + } - private fun getActualInputStream(stream: InputStream): Pair { - return try { - DataInputStream(GZIPInputStream(stream)) to true - } catch (e: ZipException) { - stream.reset() - DataInputStream(stream) to false + private fun getActualInputStream(stream: InputStream): Pair { + var res: DataInput? = null + var mode: NbtFormat = NbtFormat.BIG_ENDIAN + stream.use { + val byteBuf: ByteBuf = Unpooled.wrappedBuffer(stream.readAllBytes()) + byteBuf.markReaderIndex() + val iss: InputStream = try { + res = DataInputStream(GZIPInputStream(ByteBufInputStream(byteBuf))) + return@use + } catch (e: ZipException) { + byteBuf.resetReaderIndex() + mode = NbtFormat.BIG_ENDIAN_GZIP + ByteBufInputStream(byteBuf) + } + + byteBuf.markReaderIndex() + var input: DataInput = DataInputStream(iss) + var r = judgeDataInput(input) + if (r) { + res = input + byteBuf.resetReaderIndex() + return@use + } + byteBuf.resetReaderIndex() + + byteBuf.markReaderIndex() + if (!isBedrockLevelDat(input)) { + byteBuf.resetReaderIndex() + } + byteBuf.markReaderIndex() + input = LittleEndianDataInputStream(iss) + r = judgeDataInput(input) + if (r) { + res = input + byteBuf.resetReaderIndex() + mode = NbtFormat.LITTLE_ENDIAN + return@use + } + byteBuf.resetReaderIndex() + + byteBuf.markReaderIndex() + input = NetworkDataInputStream(iss) + r = judgeDataInput(input) + if (r) { + res = input + byteBuf.resetReaderIndex() + mode = NbtFormat.LITTLE_ENDIAN_NETWORK + return@use + } + } + if (res == null) { + throw MalformedNbtFileException(MCDevBundle("nbt.lang.errors.reading")) } + return res!! to mode } /** @@ -58,13 +121,13 @@ object Nbt { * it is finished with it. */ @Throws(MalformedNbtFileException::class) - fun buildTagTree(inputStream: InputStream, timeout: Long): Pair { + fun buildTagTree(inputStream: InputStream, timeout: Long): Pair { try { - val (stream, isCompressed) = getActualInputStream(inputStream) + val (stream, mode) = getActualInputStream(inputStream) - stream.use { - val tagIdByte = stream.readByte() - val tagId = NbtTypeId.getById(tagIdByte) + (stream as InputStream).use { + val tagIdByte = stream.readUnsignedByte() + val tagId = NbtTypeId.getById(tagIdByte.toByte()) ?: throw MalformedNbtFileException(MCDevBundle("nbt.lang.errors.wrong_tag_id", tagIdByte)) if (tagId != NbtTypeId.COMPOUND) { @@ -73,7 +136,7 @@ object Nbt { val start = System.currentTimeMillis() - return RootCompound(stream.readUTF(), stream.readCompoundTag(start, timeout).tagMap) to isCompressed + return RootCompound(stream.readUTF(), stream.readCompoundTag(start, timeout).tagMap) to mode } } catch (e: Throwable) { if (e is MalformedNbtFileException) { @@ -84,10 +147,10 @@ object Nbt { } } - private fun DataInputStream.readCompoundTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { + private fun DataInput.readCompoundTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { val tagMap = HashMap() - var tagIdByte = this.readByte() + var tagIdByte = this.readUnsignedByte().toByte() var tagId = NbtTypeId.getById(tagIdByte) ?: run { throw MalformedNbtFileException(MCDevBundle("nbt.lang.errors.wrong_tag_id", tagIdByte)) @@ -97,7 +160,7 @@ object Nbt { tagMap[name] = this.readTag(tagId, start, timeout) - tagIdByte = this.readByte() + tagIdByte = this.readUnsignedByte().toByte() tagId = NbtTypeId.getById(tagIdByte) ?: run { throw MalformedNbtFileException(MCDevBundle("nbt.lang.errors.wrong_tag_id", tagIdByte)) @@ -107,28 +170,28 @@ object Nbt { return@checkTimeout TagCompound(tagMap) } - private fun DataInputStream.readByteTag(start: Long, timeout: Long) = + private fun DataInput.readByteTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { TagByte(this.readByte()) } - private fun DataInputStream.readShortTag(start: Long, timeout: Long) = + private fun DataInput.readShortTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { TagShort(this.readShort()) } - private fun DataInputStream.readIntTag(start: Long, timeout: Long) = + private fun DataInput.readIntTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { TagInt(this.readInt()) } - private fun DataInputStream.readLongTag(start: Long, timeout: Long) = + private fun DataInput.readLongTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { TagLong(this.readLong()) } - private fun DataInputStream.readFloatTag(start: Long, timeout: Long) = + private fun DataInput.readFloatTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { TagFloat(this.readFloat()) } - private fun DataInputStream.readDoubleTag(start: Long, timeout: Long) = + private fun DataInput.readDoubleTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { TagDouble(this.readDouble()) } - private fun DataInputStream.readStringTag(start: Long, timeout: Long) = + private fun DataInput.readStringTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { TagString(this.readUTF()) } - private fun DataInputStream.readListTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { + private fun DataInput.readListTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { val tagIdByte = this.readByte() val tagId = NbtTypeId.getById(tagIdByte) ?: run { @@ -146,7 +209,7 @@ object Nbt { return@checkTimeout TagList(tagId, list) } - private fun DataInputStream.readByteArrayTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { + private fun DataInput.readByteArrayTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { val length = this.readInt() val bytes = ByteArray(length) @@ -154,7 +217,7 @@ object Nbt { return@checkTimeout TagByteArray(bytes) } - private fun DataInputStream.readIntArrayTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { + private fun DataInput.readIntArrayTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { val length = this.readInt() val ints = IntArray(length) { @@ -164,7 +227,7 @@ object Nbt { return@checkTimeout TagIntArray(ints) } - private fun DataInputStream.readLongArrayTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { + private fun DataInput.readLongArrayTag(start: Long, timeout: Long) = checkTimeout(start, timeout) { val length = this.readInt() val longs = LongArray(length) { @@ -174,7 +237,7 @@ object Nbt { return@checkTimeout TagLongArray(longs) } - private fun DataInputStream.readTag(tagId: NbtTypeId, start: Long, timeout: Long): NbtTag { + private fun DataInput.readTag(tagId: NbtTypeId, start: Long, timeout: Long): NbtTag { return when (tagId) { NbtTypeId.END -> TagEnd NbtTypeId.BYTE -> this.readByteTag(start, timeout) diff --git a/src/main/kotlin/nbt/NbtVirtualFile.kt b/src/main/kotlin/nbt/NbtVirtualFile.kt index e93246ad6..1981245d7 100644 --- a/src/main/kotlin/nbt/NbtVirtualFile.kt +++ b/src/main/kotlin/nbt/NbtVirtualFile.kt @@ -21,10 +21,12 @@ package com.demonwav.mcdev.nbt import com.demonwav.mcdev.asset.MCDevBundle -import com.demonwav.mcdev.nbt.editor.CompressionSelection +import com.demonwav.mcdev.nbt.editor.NbtFormat import com.demonwav.mcdev.nbt.editor.NbtToolbar import com.demonwav.mcdev.nbt.lang.NbttFile import com.demonwav.mcdev.nbt.lang.NbttLanguage +import com.demonwav.mcdev.nbt.util.LittleEndianDataOutputStream +import com.demonwav.mcdev.nbt.util.NetworkDataOutputStream import com.demonwav.mcdev.util.loggerForTopLevel import com.demonwav.mcdev.util.runReadActionAsync import com.demonwav.mcdev.util.runWriteTaskLater @@ -38,6 +40,7 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager import com.intellij.testFramework.LightVirtualFile import com.intellij.util.ThreeState +import java.io.DataOutput import java.io.DataOutputStream import java.util.concurrent.TimeUnit import java.util.zip.GZIPOutputStream @@ -48,17 +51,17 @@ fun NbtVirtualFile(backingFile: VirtualFile, project: Project): NbtVirtualFile { var language: Language = NbttLanguage var text: String - var compressed: Boolean + var nbtFormat: NbtFormat? var parseSuccessful: Boolean try { - val (rootCompound, isCompressed) = Nbt.buildTagTree(backingFile.inputStream, TimeUnit.SECONDS.toMillis(10)) + val (rootCompound, mode) = Nbt.buildTagTree(backingFile.inputStream, TimeUnit.SECONDS.toMillis(10)) text = rootCompound.toString() - compressed = isCompressed + nbtFormat = mode parseSuccessful = true } catch (e: MalformedNbtFileException) { text = MCDevBundle("nbt.lang.errors.wrapped_error_message", e.message) - compressed = false + nbtFormat = null parseSuccessful = false } @@ -66,7 +69,7 @@ fun NbtVirtualFile(backingFile: VirtualFile, project: Project): NbtVirtualFile { language = PlainTextLanguage.INSTANCE } - return NbtVirtualFile(backingFile, project, language, text, compressed, parseSuccessful) + return NbtVirtualFile(backingFile, project, language, text, nbtFormat, parseSuccessful) } class NbtVirtualFile( @@ -74,7 +77,7 @@ class NbtVirtualFile( private val project: Project, language: Language, text: String, - val isCompressed: Boolean, + val nbtFormat: NbtFormat?, val parseSuccessful: Boolean, ) : LightVirtualFile(backingFile.name + ".nbtt", language, text), IdeDocumentHistoryImpl.SkipFromDocumentHistory { @@ -129,13 +132,16 @@ class NbtVirtualFile( runWriteTaskLater { // just to be safe this.parent.bom = null - val filteredStream = when (toolbar.selection) { - CompressionSelection.GZIP -> GZIPOutputStream(this.parent.getOutputStream(requester)) - CompressionSelection.UNCOMPRESSED -> this.parent.getOutputStream(requester) + + val dataOuput: DataOutput = when (toolbar.selection) { + NbtFormat.BIG_ENDIAN_GZIP -> DataOutputStream(GZIPOutputStream(this.parent.getOutputStream(requester))) + NbtFormat.BIG_ENDIAN -> DataOutputStream(this.parent.getOutputStream(requester)) + NbtFormat.LITTLE_ENDIAN -> LittleEndianDataOutputStream(this.parent.getOutputStream(requester)) + NbtFormat.LITTLE_ENDIAN_NETWORK -> NetworkDataOutputStream(this.parent.getOutputStream(requester)) } - DataOutputStream(filteredStream).use { stream -> - rootTag.write(stream) + (dataOuput as DataOutputStream).use { + rootTag.write(dataOuput) } Notification( diff --git a/src/main/kotlin/nbt/editor/CompressionSelection.kt b/src/main/kotlin/nbt/editor/NbtFormat.kt similarity index 73% rename from src/main/kotlin/nbt/editor/CompressionSelection.kt rename to src/main/kotlin/nbt/editor/NbtFormat.kt index d89e20e6c..d4e904a36 100644 --- a/src/main/kotlin/nbt/editor/CompressionSelection.kt +++ b/src/main/kotlin/nbt/editor/NbtFormat.kt @@ -22,10 +22,11 @@ package com.demonwav.mcdev.nbt.editor import com.demonwav.mcdev.asset.MCDevBundle -enum class CompressionSelection(private val selectionNameFunc: () -> String) { - GZIP({ MCDevBundle("nbt.compression.gzip") }), - UNCOMPRESSED({ MCDevBundle("nbt.compression.uncompressed") }), - ; +enum class NbtFormat(private val selectionNameFunc: () -> String) { + LITTLE_ENDIAN_NETWORK({ MCDevBundle("nbt.format.little_network") }), + BIG_ENDIAN_GZIP({ MCDevBundle("nbt.format.big_gzip") }), + LITTLE_ENDIAN({ MCDevBundle("nbt.format.little") }), + BIG_ENDIAN({ MCDevBundle("nbt.format.big") }), ; override fun toString(): String = selectionNameFunc() } diff --git a/src/main/kotlin/nbt/editor/NbtToolbar.kt b/src/main/kotlin/nbt/editor/NbtToolbar.kt index d3cf462f0..f88ccaf8c 100644 --- a/src/main/kotlin/nbt/editor/NbtToolbar.kt +++ b/src/main/kotlin/nbt/editor/NbtToolbar.kt @@ -30,19 +30,17 @@ import com.intellij.ui.dsl.builder.panel class NbtToolbar(nbtFile: NbtVirtualFile) { - private var compressionSelection: CompressionSelection? = - if (nbtFile.isCompressed) CompressionSelection.GZIP else CompressionSelection.UNCOMPRESSED + private var nbtFormat: NbtFormat? = nbtFile.nbtFormat - val selection: CompressionSelection - get() = compressionSelection!! + val selection: NbtFormat get() = nbtFormat!! lateinit var panel: DialogPanel init { panel = panel { - row(MCDevBundle("nbt.compression.file_type.label")) { - comboBox(EnumComboBoxModel(CompressionSelection::class.java)) - .bindItem(::compressionSelection) + row(MCDevBundle("nbt.format.label")) { + comboBox(EnumComboBoxModel(NbtFormat::class.java)) + .bindItem(::nbtFormat) .enabled(nbtFile.isWritable && nbtFile.parseSuccessful) button(MCDevBundle("nbt.compression.save.button")) { panel.apply() diff --git a/src/main/kotlin/nbt/util/LittleEndianDataInputStream.kt b/src/main/kotlin/nbt/util/LittleEndianDataInputStream.kt new file mode 100644 index 000000000..8b495a5af --- /dev/null +++ b/src/main/kotlin/nbt/util/LittleEndianDataInputStream.kt @@ -0,0 +1,87 @@ +package com.demonwav.mcdev.nbt.util + +import java.io.* +import java.nio.charset.StandardCharsets + +open class LittleEndianDataInputStream(stream: InputStream) : FilterInputStream(DataInputStream(stream)), DataInput { + + protected val dataStream = DataInputStream(stream) + + @Throws(IOException::class) + override fun readFully(bytes: ByteArray) { + dataStream.readFully(bytes) + } + + @Throws(IOException::class) + override fun readFully(bytes: ByteArray, offset: Int, length: Int) { + dataStream.readFully(bytes, offset, length) + } + + @Throws(IOException::class) + override fun skipBytes(amount: Int): Int { + return dataStream.skipBytes(amount) + } + + @Throws(IOException::class) + override fun readBoolean(): Boolean { + return dataStream.readBoolean() + } + + @Throws(IOException::class) + override fun readByte(): Byte { + return dataStream.readByte() + } + + @Throws(IOException::class) + override fun readUnsignedByte(): Int { + return dataStream.readUnsignedByte() + } + + @Throws(IOException::class) + override fun readShort(): Short { + return java.lang.Short.reverseBytes(dataStream.readShort()) + } + + @Throws(IOException::class) + override fun readUnsignedShort(): Int { + return java.lang.Short.toUnsignedInt(java.lang.Short.reverseBytes(dataStream.readShort())) + } + + @Throws(IOException::class) + override fun readChar(): Char { + return java.lang.Character.reverseBytes(dataStream.readChar()) + } + + @Throws(IOException::class) + override fun readInt(): Int { + return Integer.reverseBytes(dataStream.readInt()) + } + + @Throws(IOException::class) + override fun readLong(): Long { + return java.lang.Long.reverseBytes(dataStream.readLong()) + } + + @Throws(IOException::class) + override fun readFloat(): Float { + return java.lang.Float.intBitsToFloat(Integer.reverseBytes(dataStream.readInt())) + } + + @Throws(IOException::class) + override fun readDouble(): Double { + return java.lang.Double.longBitsToDouble(java.lang.Long.reverseBytes(dataStream.readLong())) + } + + @Deprecated("") + @Throws(IOException::class) + override fun readLine(): String { + return dataStream.readLine() + } + + @Throws(IOException::class) + override fun readUTF(): String { + val bytes = ByteArray(readUnsignedShort()) + readFully(bytes) + return String(bytes, StandardCharsets.UTF_8) + } +} diff --git a/src/main/kotlin/nbt/util/LittleEndianDataOutputStream.kt b/src/main/kotlin/nbt/util/LittleEndianDataOutputStream.kt new file mode 100644 index 000000000..d95fea217 --- /dev/null +++ b/src/main/kotlin/nbt/util/LittleEndianDataOutputStream.kt @@ -0,0 +1,77 @@ +package com.demonwav.mcdev.nbt.util + +import java.io.* +import java.nio.charset.StandardCharsets + +open class LittleEndianDataOutputStream(stream: OutputStream) : FilterOutputStream(DataOutputStream(stream)), + DataOutput { + + protected val dataStream = DataOutputStream(stream) + + @Throws(IOException::class) + override fun write(bytes: ByteArray) { + dataStream.write(bytes) + } + + @Throws(IOException::class) + override fun write(bytes: ByteArray, offset: Int, length: Int) { + dataStream.write(bytes, offset, length) + } + + @Throws(IOException::class) + override fun writeBoolean(value: Boolean) { + dataStream.writeBoolean(value) + } + + @Throws(IOException::class) + override fun writeByte(value: Int) { + dataStream.writeByte(value) + } + + @Throws(IOException::class) + override fun writeShort(value: Int) { + dataStream.writeShort(java.lang.Short.reverseBytes(value.toShort()).toInt()) + } + + @Throws(IOException::class) + override fun writeChar(value: Int) { + dataStream.writeChar(Character.reverseBytes(value.toChar()).code) + } + + @Throws(IOException::class) + override fun writeInt(value: Int) { + dataStream.writeInt(Integer.reverseBytes(value)) + } + + @Throws(IOException::class) + override fun writeLong(value: Long) { + dataStream.writeLong(java.lang.Long.reverseBytes(value)) + } + + @Throws(IOException::class) + override fun writeFloat(value: Float) { + dataStream.writeInt(Integer.reverseBytes(java.lang.Float.floatToIntBits(value))) + } + + @Throws(IOException::class) + override fun writeDouble(value: Double) { + dataStream.writeLong(java.lang.Long.reverseBytes(java.lang.Double.doubleToLongBits(value))) + } + + @Throws(IOException::class) + override fun writeBytes(string: String) { + dataStream.writeBytes(string) + } + + @Throws(IOException::class) + override fun writeChars(string: String) { + dataStream.writeChars(string) + } + + @Throws(IOException::class) + override fun writeUTF(string: String) { + val bytes = string.toByteArray(StandardCharsets.UTF_8) + writeShort(bytes.size) + write(bytes) + } +} diff --git a/src/main/kotlin/nbt/util/NetworkDataInputStream.kt b/src/main/kotlin/nbt/util/NetworkDataInputStream.kt new file mode 100644 index 000000000..37a4660fa --- /dev/null +++ b/src/main/kotlin/nbt/util/NetworkDataInputStream.kt @@ -0,0 +1,30 @@ +package com.demonwav.mcdev.nbt.util + +import java.io.DataInputStream +import java.io.IOException +import java.io.InputStream +import java.nio.charset.StandardCharsets + +open class NetworkDataInputStream : LittleEndianDataInputStream { + + constructor(stream: InputStream) : super(stream) + constructor(stream: DataInputStream) : super(stream) + + @Throws(IOException::class) + override fun readInt(): Int { + return VarInts.readInt(dataStream) + } + + @Throws(IOException::class) + override fun readLong(): Long { + return VarInts.readLong(dataStream) + } + + @Throws(IOException::class) + override fun readUTF(): String { + val length = VarInts.readUnsignedInt(dataStream) + val bytes = ByteArray(length) + readFully(bytes) + return String(bytes, StandardCharsets.UTF_8) + } +} diff --git a/src/main/kotlin/nbt/util/NetworkDataOutputStream.kt b/src/main/kotlin/nbt/util/NetworkDataOutputStream.kt new file mode 100644 index 000000000..6d06ba9ce --- /dev/null +++ b/src/main/kotlin/nbt/util/NetworkDataOutputStream.kt @@ -0,0 +1,29 @@ +package com.demonwav.mcdev.nbt.util + +import java.io.DataOutputStream +import java.io.IOException +import java.io.OutputStream +import java.nio.charset.StandardCharsets + +open class NetworkDataOutputStream : LittleEndianDataOutputStream { + + constructor(stream: OutputStream) : super(stream) + constructor(stream: DataOutputStream) : super(stream) + + @Throws(IOException::class) + override fun writeInt(value: Int) { + VarInts.writeInt(dataStream, value) + } + + @Throws(IOException::class) + override fun writeLong(value: Long) { + VarInts.writeLong(dataStream, value) + } + + @Throws(IOException::class) + override fun writeUTF(string: String) { + val bytes = string.toByteArray(StandardCharsets.UTF_8) + VarInts.writeUnsignedInt(dataStream, bytes.size.toLong()) + write(bytes) + } +} diff --git a/src/main/kotlin/nbt/util/VarInts.kt b/src/main/kotlin/nbt/util/VarInts.kt new file mode 100644 index 000000000..a97faad51 --- /dev/null +++ b/src/main/kotlin/nbt/util/VarInts.kt @@ -0,0 +1,77 @@ +package com.demonwav.mcdev.nbt.util + +import java.io.DataInput +import java.io.DataOutput +import java.io.IOException + +object VarInts { + + @Throws(IOException::class) + fun writeInt(buffer: DataOutput, integer: Int) { + encodeUnsigned(buffer, ((integer shl 1) xor (integer shr 31)).toLong()) + } + + @Throws(IOException::class) + fun readInt(buffer: DataInput): Int { + val n = decodeUnsigned(buffer).toInt() + return (n ushr 1) xor -(n and 1) + } + + @Throws(IOException::class) + fun writeUnsignedInt(buffer: DataOutput, integer: Long) { + encodeUnsigned(buffer, integer) + } + + @Throws(IOException::class) + fun readUnsignedInt(buffer: DataInput): Int { + return decodeUnsigned(buffer).toInt() + } + + @Throws(IOException::class) + fun writeLong(buffer: DataOutput, longInteger: Long) { + encodeUnsigned(buffer, (longInteger shl 1) xor (longInteger shr 63)) + } + + @Throws(IOException::class) + fun readLong(buffer: DataInput): Long { + val n = decodeUnsigned(buffer) + return (n ushr 1) xor -(n and 1) + } + + @Throws(IOException::class) + fun writeUnsignedLong(buffer: DataOutput, longInteger: Long) { + encodeUnsigned(buffer, longInteger) + } + + @Throws(IOException::class) + fun readUnsignedLong(buffer: DataInput): Long { + return decodeUnsigned(buffer) + } + + @Throws(IOException::class) + private fun decodeUnsigned(buffer: DataInput): Long { + var result: Long = 0 + for (shift in 0 until 64 step 7) { + val b = buffer.readByte().toInt() + result = result or ((b and 0x7F).toLong() shl shift) + if ((b and 0x80) == 0) { + return result + } + } + throw ArithmeticException("Varint was too large") + } + + @Throws(IOException::class) + private fun encodeUnsigned(buffer: DataOutput, value: Long) { + var v = value + while (true) { + if (v and 0x7FL.inv() == 0L) { + buffer.writeByte(v.toInt()) + return + } else { + buffer.writeByte(((v.toInt() and 0x7F) or 0x80)) + v = v ushr 7 + } + } + } +} diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 6e09278c6..9bc30539f 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -191,10 +191,12 @@ inspection.entity_data_param.description=Reports when the class passed to an ent inspection.entity_data_param.message=Entity class does not match this entity class inspection.entity_data_param.fix=Replace other entity class with this entity class -nbt.compression.gzip=GZipped -nbt.compression.uncompressed=Uncompressed -nbt.compression.file_type.label=Compression: nbt.compression.save.button=Save +nbt.format.little=LittleEndian +nbt.format.big=BigEndian +nbt.format.little_network=LittleEndian Network +nbt.format.big_gzip=GZipped +nbt.format.label=NBT Format: nbt.file_type.name=NBT nbt.file_type.description=NBT diff --git a/src/main/resources/messages/MinecraftDevelopment_zh.properties b/src/main/resources/messages/MinecraftDevelopment_zh.properties index c1f1e223e..3816c9999 100644 --- a/src/main/resources/messages/MinecraftDevelopment_zh.properties +++ b/src/main/resources/messages/MinecraftDevelopment_zh.properties @@ -121,10 +121,12 @@ inspection.entity_data_param.description=当传递给实体数据参数定义的 inspection.entity_data_param.message=实体类与此实体类不匹配 inspection.entity_data_param.fix=用该实体类替换其他实体类 -nbt.compression.gzip=GZipped -nbt.compression.uncompressed=未压缩 -nbt.compression.file_type.label=压缩: nbt.compression.save.button=保存 +nbt.format.little=小端序 +nbt.format.big=大端序 +nbt.format.little_network=小端序网络 +nbt.format.big_gzip=Gzip压缩 +nbt.format.label=NBT格式: nbt.file_type.name=NBT nbt.file_type.description=NBT