All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.bixilon.mbf.MBFBinaryReader.kt Maven / Gradle / Ivy

/*
 * Minosoft
 * Copyright (C) 2021 Moritz Zwerger
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program. If not, see .
 *
 * This software is not affiliated with Mojang AB, the original developer of Minecraft.
 */

package de.bixilon.mbf

import com.github.luben.zstd.ZstdInputStream
import de.bixilon.mbf.exceptions.UnexpectedStreamEndException
import de.bixilon.mbf.exceptions.UnsupportedMBFVersionException
import java.io.BufferedInputStream
import java.io.InputStream
import java.nio.charset.StandardCharsets
import java.util.*
import java.util.zip.GZIPInputStream
import java.util.zip.InflaterInputStream

class MBFBinaryReader(
    private val input: InputStream,
) {
    /**
     * If the reader should use variable length prefixes
     */
    var variableLengthPrefix = false

    /**
     * Available bytes
     */
    val available: Int
        get() = input.available()

    /**
     * Reads one byte from the stream
     */
    fun readByte(): Byte {
        val read = input.read()
        if (read < 0) {
            throw UnexpectedStreamEndException("Input stream ended!")
        }
        return read.toByte()
    }

    /**
     * Reads an unsigned byte from the input stream
     */
    fun readUnsignedByte(): Int {
        return readByte().toInt() and ((1 shl Byte.SIZE_BITS) - 1)
    }

    /**
     * Reads a byte array from the input stream
     * @param length How many bytes to read, defaults to the length prefix
     */
    fun readByteArray(length: Int = readLength()): ByteArray {
        checkArrayLength(length)
        val array = ByteArray(length)
        input.read(array)
        return array
    }

    /**
     * Reads a byte from the input stream and converts it to a boolean
     */
    fun readBoolean(): Boolean {
        return readByte() != 0x00.toByte()
    }

    /**
     * Reads a byte boolean array from the input stream
     * @param length How many booleans to read, defaults to the length prefix
     */
    fun readBooleanArray(length: Int = readLength()): BooleanArray {
        val array = BooleanArray(length)
        for (index in 0 until length) {
            array[index] = readBoolean()
        }
        return array
    }

    /**
     * Reads a short
     */
    fun readShort(): Short {
        return (readUnsignedByte() shl Byte.SIZE_BITS or readUnsignedByte()).toShort()
    }

    /**
     * Reads an unsigned short
     */
    fun readUnsignedShort(): Int {
        return readShort().toInt() and ((1 shl Short.SIZE_BITS) - 1)
    }


    /**
     * Reads a short array
     * @param length How many shorts to read, defaults to the length prefix
     */
    fun readShortArray(length: Int = readLength()): ShortArray {
        checkArrayLength(length * Short.SIZE_BYTES)
        val array = ShortArray(length)
        for (index in 0 until length) {
            array[index] = readShort()
        }
        return array
    }

    /**
     * Reads an int
     */
    fun readInt(): Int {
        return readUnsignedShort() shl Short.SIZE_BITS or readUnsignedShort()
    }

    /**
     * Reads an unsigned int
     */
    fun readUnsignedInt(): Long {
        return readInt().toLong() and ((1L shl Int.SIZE_BITS) - 1)
    }


    /**
     * Reads an int array
     * @param length How many ints to read, defaults to the length prefix
     */
    fun readIntArray(length: Int = readLength()): IntArray {
        checkArrayLength(length * Int.SIZE_BYTES)
        val array = IntArray(length)
        for (index in 0 until length) {
            array[index] = readInt()
        }
        return array
    }

    /**
     * Reads a long
     */
    fun readLong(): Long {
        return (readUnsignedInt() shl Int.SIZE_BITS) or readUnsignedInt()
    }

    /**
     * Reads a 128bit unit (uuid in java)
     */
    fun readUUID(): UUID {
        return UUID(readLong(), readLong())
    }

    /**
     * Reads a long array
     * @param length How many longs to read, defaults to the length prefix
     */
    fun readLongArray(length: Int = readLength()): LongArray {
        checkArrayLength(length * Long.SIZE_BYTES)
        val array = LongArray(length)
        for (index in 0 until length) {
            array[index] = readLong()
        }
        return array
    }

    /**
     * Reads a float (32bit)
     */
    fun readFloat(): Float {
        return Float.fromBits(readInt())
    }

    /**
     * Reads a float float
     * @param length How many floats to read, defaults to the length prefix
     */
    fun readFloatArray(length: Int = readLength()): FloatArray {
        checkArrayLength(length * Float.SIZE_BYTES)
        val array = FloatArray(length)
        for (index in 0 until length) {
            array[index] = readFloat()
        }
        return array
    }

    /**
     * Reads a double (64bit)
     */
    fun readDouble(): Double {
        return Double.fromBits(readLong())
    }

    /**
     * Reads a double array
     * @param length How many doubles to read, defaults to the length prefix
     */
    fun readDoubleArray(length: Int = readLength()): DoubleArray {
        checkArrayLength(length * Double.SIZE_BYTES)
        val array = DoubleArray(length)
        for (index in 0 until length) {
            array[index] = readDouble()
        }
        return array
    }

    /**
     * Reads a VarInt
     */
    fun readVarInt(): Int {
        var byteCount = 0
        var result = 0
        var read: Int
        do {
            read = readUnsignedByte()
            result = result or (read and 0x7F shl (Byte.SIZE_BITS - 1) * byteCount)
            byteCount++
            require(byteCount <= Int.SIZE_BYTES + 1) { "VarInt is too big" }
        } while (read and 0x80 != 0)

        return result
    }


    /**
     * Reads a VarInt array
     * @param length How many VarInts to read, defaults to the length prefix
     */
    fun readVarIntArray(length: Int = readLength()): IntArray {
        checkArrayLength(length)
        val array = IntArray(length)
        for (index in 0 until length) {
            array[index] = readVarInt()
        }
        return array
    }

    /**
     * Reads a VarLong
     */
    fun readVarLong(): Long {
        var byteCount = 0
        var result = 0L
        var read: Long
        do {
            read = readByte().toLong() and ((1 shl Byte.SIZE_BITS) - 1).toLong()
            result = result or (read and 0x7F shl (Byte.SIZE_BITS - 1) * byteCount)
            byteCount++
            require(byteCount <= Int.SIZE_BYTES + 1) { "VarLong is too big" }
        } while (read and 0x80L != 0L)

        return result
    }

    /**
     * Reads a VarLong array
     * @param length How many VarLongs to read, defaults to the length prefix
     */
    fun readVarLongArray(length: Int = readLength()): LongArray {
        checkArrayLength(length)
        val array = LongArray(length)
        for (index in 0 until length) {
            array[index] = readVarLong()
        }
        return array
    }

    inline fun  readArray(length: Int = readLength(), reader: () -> T): Array {
        checkArrayLength(length)
        val array: Array = arrayOfNulls(length)
        for (i in 0 until length) {
            array[i] = reader()
        }
        return array as Array
    }

    fun readString(length: Int = readLength()): String {
        return String(readByteArray(length), StandardCharsets.UTF_8)
    }

    fun readLength(variable: Boolean = variableLengthPrefix): Int {
        if (variable) {
            return readVarInt()
        }
        return readInt()
    }

    fun readMBFDataType(): MBFDataTypes {
        return MBFDataTypes.VALUES.getOrElse(readByte().toInt()) { type -> error("Can not find data type $type") }
    }

    fun readMBFEntry(): Any? {
        return readMBFDataType().type.read(this)
    }

    /**
     * Reads a full mbf object including meta data
     */
    fun readMBF(): MBFData {
        check(readByte().toInt().toChar() == 'M' && readByte().toInt().toChar() == 'B' && readByte().toInt().toChar() == 'F') { "Data is prefixed with MBF!" }

        val version = readByte().toInt()
        if (version != 0) {
            throw UnsupportedMBFVersionException(version, "Unknown MBF version ($version). Only version 0 is supported!")
        }

        val flags = readByte().toInt()

        val dataInfo = MBFDataInfo(
            compression = MBFCompressionTypes.VALUES.getOrElse(flags and 0b11) { error("Can not find compression ($it)!") },
            encryption = flags and 0b100 > 0,
            variableLengthPrefix = flags and 0b1000 > 0,
            preferVariableTypes = flags and 0b10000 > 0,
            version = version,
        )

        var data = this
        data.variableLengthPrefix = dataInfo.variableLengthPrefix

        if (dataInfo.encryption) {
            TODO("Encryption is not implemented yet")
        }

        if (dataInfo.compression != MBFCompressionTypes.NONE) {
            val length = data.readLength()
            val compressed = LimitedInputStream(data.input, length)
            val stream = when (dataInfo.compression) {
                MBFCompressionTypes.ZSTD -> ZstdInputStream(compressed)
                MBFCompressionTypes.DEFLATE -> InflaterInputStream(compressed)
                MBFCompressionTypes.GZIP -> GZIPInputStream(compressed)
                else -> TODO("Compression type is not implemented yet!")
            }
            data = MBFBinaryReader(BufferedInputStream(stream))
            data.variableLengthPrefix = dataInfo.variableLengthPrefix
        }


        return MBFData(
            dataInfo = dataInfo,
            data = data.readMBFEntry(),
        )
    }


    companion object {
        fun checkArrayLength(length: Int) {
            check(length <= MBFUtil.ARRAY_MAX_BYTES) { "Trying to allocate too much memory!" }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy