├── .github
└── workflows
│ └── gradle.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.gradle.kts
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
├── main
└── kotlin
│ └── br
│ └── com
│ └── gamemods
│ └── nbtmanipulator
│ ├── ComplexNbtStringParser.kt
│ ├── LittleEndianDataInputStream.kt
│ ├── LittleEndianDataOutputStream.kt
│ ├── NbtByte.kt
│ ├── NbtByteArray.kt
│ ├── NbtCompound.kt
│ ├── NbtCompoundStringParser.kt
│ ├── NbtDouble.kt
│ ├── NbtEnd.kt
│ ├── NbtFile.kt
│ ├── NbtFloat.kt
│ ├── NbtIO.kt
│ ├── NbtInt.kt
│ ├── NbtIntArray.kt
│ ├── NbtList.kt
│ ├── NbtListStringParser.kt
│ ├── NbtLong.kt
│ ├── NbtLongArray.kt
│ ├── NbtShort.kt
│ ├── NbtString.kt
│ ├── NbtTag.kt
│ ├── NbtUtil-add.kt
│ ├── NbtUtil-contains.kt
│ ├── NbtUtil-minusAssign.kt
│ ├── NbtUtil-plusAssign.kt
│ ├── NbtUtil-remove.kt
│ ├── NbtUtil-toNbtList.kt
│ └── NbtUtil.kt
├── pages
├── _config.yml
└── assets
│ └── css
│ └── style.scss
└── test
├── java
└── test
│ └── JavaUsage.java
├── kotlin
└── br
│ └── com
│ └── gamemods
│ └── nbtmanipulator
│ ├── CompoundStringParserTest.kt
│ ├── KotlinUsage.kt
│ ├── LevelDatTest.kt
│ ├── ListStringParserTest.kt
│ ├── SimpleStringParserTest.kt
│ ├── StringValueWithStringConstructorTest.kt
│ └── ToStringTest.kt
└── resources
├── level-bedrock-edition.dat
└── level-java-edition.dat
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | name: Java CI
2 |
3 | on:
4 | pull_request:
5 | branches-ignore: 'gh-pages'
6 | push:
7 | branches-ignore: 'gh-pages'
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v1
16 | - uses: actions/cache@v1
17 | with:
18 | path: ~/.gradle/caches
19 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
20 | restore-keys: |
21 | ${{ runner.os }}-gradle-
22 | - name: Set up JDK 1.8
23 | uses: actions/setup-java@v1
24 | with:
25 | java-version: 1.8
26 | - name: Build with Gradle
27 | run: ./gradlew build
28 | - name: Rename artifacts
29 | run: mv build/libs/nbt-manipulator-*.jar build/libs/nbt-manipulator.jar
30 | - name: Archive artifacts
31 | uses: actions/upload-artifact@v1
32 | if: success()
33 | with:
34 | name: NBT-Manipulator
35 | path: build/libs/nbt-manipulator.jar
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build
3 | *.iws
4 | *.ipr
5 | .idea
6 | nukkit-*.jar
7 | /out
8 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "gh-pages"]
2 | path = gh-pages
3 | url = https://github.com/GameModsBR/NBT-Manipulator.git
4 | branch = gh-pages
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 | Click the link above to see the future.
9 |
10 | ## [3.1.0] - 2021-10-28
11 | [Downloads from maven central.][Download 3.1.0]
12 |
13 | [Kotlin Documentation][KDoc 3.1.0]
14 |
15 | ### Added
16 | - Automatic Java Jigsaw Module named `br.com.gamemods.nbtmanipulator`
17 | - `NbtIO.writeNbtTagDirectly()` and `NbtIO.readNbtTagDirectly()` allowing reading and writing arbitrary tags.
18 |
19 | ### Changed
20 | - Updated to Kotlin `1.5.31`
21 | - The source code was split in multiple files and now uses the explicit API feature
22 |
23 | ## [3.0.0] - 2021-08-12
24 | [Downloads from maven central.][Download 3.0.0]
25 |
26 | [Kotlin Documentation][KDoc 3.0.0]
27 |
28 | ### Fixed
29 | - Made a micro-optimization to the `NbtCompound` constructor.
30 |
31 | ### Added
32 | - Empty constructor for the array tag types. It may break old method references like `::NbtByteArray` **(Breaking Change)**
33 | - A constructor which accepts `Iterable` to `NbtCompound`
34 | - Constructors which accepts `String` to all `NbtTag` types
35 | - `stringValue` property to `NbtTag`
36 | - `unsiged` property to `NbtByte`
37 | - A static function `NbtByte.unsigned(String)` to a create `NbtByte` from unsigned strings.
38 | - Little endian `DataInput` and `DataOutput` implementations
39 | - Support to read and write Bedrock Edition NBT files using `NbtIO`
40 |
41 | ### Changed
42 | - `NbtByte`'s `Int` constructor now throws `NumberFormatException` if the number is outside the `0..255` range. **(Breaking Change)**
43 | - Improved the `toString()` methods of all `NbtTag` types **(Breaking Change)**
44 | - `NbtList` now enforces the contents to have the same type on modifications **(Breaking Change)**
45 | - `NbtCompound` now implements `equals` and `hashCode` as described by the `Map` interface **(Breaking Change)**
46 | - `NbtList` now implements `equals` and `hashCode` as described by the `List` interface **(Breaking Change)**
47 | - Renamed `NbtByte.value` to `NbtByte.signed`. `NbtByte.value` is still usable but it is deprecated,
48 | is being replaced in byte-code and will be removed in a future version.
49 | - `NbtList` and the returned sub-lists now implements `RandomAccess`
50 |
51 | ## [2.0.0] - 2020-01-24
52 | [Downloads from maven central.][Download 2.0.0]
53 |
54 | [Kotlin Documentation][KDoc 2.0.0]
55 |
56 | ### Changed
57 | - Upgraded to Kotlin 1.3.61
58 | - `NbtIO.writeNbtFile` and `NbtIO.readNbtFile` now throws `IOException` in Java **(Breaking Change)**
59 |
60 | ## [1.1.0] - 2019-06-02
61 | [Downloads from maven central.][Download 1.1.0]
62 |
63 | [Kotlin Documentation][KDoc 1.1.0]
64 |
65 | ### Added
66 | - New constructor to `NbtList` which accepts an other `NbtList` to resolve an ambiguity call in this scenario.
67 | - `toNbtList()` extension function to many combinations of `Iterable` and `Array` which contains valid values for `NbtList`, including list of list.
68 | - `NbtList.create` and it's sublist flavours to simplify list creations for Java users
69 | - `add`, `remove`, `contains`, `minusAssign` and `plusAssign` extension functions to many possible `NbtList` for easy usage by Kotlin users
70 |
71 | ## [1.0.1] - 2019-05-27
72 | [Downloads from maven central.][Download 1.0.1]
73 |
74 | [Kotlin Documentation][KDoc 1.0.1]
75 |
76 | ### Fixes
77 | - StackOverflowException on `NbtCompound.set(String, NbtTag)`
78 |
79 | ## [1.0.0] - 2019-05-27
80 | [Downloads from maven central.][Download 1.0.0]
81 |
82 | [Kotlin Documentation][KDoc 1.0.0]
83 | ### Added
84 | - `deepClone` method to all tags
85 | - `require` method to `NbtCompound`
86 |
87 | ### Changed
88 | - `NbtList` is now a `MutableList` and not a `data class` anymore. This completely changes how API users interacts with them.
89 | - `NbtCompound` is now a `MutableMap` and not a `data class` anymore. This completely changes how API users interacts with them.
90 | - Renamed the parameter `name` to `key` in all methods of `NbtCompound`
91 | - Renamed the parameter `tagName` to `tagKey` in all copy methods of `NbtCompound`
92 | - All get methods from `NbtCompound` will now throw `NoSuchElementException` if the requested key does not exists in the compound
93 | - All methods which throws exceptions now have the exception which is thrown registered in the binary files.
94 | Useful for Java users and who couldn't get the sources or javadoc.
95 |
96 |
97 | ## [0.0.2] - 2019-05-27
98 | [Downloads from maven central.][Download 0.0.2]
99 | ### Added
100 | - Static methods for java users calling `NbtIO`
101 | - Documentation to all public types, functions and properties
102 | - The methods `NbtCompound.remove(String)`, `NbtCompound.remove(String, NbtTag)` and `NbtCompound.minusAssign(String)`
103 |
104 | ### Changed
105 | - JavaDoc will not generate when building on Java 9+ due to a dokka issue
106 | - The targetCompatibility to Java 8
107 |
108 | ## [0.0.1] - 2019-05-23
109 | [Downloads from maven central.][Download 0.0.1]
110 | ### Added
111 | - API to read and write to/from NBT files/streams using `NbtIO`
112 | - API to freely manipulate NBT data loaded in memory
113 |
114 | [Unreleased]: https://github.com/GameModsBR/NBT-Manipulator/compare/v3.1.0...HEAD
115 | [3.1.0]: https://github.com/GameModsBR/NBT-Manipulator/compare/v3.0.0...v3.1.0
116 | [3.0.0]: https://github.com/GameModsBR/NBT-Manipulator/compare/v2.0.0...v3.0.0
117 | [2.0.0]: https://github.com/GameModsBR/NBT-Manipulator/compare/v2.0.0...v3.0.0
118 | [2.0.0]: https://github.com/GameModsBR/NBT-Manipulator/compare/v1.1.1...v2.0.0
119 | [1.1.0]: https://github.com/GameModsBR/NBT-Manipulator/compare/v1.0.1...v1.1.0
120 | [1.0.1]: https://github.com/GameModsBR/NBT-Manipulator/compare/v1.0.0...v1.0.1
121 | [1.0.0]: https://github.com/GameModsBR/NBT-Manipulator/compare/v0.0.2...v1.0.0
122 | [0.0.2]: https://github.com/GameModsBR/NBT-Manipulator/compare/v0.0.1...v0.0.2
123 | [0.0.1]: https://github.com/GameModsBR/NBT-Manipulator/compare/v0.0.0...v0.0.1
124 |
125 | [Download 3.1.0]: https://search.maven.org/artifact/br.com.gamemods/nbt-manipulator/3.1.0/jar
126 | [Download 3.0.0]: https://search.maven.org/artifact/br.com.gamemods/nbt-manipulator/3.0.0/jar
127 | [Download 2.0.0]: https://search.maven.org/artifact/br.com.gamemods/nbt-manipulator/2.0.0/jar
128 | [Download 1.1.0]: https://search.maven.org/artifact/br.com.gamemods/nbt-manipulator/1.1.0/jar
129 | [Download 1.0.1]: https://search.maven.org/artifact/br.com.gamemods/nbt-manipulator/1.0.1/jar
130 | [Download 1.0.0]: https://search.maven.org/artifact/br.com.gamemods/nbt-manipulator/1.0.0/jar
131 | [Download 0.0.2]: https://search.maven.org/artifact/br.com.gamemods/nbt-manipulator/0.0.2/jar
132 | [Download 0.0.1]: https://search.maven.org/artifact/br.com.gamemods/nbt-manipulator/0.0.1/jar
133 |
134 | [KDoc 3.1.0]: https://github.com/GameModsBR/NBT-Manipulator/blob/5ec17b901033d589bce5b613420aa2682df50c9a/kdoc/br.com.gamemods.nbtmanipulator/index.md
135 | [KDoc 3.0.0]: https://github.com/GameModsBR/NBT-Manipulator/blob/e4b3e63039419ce9d927dbf2d283f5b56ab762c7/kdoc/br.com.gamemods.nbtmanipulator/index.md
136 | [KDoc 2.0.0]: https://github.com/GameModsBR/NBT-Manipulator/blob/144c1aec6b9fbb2ce7996e200a9637f9b868c8d9/kdoc/br.com.gamemods.nbtmanipulator/index.md
137 | [KDoc 1.1.0]: https://github.com/GameModsBR/NBT-Manipulator/blob/f188707e1d9a5616db1ccd45e892171349ee5a62/kdoc/br.com.gamemods.nbtmanipulator/index.md
138 | [KDoc 1.0.1]: https://github.com/GameModsBR/NBT-Manipulator/blob/51f0f36511b8d4979d5d3e322f2fb766095a174c/kdoc/br.com.gamemods.nbtmanipulator/index.md
139 | [KDoc 1.0.0]: https://github.com/GameModsBR/NBT-Manipulator/blob/0ef42323681f9960cb2c9698d7b8b1d02632691b/kdoc/br.com.gamemods.nbtmanipulator/index.md
140 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2019 José Roberto de Araújo Júnior
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NBT Manipulator
2 |
3 | This is a Kotlin/Java library that allows you to read and write NBT files and data in a clean and simple way.
4 |
5 | Can be used both for Minecraft Java and Minecraft Bedrock Edition NBT structures.
6 |
7 | Here you can find the library documentation:
8 | * [Java Documentation](https://powernukkit.github.io/NBT-Manipulator/javadoc/br/com/gamemods/nbtmanipulator/package-summary.html)
9 | * [Kotlin Documentation](https://powernukkit.github.io/NBT-Manipulator/kdoc/br.com.gamemods.nbtmanipulator/index.html)
10 |
11 | You may also want to see the [changelog](CHANGELOG.md) file to be aware of all changes in the tool that may impact you.
12 |
13 | **TIP:** If you are using `NbtList` with Kotlin, add a `import br.com.gamemods.nbtmanipulator.*` for easy list management.
14 |
15 | For example:
16 | ```kotlin
17 | val list = listOf(2,3,4,5,6).toNbtList()
18 | list += 7
19 | check(7 in list)
20 | list -= 7
21 | check(7 !in list)
22 | ```
23 |
24 | ## Adding to your project
25 |
26 | The library is shared in the maven center, so you don't need to declare any custom repository.
27 |
28 | ### Gradle
29 |
30 | #### Kotlin DSL
31 | ```kotlin
32 | repositories {
33 | mavenCentral()
34 | }
35 |
36 | dependencies {
37 | implementation("br.com.gamemods:nbt-manipulator:3.1.0")
38 | }
39 | ```
40 |
41 | #### Groovy DSL
42 | ```groovy
43 | repositories {
44 | mavenCentral()
45 | }
46 |
47 | dependencies {
48 | implementation 'br.com.gamemods:nbt-manipulator:3.1.0'
49 | }
50 | ```
51 |
52 | ### Maven
53 |
54 | ```xml
55 |
56 |
57 | br.com.gamemods
58 | nbt-manipulator
59 | 3.1.0
60 |
61 |
62 | ```
63 |
64 | ### Ivy
65 |
66 | ```xml
67 |
68 | ```
69 |
70 | ### Direct JAR
71 | Download it from [maven central](https://search.maven.org/artifact/br.com.gamemods/nbt-manipulator).
72 |
73 | ## Examples
74 |
75 | ```kotlin
76 | val compound = NbtCompound()
77 | compound["A string tag"] = "The string tag value"
78 | compound["An int tag"] = 2
79 | val byteValue: Byte = 3
80 | compound["An byte tag"] = byteValue
81 |
82 | println(compound.getString("A string tag"))
83 | println(compound.getInt("A string tag"))
84 | println(compound.getInt("An int tag"))
85 | println(compound.getNullableByte("An byte tag"))
86 |
87 | println(compound.getNullableStringList("This is missing.."))
88 |
89 | val otherConstructor = NbtCompound(
90 | "a" to NbtString("string"),
91 | "b" to NbtList(listOf(NbtDouble(0.0), NbtDouble(1.1)))
92 | )
93 | println(otherConstructor["a"])
94 | println(otherConstructor.getDoubleList("b"))
95 | ```
96 |
97 | ```kotlin
98 | internal fun convertLevelFile(from: File, to: File) {
99 | val input = NbtIO.readNbtFile(from)
100 | val inputData = input.compound.getCompound("Data")
101 |
102 | val outputData = NbtCompound()
103 | outputData.copyFrom(inputData, "GameRules")
104 | outputData.copyFrom(inputData, "DayTime")
105 | outputData.copyFrom(inputData, "GameType")
106 | outputData.copyFrom(inputData, "generatorName")
107 | outputData.copyFrom(inputData, "generatorVersion")
108 | outputData.copyFrom(inputData, "generatorVersion")
109 | outputData.copyFrom(inputData, "generatorOptions", NbtString(""))
110 | outputData["hardcore"] = false
111 | outputData["initialized"] = false
112 | outputData.copyFrom(inputData, "LastPlayed")
113 | outputData.copyFrom(inputData, "LevelName")
114 | outputData.copyFrom(inputData, "raining")
115 | outputData.copyFrom(inputData, "rainTime")
116 | outputData.copyFrom(inputData, "RandomSeed")
117 | outputData.copyFrom(inputData, "SizeOnDisk")
118 | outputData.copyFrom(inputData, "SpawnX")
119 | outputData.copyFrom(inputData, "SpawnY")
120 | outputData.copyFrom(inputData, "SpawnZ")
121 | outputData.copyFrom(inputData, "thundering")
122 | outputData.copyFrom(inputData, "thunderTime")
123 | outputData.copyFrom(inputData, "Time")
124 | outputData.copyFrom(inputData, "version")
125 |
126 | val output = NbtCompound("Data" to outputData)
127 | val file = NbtFile("", output)
128 | NbtIO.writeNbtFile(to, file)
129 | }
130 | ```
131 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.nio.file.Files
2 | import java.nio.file.StandardCopyOption
3 |
4 | plugins {
5 | java
6 | id("org.jetbrains.kotlin.jvm") version "1.5.31"
7 | maven
8 | signing
9 | id("org.jetbrains.dokka") version "1.5.30"
10 | id("org.ajoberstar.git-publish") version "3.0.0"
11 | }
12 | val kotlinVersion = "1.5.31"
13 | val ossrhUsername: String by project
14 | val ossrhPassword: String by project
15 |
16 | group = "br.com.gamemods"
17 | version = "3.1.0"
18 | base {
19 | archivesBaseName = name
20 | }
21 |
22 | sourceSets.main {
23 | java {
24 | srcDirs("src/main/kotlin")
25 | }
26 | }
27 |
28 | java {
29 | sourceCompatibility = JavaVersion.VERSION_1_8
30 | targetCompatibility = sourceCompatibility
31 | }
32 |
33 | kotlin {
34 | explicitApi()
35 | }
36 |
37 | repositories {
38 | mavenCentral()
39 | }
40 |
41 | dependencies {
42 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
43 | implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
44 | testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
45 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion")
46 | }
47 |
48 | tasks {
49 | compileKotlin {
50 | kotlinOptions.jvmTarget = "1.8"
51 | }
52 | compileTestKotlin {
53 | kotlinOptions.jvmTarget = "1.8"
54 | }
55 |
56 | jar {
57 | manifest {
58 | attributes["Automatic-Module-Name"] = "br.com.gamemods.nbtmanipulator"
59 | }
60 | }
61 |
62 | "uploadArchives"(Upload::class) {
63 | repositories {
64 | withConvention(MavenRepositoryHandlerConvention::class) {
65 | mavenDeployer {
66 | if (findProperty("signing.secretKeyRingFile") != "undefined") {
67 | beforeDeployment {
68 | @Suppress("DEPRECATION")
69 | signing.signPom(this)
70 | }
71 | }
72 | withGroovyBuilder {
73 | "repository"("url" to uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")) {
74 | "authentication"("userName" to ossrhUsername, "password" to ossrhPassword)
75 | }
76 | "snapshotRepository"("url" to uri("https://oss.sonatype.org/content/repositories/snapshots/")) {
77 | "authentication"("userName" to ossrhUsername, "password" to ossrhPassword)
78 | }
79 | }
80 |
81 | pom.project {
82 | withGroovyBuilder {
83 | "packaging"("jar")
84 | "groupId"(project.group)
85 | "artifactId"(project.name)
86 | "version"(project.version)
87 | "name"(project.name)
88 | "description"("A kotlin/java lib that allows you to read and write NBT data in a clean way")
89 | "url"("https://github.com/PowerNukkit/NBT-Manipulator")
90 | "inceptionYear"("2020")
91 | "licenses" {
92 | "license" {
93 | "name"("MIT")
94 | "url"("https://raw.githubusercontent.com/PowerNukkit/NBT-Manipulator/master/LICENSE")
95 | "distribution"("repo")
96 | }
97 | }
98 | "developers" {
99 | "developer" {
100 | "id"("joserobjr")
101 | "name"("José Roberto de Araújo Júnior")
102 | "email"("joserobjr@powernukkit.org")
103 | }
104 | }
105 | "scm" {
106 | "connection"("https://github.com/PowerNukkit/NBT-Manipulator.git")
107 | "developerConnection"("https://github.com/PowerNukkit/NBT-Manipulator.git")
108 | "url"("https://github.com/PowerNukkit/NBT-Manipulator")
109 | }
110 | }
111 | }
112 | }
113 | }
114 | }
115 | }
116 | }
117 |
118 | tasks.dokkaJavadoc.configure {
119 | outputDirectory.set(File(buildDir, "javadoc"))
120 | }
121 |
122 | tasks.dokkaGfm.configure {
123 | outputDirectory.set(File(buildDir, "kdoc"))
124 | }
125 |
126 | val createReadmeFiles: Task by tasks.creating {
127 | dependsOn(tasks.dokkaGfm)
128 | doFirst {
129 | Files.copy(projectDir.toPath().resolve("README.md"), tasks.dokkaGfm.get().outputDirectory.get().toPath().resolve("nbt-manipulator/index.md"), StandardCopyOption.REPLACE_EXISTING)
130 | }
131 | }
132 |
133 | val createIndexMd = tasks.create("createIndexMd") {
134 | from(File(projectDir, "README.md"))
135 | into(File(buildDir, "pages"))
136 | rename("README.md", "index.md")
137 | }
138 |
139 | val javadocJar = tasks.create("javadocJar") {
140 | dependsOn(tasks.dokkaJavadoc)
141 | archiveClassifier.set("javadoc")
142 | from(tasks.dokkaJavadoc.get().outputDirectory)
143 | from(File(projectDir, "LICENSE"))
144 | from(File(projectDir, "README.md"))
145 | from(File(projectDir, "CHANGELOG.md"))
146 | }
147 |
148 | val sourcesJar = tasks.create("sourcesJar") {
149 | from(sourceSets.main.get().java.srcDirs)
150 | from(File(projectDir, "build.gradle"))
151 | from(File(projectDir, "gradle.properties"))
152 | from(File(projectDir, "settings.gradle.kts"))
153 | from(File(projectDir, "LICENSE"))
154 | from(File(projectDir, "README.md"))
155 | from(File(projectDir, "CHANGELOG.md"))
156 | archiveClassifier.set("sources")
157 | }
158 |
159 | tasks.jar {
160 | from(File(projectDir, "LICENSE"))
161 | from(File(projectDir, "README.md"))
162 | from(File(projectDir, "CHANGELOG.md"))
163 | }
164 |
165 | artifacts {
166 | archives(sourcesJar)
167 | archives(javadocJar)
168 | }
169 |
170 | if (findProperty("signing.secretKeyRingFile") != "undefined") {
171 | signing {
172 | sign(configurations.archives.get())
173 | }
174 | }
175 |
176 | if (ext.has("org.ajoberstar.grgit.auth.username")) {
177 | System.setProperty("org.ajoberstar.grgit.auth.username", ext["org.ajoberstar.grgit.auth.username"].toString())
178 | System.setProperty("org.ajoberstar.grgit.auth.password", ext["org.ajoberstar.grgit.auth.password"].toString())
179 | }
180 |
181 | gitPublish {
182 | // where to publish to (repo must exist)
183 | repoUri.set("https://github.com/PowerNukkit/NBT-Manipulator.git")
184 |
185 | // where to fetch from prior to fetching from the remote (i.e. a local repo to save time)
186 | //referenceRepoUri = file("$projectDir/gh-pages").toURI().toString()
187 |
188 | // branch will be created if it doesn't exist
189 | branch.set("gh-pages")
190 |
191 | // generally, you don't need to touch this
192 | repoDir.set(File(buildDir, "gh-pages-repo")) // defaults to $buildDir/gitPublish
193 |
194 | // what to publish, this is a standard CopySpec
195 | contents {
196 | from(File(buildDir, "javadoc")) {
197 | into("javadoc")
198 | }
199 | from(File(buildDir, "kdoc").resolve("nbt-manipulator")) {
200 | into("kdoc")
201 | }
202 | from(File(buildDir, "pages"))
203 | from(File("src", "pages"))
204 | from("README.md")
205 | from("CHANGELOG.md")
206 | }
207 |
208 | // what to keep in the existing branch (include=keep)
209 | preserve {
210 | include("1.0.0/**")
211 | exclude("1.0.0/temp.txt")
212 | }
213 |
214 | sign.set(false)
215 |
216 | // message used when committing changes
217 | commitMessage.set("Github Pages update") // defaults to 'Generated by gradle-git-publish'
218 | }
219 |
220 | tasks {
221 | gitPublishCopy {
222 | dependsOn(dokkaJavadoc)
223 | dependsOn(createReadmeFiles)
224 | dependsOn(createIndexMd)
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | org.gradle.jvmargs=-Xmx2048m
3 |
4 | signing.keyId=undefined
5 | signing.password=undefined
6 | signing.secretKeyRingFile=undefined
7 |
8 | ossrhUsername=undefined
9 | ossrhPassword=undefined
10 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PowerNukkit/NBT-Manipulator/b63a1bd4a406310097e314ef911650026dd812cb/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MSYS* | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "nbt-manipulator"
2 |
3 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/ComplexNbtStringParser.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | internal abstract class ComplexNbtStringParser(fatherOpenChar: Char, fatherCloseChar: Char, input: String) {
4 | protected val input = input.let {
5 | require(it.isNotEmpty() && it.first() == fatherOpenChar && it.last() == fatherCloseChar) {
6 | "Invalid input. Missing open and close chars."
7 | }
8 | it.removeSurrounding(fatherOpenChar.toString(), fatherCloseChar.toString())
9 | }
10 |
11 | protected var startIndex = 0
12 | protected lateinit var type: String
13 | protected lateinit var constructor: (String) -> NbtTag
14 | protected var openChar = '\u0000'
15 | protected var closeChar = '\u0000'
16 | protected var endIndex = -2
17 |
18 | private var openBlocks = 0
19 | private var openString = false
20 | private val scanChars = charArrayOf('"', openChar, closeChar)
21 |
22 | protected fun parseBeginning() {
23 | val startingAt = startIndex
24 | startIndex = input.indexOfAny(charArrayOf('(', '[', '{'), startingAt).also {
25 | require(it > 0) { "Invalid input. Unable to determine the beginning of the next tag" }
26 | }
27 |
28 | type = input.substring(startingAt, startIndex)
29 | constructor =
30 | requireNotNull(constructors[type]) {
31 | "Invalid input. Unexpected children tag type: $type"
32 | }
33 |
34 | openChar =
35 | if (type == "NbtList" || type.endsWith("Array")) '['
36 | else if (type == "NbtCompound") '{'
37 | else '('
38 |
39 | closeChar =
40 | if (type == "NbtList" || type.endsWith("Array")) ']'
41 | else if (type == "NbtCompound") '}'
42 | else ')'
43 |
44 | scanChars[1] = openChar
45 | scanChars[2] = closeChar
46 | }
47 |
48 | protected fun parseTag(): NbtTag {
49 | parseBeginning()
50 |
51 | return when(type) {
52 | "NbtByteArray", "NbtIntArray", "NbtLongArray" -> parseTag(includeSurrounding = true)
53 | "NbtString" -> parseStringTag()
54 | "NbtList", "NbtCompound" -> parseTag(includeSurrounding = true, father = true)
55 | else -> parseTag(includeSurrounding = false)
56 | }
57 | }
58 |
59 | protected tailrec fun findClosingQuote(closingQuote: String, startIndex: Int = this.startIndex): String {
60 | endIndex = input.indexOf(closingQuote, startIndex)
61 | require(endIndex >= startIndex) { "Invalid input. Wrong string format." }
62 | if (input[endIndex - 1] == '\\') {
63 | val newStart = endIndex + closingQuote.length
64 | require(newStart < input.length) { "Invalid input. A string is not closed." }
65 | return findClosingQuote(closingQuote, newStart)
66 | }
67 | return input.substring(this.startIndex, endIndex)
68 | .replace("\\\"", "\"")
69 | .replace("\\\\", "\\")
70 | }
71 |
72 | protected fun parseStringTag(): NbtString {
73 | startIndex += 2
74 | return NbtString(findClosingQuote("\")"))
75 | }
76 |
77 | protected fun parseTag(includeSurrounding: Boolean, father: Boolean = false): NbtTag {
78 | if (includeSurrounding) {
79 | if (father) {
80 | openBlocks++
81 | endIndex = startIndex
82 | require(endIndex + 1 < input.length) { "Invalid input. Unexpected ending." }
83 | findTheCloseBracket()
84 | } else {
85 | endIndex = input.indexOf(closeChar, startIndex + 1)
86 | }
87 | require(endIndex >= startIndex + 1) { "Invalid input. Unexpected ending." }
88 | endIndex++
89 | } else {
90 | startIndex++
91 | endIndex = input.indexOf(closeChar, startIndex)
92 | require(endIndex > startIndex) { "Invalid input. Unexpected ending." }
93 | }
94 | try {
95 | return constructor(input.substring(startIndex, endIndex))
96 | } catch (e: Exception) {
97 | throw IllegalArgumentException("Invalid input. Failed to create an instance of a child tag.", e)
98 | }
99 | }
100 |
101 | private tailrec fun findTheCloseBracket() {
102 | val index = input.indexOfAny(scanChars, endIndex + 1)
103 | require(index > endIndex) { "Invalid input. Could not find the close bracket." }
104 | endIndex = index
105 | when (input[index]) {
106 | openChar -> {
107 | if (!openString) {
108 | openBlocks++
109 | }
110 | }
111 | closeChar -> {
112 | if (!openString && --openBlocks == 0) {
113 | return
114 | }
115 | }
116 | '"' -> {
117 | when(input[index - 1]) {
118 | '(' -> {
119 | require(!openString) { "Invalid input. Unbalanced string tag." }
120 | openString = true
121 | }
122 | '\\' -> {
123 | require(openString) { "Invalid input. Unexpected escaped quotes." }
124 | }
125 | else -> {
126 | require(index + 1 < input.length) { "Invalid input. Premature input ending." }
127 | when (input[index + 1]) {
128 | ')', '=' -> {
129 | require(openString) { "Invalid input. Unbalanced string tag." }
130 | openString = false
131 | endIndex++
132 | }
133 | else -> {
134 | require(!openString) { "Invalid input. Unbalanced string tag." }
135 | openString = true
136 | }
137 | }
138 | }
139 | }
140 | }
141 | }
142 | findTheCloseBracket()
143 | }
144 |
145 | private companion object {
146 | private val constructors: Map NbtTag> = mapOf(
147 | "NbtByte" to ::NbtByte,
148 | "NbtShort" to ::NbtShort,
149 | "NbtInt" to ::NbtInt,
150 | "NbtLong" to ::NbtLong,
151 | "NbtFloat" to ::NbtFloat,
152 | "NbtDouble" to ::NbtDouble,
153 | "NbtByteArray" to ::NbtByteArray,
154 | "NbtString" to ::NbtString,
155 | "NbtList" to { value: String -> NbtList(value) },
156 | "NbtCompound" to ::NbtCompound,
157 | "NbtIntArray" to ::NbtIntArray,
158 | "NbtLongArray" to ::NbtLongArray,
159 | )
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/LittleEndianDataInputStream.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | import java.io.*
4 | import java.lang.Double.longBitsToDouble
5 | import java.lang.Float.intBitsToFloat
6 | import java.lang.UnsupportedOperationException
7 |
8 | /**
9 | * Implementation of [DataInput] that reads data from an [InputStream] using little endian byte order.
10 | * @param in the underlying input stream.
11 | * @author joserobjr
12 | * @since 2020-10-20
13 | */
14 | public class LittleEndianDataInputStream(`in`: InputStream) : FilterInputStream(`in`), DataInput {
15 | override fun readFully(b: ByteArray) {
16 | readFully(b, 0, b.size)
17 | }
18 |
19 | override fun readFully(b: ByteArray, off: Int, len: Int) {
20 | if (len < 0) {
21 | throw IndexOutOfBoundsException()
22 | }
23 | var n = 0
24 | while (n < len) {
25 | val count = `in`.read(b, off + n, len - n)
26 | if (count < 0) {
27 | throw EOFException()
28 | }
29 | n += count
30 | }
31 | }
32 |
33 | override fun skipBytes(n: Int): Int {
34 | if (n <= 0) {
35 | return 0
36 | }
37 |
38 | val toSkip = n.toLong()
39 |
40 | var total = 0L
41 | var cur: Long
42 |
43 | do {
44 | cur = `in`.skip(toSkip - total)
45 | if (cur <= 0) {
46 | break
47 | }
48 | total += cur
49 | } while (total < toSkip)
50 |
51 | return total.toInt()
52 | }
53 |
54 | override fun readBoolean(): Boolean {
55 | return readUnsignedByte() != 0
56 | }
57 |
58 | override fun readByte(): Byte {
59 | return readUnsignedByte().toByte()
60 | }
61 |
62 | override fun readUnsignedByte(): Int {
63 | val b1 = `in`.read()
64 | if (b1 < 0) {
65 | throw EOFException()
66 | }
67 | return b1
68 | }
69 |
70 | override fun readShort(): Short {
71 | return readUnsignedShort().toShort()
72 | }
73 |
74 | override fun readUnsignedShort(): Int {
75 | val b1 = `in`.read()
76 | val b2 = `in`.read()
77 | if (b1 or b2 < 0) {
78 | throw EOFException()
79 | }
80 | return (b2 shl 8) or b1
81 | }
82 |
83 | override fun readChar(): Char {
84 | return readUnsignedShort().toChar()
85 | }
86 |
87 | override fun readInt(): Int {
88 | val b1 = `in`.read()
89 | val b2 = `in`.read()
90 | val b3 = `in`.read()
91 | val b4 = `in`.read()
92 | if (b1 or b2 or b3 or b4 < 0) {
93 | throw EOFException()
94 | }
95 | return (b4 shl 24) or (b3 shl 16) or (b2 shl 8) or b1
96 | }
97 |
98 | override fun readLong(): Long {
99 | val b1 = `in`.read().toLong()
100 | val b2 = `in`.read().toLong()
101 | val b3 = `in`.read().toLong()
102 | val b4 = `in`.read().toLong()
103 | val b5 = `in`.read().toLong()
104 | val b6 = `in`.read().toLong()
105 | val b7 = `in`.read().toLong()
106 | val b8 = `in`.read().toLong()
107 | if (b1 or b2 or b3 or b4 or b5 or b6 or b7 or b8 < 0) {
108 | throw EOFException()
109 | }
110 | return (b8 shl 56) or (b7 shl 48) or (b6 shl 40) or (b5 shl 32) or
111 | (b4 shl 24) or (b3 shl 16) or (b2 shl 8) or b1
112 | }
113 |
114 | override fun readFloat(): Float {
115 | return intBitsToFloat(readInt())
116 | }
117 |
118 | override fun readDouble(): Double {
119 | return longBitsToDouble(readLong())
120 | }
121 |
122 | override fun readLine(): String {
123 | throw UnsupportedOperationException()
124 | }
125 |
126 | override fun readUTF(): String {
127 | return DataInputStream.readUTF(this)
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/LittleEndianDataOutputStream.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | import java.io.*
4 | import java.lang.Double.doubleToLongBits
5 | import java.lang.Float.floatToIntBits
6 | import java.lang.Long.reverseBytes
7 |
8 | /**
9 | * Implementation of [DataOutput] that writes data to an [OutputStream] using little endian byte order.
10 | * @param out the underlying output stream.
11 | * @author joserobjr
12 | * @since 2020-10-20
13 | */
14 | public class LittleEndianDataOutputStream(out: OutputStream) : FilterOutputStream(DataOutputStream(out)), DataOutput {
15 | private val data = out as DataOutputStream
16 | override fun writeBoolean(v: Boolean) {
17 | data.writeBoolean(v)
18 | }
19 |
20 | override fun writeByte(v: Int) {
21 | data.writeByte(v)
22 | }
23 |
24 | override fun writeShort(v: Int) {
25 | out.write(v and 0xFF)
26 | out.write((v ushr 8) and 0xFF)
27 | }
28 |
29 | override fun writeChar(v: Int) {
30 | writeShort(v)
31 | }
32 |
33 | override fun writeInt(v: Int) {
34 | out.write(v and 0xFF)
35 | out.write((v ushr 8) and 0xFF)
36 | out.write((v ushr 16) and 0xFF)
37 | out.write((v ushr 24) and 0xFF)
38 | }
39 |
40 | override fun writeLong(v: Long) {
41 | data.writeLong(reverseBytes(v))
42 | }
43 |
44 | override fun writeFloat(v: Float) {
45 | writeInt(floatToIntBits(v))
46 | }
47 |
48 | override fun writeDouble(v: Double) {
49 | writeLong(doubleToLongBits(v))
50 | }
51 |
52 | override fun writeBytes(s: String) {
53 | data.writeBytes(s)
54 | }
55 |
56 | override fun writeChars(s: String) {
57 | s.forEach { char ->
58 | writeChar(char.code)
59 | }
60 | }
61 |
62 | override fun writeUTF(s: String) {
63 | val length = s.length
64 | if (length > 65535) {
65 | throw UTFDataFormatException("encoded string too long: $length bytes")
66 | }
67 | writeShort(length)
68 | write(s.toByteArray(Charsets.UTF_8))
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtByte.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | /**
4 | * A tag which wraps a byte value.
5 | * @property signed A signed byte from `-128` to `127`
6 | */
7 | public data class NbtByte(var signed: Byte) : NbtTag() {
8 | /**
9 | * Read or write the [signed] as a signed byte from `0` to `255`.
10 | */
11 | var unsigned: Int
12 | get() = signed.toInt() and 0xFF
13 | set(value) {
14 | this.signed = (value and 0xFF).toByte()
15 | }
16 |
17 | /**
18 | * A signed byte from `-128` to `127`.
19 | */
20 | @Deprecated(
21 | "Deprecated in favor of signed and unsigned flavours. Replace with the signed property.",
22 | ReplaceWith("signed")
23 | )
24 | inline var value: Byte
25 | get() = signed
26 | set(value) {
27 | signed = value
28 | }
29 |
30 | /**
31 | * Returns a string representation of the tag's signed value.
32 | *
33 | * The returned string is compatible with string constructors of the same type.
34 | */
35 | override val stringValue: String
36 | get() = signed.toString()
37 |
38 | /**
39 | * Wraps a byte `1` if the value is `true` and `0` otherwise.
40 | * @param value The value to be checked
41 | */
42 | public constructor(value: Boolean): this(if (value) BYTE_TRUE else 0)
43 |
44 | /**
45 | * Converts the int value to an unsigned byte and wraps it.
46 | * @param unsigned Unsigned value from `0` to `255`.
47 | * @throws NumberFormatException if the number is not within the 0..255 range
48 | */
49 | @Throws(NumberFormatException::class)
50 | public constructor(unsigned: Int): this(unsigned.let {
51 | if(it < 0 || it > 255) {
52 | throw NumberFormatException("Expected an unsigned byte of range 0 to 255. Got $it.")
53 | }
54 | it.toByte()
55 | })
56 |
57 | /**
58 | * Parses the string value as a signed byte and wraps it.
59 | * @param signed Signed value from `-128` to `127`.
60 | * @throws NumberFormatException if the number is not within a valid range or if the string does not contain a valid number.
61 | */
62 | @Throws(NumberFormatException::class)
63 | public constructor(signed: String): this(signed.toByte())
64 |
65 | /**
66 | * Returns a new wrapper with the current value.
67 | */
68 | override fun deepCopy(): NbtByte = copy()
69 |
70 | /**
71 | * A technical string representation of this tag, containing the tag type, and it's [signed] value,
72 | * appropriated for developer inspections.
73 | */
74 | override fun toTechnicalString(): String {
75 | return "NbtByte($signed)"
76 | }
77 |
78 | public companion object {
79 | /**
80 | * Parses the string value as an unsigned byte and wraps it.
81 | * @param unsigned Unsigned value from `0` to `255`.
82 | * @throws NumberFormatException if the number is not within a valid range or if the string does not contain a valid number.
83 | */
84 | @JvmStatic
85 | public fun unsigned(unsigned: String): NbtByte = NbtByte(unsigned = unsigned.toInt())
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtByteArray.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | /**
4 | * A tag which wraps a mutable byte array.
5 | * @property value The wrapped value
6 | */
7 | public data class NbtByteArray(var value: ByteArray): NbtTag() {
8 | /**
9 | * Returns a string representation of the tag's value with a structure similar to a normal [List].
10 | *
11 | * The returned string is compatible with string constructors of the same type.
12 | *
13 | * Like [NbtByte], the bytes returned are signed, ranging from `-128` to `127`.
14 | *
15 | * Be aware that this may be a slow operation on big arrays.
16 | */
17 | override val stringValue: String
18 | get() = value.takeIf { it.isNotEmpty() }?.joinToString(prefix = "[", postfix = "]") ?: "[]"
19 |
20 | /**
21 | * Creates a new tag with an empty array.
22 | */
23 | public constructor(): this(byteArrayOf())
24 |
25 | /**
26 | * Parses the string using the same structure which is returned by [stringValue].
27 | *
28 | * The bytes should be signed, ranging from `-127` to `127`.
29 | *
30 | * @param value A string with a structure like `[0, -32, 48, 127]`
31 | *
32 | * @throws IllegalArgumentException if the string does not have the exact format outputted by [stringValue]
33 | */
34 | @Throws(IllegalArgumentException::class)
35 | public constructor(value: String): this(value
36 | .removeSurrounding("[", "]")
37 | .split(", ")
38 | .takeIf { it.size > 1 || it.firstOrNull()?.isNotEmpty() == true }
39 | ?.map { it.toByte() }
40 | ?.toByteArray()
41 | ?: byteArrayOf()
42 | )
43 |
44 | override fun toTechnicalString(): String {
45 | return "NbtByteArray$stringValue"
46 | }
47 |
48 | /**
49 | * Properly checks the equality of the array.
50 | */
51 | override fun equals(other: Any?): Boolean {
52 | if (this === other) return true
53 | if (javaClass != other?.javaClass) return false
54 |
55 | other as NbtByteArray
56 |
57 | if (!value.contentEquals(other.value)) return false
58 |
59 | return true
60 | }
61 |
62 | /**
63 | * Properly calculates the hashcode of the array.
64 | */
65 | override fun hashCode(): Int {
66 | return value.contentHashCode()
67 | }
68 |
69 | /**
70 | * Returns a new wrapper with a copy of the current value.
71 | */
72 | override fun deepCopy(): NbtByteArray = copy(value = value.copyOf())
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtCompound.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | /**
4 | * A tag which contains a [MutableMap] structure associating [String]s to [NbtTag]s.
5 | *
6 | * It's the main heart of NBT files and usually contains complex structures.
7 | *
8 | * The returned tags by this class will be linked, so modifications to it will also affect the compound value.
9 | *
10 | * All get functions which are not prefixed with `Nullable` and `get` will throw a [ClassCastException]
11 | * if the tag is mapped to a different class then the method used. For example if a given compound
12 | * have an example=NbtInt(2) and you try to read it using [NbtCompound.getShort], an exception will be thrown.
13 | *
14 | * All get functions which are not prefixed with `Nullable` and `get` will throw [NoSuchElementException]
15 | * if no value is mapped to the given name. This will change in the future.
16 | *
17 | * All get list functions which returns lists of specific types will throw [IllegalStateException] if the list content
18 | * does not match the requested type.
19 | *
20 | * @param value A [Map] which contains all key-value mappings.
21 | */
22 | public class NbtCompound private constructor(private val value: LinkedHashMap) : NbtTag(), MutableMap by value {
23 | /**
24 | * Returns a string representation of the tag's value.
25 | *
26 | * The output will be similar to a normal [Map].
27 | *
28 | * The class names of the children will expose.
29 | *
30 | * The returned string is compatible with string constructors of the same type.
31 | *
32 | * Be aware that this may be a slow operation on compounds.
33 | */
34 | override val stringValue: String
35 | get() = value.takeIf { it.isNotEmpty() }?.entries?.joinToString(prefix = "{", postfix = "}") { (key, tag) ->
36 | '"' + key.replace("\\", "\\\\").replace("\"", "\\\"") + "\"=" + tag
37 | } ?: "{}"
38 |
39 | /**
40 | * Creates a new compound containing the same mappings as the given [Map].
41 | *
42 | * The tags in the map will be linked so any modification will also change this tag contents.
43 | */
44 | public constructor(value: Map): this(LinkedHashMap(value))
45 |
46 | /**
47 | * Creates an empty compound.
48 | */
49 | public constructor(): this(emptyMap())
50 |
51 | /**
52 | * Creates a compound which maps the [Pair.first] value to the [Pair.second] tag initially.
53 | *
54 | * The given tags will be linked, so modifications to them will also affect the compound value.
55 | */
56 | public constructor(vararg tags: Pair): this(mapOf(*tags))
57 |
58 | /**
59 | * Creates a compound which maps the [Pair.first] value to the [Pair.second] tag initially.
60 | *
61 | * The given tags will be linked, so modifications to them will also affect the compound value.
62 | */
63 | public constructor(tags: Iterable>): this(tags.toMap())
64 |
65 | /**
66 | * @throws IllegalArgumentException if the string does not have the exact format outputted by [stringValue]
67 | */
68 | @Throws(IllegalArgumentException::class)
69 | public constructor(value: String): this(NbtCompoundStringParser(value).parseCompound())
70 |
71 | /**
72 | * Directly maps a [NbtTag] to a key. The value must not be [NbtEnd].
73 | * The given tag will be linked, so modifications to it will also affect the compound value.
74 | */
75 | public operator fun set(key: String, value: NbtTag) {
76 | put(key, value)
77 | }
78 |
79 | /**
80 | * Maps a [NbtByte] `1` if the value is `true` and `0` otherwise.
81 | */
82 | public operator fun set(key: String, value: Boolean): Unit = set(key, if (value) BYTE_TRUE else 0)
83 | /**
84 | * Maps a [NbtByte] with the given value.
85 | */
86 | public operator fun set(key: String, value: Byte): Unit = set(key, NbtByte(value))
87 | /**
88 | * Maps a [NbtShort] with the given value.
89 | */
90 | public operator fun set(key: String, value: Short): Unit = set(key, NbtShort(value))
91 | /**
92 | * Maps a [NbtInt] with the given value.
93 | */
94 | public operator fun set(key: String, value: Int): Unit = set(key, NbtInt(value))
95 | /**
96 | * Maps a [NbtLong] with the given value.
97 | */
98 | public operator fun set(key: String, value: Long): Unit = set(key, NbtLong(value))
99 | /**
100 | * Maps a [NbtFloat] with the given value.
101 | */
102 | public operator fun set(key: String, value: Float): Unit = set(key, NbtFloat(value))
103 | /**
104 | * Maps a [NbtDouble] with the given value.
105 | */
106 | public operator fun set(key: String, value: Double): Unit = set(key, NbtDouble(value))
107 | /**
108 | * Maps a [NbtByteArray] with the given value. The array instance will be linked so any modification will also
109 | * change the tag value.
110 | */
111 | public operator fun set(key: String, value: ByteArray): Unit = set(key, NbtByteArray(value))
112 | /**
113 | * Maps a [NbtDouble] with the given value.
114 | */
115 | public operator fun set(key: String, value: String): Unit = set(key, NbtString(value))
116 | /**
117 | * Maps a [NbtByteArray] with the given value. The array instance will be linked so any modification will also
118 | * change the tag value.
119 | */
120 | public operator fun set(key: String, value: IntArray): Unit = set(key, NbtIntArray(value))
121 | /**
122 | * Maps a [NbtByteArray] with the given value. The array instance will be linked so any modification will also
123 | * change the tag value.
124 | */
125 | public operator fun set(key: String, value: LongArray): Unit = set(key, NbtLongArray(value))
126 |
127 | /**
128 | * Returns `true` if [getByte] returns `1`, `false` otherwise.
129 | * Will also return `false` if the value is not mapped.
130 | * @throws ClassCastException If the [NbtTag] is not a [NbtByte]
131 | */
132 | @Throws(ClassCastException::class)
133 | public fun getNullableBooleanByte(key: String): Boolean = getNullableByte(key) == BYTE_TRUE
134 | /**
135 | * Returns `true` if [getByte] returns `1`, `false` otherwise.
136 | * @throws ClassCastException If the [NbtTag] is not a [NbtByte]
137 | * @throws NoSuchElementException If the key is not present on the compound
138 | */
139 | @Throws(ClassCastException::class, NoSuchElementException::class)
140 | public fun getBooleanByte(key: String): Boolean = getByte(key) == BYTE_TRUE
141 |
142 | /**
143 | * Returns the value corresponding to the given [key], or throw an exception if such a key is not present in the compound.
144 | * @throws NoSuchElementException If the key is not present on the compound
145 | */
146 | @Throws(NoSuchElementException::class)
147 | public fun require(key: String): NbtTag = get(key) ?: throw NoSuchElementException(key)
148 |
149 | /**
150 | * Returns the unwrapped byte value.
151 | * @throws ClassCastException If the [NbtTag] is not a [NbtByte]
152 | * @throws NoSuchElementException If the key is not present on the compound
153 | */
154 | @Throws(ClassCastException::class, NoSuchElementException::class)
155 | public fun getByte(key: String): Byte = (require(key) as NbtByte).signed
156 | /**
157 | * Returns the unwrapped short value.
158 | * @throws ClassCastException If the [NbtTag] is not a [NbtShort]
159 | * @throws NoSuchElementException If the key is not present on the compound
160 | */
161 | @Throws(ClassCastException::class, NoSuchElementException::class)
162 | public fun getShort(key: String): Short = (require(key) as NbtShort).value
163 | /**
164 | * Returns the unwrapped int value.
165 | * @throws ClassCastException If the [NbtTag] is not a [NbtInt]
166 | * @throws NoSuchElementException If the key is not present on the compound
167 | */
168 | @Throws(ClassCastException::class, NoSuchElementException::class)
169 | public fun getInt(key: String): Int = (require(key) as NbtInt).value
170 | /**
171 | * Returns the unwrapped long value.
172 | * @throws ClassCastException If the [NbtTag] is not a [NbtLongArray]
173 | * @throws NoSuchElementException If the key is not present on the compound
174 | */
175 | @Throws(ClassCastException::class, NoSuchElementException::class)
176 | public fun getLong(key: String): Long = (require(key) as NbtLong).value
177 | /**
178 | * Returns the unwrapped float value.
179 | * @throws ClassCastException If the [NbtTag] is not a [NbtFloat]
180 | * @throws NoSuchElementException If the key is not present on the compound
181 | */
182 | @Throws(ClassCastException::class, NoSuchElementException::class)
183 | public fun getFloat(key: String): Float = (require(key) as NbtFloat).value
184 | /**
185 | * Returns the unwrapped double value.
186 | * @throws ClassCastException If the [NbtTag] is not a [NbtDouble]
187 | * @throws NoSuchElementException If the key is not present on the compound
188 | */
189 | @Throws(ClassCastException::class, NoSuchElementException::class)
190 | public fun getDouble(key: String): Double = (require(key) as NbtDouble).value
191 | /**
192 | * Returns the unwrapped byte array value. The array will be linked and any modification will
193 | * also change wrapper and the mapped value.
194 | * @throws ClassCastException If the [NbtTag] is not a [NbtByteArray]
195 | * @throws NoSuchElementException If the key is not present on the compound
196 | */
197 | @Throws(ClassCastException::class, NoSuchElementException::class)
198 | public fun getByteArray(key: String): ByteArray = (require(key) as NbtByteArray).value
199 | /**
200 | * Returns the unwrapped string value.
201 | * @throws ClassCastException If the [NbtTag] is not a [NbtString]
202 | * @throws NoSuchElementException If the key is not present on the compound
203 | */
204 | @Throws(ClassCastException::class, NoSuchElementException::class)
205 | public fun getString(key: String): String = (require(key) as NbtString).value
206 | /**
207 | * Returns the unwrapped int array value. The array will be linked and any modification will
208 | * also change wrapper and the mapped value.
209 | * @throws ClassCastException If the [NbtTag] is not a [NbtIntArray]
210 | * @throws NoSuchElementException If the key is not present on the compound
211 | */
212 | @Throws(ClassCastException::class, NoSuchElementException::class)
213 | public fun getIntArray(key: String): IntArray = (require(key) as NbtIntArray).value
214 | /**
215 | * Returns the unwrapped long array value. The array will be linked and any modification will
216 | * also change wrapper and the mapped value.
217 | * @throws ClassCastException If the [NbtTag] is not a [NbtLongArray]
218 | * @throws NoSuchElementException If the key is not present on the compound
219 | */
220 | @Throws(ClassCastException::class, NoSuchElementException::class)
221 | public fun getLongArray(key: String): LongArray = (require(key) as NbtLongArray).value
222 | /**
223 | * Returns the [NbtCompound] mapped to that key. The tag will be linked and any modification will
224 | * also change the mapped value.
225 | * @throws ClassCastException If the [NbtTag] is not a [NbtCompound]
226 | * @throws NoSuchElementException If the key is not present on the compound
227 | */
228 | @Throws(ClassCastException::class, NoSuchElementException::class)
229 | public fun getCompound(key: String): NbtCompound = require(key) as NbtCompound
230 | /**
231 | * Returns the [NbtList] mapped to that key. The tag will be linked and any modification will
232 | * also change the mapped value.
233 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
234 | * @throws NoSuchElementException If the key is not present on the compound
235 | */
236 | @Throws(ClassCastException::class, NoSuchElementException::class)
237 | public fun getList(key: String): NbtList<*> = require(key) as NbtList<*>
238 |
239 | /**
240 | * Returns the [NbtList] of bytes mapped to that key. The tag will be linked and any modification will
241 | * also change the mapped value.
242 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
243 | * @throws NoSuchElementException If the key is not present on the compound
244 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtByte]
245 | */
246 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
247 | public fun getByteList(key: String): NbtList = getList(key).cast()
248 | /**
249 | * Returns the [NbtList] of shorts mapped to that key. The tag will be linked and any modification will
250 | * also change the mapped value.
251 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
252 | * @throws NoSuchElementException If the key is not present on the compound
253 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtShort]
254 | */
255 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
256 | public fun getShortList(key: String): NbtList = getList(key).cast()
257 | /**
258 | * Returns the [NbtList] of integers mapped to that key. The tag will be linked and any modification will
259 | * also change the mapped value.
260 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
261 | * @throws NoSuchElementException If the key is not present on the compound
262 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtIntArray]
263 | */
264 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
265 | public fun getIntList(key: String): NbtList = getList(key).cast()
266 | /**
267 | * Returns the [NbtList] of longs mapped to that key. The tag will be linked and any modification will
268 | * also change the mapped value.
269 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
270 | * @throws NoSuchElementException If the key is not present on the compound
271 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtLong]
272 | */
273 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
274 | public fun getLongList(key: String): NbtList = getList(key).cast()
275 | /**
276 | * Returns the [NbtList] of floats mapped to that key. The tag will be linked and any modification will
277 | * also change the mapped value.
278 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
279 | * @throws NoSuchElementException If the key is not present on the compound
280 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtFloat]
281 | */
282 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
283 | public fun getFloatList(key: String): NbtList = getList(key).cast()
284 | /**
285 | * Returns the [NbtList] of doubles mapped to that key. The tag will be linked and any modification will
286 | * also change the mapped value.
287 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
288 | * @throws NoSuchElementException If the key is not present on the compound
289 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtDouble]
290 | */
291 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
292 | public fun getDoubleList(key: String): NbtList = getList(key).cast()
293 | /**
294 | * Returns the [NbtList] of byte arrays mapped to that key. The tag and it's value will be linked and any modification will
295 | * also change the mapped value.
296 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
297 | * @throws NoSuchElementException If the key is not present on the compound
298 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtByteArray]
299 | */
300 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
301 | public fun getByteArrayList(key: String): NbtList = getList(key).cast()
302 | /**
303 | * Returns the [NbtList] of strings mapped to that key. The tag will be linked and any modification will
304 | * also change the mapped value.
305 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
306 | * @throws NoSuchElementException If the key is not present on the compound
307 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtString]
308 | */
309 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
310 | public fun getStringList(key: String): NbtList = getList(key).cast()
311 | /**
312 | * Returns the [NbtList] of int arrays mapped to that key. The tag and it's value will be linked and any modification will
313 | * also change the mapped value.
314 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
315 | * @throws NoSuchElementException If the key is not present on the compound
316 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtIntArray]
317 | */
318 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
319 | public fun getIntArrayList(key: String): NbtList = getList(key).cast()
320 | /**
321 | * Returns the [NbtList] of long arrays mapped to that key. The tag and it's values will be linked and any modification will
322 | * also change the mapped value.
323 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
324 | * @throws NoSuchElementException If the key is not present on the compound
325 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtLongArray]
326 | */
327 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
328 | public fun getLongArrayList(key: String): NbtList = getList(key).cast()
329 | /**
330 | * Returns the [NbtList] of compounds mapped to that key. The tag and it's values will be linked and any modification will
331 | * also change the mapped value.
332 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
333 | * @throws NoSuchElementException If the key is not present on the compound
334 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtCompound]
335 | */
336 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
337 | public fun getCompoundList(key: String): NbtList = getList(key).cast()
338 | /**
339 | * Returns the [NbtList] of lists mapped to that key. The tag and it's values will be linked and any modification will
340 | * also change the mapped value.
341 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
342 | * @throws NoSuchElementException If the key is not present on the compound
343 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtList]
344 | */
345 | @Throws(ClassCastException::class, NoSuchElementException::class, IllegalStateException::class)
346 | public fun getListOfList(key: String): NbtList> = getList(key).cast()
347 |
348 | /**
349 | * Returns the unwrapped byte value or null if no value is mapped, or it is mapped to another type tag.
350 | * @throws ClassCastException If the [NbtTag] is not a [NbtByte]
351 | */
352 | @Throws(ClassCastException::class)
353 | public fun getNullableByte(key: String): Byte? = this[key]?.let { it as NbtByte }?.signed
354 | /**
355 | * Returns the unwrapped short value or null if no value is mapped, or it is mapped to another type tag.
356 | * @throws ClassCastException If the [NbtTag] is not a [NbtShort]
357 | */
358 | @Throws(ClassCastException::class)
359 | public fun getNullableShort(key: String): Short? = this[key]?.let { it as NbtShort }?.value
360 | /**
361 | * Returns the unwrapped int value or null if no value is mapped, or it is mapped to another type tag.
362 | * @throws ClassCastException If the [NbtTag] is not a [NbtInt]
363 | */
364 | @Throws(ClassCastException::class)
365 | public fun getNullableInt(key: String): Int? = this[key]?.let { it as NbtInt }?.value
366 | /**
367 | * Returns the unwrapped long value or null if no value is mapped, or it is mapped to another type tag.
368 | * @throws ClassCastException If the [NbtTag] is not a [NbtLong]
369 | */
370 | @Throws(ClassCastException::class)
371 | public fun getNullableLong(key: String): Long? = this[key]?.let { it as NbtLong }?.value
372 | /**
373 | * Returns the unwrapped float value or null if no value is mapped, or it is mapped to another type tag.
374 | * @throws ClassCastException If the [NbtTag] is not a [NbtFloat]
375 | */
376 | @Throws(ClassCastException::class)
377 | public fun getNullableFloat(key: String): Float? = this[key]?.let { it as NbtFloat }?.value
378 | /**
379 | * Returns the unwrapped double value or null if no value is mapped, or it is mapped to another type tag.
380 | * @throws ClassCastException If the [NbtTag] is not a [NbtDouble]
381 | */
382 | @Throws(ClassCastException::class)
383 | public fun getNullableDouble(key: String): Double? = this[key]?.let { it as NbtDouble }?.value
384 | /**
385 | * Returns the unwrapped byte array value. The array will be linked and any modification will
386 | * also change wrapper and the mapped value.
387 | *
388 | * Will return null if no value is mapped, or it is mapped to another type tag.
389 | * @throws ClassCastException If the [NbtTag] is not a [NbtByteArray]
390 | */
391 | @Throws(ClassCastException::class)
392 | public fun getNullableByteArray(key: String): ByteArray? = this[key]?.let { it as NbtByteArray }?.value
393 | /**
394 | * Returns the unwrapped string value or null if no value is mapped, or it is mapped to another type tag.
395 | * @throws ClassCastException If the [NbtTag] is not a [NbtString]
396 | */
397 | @Throws(ClassCastException::class)
398 | public fun getNullableString(key: String): String? = this[key]?.let { it as NbtString }?.value
399 | /**
400 | * Returns the unwrapped int array value. The array will be linked and any modification will
401 | * also change wrapper and the mapped value.
402 | *
403 | * Will return null if no value is mapped, or it is mapped to another type tag.
404 | * @throws ClassCastException If the [NbtTag] is not a [NbtIntArray]
405 | */
406 | @Throws(ClassCastException::class)
407 | public fun getNullableIntArray(key: String): IntArray? = this[key]?.let { it as NbtIntArray }?.value
408 | /**
409 | * Returns the unwrapped long array value. The array will be linked and any modification will
410 | * also change wrapper and the mapped value.
411 | *
412 | * Will return null if no value is mapped, or it is mapped to another type tag.
413 | * @throws ClassCastException If the [NbtTag] is not a [NbtLongArray]
414 | */
415 | @Throws(ClassCastException::class)
416 | public fun getNullableLongArray(key: String): LongArray? = this[key]?.let { it as NbtLongArray }?.value
417 | /**
418 | * Returns the [NbtCompound] mapped to that key. The tag will be linked and any modification will
419 | * also change the mapped value.
420 | *
421 | * Will return null if no value is mapped, or it is mapped to another type tag.
422 | * @throws ClassCastException If the [NbtTag] is not a [NbtCompound]
423 | */
424 | @Throws(ClassCastException::class)
425 | public fun getNullableCompound(key: String): NbtCompound? = this[key]?.let { it as NbtCompound }
426 | /**
427 | * Returns the [NbtList] mapped to that key. The tag will be linked and any modification will
428 | * also change the mapped value.
429 | *
430 | * Will return null if no value is mapped, or it is mapped to another type tag.
431 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
432 | */
433 | @Throws(ClassCastException::class)
434 | public fun getNullableList(key: String): NbtList<*>? = this[key]?.let { it as NbtList<*> }
435 |
436 | /**
437 | * Returns the [NbtList] of bytes mapped to that key. The tag will be linked and any modification will
438 | * also change the mapped value.
439 | *
440 | * Will return null if no value is mapped, or it is mapped to another type tag.
441 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
442 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtByte]
443 | */
444 | @Throws(ClassCastException::class, IllegalStateException::class)
445 | public fun getNullableByteList(key: String): NbtList? = getNullableList(key)?.cast()
446 | /**
447 | * Returns the [NbtList] of shorts mapped to that key. The tag will be linked and any modification will
448 | * also change the mapped value.
449 | *
450 | * Will return null if no value is mapped, or it is mapped to another type tag.
451 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
452 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtShort]
453 | */
454 | @Throws(ClassCastException::class, IllegalStateException::class)
455 | public fun getNullableShortList(key: String): NbtList? = getNullableList(key)?.cast()
456 | /**
457 | * Returns the [NbtList] of integers mapped to that key. The tag will be linked and any modification will
458 | * also change the mapped value.
459 | *
460 | * Will return null if no value is mapped, or it is mapped to another type tag.
461 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
462 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtIntArray]
463 | */
464 | @Throws(ClassCastException::class, IllegalStateException::class)
465 | public fun getNullableIntList(key: String): NbtList? = getNullableList(key)?.cast()
466 | /**
467 | * Returns the [NbtList] of longs mapped to that key. The tag will be linked and any modification will
468 | * also change the mapped value.
469 | *
470 | * Will return null if no value is mapped, or it is mapped to another type tag.
471 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
472 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtLong]
473 | */
474 | @Throws(ClassCastException::class, IllegalStateException::class)
475 | public fun getNullableLongList(key: String): NbtList? = getNullableList(key)?.cast()
476 | /**
477 | * Returns the [NbtList] of floats mapped to that key. The tag will be linked and any modification will
478 | * also change the mapped value.
479 | *
480 | * Will return null if no value is mapped, or it is mapped to another type tag.
481 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
482 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtFloat]
483 | */
484 | @Throws(ClassCastException::class, IllegalStateException::class)
485 | public fun getNullableFloatList(key: String): NbtList? = getNullableList(key)?.cast()
486 | /**
487 | * Returns the [NbtList] of doubles mapped to that key. The tag will be linked and any modification will
488 | * also change the mapped value.
489 | *
490 | * Will return null if no value is mapped, or it is mapped to another type tag.
491 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
492 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtDouble]
493 | */
494 | @Throws(ClassCastException::class, IllegalStateException::class)
495 | public fun getNullableDoubleList(key: String): NbtList? = getNullableList(key)?.cast()
496 | /**
497 | * Returns the [NbtList] of byte arrays mapped to that key. The tag and it's value will be linked and any modification will
498 | * also change the mapped value.
499 | *
500 | * Will return null if no value is mapped, or it is mapped to another type tag.
501 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
502 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtByteArray]
503 | */
504 | @Throws(ClassCastException::class, IllegalStateException::class)
505 | public fun getNullableByteArrayList(key: String): NbtList? = getNullableList(key)?.cast()
506 | /**
507 | * Returns the [NbtList] of strings mapped to that key. The tag will be linked and any modification will
508 | * also change the mapped value.
509 | *
510 | * Will return null if no value is mapped, or it is mapped to another type tag.
511 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
512 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtString]
513 | */
514 | @Throws(ClassCastException::class, IllegalStateException::class)
515 | public fun getNullableStringList(key: String): NbtList? = getNullableList(key)?.cast()
516 | /**
517 | * Returns the [NbtList] of int arrays mapped to that key. The tag and it's value will be linked and any modification will
518 | * also change the mapped value.
519 | *
520 | * Will return null if no value is mapped, or it is mapped to another type tag.
521 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
522 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtIntArray]
523 | */
524 | @Throws(ClassCastException::class, IllegalStateException::class)
525 | public fun getNullableIntArrayList(key: String): NbtList? = getNullableList(key)?.cast()
526 | /**
527 | * Returns the [NbtList] of long arrays mapped to that key. The tag and it's values will be linked and any modification will
528 | * also change the mapped value.
529 | *
530 | * Will return null if no value is mapped, or it is mapped to another type tag.
531 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
532 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtLongArray]
533 | */
534 | @Throws(ClassCastException::class, IllegalStateException::class)
535 | public fun getNullableLongArrayList(key: String): NbtList? = getNullableList(key)?.cast()
536 | /**
537 | * Returns the [NbtList] of compounds mapped to that key. The tag and it's values will be linked and any modification will
538 | * also change the mapped value.
539 | *
540 | * Will return null if no value is mapped, or it is mapped to another type tag.
541 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
542 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtCompound]
543 | */
544 | @Throws(ClassCastException::class, IllegalStateException::class)
545 | public fun getNullableCompoundList(key: String): NbtList? = getNullableList(key)?.cast()
546 | /**
547 | * Returns the [NbtList] of lists mapped to that key. The tag and it's values will be linked and any modification will
548 | * also change the mapped value.
549 | *
550 | * Will return null if no value is mapped, or it is mapped to another type tag.
551 | * @throws ClassCastException If the [NbtTag] is not a [NbtList]
552 | * @throws IllegalStateException If the list is not empty and contains any tag with class different then [NbtList]
553 | */
554 | @Throws(ClassCastException::class, IllegalStateException::class)
555 | public fun getNullableListOfList(key: String): NbtList>? = getNullableList(key)?.cast()
556 |
557 | /**
558 | * Checks if the other compound have a given tag, if it has been placed it in this compound.
559 | *
560 | * The tag will be linked, so any change in the tag will also affect both compounds.
561 | * @param other The compound that will be checked
562 | * @param tagKey The name of the tag that will be mapped
563 | * @param default If the other compound doesn't have the tag then this parameter will be used.
564 | */
565 | public fun copyFrom(other: NbtCompound, tagKey: String, default: NbtTag? = null) {
566 | val tag = other[tagKey] ?: default
567 | if (tag != null) {
568 | this[tagKey] = tag
569 | }
570 | }
571 |
572 | /**
573 | * Checks if the compound have a given tag, if it has been placed it in the other compound.
574 | *
575 | * The tag will be linked, so any change in the tag will also affect both compounds.
576 | * @param other The compound that will be modified
577 | * @param tagKey The name of the tag that will be mapped
578 | * @param default If the compound doesn't have the tag then this parameter will be used.
579 | */
580 | public fun copyTo(other: NbtCompound, tagKey: String, default: NbtTag? = null) {
581 | val tag = this[tagKey] ?: default
582 | if (tag != null) {
583 | other[tagKey] = tag
584 | }
585 | }
586 |
587 | /**
588 | * Returns a new NbtCompound with all nested values copied deeply.
589 | */
590 | override fun deepCopy(): NbtCompound = NbtCompound(mapValues { it.value.deepCopy() })
591 |
592 | /**
593 | * A technical string representation of this tag, containing the tag type, and it's value,
594 | * appropriated for developer inspections.
595 | *
596 | * The output will be similar to a normal [Map].
597 | *
598 | * Be aware that this may be a slow operation on compounds.
599 | */
600 | override fun toTechnicalString(): String {
601 | if (value.isEmpty()) {
602 | return "NbtCompound{}"
603 | }
604 | return "NbtCompound$stringValue"
605 | /*return buildString {
606 | append("NbtCompound{")
607 | val iterator = value.iterator()
608 | while (iterator.hasNext()) {
609 | val (key, tag) = iterator.next()
610 | append(key).append('=').append(tag)
611 | if (iterator.hasNext()) {
612 | append(", ")
613 | }
614 | }
615 | append('}')
616 | }*/
617 | }
618 |
619 | override fun equals(other: Any?): Boolean {
620 | return value == other
621 | }
622 |
623 | override fun hashCode(): Int {
624 | return value.hashCode()
625 | }
626 |
627 | }
628 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtCompoundStringParser.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | internal class NbtCompoundStringParser(value: String): ComplexNbtStringParser('{', '}', value) {
4 |
5 | private val result = LinkedHashMap()
6 |
7 | internal fun parseCompound(): LinkedHashMap {
8 | if (input.isEmpty()) {
9 | return result
10 | }
11 |
12 | check(endIndex == -2) {
13 | "Already processed."
14 | }
15 |
16 | endIndex = 0
17 |
18 | parseEntries()
19 | return result
20 | }
21 |
22 | private tailrec fun parseEntries() {
23 | require(input[endIndex] == '"') { "Invalid input. Expected an open quote." }
24 | startIndex = endIndex
25 | val key = parseKey()
26 | startIndex = endIndex + 2
27 | require(startIndex < input.length) { "Invalid input. Expected entry value." }
28 | val tag = parseTag()
29 | result[key] = tag
30 | if (tag.javaClass == NbtString::class.java) {
31 | endIndex += 2
32 | } else if (closeChar == ')') {
33 | endIndex++
34 | }
35 | if (endIndex < input.length) {
36 | require(endIndex + 3 < input.length) { "Invalid input. Expected more entries." }
37 | require(input.substring(endIndex, endIndex + 2) == ", ") { "Invalid input. Incorrect entry separator." }
38 | endIndex += 2
39 | parseEntries()
40 | }
41 | }
42 |
43 | private fun parseKey(): String {
44 | startIndex++
45 | return findClosingQuote("\"=")
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtDouble.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | /**
4 | * A tag which wraps a double value.
5 | * @property value The wrapped value
6 | */
7 | public data class NbtDouble(var value: Double) : NbtTag() {
8 | /**
9 | * Returns a string representation of the tag's value.
10 | *
11 | * The returned string is compatible with string constructors of the same type.
12 | */
13 | override val stringValue: String
14 | get() = value.toString()
15 |
16 | /**
17 | * Parses the string value as a signed double and wraps it.
18 | * @param signed Signed value from `4.9e-324` to `1.7976931348623157e+308`. NaN and Infinity are also accepted.
19 | * @throws NumberFormatException if the number is not within a valid range or if the string does not contain a valid number.
20 | */
21 | @Throws(NumberFormatException::class)
22 | public constructor(signed: String): this(signed.toDouble())
23 |
24 | /**
25 | * Returns a new wrapper with the current value.
26 | */
27 | override fun deepCopy(): NbtDouble = copy()
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtEnd.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | /**
4 | * A special tag which indicates the end of a compound stream or empty lists.
5 | *
6 | * Should not be used directly.
7 | */
8 | public object NbtEnd : NbtTag() {
9 | /**
10 | * Returns an empty string.
11 | */
12 | override val stringValue: String
13 | get() = ""
14 |
15 | /**
16 | * Returns `"NbtEnd"`.
17 | */
18 | override fun toTechnicalString(): String {
19 | return "NbtEnd"
20 | }
21 |
22 | /**
23 | * Returns itself.
24 | */
25 | override fun deepCopy(): NbtEnd = NbtEnd
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtFile.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | /**
4 | * The root component of a file, it contains a hint for the file name and the first tag in the file.
5 | * @property name The key for the file name. Empty in most cases.
6 | * @property tag The first tag in the file. A [NbtCompound] in most cases.
7 | * @property compound A shortcut to read or write [NbtFile.tag] as a [NbtCompound].
8 | * @property version The version of the data stored in this file.
9 | * @property length The length of the file which is cached in the file's header.
10 | * @property isCompressed If the file needed to be uncompressed to load.
11 | * @property isLittleEndian If the file's byte order is little endian instead of big endian.
12 | * Will throw a [ClassCastException] if the tag value is not a [NbtCompound]
13 | */
14 | public data class NbtFile @JvmOverloads constructor(
15 | var name: String,
16 | var tag: NbtTag,
17 | var version: Int? = null,
18 | var length: Int? = null,
19 | var isCompressed: Boolean? = null,
20 | var isLittleEndian: Boolean? = null
21 | ) {
22 | @Suppress("MemberVisibilityCanBePrivate")
23 | public var compound: NbtCompound
24 | @Throws(ClassCastException::class)
25 | get() = tag as NbtCompound
26 | set(value) {
27 | tag = value
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtFloat.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | /**
4 | * A tag which wraps a float value.
5 | * @property value The wrapped value
6 | */
7 | public data class NbtFloat(var value: Float) : NbtTag() {
8 | /**
9 | * Returns a string representation of the tag's value.
10 | *
11 | * The returned string is compatible with string constructors of the same type.
12 | */
13 | override val stringValue: String
14 | get() = value.toString()
15 |
16 | /**
17 | * Parses the string value as a signed float and wraps it.
18 | * @param signed Signed value from `1.4e-45` to `3.4028235e+38`. NaN and Infinity are also accepted.
19 | * @throws NumberFormatException if the number is not within a valid range or if the string does not contain a valid number.
20 | */
21 | @Throws(NumberFormatException::class)
22 | public constructor(signed: String): this(signed.toFloat())
23 |
24 | /**
25 | * Returns a new wrapper with the current value.
26 | */
27 | override fun deepCopy(): NbtFloat = copy()
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtIO.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("_NbtIO_Internal")
2 |
3 | package br.com.gamemods.nbtmanipulator
4 |
5 | import java.io.*
6 | import java.util.zip.GZIPInputStream
7 | import java.util.zip.GZIPOutputStream
8 | import kotlin.reflect.KClass
9 | import kotlin.reflect.cast
10 |
11 | /**
12 | * Contains useful methods do read and write [NbtFile] from [File] and [InputStream]/[OutputStream].
13 | */
14 | public object NbtIO {
15 | /**
16 | * Calls [writeNbtFile] using the information stored in the [NbtFile], uses the method's default when the information
17 | * is missing (null). This method does not write the Bedrock Edition version and length headers.
18 | * @param outputStream The stream that the file will be written
19 | * @param file The file that will be written to the stream
20 | */
21 | @JvmStatic
22 | @Throws(IOException::class)
23 | public fun writeNbtFileAsOriginal(outputStream: OutputStream, file: NbtFile) {
24 | writeNbtFile(outputStream, file,
25 | compressed = file.isCompressed ?: true,
26 | littleEndian = file.isLittleEndian ?: false
27 | )
28 | }
29 |
30 | /**
31 | * Writes the [NbtFile] in the stream. This method does not write the Bedrock Edition version and length headers.
32 | * @param outputStream The stream that the file will be written
33 | * @param file The file that will be written to the stream
34 | * @param compressed If the file will be compressed by [GZIPOutputStream].
35 | * @param littleEndian Uses little endian to write to the stream as in Bedrock Edition
36 | */
37 | @JvmStatic
38 | @Throws(IOException::class)
39 | @JvmOverloads
40 | public fun writeNbtFile(outputStream: OutputStream, file: NbtFile, compressed: Boolean = true, littleEndian: Boolean = false) {
41 | val output = if (compressed) GZIPOutputStream(outputStream) else outputStream
42 | val dataOut: DataOutput = if (littleEndian) LittleEndianDataOutputStream(output) else DataOutputStream(output)
43 | writeNbtFileDirectly(dataOut, file)
44 | (dataOut as OutputStream).flush()
45 | if (output is GZIPOutputStream) {
46 | output.finish()
47 | }
48 | }
49 |
50 | /**
51 | * Writes the [NbtFile] to the output. This method does not write the Bedrock Edition version and length headers.
52 | * @param output Where the file will be written, needs to handle compression and endianness.
53 | * @param file The file that will be written to the output
54 | */
55 | @JvmStatic
56 | @Throws(IOException::class)
57 | public fun writeNbtFileDirectly(output: DataOutput, file: NbtFile) {
58 | val tag = file.tag
59 | val typeId = tag.typeId
60 | val serializer = serializers[typeId]
61 | output.writeByte(typeId)
62 | output.writeUTF(file.name)
63 |
64 | serializer.writeTag(output, tag)
65 | }
66 |
67 | /**
68 | * Writes the [NbtFile] in a [File].
69 | * @param file The output file
70 | * @param file The NBT file that will be written on the output file
71 | * @param compressed If the file will be compressed by [GZIPOutputStream]
72 | * @param littleEndian Uses little endian to write to the stream as in Bedrock Edition
73 | * @param writeHeaders Writes the [NbtFile.version] and content size headers to the file.
74 | * The [NbtFile.length] property will be updated when this flag is set to true.
75 | * If [NbtFile.version] is null when this flag is true, `0` is assumed.
76 | * The header is always written in little endian regardless of the [littleEndian] param.
77 | */
78 | @JvmStatic
79 | @Throws(IOException::class)
80 | @JvmOverloads
81 | public fun writeNbtFile(
82 | file: File, tag: NbtFile, compressed: Boolean = true,
83 | littleEndian: Boolean = false, writeHeaders: Boolean = false
84 | ) {
85 | if (!writeHeaders) {
86 | file.outputStream().buffered().use { stream ->
87 | writeNbtFile(stream, tag, compressed, littleEndian)
88 | stream.flush()
89 | }
90 | } else {
91 | RandomAccessFile(file, "rw").use { openFile ->
92 | openFile.setLength(8)
93 | FileOutputStream(openFile.fd).buffered().let { stream ->
94 | with(LittleEndianDataOutputStream(stream)) {
95 | writeInt(tag.version ?: 0)
96 | writeInt(0)
97 | flush()
98 | }
99 | writeNbtFile(stream, tag, compressed, littleEndian)
100 | stream.flush()
101 | }
102 |
103 | val fileLength = openFile.length() - 8L
104 | val intLength = if (fileLength > Int.MAX_VALUE) Int.MAX_VALUE else fileLength.toInt()
105 | tag.length = intLength
106 |
107 | openFile.seek(8)
108 | with(LittleEndianDataOutputStream(FileOutputStream(openFile.fd))) {
109 | writeInt(intLength)
110 | flush()
111 | }
112 | }
113 | }
114 | }
115 |
116 | /**
117 | * Read a [NbtFile] from the [InputStream].
118 | * @param inputStream The input stream that will be read
119 | * @param compressed If the file needs to be decompressed by [GZIPInputStream]
120 | * @param littleEndian Reads the NBT file using little endian byte order
121 | * @param readHeaders Reads the NBT version and length headers before the content
122 | * These data are read in little endian byte order regardless of the [littleEndian] parameter.
123 | */
124 | @JvmStatic
125 | @Throws(IOException::class)
126 | @JvmOverloads
127 | public fun readNbtFile(
128 | inputStream: InputStream, compressed: Boolean = true,
129 | littleEndian: Boolean = false, readHeaders: Boolean = false
130 | ): NbtFile {
131 | var version: Int? = null
132 | var length: Int? = null
133 |
134 | if (readHeaders) {
135 | with(LittleEndianDataInputStream(inputStream)) {
136 | version = readInt()
137 | length = readInt()
138 | }
139 | }
140 |
141 | val input = if (compressed) GZIPInputStream(inputStream) else inputStream
142 | val dataIn: DataInput = if (littleEndian) LittleEndianDataInputStream(input) else DataInputStream(input)
143 | val nbtFile = readNbtFileDirectly(dataIn)
144 | nbtFile.version = version
145 | nbtFile.length = length
146 | nbtFile.isCompressed = compressed
147 | nbtFile.isLittleEndian = littleEndian
148 | return nbtFile
149 | }
150 |
151 | /**
152 | * Reads a [NbtFile] from the input. This method does not read the Bedrock Edition version and length headers.
153 | * @param input Where the file will be read, needs to handle compression and endianness.
154 | */
155 | @JvmStatic
156 | @Throws(IOException::class)
157 | public fun readNbtFileDirectly(input: DataInput): NbtFile {
158 | val typeId = input.readUnsignedByte()
159 | val serializer = serializers[typeId]
160 |
161 | val name = input.readUTF()
162 |
163 | return NbtFile(name, serializer.readTag(input))
164 | }
165 |
166 | /**
167 | * Read a [NbtFile] from a [File].
168 | * @param file The input file that will be read
169 | * @param compressed If the file needs to be decompressed by [GZIPInputStream]
170 | * @param littleEndian
171 | * @param readHeaders
172 | */
173 | @JvmStatic
174 | @Throws(IOException::class)
175 | @JvmOverloads
176 | public fun readNbtFile(
177 | file: File, compressed: Boolean = true,
178 | littleEndian: Boolean = false,
179 | readHeaders: Boolean = false
180 | ): NbtFile {
181 | return file.inputStream().buffered().use { readNbtFile(it, compressed, littleEndian, readHeaders) }
182 | }
183 |
184 |
185 | /**
186 | * Does an exhaustive attempts to load the NBT file, returning it if any of the attempts is successful.
187 | * @param file
188 | */
189 | @JvmStatic
190 | @Throws(IOException::class)
191 | public fun readNbtFileDetectingSettings(file: File): NbtFile {
192 | RandomAccessFile(file, "r").use { openFile ->
193 | var ex: IOException? = null
194 | fun retry(compressed: Boolean, littleEndian: Boolean, readHeaders: Boolean): NbtFile? {
195 | return try {
196 | openFile.seek(0)
197 | readNbtFile(FileInputStream(openFile.fd).buffered(), compressed, littleEndian, readHeaders)
198 | } catch (e: IOException) {
199 | val lastEx = ex
200 | if (lastEx == null) {
201 | ex = lastEx
202 | } else {
203 | lastEx.addSuppressed(e)
204 | }
205 | null
206 | }
207 | }
208 | return retry(compressed = true, littleEndian = false, readHeaders = false) // Java's level.dat
209 | ?: retry(compressed = false, littleEndian = true, readHeaders = true) // Bedrock's level.dat
210 | ?: retry(compressed = true, littleEndian = false, readHeaders = true) // Trying all possibilities
211 | ?: retry(compressed = true, littleEndian = true, readHeaders = false)
212 | ?: retry(compressed = true, littleEndian = true, readHeaders = true)
213 | ?: retry(compressed = false, littleEndian = false, readHeaders = false)
214 | ?: retry(compressed = false, littleEndian = false, readHeaders = true)
215 | ?: retry(compressed = false, littleEndian = true, readHeaders = false)
216 | ?: retry(compressed = false, littleEndian = true, readHeaders = true)
217 | ?: throw IOException("Could not load the NbtFile $file with any settings", ex)
218 | }
219 | }
220 |
221 | /**
222 | * Writes the [NbtTag] directly, without name and optionally without type id.
223 | * @param output Where the file will be written, needs to handle compression and endianness.
224 | * @param tag The tag that will be written to the output
225 | * @param writeTypeId If the first byte written should be the NBT tag type id.
226 | */
227 | @JvmStatic
228 | @Throws(IOException::class)
229 | public fun writeNbtTagDirectly(output: DataOutput, tag: NbtTag, writeTypeId: Boolean = true) {
230 | val typeId = tag.typeId
231 | val serializer = serializers[typeId]
232 | output.takeIf { writeTypeId }?.writeByte(typeId)
233 | serializer.writeTag(output, tag)
234 | }
235 |
236 | /**
237 | * Reads a [NbtTag] of type [T] from the input directly, this is a reader for [writeNbtTagDirectly].
238 | * @param input Where the file will be read, needs to handle compression and endianness.
239 | * @param tagType The type of the tag that will be read, use `null` if the tag was written with `writeTypeId` enabled
240 | * @throws IllegalArgumentException If [T] is exactly [NbtTag].
241 | */
242 | @JvmStatic
243 | @Throws(IOException::class, IllegalArgumentException::class)
244 | public fun readNbtTagDirectly(input: DataInput, tagType: Class? = null): T {
245 | if (tagType == null) {
246 | val typeId = input.readByte().toInt()
247 | val serializer = serializers[typeId]
248 |
249 | @Suppress("UNCHECKED_CAST")
250 | return serializer.readTag(input) as T
251 | } else {
252 | return readNbtTagDirectly(input, tagType.kotlin)
253 | }
254 | }
255 |
256 | /**
257 | * Reads a [NbtTag] of type [T] from the input directly, this is a reader for [writeNbtTagDirectly].
258 | * @param input Where the file will be read, needs to handle compression and endianness.
259 | * @param tagType The type of the tag that will be read.
260 | * @throws IllegalArgumentException If [T] is exactly [NbtTag].
261 | */
262 | @PublishedApi
263 | @Throws(IOException::class, IllegalArgumentException::class)
264 | internal fun readNbtTagDirectly(input: DataInput, tagType: KClass): T {
265 | require(tagType != NbtTag::class) {
266 | "A final tag type is required. The NbtTag class is not a valid option for the tag type parameter."
267 | }
268 | val serializer = serializers.first { it.kClass == tagType }
269 | return tagType.cast(serializer.readTag(input))
270 | }
271 |
272 | /**
273 | * Reads a [NbtTag] of type [T] from the input directly, this is a reader for [writeNbtTagDirectly].
274 | * @param input Where the file will be read, needs to handle compression and endianness.
275 | * @param T The type of the tag that will be read.
276 | * @throws IllegalArgumentException If [T] is exactly [NbtTag].
277 | */
278 | @JvmStatic
279 | @Throws(IOException::class, IllegalArgumentException::class)
280 | public inline fun readNbtTagDirectly(input: DataInput): T {
281 | return readNbtTagDirectly(input, T::class)
282 | }
283 |
284 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
285 | /// PRIVATE AREA // PRIVATE AREA // PRIVATE AREA // PRIVATE AREA // PRIVATE AREA // PRIVATE AREA ///
286 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
287 |
288 |
289 | private val serializers = listOf(
290 | NbtEndSerial,
291 | NbtByteSerial,
292 | NbtShortSerial,
293 | NbtIntSerial,
294 | NbtLongSerial,
295 | NbtFloatSerial,
296 | NbtDoubleSerial,
297 | NbtByteArraySerial,
298 | NbtStringSerial,
299 | NbtListSerial,
300 | NbtCompoundSerial,
301 | NbtIntArraySerial,
302 | NbtLongArraySerial
303 | )
304 |
305 | private val NbtTag.typeId
306 | get() = serializers.indexOfFirst { it.kClass == this::class }
307 |
308 | private sealed class NbtSerial (val kClass: KClass){
309 | abstract fun readTag(input: DataInput): T
310 | abstract fun writeTag(output: DataOutput, tag: T)
311 |
312 | @Suppress("UNCHECKED_CAST")
313 | @JvmName("writeRawTag")
314 | fun writeTag(output: DataOutput, tag: NbtTag) {
315 | writeTag(output, tag as T)
316 | }
317 | }
318 |
319 | private object NbtEndSerial: NbtSerial(NbtEnd::class) {
320 | override fun readTag(input: DataInput): NbtEnd {
321 | return NbtEnd
322 | }
323 |
324 | override fun writeTag(output: DataOutput, tag: NbtEnd) {
325 | }
326 | }
327 |
328 | private object NbtByteSerial: NbtSerial(NbtByte::class) {
329 | override fun readTag(input: DataInput): NbtByte {
330 | return NbtByte(input.readByte())
331 | }
332 |
333 | override fun writeTag(output: DataOutput, tag: NbtByte) {
334 | output.writeByte(tag.signed.toInt())
335 | }
336 | }
337 |
338 | private object NbtShortSerial: NbtSerial(NbtShort::class) {
339 | override fun readTag(input: DataInput): NbtShort {
340 | return NbtShort(input.readShort())
341 | }
342 |
343 | override fun writeTag(output: DataOutput, tag: NbtShort) {
344 | output.writeShort(tag.value.toInt())
345 | }
346 | }
347 |
348 | private object NbtIntSerial: NbtSerial(NbtInt::class) {
349 | override fun readTag(input: DataInput): NbtInt {
350 | return NbtInt(input.readInt())
351 | }
352 |
353 | override fun writeTag(output: DataOutput, tag: NbtInt) {
354 | output.writeInt(tag.value)
355 | }
356 | }
357 |
358 | private object NbtLongSerial: NbtSerial(NbtLong::class) {
359 | override fun readTag(input: DataInput): NbtLong {
360 | return NbtLong(input.readLong())
361 | }
362 |
363 | override fun writeTag(output: DataOutput, tag: NbtLong) {
364 | output.writeLong(tag.value)
365 | }
366 | }
367 |
368 | private object NbtFloatSerial: NbtSerial(NbtFloat::class) {
369 | override fun readTag(input: DataInput): NbtFloat {
370 | return NbtFloat(input.readFloat())
371 | }
372 |
373 | override fun writeTag(output: DataOutput, tag: NbtFloat) {
374 | output.writeFloat(tag.value)
375 | }
376 | }
377 |
378 | private object NbtDoubleSerial: NbtSerial(NbtDouble::class) {
379 | override fun readTag(input: DataInput): NbtDouble {
380 | return NbtDouble(input.readDouble())
381 | }
382 |
383 | override fun writeTag(output: DataOutput, tag: NbtDouble) {
384 | output.writeDouble(tag.value)
385 | }
386 | }
387 |
388 | private object NbtByteArraySerial: NbtSerial(NbtByteArray::class) {
389 | override fun readTag(input: DataInput): NbtByteArray {
390 | val size = input.readInt()
391 | val bytes = ByteArray(size)
392 | input.readFully(bytes)
393 | return NbtByteArray(bytes)
394 | }
395 |
396 | override fun writeTag(output: DataOutput, tag: NbtByteArray) {
397 | output.writeInt(tag.value.size)
398 | output.write(tag.value)
399 | }
400 | }
401 |
402 | private object NbtStringSerial: NbtSerial(NbtString::class) {
403 | override fun readTag(input: DataInput): NbtString {
404 | return NbtString(input.readUTF())
405 | }
406 |
407 | override fun writeTag(output: DataOutput, tag: NbtString) {
408 | output.writeUTF(tag.value)
409 | }
410 | }
411 |
412 | private object NbtListSerial: NbtSerial>(NbtList::class) {
413 | override fun readTag(input: DataInput): NbtList<*> {
414 | val type = input.readUnsignedByte()
415 | val size = input.readInt()
416 | if (type == 0 && size > 0) {
417 | error("Missing type on NbtList")
418 | }
419 | val serializer = serializers[type]
420 | val list = mutableListOf()
421 | for (i in 1..size) {
422 | list += serializer.readTag(input)
423 | }
424 | return NbtList(list)
425 | }
426 |
427 | override fun writeTag(output: DataOutput, tag: NbtList<*>) {
428 | val sample = tag.firstOrNull() ?: NbtEnd
429 | val typeId = sample.typeId
430 | val serializer = serializers[typeId]
431 |
432 | if (typeId == 0 && tag.size > 0) {
433 | error("NbtList cannot have NbtEnd")
434 | }
435 |
436 | output.writeByte(typeId)
437 | output.writeInt(tag.size)
438 | tag.forEach {
439 | serializer.writeTag(output, it)
440 | }
441 | }
442 | }
443 |
444 | private object NbtCompoundSerial: NbtSerial(NbtCompound::class) {
445 | override fun readTag(input: DataInput): NbtCompound {
446 | val map = mutableMapOf()
447 | while (true) {
448 | val typeId = input.readUnsignedByte()
449 | if (typeId == 0) {
450 | break
451 | }
452 |
453 | val name = input.readUTF()
454 | val serializer = serializers[typeId]
455 | val childTag = serializer.readTag(input)
456 | map[name] = childTag
457 | }
458 | return NbtCompound(map)
459 | }
460 |
461 | override fun writeTag(output: DataOutput, tag: NbtCompound) {
462 | check(tag.values.none { it == NbtEnd }) {
463 | "NbtCompound cannot have an NbtEnd"
464 | }
465 |
466 | tag.forEach { (name, childTag) ->
467 | val typeId = childTag.typeId
468 | val serializer = serializers[typeId]
469 | output.writeByte(typeId)
470 | output.writeUTF(name)
471 | serializer.writeTag(output, childTag)
472 | }
473 |
474 | output.writeByte(0)
475 | }
476 | }
477 |
478 | private object NbtIntArraySerial: NbtSerial(NbtIntArray::class) {
479 | override fun readTag(input: DataInput): NbtIntArray {
480 | val size = input.readInt()
481 | val array = IntArray(size)
482 | for (i in 0 until size) {
483 | array[i] = input.readInt()
484 | }
485 | return NbtIntArray(array)
486 | }
487 |
488 | override fun writeTag(output: DataOutput, tag: NbtIntArray) {
489 | output.writeInt(tag.value.size)
490 | tag.value.forEach {
491 | output.writeInt(it)
492 | }
493 | }
494 | }
495 |
496 | private object NbtLongArraySerial: NbtSerial(NbtLongArray::class) {
497 | override fun readTag(input: DataInput): NbtLongArray {
498 | val size = input.readInt()
499 | val array = LongArray(size)
500 | for (i in 0 until size) {
501 | array[i] = input.readLong()
502 | }
503 | return NbtLongArray(array)
504 | }
505 |
506 | override fun writeTag(output: DataOutput, tag: NbtLongArray) {
507 | output.writeInt(tag.value.size)
508 | tag.value.forEach {
509 | output.writeLong(it)
510 | }
511 | }
512 | }
513 | }
514 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtInt.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | /**
4 | * A tag which wraps an int value.
5 | * @property value The wrapped value
6 | */
7 | public data class NbtInt(var value: Int) : NbtTag() {
8 | /**
9 | * Returns a string representation of the tag's value.
10 | *
11 | * The returned string is compatible with string constructors of the same type.
12 | */
13 | override val stringValue: String
14 | get() = value.toString()
15 |
16 | /**
17 | * Parses the string value as a signed int and wraps it.
18 | * @param signed Signed value from `-2147483648` to `2147483647`.
19 | * @throws NumberFormatException if the number is not within a valid range or if the string does not contain a valid number.
20 | */
21 | @Throws(NumberFormatException::class)
22 | public constructor(signed: String): this(signed.toInt())
23 |
24 | /**
25 | * Returns a new wrapper with the current value.
26 | */
27 | override fun deepCopy(): NbtInt = copy()
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtIntArray.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | /**
4 | * A tag which wraps a mutable int array.
5 | * @property value The wrapped value
6 | */
7 | public data class NbtIntArray(var value: IntArray): NbtTag() {
8 | /**
9 | * Returns a string representation of the tag's value with a structure similar to a normal [List].
10 | *
11 | * The returned string is compatible with string constructors of the same type.
12 | *
13 | * Be aware that this may be a slow operation on big arrays.
14 | */
15 | override val stringValue: String
16 | get() = value.takeIf { it.isNotEmpty() }?.joinToString(prefix = "[", postfix = "]") ?: "[]"
17 |
18 | /**
19 | * Creates a new tag with an empty array.
20 | */
21 | public constructor(): this(intArrayOf())
22 |
23 | /**
24 | * Parses the string using the same structure which is returned by [stringValue].
25 | *
26 | * @param value A string with a structure like `[0, -32, 48, 127]`
27 | *
28 | * @throws IllegalArgumentException if the string does not have the exact format outputted by [stringValue]
29 | */
30 | @Throws(IllegalArgumentException::class)
31 | public constructor(value: String): this(value
32 | .removeSurrounding("[", "]")
33 | .split(", ")
34 | .takeIf { it.size > 1 || it.firstOrNull()?.isNotEmpty() == true }
35 | ?.map { it.toInt() }
36 | ?.toIntArray()
37 | ?: intArrayOf()
38 | )
39 |
40 | /**
41 | * A technical string representation of this tag, containing the tag type, and it's value,
42 | * appropriated for developer inspections.
43 | *
44 | * The output will be similar to a normal [List].
45 | *
46 | * Be aware that this may be a slow operation on big arrays.
47 | */
48 | override fun toTechnicalString(): String {
49 | return "NbtIntArray$stringValue"
50 | }
51 |
52 | /**
53 | * Properly checks the equality of the array.
54 | */
55 | override fun equals(other: Any?): Boolean {
56 | if (this === other) return true
57 | if (javaClass != other?.javaClass) return false
58 |
59 | other as NbtIntArray
60 |
61 | if (!value.contentEquals(other.value)) return false
62 |
63 | return true
64 | }
65 |
66 | /**
67 | * Properly calculates the hashcode of the array.
68 | */
69 | override fun hashCode(): Int {
70 | return value.contentHashCode()
71 | }
72 |
73 | /**
74 | * Returns a new wrapper with a copy of the current value.
75 | */
76 | override fun deepCopy(): NbtIntArray = copy(value = value.copyOf())
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/gamemods/nbtmanipulator/NbtList.kt:
--------------------------------------------------------------------------------
1 | package br.com.gamemods.nbtmanipulator
2 |
3 | /**
4 | * A tag which contains a [MutableList] structure of [NbtTag]s. All children must have the same class.
5 | *
6 | * @param T The type of the tag that will be wrapped. [NbtEnd] and [NbtTag] are not valid.
7 | */
8 | public class NbtList private constructor(private val tags: ArrayList): NbtTag(), MutableList by tags, RandomAccess {
9 | /**
10 | * Returns a string representation of the tag's value.
11 | *
12 | * The output will be similar to a normal [List].
13 | *
14 | * The class names of the children tags will expose.
15 | *
16 | * The returned string is compatible with string constructors of the same type.
17 | *
18 | * Be aware that this may be a slow operation on big lists, arrays or compounds.
19 | */
20 | override val stringValue: String
21 | get() = tags.toString()
22 |
23 | /**
24 | * Constructs a [NbtList] with the same contents of the given [Collection].
25 | *
26 | * All items in the list must have the same class.
27 | *
28 | * Null values in the list are not allowed.
29 | *
30 | * The tags in the list will be linked so any modification will also change this tag contents.
31 | */
32 | public constructor(tags: Collection): this(ArrayList(tags))
33 |
34 | /**
35 | * Creates an empty list.
36 | */
37 | public constructor(): this(emptyList())
38 |
39 | /**
40 | * Uses all tags as initial value of this list. Make sure to use the same class in all values.
41 | */
42 | public constructor(vararg tags: T): this(tags.toList())
43 |
44 | /**
45 | * Uses all tags as initial value of this list. Make sure to use the same class in all values.
46 | */
47 | public constructor(tags: Iterable): this(tags.toList())
48 |
49 | /**
50 | * Uses all tags as initial value of this list. Make sure to use the same class in all values.
51 | */
52 | public constructor(tags: Sequence): this(tags.toList())
53 |
54 | /**
55 | * Uses all tags as initial value of this list. Make sure to use the same class in all values.
56 | */
57 | public constructor(tags: NbtList): this(tags as Collection)
58 |
59 | /**
60 | * Parses the string using the same structure which is returned by [stringValue].
61 | *
62 | * @param value A string with a structure like `[NbtInt(0), NbtInt(-32), NbtInt(48), NbtInt(127)]`
63 | *
64 | * @throws IllegalArgumentException if the string does not have the exact format outputted by [stringValue]
65 | */
66 | @Suppress("UNCHECKED_CAST")
67 | @Throws(IllegalArgumentException::class)
68 | public constructor(value: String): this(NbtListStringParser(value).parseList() as ArrayList)
69 |
70 | override fun add(element: T): Boolean {
71 | checkTagType(element)
72 | return tags.add(element)
73 | }
74 |
75 | override fun add(index: Int, element: T) {
76 | checkTagType(element)
77 | return tags.add(index, element)
78 | }
79 |
80 | override fun set(index: Int, element: T): T {
81 | if (size > 1) {
82 | checkTagType(element)
83 | }
84 | return tags.set(index, element)
85 | }
86 |
87 | override fun subList(fromIndex: Int, toIndex: Int): MutableList {
88 | val subList = tags.subList(fromIndex, toIndex)
89 | return object : MutableList by subList, RandomAccess {
90 | override fun set(index: Int, element: T): T {
91 | checkTagType(element)
92 | return subList.set(index, element)
93 | }
94 |
95 | override fun toString(): String {
96 | return tags.toString()
97 | }
98 |
99 | override fun equals(other: Any?): Boolean {
100 | return subList == other
101 | }
102 |
103 | override fun hashCode(): Int {
104 | return subList.hashCode()
105 | }
106 | }
107 | }
108 |
109 | private fun checkTagType(tag: NbtTag) {
110 | val childrenType = firstOrNull()?.javaClass ?: return
111 | require(childrenType == tag.javaClass) {
112 | "NbtList must have all children tags of the same type. \n" +
113 | "Tried to add a ${tag.javaClass.simpleName} tag in a NbtList of ${childrenType.javaClass.simpleName}"
114 | }
115 | }
116 |
117 | /**
118 | * Returns a new NbtList with all nested values copied deeply.
119 | */
120 | override fun deepCopy(): NbtList = NbtList(map {
121 | @Suppress("UNCHECKED_CAST")
122 | it.deepCopy() as T
123 | })
124 |
125 | /**
126 | * A technical string representation of this tag, containing the tag type, and it's value,
127 | * appropriated for developer inspections.
128 | *
129 | * The output will be similar to a normal [List].
130 | *
131 | * Be aware that this may be a slow operation on big lists, arrays or compounds.
132 | */
133 | override fun toTechnicalString(): String {
134 | if (tags.isEmpty()) {
135 | return "NbtList[]"
136 | }
137 | return tags.joinToString(prefix = "NbtList[", postfix = "]")
138 | }
139 |
140 | override fun equals(other: Any?): Boolean {
141 | return tags == other
142 | }
143 |
144 | override fun hashCode(): Int {
145 | return tags.hashCode()
146 | }
147 |
148 |
149 | /**
150 | * Contains useful methods to create [NbtList]s from Java.
151 | *
152 | * Kotlin's users may call `list(1,2,3).toNbtList()` or similar methods.
153 | */
154 | public companion object {
155 | /**
156 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
157 | */
158 | @JvmStatic
159 | public fun create(vararg tags: Byte): NbtList = tags.toNbtList()
160 | /**
161 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
162 | */
163 | @JvmStatic
164 | public fun create(vararg tags: Short): NbtList = tags.toNbtList()
165 | /**
166 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
167 | */
168 | @JvmStatic
169 | public fun create(vararg tags: Int): NbtList = tags.toNbtList()
170 | /**
171 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
172 | */
173 | @JvmStatic
174 | public fun create(vararg tags: Long): NbtList = tags.toNbtList()
175 | /**
176 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
177 | */
178 | @JvmStatic
179 | public fun create(vararg tags: Float): NbtList = tags.toNbtList()
180 | /**
181 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
182 | */
183 | @JvmStatic
184 | public fun create(vararg tags: Double): NbtList = tags.toNbtList()
185 | /**
186 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
187 | */
188 | @JvmStatic
189 | public fun create(vararg tags: String): NbtList = tags.toNbtList()
190 | /**
191 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
192 | */
193 | @JvmStatic
194 | public fun create(vararg tags: ByteArray): NbtList = tags.toNbtList()
195 | /**
196 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
197 | */
198 | @JvmStatic
199 | public fun create(vararg tags: IntArray): NbtList = tags.toNbtList()
200 | /**
201 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
202 | */
203 | @JvmStatic
204 | public fun create(vararg tags: LongArray): NbtList = tags.toNbtList()
205 | /**
206 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
207 | */
208 | @JvmStatic
209 | public fun create(vararg tags: Map): NbtList = tags.toNbtList()
210 | /**
211 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
212 | */
213 | @JvmStatic
214 | public fun create(vararg tags: Iterable): NbtList> = tags.toNbtList()
215 | /**
216 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
217 | */
218 | @JvmStatic
219 | public fun createByteSublist(vararg tags: Iterable): NbtList> = tags.toNbtList()
220 | /**
221 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
222 | */
223 | @JvmStatic
224 | public fun createByteSublist(vararg tags: Array): NbtList> = tags.toNbtList()
225 | /**
226 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
227 | */
228 | @JvmStatic
229 | public fun createByteSublist(vararg tags: ByteArray): NbtList> = tags.map { it.asIterable() }.toNbtList()
230 | /**
231 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
232 | */
233 | @JvmStatic
234 | public fun createShortSublist(vararg tags: Iterable): NbtList> = tags.toNbtList()
235 | /**
236 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
237 | */
238 | @JvmStatic
239 | public fun createShortSublist(vararg tags: Array): NbtList> = tags.toNbtList()
240 | /**
241 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
242 | */
243 | @JvmStatic
244 | public fun createShortSublist(vararg tags: ShortArray): NbtList> = tags.map { it.asIterable() }.toNbtList()
245 | /**
246 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
247 | */
248 | @JvmStatic
249 | public fun createIntSublist(vararg tags: Iterable): NbtList> = tags.toNbtList()
250 | /**
251 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
252 | */
253 | @JvmStatic
254 | public fun createIntSublist(vararg tags: Array): NbtList> = tags.toNbtList()
255 | /**
256 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
257 | */
258 | @JvmStatic
259 | public fun createIntSublist(vararg tags: IntArray): NbtList> = tags.map { it.asIterable() }.toNbtList()
260 | /**
261 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
262 | */
263 | @JvmStatic
264 | public fun createFloatSublist(vararg tags: Iterable): NbtList> = tags.toNbtList()
265 | /**
266 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
267 | */
268 | @JvmStatic
269 | public fun createFloatSublist(vararg tags: Array): NbtList> = tags.toNbtList()
270 | /**
271 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
272 | */
273 | @JvmStatic
274 | public fun createFloatSublist(vararg tags: FloatArray): NbtList> = tags.map { it.asIterable() }.toNbtList()
275 | /**
276 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
277 | */
278 | @JvmStatic
279 | public fun createDoubleSublist(vararg tags: Iterable): NbtList> = tags.toNbtList()
280 | /**
281 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
282 | */
283 | @JvmStatic
284 | public fun createDoubleSublist(vararg tags: Array): NbtList> = tags.toNbtList()
285 | /**
286 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
287 | */
288 | @JvmStatic
289 | public fun createDoubleSublist(vararg tags: DoubleArray): NbtList> = tags.map { it.asIterable() }.toNbtList()
290 | /**
291 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
292 | */
293 | @JvmStatic
294 | public fun createStringSublist(vararg tags: Iterable): NbtList> = tags.toNbtList()
295 | /**
296 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
297 | */
298 | @JvmStatic
299 | public fun createStringSublist(vararg tags: Array): NbtList> = tags.toNbtList()
300 | /**
301 | * Returns a [NbtList] contained all elements wrapped in the appropriated [NbtTag].
302 | */
303 | @JvmStatic
304 | public fun createCompoundSublist(vararg tags: Iterable