BenWoodworth / knbt

Kotlin NBT library for kotlinx.serialization
GNU Lesser General Public License v3.0
79 stars 3 forks source link
kotlin kotlin-multiplatform kotlinx-serialization library minecraft named-binary-tag nbt okio serialization

knbt

Maven Central Sonatype Nexus (Snapshots) KDoc Kotlin kotlinx.serialization

An implementation of Minecraft's NBT format for kotlinx.serialization.

Technical information about NBT can be found here.

Features

Serialization

Nbt and StringifiedNbt are used to encode/decode @Serializable data.

Configuration

import net.benwoodworth.knbt.*

val nbt = Nbt {
    variant = NbtVariant. // Java, Bedrock, BedrockNetwork
    compression = NbtCompression. // None, Gzip, Zlib
    //compressionLevel = null // in 0..9
    //encodeDefaults = false
    //ignoreUnknownKeys = false
    //serializersModule = EmptySerializersModule
}

val snbt = StringifiedNbt {
    //prettyPrint = false
    //prettyPrintIndent = "    "
    //encodeDefaults = false
    //ignoreUnknownKeys = false
    //serializersModule = EmptySerializersModule
}

Encoding and Decoding

// ByteArray
byteArray = nbt.encodeToByteArray(data)
data = nbt.decodeFromByteArray(byteArray)

// NbtTag
nbtTag = nbt.encodeToNbtTag(data)
data = nbt.decodeFromNbtTag(nbtTag)

// Okio Sink/Source (Multiplatform)
nbt.encodeToSink(data, sink)
data = nbt.decodeFromSource(source)

// OutputStream/InputStream (JVM)
nbt.encodeToStream(data, outputStream)
data = nbt.decodeFromStream(inputStream)

// SNBT String
string = snbt.encodeToString(data)
data = snbt.decodeFromString(string)

Serializing Classes

Serializable classes will have their @SerialName used for the root tag's name.

@Serializable
@SerialName("root")
class Example(val string: String, val int: Int)

// Serializes to: {root : {string : "Hello, world!", int : 42}}
nbt.encodeToNbtTag(Example(string = "Hello, World!", int = 42))

Reading/Writing NBT Files (JVM)

import kotlin.io.path.*
import net.benwoodworth.knbt.*

val file = Path("file.nbt")

val nbt = Nbt {
    TODO()
}

// Read from file
val tag: NbtTag = file.inputStream().use { input ->
    nbt.decodeFromStream(input)
}

// Write to file
file.outputStream().use { output ->
    nbt.encodeToStream(tag, output)
}

NbtTag Classes

sealed interface NbtTag

class NbtByte : NbtTag
class NbtShort : NbtTag
class NbtInt : NbtTag
class NbtLong : NbtTag
class NbtFloat : NbtTag
class NbtDouble : NbtTag
class NbtString : NbtTag
class NbtByteArray : NbtTag, List<Byte>
class NbtIntArray : NbtTag, List<Int>
class NbtLongArray : NbtTag, List<Long>
class NbtList<T : NbtTag> : NbtTag, List<T> // Only contains entries of a single type
class NbtCompound : NbtTag, Map<String, NbtTag>

Creating NbtTags

NbtTags can be created with constructors and builder functions:

val nbtByte = NbtByte(5)
val boolean = NbtByte(true)

val nbtIntArray = NbtIntArray(intArrayOf(1, 2, 3, 4, 5))

val nbtListOfStrings = buildNbtList {
    add("these")
    add("are")
    add("strings")
}

val nbtCompound = buildNbtCompound {
    put("int", 1)
    put("string", ":)")
    put("byteArray", byteArrayOf(1, 1, 2, 3, 5, 8))

    putNbtList("floatList") {
        add(3f)
        add(1f)
        add(4f)
    }
}
Building bigtest.nbt with the DSL (wiki.vg/NBT#bigtest.nbt) ```kotlin val bigtest = buildNbtCompound("Level") { put("longTest", 9223372036854775807L) put("shortTest", 32767.toShort()) put("stringTest", "HELLO WORLD THIS IS A TEST STRING ÅÄÖ!") put("floatTest", 0.49823147f) put("intTest", 2147483647) putNbtCompound("nested compound test") { putNbtCompound("ham") { put("name", "Hampus") put("value", 0.75f) } putNbtCompound("egg") { put("name", "Eggbert") put("value", 0.5f) } } putNbtList("listTest (long)") { add(11L) add(12L) add(13L) add(14L) add(15L) } putNbtList("listTest (compound)") { addNbtCompound { put("name", "Compound tag #0") put("created-on", 1264099775885L) } addNbtCompound { put("name", "Compound tag #1") put("created-on", 1264099775885L) } } put("byteTest", 127.toByte()) put( "byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))", ByteArray(1000) { n -> ((n * n * 255 + n * 7) % 100).toByte() } ) put("doubleTest", 0.4931287132182315) } ```

Setup

Using the same version of kotlinx.serialization is recommended since parts of its API required for custom formats are still experimental, and newer versions may have binary-incompatible changes that could break knbt's implementation.

Upgrading knbt

While in beta, all new minor releases (v0.#.0) will have breaking API/functionality changes. Read the release notes for information.

Replacement refactorings will be provided where possible for broken APIs. Change the minor version one at a time (e.g. 0.1.0 -> 0.2.0 -> 0.3.0) and apply quick fixes. Deprecated APIs will then be removed in 0.#.1 releases.

Gradle

plugins {
    kotlin("jvm") version "1.9.23" // or kotlin("multiplatform"), etc.
    //kotlin("plugin.serialization") version "1.9.23"
}

repositories {
    mavenCentral()
    //maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}

dependencies {
    implementation("net.benwoodworth.knbt:knbt:$knbt_version")
    //implementation("com.squareup.okio:okio:3.9.0")
}