├── .github └── workflows │ ├── gradle.yml │ └── tests.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── antlr ├── build.gradle.kts └── src │ └── main │ ├── java │ └── org │ │ └── jglrxavpok │ │ └── hephaistos │ │ └── antlr │ │ ├── SNBT.interp │ │ ├── SNBT.tokens │ │ ├── SNBTBaseListener.java │ │ ├── SNBTBaseVisitor.java │ │ ├── SNBTLexer.interp │ │ ├── SNBTLexer.java │ │ ├── SNBTLexer.tokens │ │ ├── SNBTListener.java │ │ ├── SNBTParser.java │ │ └── SNBTVisitor.java │ └── resources │ └── SNBT.g4 ├── build.gradle.kts ├── common ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── jglrxavpok │ │ └── hephaistos │ │ ├── Options.kt │ │ ├── SNBTParsingVisitor.kt │ │ ├── collections │ │ ├── ImmutableByteArray.kt │ │ ├── ImmutableIntArray.kt │ │ └── ImmutableLongArray.kt │ │ ├── data │ │ ├── DataSource.kt │ │ ├── GrowableSource.kt │ │ └── RandomAccessFileSource.kt │ │ ├── mca │ │ ├── AnvilException.kt │ │ ├── BlockState.kt │ │ ├── ChunkColumn.kt │ │ ├── ChunkSection.kt │ │ ├── Coordinates.kt │ │ ├── Heightmap.kt │ │ ├── LongCompactor.kt │ │ ├── Palette.kt │ │ ├── RegionFile.kt │ │ ├── SupportedVersion.kt │ │ ├── readers │ │ │ ├── ChunkReader.kt │ │ │ ├── ChunkSectionReader.kt │ │ │ ├── ChunkVersionedFields.kt │ │ │ └── SectionBiomeInformation.kt │ │ └── writer │ │ │ ├── ChunkSectionWriter.kt │ │ │ └── ChunkWriter.kt │ │ ├── mcdata │ │ ├── Biome.kt │ │ └── Sizes.kt │ │ ├── nbt │ │ ├── CompressedProcesser.kt │ │ ├── Extensions.kt │ │ ├── NBT.kt │ │ ├── NBTByte.kt │ │ ├── NBTByteArray.kt │ │ ├── NBTCompound.kt │ │ ├── NBTCompoundGetters.kt │ │ ├── NBTCompoundLike.kt │ │ ├── NBTDouble.kt │ │ ├── NBTEnd.kt │ │ ├── NBTException.kt │ │ ├── NBTFloat.kt │ │ ├── NBTInt.kt │ │ ├── NBTIntArray.kt │ │ ├── NBTList.kt │ │ ├── NBTLong.kt │ │ ├── NBTLongArray.kt │ │ ├── NBTNumber.kt │ │ ├── NBTReader.kt │ │ ├── NBTReaderCompanion.kt │ │ ├── NBTShort.kt │ │ ├── NBTString.kt │ │ ├── NBTType.kt │ │ ├── NBTWriter.kt │ │ └── mutable │ │ │ └── MutableNBTCompound.kt │ │ └── parser │ │ └── SNBTParser.kt │ └── test │ ├── java │ ├── LoadTest.java │ ├── mca │ │ ├── ChunkHeightGuessing.java │ │ ├── ChunkMutability.java │ │ ├── DataSourceProvider.java │ │ ├── HeightmapCompression.java │ │ ├── MCACoordinates.java │ │ ├── MCALoadAndSave.java │ │ ├── MCALoading.java │ │ ├── MCASaving.java │ │ ├── PaletteTests.java │ │ ├── ParallelizationTests.java │ │ ├── PathProvider.java │ │ ├── ReadLevelDat.java │ │ ├── SectionTests.java │ │ └── SupportedVersionProvider.java │ ├── nbt │ │ ├── CompressedModeProvider.java │ │ ├── Misc.java │ │ ├── NBTCompoundMethods.java │ │ ├── NBTLoading.java │ │ ├── NBTSaving.java │ │ └── NBTValue.java │ ├── regression │ │ ├── Issue17.java │ │ ├── Issue29.java │ │ ├── Issue3.java │ │ └── Issue8.java │ └── snbt │ │ ├── SNBTCoherencyTests.java │ │ ├── SNBTParserTests.java │ │ └── WhitespaceParsing.java │ ├── kotlin │ └── GrowableSourceTests.kt │ └── resources │ ├── 1.18-pre4regions │ └── r.0.0.mca │ ├── 1.20 │ └── r.0.0.mca │ ├── bigtest.nbt │ ├── hello_world.nbt │ ├── issue_3 │ ├── README.md │ └── r.0.0.mca │ ├── issue_4 │ └── r.0.0.mca │ ├── level.1.16.dat │ ├── level.1.18.dat │ └── r.0.0.mca ├── convention-plugins ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── convention.publication.gradle.kts ├── docs └── migration_guide_v1_v2.md ├── example └── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── gson ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── jglrxavpok │ │ └── hephaistos │ │ └── json │ │ ├── NBTGsonReader.kt │ │ └── NBTGsonWriter.kt │ └── test │ └── java │ ├── TestReaderCompound.java │ ├── TestReaderLists.java │ ├── TestReaderPrimitives.java │ ├── TestWriterCompound.java │ ├── TestWriterLists.java │ └── TestWriterPrimitives.java ├── jitpack.yml ├── settings.gradle.kts └── viewer ├── build.gradle.kts └── src └── main └── kotlin └── io └── github └── jglrxavpok └── ViewerApp.kt /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Gradle Build 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up JDK 11 16 | uses: actions/setup-java@v2 17 | with: 18 | distribution: 'adopt' 19 | java-version: 11 20 | - name: Grant execute permission for gradlew 21 | run: chmod +x gradlew 22 | - name: Build with Gradle 23 | run: ./gradlew build -x test 24 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Gradle Tests 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up JDK 11 16 | uses: actions/setup-java@v2 17 | with: 18 | distribution: 'adopt' 19 | java-version: 11 20 | - name: Grant execute permission for gradlew 21 | run: chmod +x gradlew 22 | - name: Build with Gradle 23 | run: ./gradlew test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | secrets.properties 2 | .gradle 3 | **/build/ 4 | !common/src/**/build/ 5 | 6 | # Ignore Gradle GUI config 7 | gradle-app.setting 8 | 9 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 10 | !gradle-wrapper.jar 11 | 12 | # Cache of project 13 | .gradletasknamecache 14 | 15 | # Full idea folder 16 | .idea 17 | common/tmp_parallel_empty_r.0.0.mca 18 | common/tmp_save_r.0.0.mca 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 Xavier "jglrxavpok" Niochaut 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. -------------------------------------------------------------------------------- /antlr/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `maven-publish` 3 | id("convention.publication") 4 | } 5 | 6 | dependencies { 7 | // https://mvnrepository.com/artifact/org.antlr/antlr4-runtime 8 | api("org.antlr:antlr4-runtime:4.10.1") 9 | } 10 | 11 | publishing { 12 | publications { 13 | create("maven") { 14 | from(components["java"]) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /antlr/src/main/java/org/jglrxavpok/hephaistos/antlr/SNBT.interp: -------------------------------------------------------------------------------- 1 | token literal names: 2 | null 3 | '{' 4 | ',' 5 | '}' 6 | ':' 7 | '[' 8 | ']' 9 | 'B' 10 | ';' 11 | 'I' 12 | 'L' 13 | null 14 | null 15 | null 16 | '-' 17 | null 18 | null 19 | null 20 | null 21 | null 22 | null 23 | null 24 | null 25 | 26 | token symbolic names: 27 | null 28 | null 29 | null 30 | null 31 | null 32 | null 33 | null 34 | null 35 | null 36 | null 37 | null 38 | DoubleQuoteText 39 | SingleQuoteText 40 | BOOLEAN 41 | NEGATIVE_SIGN 42 | FLOAT 43 | DOUBLE 44 | INTEGER 45 | LONG 46 | BYTE 47 | SHORT 48 | WS 49 | IDENTIFIER_LETTERS 50 | 51 | rule names: 52 | snbt 53 | element 54 | compound 55 | namedElement 56 | list 57 | byteArray 58 | intArray 59 | longArray 60 | doubleNBT 61 | floatNBT 62 | longNBT 63 | byteNBT 64 | shortNBT 65 | intNBT 66 | stringNBT 67 | identifier 68 | 69 | 70 | atn: 71 | [4, 1, 22, 162, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 48, 8, 1, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 54, 8, 2, 10, 2, 12, 2, 57, 9, 2, 3, 2, 59, 8, 2, 1, 2, 3, 2, 62, 8, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 5, 4, 74, 8, 4, 10, 4, 12, 4, 77, 9, 4, 3, 4, 79, 8, 4, 1, 4, 3, 4, 82, 8, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 92, 8, 5, 10, 5, 12, 5, 95, 9, 5, 1, 5, 3, 5, 98, 8, 5, 3, 5, 100, 8, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 110, 8, 6, 10, 6, 12, 6, 113, 9, 6, 1, 6, 3, 6, 116, 8, 6, 3, 6, 118, 8, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 128, 8, 7, 10, 7, 12, 7, 131, 9, 7, 1, 7, 3, 7, 134, 8, 7, 3, 7, 136, 8, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 3, 14, 155, 8, 14, 1, 15, 4, 15, 158, 8, 15, 11, 15, 12, 15, 159, 1, 15, 0, 0, 16, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 0, 1, 2, 0, 13, 13, 19, 19, 174, 0, 32, 1, 0, 0, 0, 2, 47, 1, 0, 0, 0, 4, 49, 1, 0, 0, 0, 6, 65, 1, 0, 0, 0, 8, 69, 1, 0, 0, 0, 10, 85, 1, 0, 0, 0, 12, 103, 1, 0, 0, 0, 14, 121, 1, 0, 0, 0, 16, 139, 1, 0, 0, 0, 18, 141, 1, 0, 0, 0, 20, 143, 1, 0, 0, 0, 22, 145, 1, 0, 0, 0, 24, 147, 1, 0, 0, 0, 26, 149, 1, 0, 0, 0, 28, 154, 1, 0, 0, 0, 30, 157, 1, 0, 0, 0, 32, 33, 3, 2, 1, 0, 33, 34, 5, 0, 0, 1, 34, 1, 1, 0, 0, 0, 35, 48, 3, 22, 11, 0, 36, 48, 3, 18, 9, 0, 37, 48, 3, 24, 12, 0, 38, 48, 3, 20, 10, 0, 39, 48, 3, 26, 13, 0, 40, 48, 3, 16, 8, 0, 41, 48, 3, 28, 14, 0, 42, 48, 3, 10, 5, 0, 43, 48, 3, 12, 6, 0, 44, 48, 3, 14, 7, 0, 45, 48, 3, 8, 4, 0, 46, 48, 3, 4, 2, 0, 47, 35, 1, 0, 0, 0, 47, 36, 1, 0, 0, 0, 47, 37, 1, 0, 0, 0, 47, 38, 1, 0, 0, 0, 47, 39, 1, 0, 0, 0, 47, 40, 1, 0, 0, 0, 47, 41, 1, 0, 0, 0, 47, 42, 1, 0, 0, 0, 47, 43, 1, 0, 0, 0, 47, 44, 1, 0, 0, 0, 47, 45, 1, 0, 0, 0, 47, 46, 1, 0, 0, 0, 48, 3, 1, 0, 0, 0, 49, 58, 5, 1, 0, 0, 50, 55, 3, 6, 3, 0, 51, 52, 5, 2, 0, 0, 52, 54, 3, 6, 3, 0, 53, 51, 1, 0, 0, 0, 54, 57, 1, 0, 0, 0, 55, 53, 1, 0, 0, 0, 55, 56, 1, 0, 0, 0, 56, 59, 1, 0, 0, 0, 57, 55, 1, 0, 0, 0, 58, 50, 1, 0, 0, 0, 58, 59, 1, 0, 0, 0, 59, 61, 1, 0, 0, 0, 60, 62, 5, 2, 0, 0, 61, 60, 1, 0, 0, 0, 61, 62, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 64, 5, 3, 0, 0, 64, 5, 1, 0, 0, 0, 65, 66, 3, 28, 14, 0, 66, 67, 5, 4, 0, 0, 67, 68, 3, 2, 1, 0, 68, 7, 1, 0, 0, 0, 69, 78, 5, 5, 0, 0, 70, 75, 3, 2, 1, 0, 71, 72, 5, 2, 0, 0, 72, 74, 3, 2, 1, 0, 73, 71, 1, 0, 0, 0, 74, 77, 1, 0, 0, 0, 75, 73, 1, 0, 0, 0, 75, 76, 1, 0, 0, 0, 76, 79, 1, 0, 0, 0, 77, 75, 1, 0, 0, 0, 78, 70, 1, 0, 0, 0, 78, 79, 1, 0, 0, 0, 79, 81, 1, 0, 0, 0, 80, 82, 5, 2, 0, 0, 81, 80, 1, 0, 0, 0, 81, 82, 1, 0, 0, 0, 82, 83, 1, 0, 0, 0, 83, 84, 5, 6, 0, 0, 84, 9, 1, 0, 0, 0, 85, 86, 5, 5, 0, 0, 86, 87, 5, 7, 0, 0, 87, 99, 5, 8, 0, 0, 88, 93, 3, 22, 11, 0, 89, 90, 5, 2, 0, 0, 90, 92, 3, 22, 11, 0, 91, 89, 1, 0, 0, 0, 92, 95, 1, 0, 0, 0, 93, 91, 1, 0, 0, 0, 93, 94, 1, 0, 0, 0, 94, 97, 1, 0, 0, 0, 95, 93, 1, 0, 0, 0, 96, 98, 5, 2, 0, 0, 97, 96, 1, 0, 0, 0, 97, 98, 1, 0, 0, 0, 98, 100, 1, 0, 0, 0, 99, 88, 1, 0, 0, 0, 99, 100, 1, 0, 0, 0, 100, 101, 1, 0, 0, 0, 101, 102, 5, 6, 0, 0, 102, 11, 1, 0, 0, 0, 103, 104, 5, 5, 0, 0, 104, 105, 5, 9, 0, 0, 105, 117, 5, 8, 0, 0, 106, 111, 3, 26, 13, 0, 107, 108, 5, 2, 0, 0, 108, 110, 3, 26, 13, 0, 109, 107, 1, 0, 0, 0, 110, 113, 1, 0, 0, 0, 111, 109, 1, 0, 0, 0, 111, 112, 1, 0, 0, 0, 112, 115, 1, 0, 0, 0, 113, 111, 1, 0, 0, 0, 114, 116, 5, 2, 0, 0, 115, 114, 1, 0, 0, 0, 115, 116, 1, 0, 0, 0, 116, 118, 1, 0, 0, 0, 117, 106, 1, 0, 0, 0, 117, 118, 1, 0, 0, 0, 118, 119, 1, 0, 0, 0, 119, 120, 5, 6, 0, 0, 120, 13, 1, 0, 0, 0, 121, 122, 5, 5, 0, 0, 122, 123, 5, 10, 0, 0, 123, 135, 5, 8, 0, 0, 124, 129, 3, 20, 10, 0, 125, 126, 5, 2, 0, 0, 126, 128, 3, 20, 10, 0, 127, 125, 1, 0, 0, 0, 128, 131, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 129, 130, 1, 0, 0, 0, 130, 133, 1, 0, 0, 0, 131, 129, 1, 0, 0, 0, 132, 134, 5, 2, 0, 0, 133, 132, 1, 0, 0, 0, 133, 134, 1, 0, 0, 0, 134, 136, 1, 0, 0, 0, 135, 124, 1, 0, 0, 0, 135, 136, 1, 0, 0, 0, 136, 137, 1, 0, 0, 0, 137, 138, 5, 6, 0, 0, 138, 15, 1, 0, 0, 0, 139, 140, 5, 16, 0, 0, 140, 17, 1, 0, 0, 0, 141, 142, 5, 15, 0, 0, 142, 19, 1, 0, 0, 0, 143, 144, 5, 18, 0, 0, 144, 21, 1, 0, 0, 0, 145, 146, 7, 0, 0, 0, 146, 23, 1, 0, 0, 0, 147, 148, 5, 20, 0, 0, 148, 25, 1, 0, 0, 0, 149, 150, 5, 17, 0, 0, 150, 27, 1, 0, 0, 0, 151, 155, 3, 30, 15, 0, 152, 155, 5, 11, 0, 0, 153, 155, 5, 12, 0, 0, 154, 151, 1, 0, 0, 0, 154, 152, 1, 0, 0, 0, 154, 153, 1, 0, 0, 0, 155, 29, 1, 0, 0, 0, 156, 158, 5, 22, 0, 0, 157, 156, 1, 0, 0, 0, 158, 159, 1, 0, 0, 0, 159, 157, 1, 0, 0, 0, 159, 160, 1, 0, 0, 0, 160, 31, 1, 0, 0, 0, 18, 47, 55, 58, 61, 75, 78, 81, 93, 97, 99, 111, 115, 117, 129, 133, 135, 154, 159] -------------------------------------------------------------------------------- /antlr/src/main/java/org/jglrxavpok/hephaistos/antlr/SNBT.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | T__9=10 11 | DoubleQuoteText=11 12 | SingleQuoteText=12 13 | BOOLEAN=13 14 | NEGATIVE_SIGN=14 15 | FLOAT=15 16 | DOUBLE=16 17 | INTEGER=17 18 | LONG=18 19 | BYTE=19 20 | SHORT=20 21 | WS=21 22 | IDENTIFIER_LETTERS=22 23 | '{'=1 24 | ','=2 25 | '}'=3 26 | ':'=4 27 | '['=5 28 | ']'=6 29 | 'B'=7 30 | ';'=8 31 | 'I'=9 32 | 'L'=10 33 | '-'=14 34 | -------------------------------------------------------------------------------- /antlr/src/main/java/org/jglrxavpok/hephaistos/antlr/SNBTBaseVisitor.java: -------------------------------------------------------------------------------- 1 | // Generated from C:/Users/jglrxavpok/Documents/Programmation/MCModding/NBTMCALib/antlr/src/main/resources\SNBT.g4 by ANTLR 4.10.1 2 | package org.jglrxavpok.hephaistos.antlr; 3 | import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; 4 | 5 | /** 6 | * This class provides an empty implementation of {@link SNBTVisitor}, 7 | * which can be extended to create a visitor which only needs to handle a subset 8 | * of the available methods. 9 | * 10 | * @param The return type of the visit operation. Use {@link Void} for 11 | * operations with no return type. 12 | */ 13 | public class SNBTBaseVisitor extends AbstractParseTreeVisitor implements SNBTVisitor { 14 | /** 15 | * {@inheritDoc} 16 | * 17 | *

The default implementation returns the result of calling 18 | * {@link #visitChildren} on {@code ctx}.

19 | */ 20 | @Override public T visitSnbt(SNBTParser.SnbtContext ctx) { return visitChildren(ctx); } 21 | /** 22 | * {@inheritDoc} 23 | * 24 | *

The default implementation returns the result of calling 25 | * {@link #visitChildren} on {@code ctx}.

26 | */ 27 | @Override public T visitElement(SNBTParser.ElementContext ctx) { return visitChildren(ctx); } 28 | /** 29 | * {@inheritDoc} 30 | * 31 | *

The default implementation returns the result of calling 32 | * {@link #visitChildren} on {@code ctx}.

33 | */ 34 | @Override public T visitCompound(SNBTParser.CompoundContext ctx) { return visitChildren(ctx); } 35 | /** 36 | * {@inheritDoc} 37 | * 38 | *

The default implementation returns the result of calling 39 | * {@link #visitChildren} on {@code ctx}.

40 | */ 41 | @Override public T visitNamedElement(SNBTParser.NamedElementContext ctx) { return visitChildren(ctx); } 42 | /** 43 | * {@inheritDoc} 44 | * 45 | *

The default implementation returns the result of calling 46 | * {@link #visitChildren} on {@code ctx}.

47 | */ 48 | @Override public T visitList(SNBTParser.ListContext ctx) { return visitChildren(ctx); } 49 | /** 50 | * {@inheritDoc} 51 | * 52 | *

The default implementation returns the result of calling 53 | * {@link #visitChildren} on {@code ctx}.

54 | */ 55 | @Override public T visitByteArray(SNBTParser.ByteArrayContext ctx) { return visitChildren(ctx); } 56 | /** 57 | * {@inheritDoc} 58 | * 59 | *

The default implementation returns the result of calling 60 | * {@link #visitChildren} on {@code ctx}.

61 | */ 62 | @Override public T visitIntArray(SNBTParser.IntArrayContext ctx) { return visitChildren(ctx); } 63 | /** 64 | * {@inheritDoc} 65 | * 66 | *

The default implementation returns the result of calling 67 | * {@link #visitChildren} on {@code ctx}.

68 | */ 69 | @Override public T visitLongArray(SNBTParser.LongArrayContext ctx) { return visitChildren(ctx); } 70 | /** 71 | * {@inheritDoc} 72 | * 73 | *

The default implementation returns the result of calling 74 | * {@link #visitChildren} on {@code ctx}.

75 | */ 76 | @Override public T visitDoubleNBT(SNBTParser.DoubleNBTContext ctx) { return visitChildren(ctx); } 77 | /** 78 | * {@inheritDoc} 79 | * 80 | *

The default implementation returns the result of calling 81 | * {@link #visitChildren} on {@code ctx}.

82 | */ 83 | @Override public T visitFloatNBT(SNBTParser.FloatNBTContext ctx) { return visitChildren(ctx); } 84 | /** 85 | * {@inheritDoc} 86 | * 87 | *

The default implementation returns the result of calling 88 | * {@link #visitChildren} on {@code ctx}.

89 | */ 90 | @Override public T visitLongNBT(SNBTParser.LongNBTContext ctx) { return visitChildren(ctx); } 91 | /** 92 | * {@inheritDoc} 93 | * 94 | *

The default implementation returns the result of calling 95 | * {@link #visitChildren} on {@code ctx}.

96 | */ 97 | @Override public T visitByteNBT(SNBTParser.ByteNBTContext ctx) { return visitChildren(ctx); } 98 | /** 99 | * {@inheritDoc} 100 | * 101 | *

The default implementation returns the result of calling 102 | * {@link #visitChildren} on {@code ctx}.

103 | */ 104 | @Override public T visitShortNBT(SNBTParser.ShortNBTContext ctx) { return visitChildren(ctx); } 105 | /** 106 | * {@inheritDoc} 107 | * 108 | *

The default implementation returns the result of calling 109 | * {@link #visitChildren} on {@code ctx}.

110 | */ 111 | @Override public T visitIntNBT(SNBTParser.IntNBTContext ctx) { return visitChildren(ctx); } 112 | /** 113 | * {@inheritDoc} 114 | * 115 | *

The default implementation returns the result of calling 116 | * {@link #visitChildren} on {@code ctx}.

117 | */ 118 | @Override public T visitStringNBT(SNBTParser.StringNBTContext ctx) { return visitChildren(ctx); } 119 | /** 120 | * {@inheritDoc} 121 | * 122 | *

The default implementation returns the result of calling 123 | * {@link #visitChildren} on {@code ctx}.

124 | */ 125 | @Override public T visitIdentifier(SNBTParser.IdentifierContext ctx) { return visitChildren(ctx); } 126 | } -------------------------------------------------------------------------------- /antlr/src/main/java/org/jglrxavpok/hephaistos/antlr/SNBTLexer.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | T__9=10 11 | DoubleQuoteText=11 12 | SingleQuoteText=12 13 | BOOLEAN=13 14 | NEGATIVE_SIGN=14 15 | FLOAT=15 16 | DOUBLE=16 17 | INTEGER=17 18 | LONG=18 19 | BYTE=19 20 | SHORT=20 21 | WS=21 22 | IDENTIFIER_LETTERS=22 23 | '{'=1 24 | ','=2 25 | '}'=3 26 | ':'=4 27 | '['=5 28 | ']'=6 29 | 'B'=7 30 | ';'=8 31 | 'I'=9 32 | 'L'=10 33 | '-'=14 34 | -------------------------------------------------------------------------------- /antlr/src/main/java/org/jglrxavpok/hephaistos/antlr/SNBTListener.java: -------------------------------------------------------------------------------- 1 | // Generated from C:/Users/jglrxavpok/Documents/Programmation/MCModding/NBTMCALib/antlr/src/main/resources\SNBT.g4 by ANTLR 4.10.1 2 | package org.jglrxavpok.hephaistos.antlr; 3 | import org.antlr.v4.runtime.tree.ParseTreeListener; 4 | 5 | /** 6 | * This interface defines a complete listener for a parse tree produced by 7 | * {@link SNBTParser}. 8 | */ 9 | public interface SNBTListener extends ParseTreeListener { 10 | /** 11 | * Enter a parse tree produced by {@link SNBTParser#snbt}. 12 | * @param ctx the parse tree 13 | */ 14 | void enterSnbt(SNBTParser.SnbtContext ctx); 15 | /** 16 | * Exit a parse tree produced by {@link SNBTParser#snbt}. 17 | * @param ctx the parse tree 18 | */ 19 | void exitSnbt(SNBTParser.SnbtContext ctx); 20 | /** 21 | * Enter a parse tree produced by {@link SNBTParser#element}. 22 | * @param ctx the parse tree 23 | */ 24 | void enterElement(SNBTParser.ElementContext ctx); 25 | /** 26 | * Exit a parse tree produced by {@link SNBTParser#element}. 27 | * @param ctx the parse tree 28 | */ 29 | void exitElement(SNBTParser.ElementContext ctx); 30 | /** 31 | * Enter a parse tree produced by {@link SNBTParser#compound}. 32 | * @param ctx the parse tree 33 | */ 34 | void enterCompound(SNBTParser.CompoundContext ctx); 35 | /** 36 | * Exit a parse tree produced by {@link SNBTParser#compound}. 37 | * @param ctx the parse tree 38 | */ 39 | void exitCompound(SNBTParser.CompoundContext ctx); 40 | /** 41 | * Enter a parse tree produced by {@link SNBTParser#namedElement}. 42 | * @param ctx the parse tree 43 | */ 44 | void enterNamedElement(SNBTParser.NamedElementContext ctx); 45 | /** 46 | * Exit a parse tree produced by {@link SNBTParser#namedElement}. 47 | * @param ctx the parse tree 48 | */ 49 | void exitNamedElement(SNBTParser.NamedElementContext ctx); 50 | /** 51 | * Enter a parse tree produced by {@link SNBTParser#list}. 52 | * @param ctx the parse tree 53 | */ 54 | void enterList(SNBTParser.ListContext ctx); 55 | /** 56 | * Exit a parse tree produced by {@link SNBTParser#list}. 57 | * @param ctx the parse tree 58 | */ 59 | void exitList(SNBTParser.ListContext ctx); 60 | /** 61 | * Enter a parse tree produced by {@link SNBTParser#byteArray}. 62 | * @param ctx the parse tree 63 | */ 64 | void enterByteArray(SNBTParser.ByteArrayContext ctx); 65 | /** 66 | * Exit a parse tree produced by {@link SNBTParser#byteArray}. 67 | * @param ctx the parse tree 68 | */ 69 | void exitByteArray(SNBTParser.ByteArrayContext ctx); 70 | /** 71 | * Enter a parse tree produced by {@link SNBTParser#intArray}. 72 | * @param ctx the parse tree 73 | */ 74 | void enterIntArray(SNBTParser.IntArrayContext ctx); 75 | /** 76 | * Exit a parse tree produced by {@link SNBTParser#intArray}. 77 | * @param ctx the parse tree 78 | */ 79 | void exitIntArray(SNBTParser.IntArrayContext ctx); 80 | /** 81 | * Enter a parse tree produced by {@link SNBTParser#longArray}. 82 | * @param ctx the parse tree 83 | */ 84 | void enterLongArray(SNBTParser.LongArrayContext ctx); 85 | /** 86 | * Exit a parse tree produced by {@link SNBTParser#longArray}. 87 | * @param ctx the parse tree 88 | */ 89 | void exitLongArray(SNBTParser.LongArrayContext ctx); 90 | /** 91 | * Enter a parse tree produced by {@link SNBTParser#doubleNBT}. 92 | * @param ctx the parse tree 93 | */ 94 | void enterDoubleNBT(SNBTParser.DoubleNBTContext ctx); 95 | /** 96 | * Exit a parse tree produced by {@link SNBTParser#doubleNBT}. 97 | * @param ctx the parse tree 98 | */ 99 | void exitDoubleNBT(SNBTParser.DoubleNBTContext ctx); 100 | /** 101 | * Enter a parse tree produced by {@link SNBTParser#floatNBT}. 102 | * @param ctx the parse tree 103 | */ 104 | void enterFloatNBT(SNBTParser.FloatNBTContext ctx); 105 | /** 106 | * Exit a parse tree produced by {@link SNBTParser#floatNBT}. 107 | * @param ctx the parse tree 108 | */ 109 | void exitFloatNBT(SNBTParser.FloatNBTContext ctx); 110 | /** 111 | * Enter a parse tree produced by {@link SNBTParser#longNBT}. 112 | * @param ctx the parse tree 113 | */ 114 | void enterLongNBT(SNBTParser.LongNBTContext ctx); 115 | /** 116 | * Exit a parse tree produced by {@link SNBTParser#longNBT}. 117 | * @param ctx the parse tree 118 | */ 119 | void exitLongNBT(SNBTParser.LongNBTContext ctx); 120 | /** 121 | * Enter a parse tree produced by {@link SNBTParser#byteNBT}. 122 | * @param ctx the parse tree 123 | */ 124 | void enterByteNBT(SNBTParser.ByteNBTContext ctx); 125 | /** 126 | * Exit a parse tree produced by {@link SNBTParser#byteNBT}. 127 | * @param ctx the parse tree 128 | */ 129 | void exitByteNBT(SNBTParser.ByteNBTContext ctx); 130 | /** 131 | * Enter a parse tree produced by {@link SNBTParser#shortNBT}. 132 | * @param ctx the parse tree 133 | */ 134 | void enterShortNBT(SNBTParser.ShortNBTContext ctx); 135 | /** 136 | * Exit a parse tree produced by {@link SNBTParser#shortNBT}. 137 | * @param ctx the parse tree 138 | */ 139 | void exitShortNBT(SNBTParser.ShortNBTContext ctx); 140 | /** 141 | * Enter a parse tree produced by {@link SNBTParser#intNBT}. 142 | * @param ctx the parse tree 143 | */ 144 | void enterIntNBT(SNBTParser.IntNBTContext ctx); 145 | /** 146 | * Exit a parse tree produced by {@link SNBTParser#intNBT}. 147 | * @param ctx the parse tree 148 | */ 149 | void exitIntNBT(SNBTParser.IntNBTContext ctx); 150 | /** 151 | * Enter a parse tree produced by {@link SNBTParser#stringNBT}. 152 | * @param ctx the parse tree 153 | */ 154 | void enterStringNBT(SNBTParser.StringNBTContext ctx); 155 | /** 156 | * Exit a parse tree produced by {@link SNBTParser#stringNBT}. 157 | * @param ctx the parse tree 158 | */ 159 | void exitStringNBT(SNBTParser.StringNBTContext ctx); 160 | /** 161 | * Enter a parse tree produced by {@link SNBTParser#identifier}. 162 | * @param ctx the parse tree 163 | */ 164 | void enterIdentifier(SNBTParser.IdentifierContext ctx); 165 | /** 166 | * Exit a parse tree produced by {@link SNBTParser#identifier}. 167 | * @param ctx the parse tree 168 | */ 169 | void exitIdentifier(SNBTParser.IdentifierContext ctx); 170 | } -------------------------------------------------------------------------------- /antlr/src/main/java/org/jglrxavpok/hephaistos/antlr/SNBTVisitor.java: -------------------------------------------------------------------------------- 1 | // Generated from C:/Users/jglrxavpok/Documents/Programmation/MCModding/NBTMCALib/antlr/src/main/resources\SNBT.g4 by ANTLR 4.10.1 2 | package org.jglrxavpok.hephaistos.antlr; 3 | import org.antlr.v4.runtime.tree.ParseTreeVisitor; 4 | 5 | /** 6 | * This interface defines a complete generic visitor for a parse tree produced 7 | * by {@link SNBTParser}. 8 | * 9 | * @param The return type of the visit operation. Use {@link Void} for 10 | * operations with no return type. 11 | */ 12 | public interface SNBTVisitor extends ParseTreeVisitor { 13 | /** 14 | * Visit a parse tree produced by {@link SNBTParser#snbt}. 15 | * @param ctx the parse tree 16 | * @return the visitor result 17 | */ 18 | T visitSnbt(SNBTParser.SnbtContext ctx); 19 | /** 20 | * Visit a parse tree produced by {@link SNBTParser#element}. 21 | * @param ctx the parse tree 22 | * @return the visitor result 23 | */ 24 | T visitElement(SNBTParser.ElementContext ctx); 25 | /** 26 | * Visit a parse tree produced by {@link SNBTParser#compound}. 27 | * @param ctx the parse tree 28 | * @return the visitor result 29 | */ 30 | T visitCompound(SNBTParser.CompoundContext ctx); 31 | /** 32 | * Visit a parse tree produced by {@link SNBTParser#namedElement}. 33 | * @param ctx the parse tree 34 | * @return the visitor result 35 | */ 36 | T visitNamedElement(SNBTParser.NamedElementContext ctx); 37 | /** 38 | * Visit a parse tree produced by {@link SNBTParser#list}. 39 | * @param ctx the parse tree 40 | * @return the visitor result 41 | */ 42 | T visitList(SNBTParser.ListContext ctx); 43 | /** 44 | * Visit a parse tree produced by {@link SNBTParser#byteArray}. 45 | * @param ctx the parse tree 46 | * @return the visitor result 47 | */ 48 | T visitByteArray(SNBTParser.ByteArrayContext ctx); 49 | /** 50 | * Visit a parse tree produced by {@link SNBTParser#intArray}. 51 | * @param ctx the parse tree 52 | * @return the visitor result 53 | */ 54 | T visitIntArray(SNBTParser.IntArrayContext ctx); 55 | /** 56 | * Visit a parse tree produced by {@link SNBTParser#longArray}. 57 | * @param ctx the parse tree 58 | * @return the visitor result 59 | */ 60 | T visitLongArray(SNBTParser.LongArrayContext ctx); 61 | /** 62 | * Visit a parse tree produced by {@link SNBTParser#doubleNBT}. 63 | * @param ctx the parse tree 64 | * @return the visitor result 65 | */ 66 | T visitDoubleNBT(SNBTParser.DoubleNBTContext ctx); 67 | /** 68 | * Visit a parse tree produced by {@link SNBTParser#floatNBT}. 69 | * @param ctx the parse tree 70 | * @return the visitor result 71 | */ 72 | T visitFloatNBT(SNBTParser.FloatNBTContext ctx); 73 | /** 74 | * Visit a parse tree produced by {@link SNBTParser#longNBT}. 75 | * @param ctx the parse tree 76 | * @return the visitor result 77 | */ 78 | T visitLongNBT(SNBTParser.LongNBTContext ctx); 79 | /** 80 | * Visit a parse tree produced by {@link SNBTParser#byteNBT}. 81 | * @param ctx the parse tree 82 | * @return the visitor result 83 | */ 84 | T visitByteNBT(SNBTParser.ByteNBTContext ctx); 85 | /** 86 | * Visit a parse tree produced by {@link SNBTParser#shortNBT}. 87 | * @param ctx the parse tree 88 | * @return the visitor result 89 | */ 90 | T visitShortNBT(SNBTParser.ShortNBTContext ctx); 91 | /** 92 | * Visit a parse tree produced by {@link SNBTParser#intNBT}. 93 | * @param ctx the parse tree 94 | * @return the visitor result 95 | */ 96 | T visitIntNBT(SNBTParser.IntNBTContext ctx); 97 | /** 98 | * Visit a parse tree produced by {@link SNBTParser#stringNBT}. 99 | * @param ctx the parse tree 100 | * @return the visitor result 101 | */ 102 | T visitStringNBT(SNBTParser.StringNBTContext ctx); 103 | /** 104 | * Visit a parse tree produced by {@link SNBTParser#identifier}. 105 | * @param ctx the parse tree 106 | * @return the visitor result 107 | */ 108 | T visitIdentifier(SNBTParser.IdentifierContext ctx); 109 | } -------------------------------------------------------------------------------- /antlr/src/main/resources/SNBT.g4: -------------------------------------------------------------------------------- 1 | grammar SNBT; 2 | 3 | snbt: element 4 | EOF 5 | ; 6 | 7 | element: byteNBT 8 | | floatNBT 9 | | shortNBT 10 | | longNBT 11 | | intNBT 12 | | doubleNBT 13 | | stringNBT 14 | | byteArray 15 | | intArray 16 | | longArray 17 | | list 18 | | compound 19 | ; 20 | 21 | compound: '{' (namedElement (',' namedElement)*)? ','? '}'; 22 | namedElement: name=stringNBT ':' value=element; 23 | 24 | list: '[' (element (',' element)*)? ','? ']'; 25 | byteArray: '[' 'B' ';' (byteNBT (',' byteNBT)* ','?)? ']'; 26 | intArray: '[' 'I' ';' (intNBT (',' intNBT)* ','?)? ']'; 27 | longArray: '[' 'L' ';' (longNBT (',' longNBT)* ','?)? ']'; 28 | doubleNBT: DOUBLE; 29 | floatNBT: FLOAT; 30 | longNBT: LONG; 31 | byteNBT: BYTE | BOOLEAN; 32 | shortNBT: SHORT; 33 | intNBT: INTEGER; 34 | stringNBT: identifier | DoubleQuoteText | SingleQuoteText; 35 | identifier: IDENTIFIER_LETTERS+; 36 | 37 | DoubleQuoteText: '"' ((~('"')| ('\\' '"'))*) '"'; 38 | SingleQuoteText: '\'' ((~('\'')| ('\\' '\''))*) '\''; 39 | 40 | BOOLEAN: 'false' | 'true'; 41 | NEGATIVE_SIGN: '-'; 42 | FLOAT: '-'? ([0-9]* ('.' [0-9]*)? | [0-9]+) (('e'|'E') '-'? [0-9]+)? ('f'|'F'); 43 | DOUBLE: '-'? ([0-9]* ('.' [0-9]*)? | [0-9]+) (('e'|'E') '-'? [0-9]+)? ('d'|'D') 44 | | '-'? [0-9]* '.' [0-9]+ (('e'|'E') '-'? [0-9]+)? 45 | | '-'? [0-9]+ '.' [0-9]* (('e'|'E') '-'? [0-9]+)? 46 | | '-'? [0-9]+ (('e'|'E') '-'? [0-9]+); // explicit rule not to conflict with integer 47 | INTEGER: '-'? [0-9]+; 48 | LONG: '-'? [0-9]+ ('l'|'L'); 49 | BYTE: '-'? [0-9]+ ('b'|'B'); 50 | SHORT: '-'? [0-9]+ ('s'|'S'); 51 | WS: (' ' | '\t' | '\r' | '\n')+ -> channel(HIDDEN); 52 | IDENTIFIER_LETTERS: [a-zA-Z0-9_]+; -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.5.10" 3 | java 4 | id("org.jetbrains.dokka") version "1.5.0" 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | allprojects { 12 | apply(plugin = "java") 13 | apply(plugin = "kotlin") 14 | apply(plugin = "org.jetbrains.dokka") 15 | 16 | group = "io.github.jglrxavpok.hephaistos" 17 | version = "2.6.1" 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | dependencies { 24 | // Use the Kotlin JDK 8 standard library. 25 | implementation(kotlin("stdlib")) 26 | 27 | // Use the JUpiter test library. 28 | testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") 29 | } 30 | 31 | tasks { 32 | test { useJUnitPlatform() } 33 | } 34 | 35 | java { 36 | sourceCompatibility = JavaVersion.VERSION_11 37 | targetCompatibility = JavaVersion.VERSION_11 38 | 39 | withSourcesJar() 40 | } 41 | 42 | val dokkaHtml by tasks.getting(org.jetbrains.dokka.gradle.DokkaTask::class) 43 | 44 | val javadocJar: TaskProvider by tasks.registering(Jar::class) { 45 | dependsOn(dokkaHtml) 46 | archiveClassifier.set("javadoc") 47 | from(dokkaHtml.outputDirectory) 48 | } 49 | 50 | val compileKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks 51 | compileKotlin.kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString() 52 | compileKotlin.kotlinOptions.languageVersion = "1.5" 53 | 54 | compileKotlin.kotlinOptions { 55 | freeCompilerArgs = listOf("-Xuse-experimental=kotlin.MultiPlatform") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `maven-publish` 3 | id("convention.publication") 4 | } 5 | 6 | dependencies { 7 | api(project(":antlr")) 8 | implementation("it.unimi.dsi:fastutil:8.5.8") 9 | } 10 | 11 | configurations { 12 | testImplementation { 13 | extendsFrom(configurations.compileOnly.get()) 14 | } 15 | } 16 | 17 | publishing { 18 | publications { 19 | create("maven") { 20 | from(components["java"]) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/Options.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos 2 | 3 | enum class Options(var active: Boolean) { 4 | 5 | WarnOnPre1_17WorldsWithInvalidYRange(false), 6 | WarnWhenLoadingSectionWithNoPaletteButWithBlocks(true), 7 | 8 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/SNBTParsingVisitor.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos 2 | 3 | import org.jglrxavpok.hephaistos.antlr.SNBTBaseVisitor 4 | import org.jglrxavpok.hephaistos.antlr.SNBTParser 5 | import org.jglrxavpok.hephaistos.nbt.* 6 | import java.lang.StringBuilder 7 | 8 | object SNBTParsingVisitor: SNBTBaseVisitor() { 9 | 10 | override fun aggregateResult(aggregate: NBT?, nextResult: NBT?): NBT? { 11 | return when { 12 | aggregate == null && nextResult != null -> nextResult 13 | aggregate != null && nextResult == null -> aggregate 14 | aggregate == null && nextResult == null -> null 15 | else -> /*aggregate != null && nextResult != null ->*/ error("Can't merge $aggregate and $nextResult") 16 | } 17 | } 18 | 19 | override fun visitCompound(ctx: SNBTParser.CompoundContext): NBT = NBT.Kompound { 20 | ctx.namedElement().forEach { 21 | this[(visit(it.name) as NBTString).value] = visit(it.value) 22 | } 23 | } 24 | 25 | 26 | override fun visitList(ctx: SNBTParser.ListContext): NBT { 27 | val elements = ctx.element().map { visit(it) } 28 | val subtagType = if (elements.isEmpty()) NBTType.TAG_String else elements[0].ID 29 | return NBT.List(subtagType, elements) 30 | } 31 | 32 | override fun visitByteArray(ctx: SNBTParser.ByteArrayContext): NBT { 33 | val array = ctx.byteNBT().map { (visitByteNBT(it) as NBTByte).value }.toByteArray() 34 | return NBT.ByteArray(*array) 35 | } 36 | 37 | override fun visitIntArray(ctx: SNBTParser.IntArrayContext): NBT { 38 | val array = ctx.intNBT().map { (visitIntNBT(it) as NBTInt).value }.toIntArray() 39 | return NBT.IntArray(*array) 40 | } 41 | 42 | override fun visitLongArray(ctx: SNBTParser.LongArrayContext): NBT { 43 | val array = ctx.longNBT().map { (visitLongNBT(it) as NBTLong).value }.toLongArray() 44 | return NBT.LongArray(*array) 45 | } 46 | 47 | override fun visitDoubleNBT(ctx: SNBTParser.DoubleNBTContext): NBT { 48 | var text = ctx.text 49 | if (text.endsWith('d') || text.endsWith('D')) { 50 | text = text.dropLast(1) 51 | } 52 | return NBT.Double(text.toDouble()) 53 | } 54 | 55 | override fun visitFloatNBT(ctx: SNBTParser.FloatNBTContext): NBT { 56 | return NBT.Float(ctx.text.dropLast(1).toFloat()) 57 | } 58 | 59 | override fun visitLongNBT(ctx: SNBTParser.LongNBTContext): NBT { 60 | val value = ctx.LONG().text.dropLast(1).toLong() 61 | return NBT.Long(value) 62 | } 63 | 64 | override fun visitByteNBT(ctx: SNBTParser.ByteNBTContext): NBT { 65 | ctx.BOOLEAN()?.let { 66 | val booleanValue = ctx.BOOLEAN().text == "true" 67 | return NBT.Boolean(booleanValue) 68 | } 69 | val value = ctx.BYTE().text.dropLast(1).toByte() 70 | return NBT.Byte(value) 71 | } 72 | 73 | override fun visitShortNBT(ctx: SNBTParser.ShortNBTContext): NBT { 74 | val value = ctx.SHORT().text.dropLast(1).toShort() 75 | return NBT.Short(value) 76 | } 77 | 78 | private fun unescape(str: String): String { 79 | var escaped = false 80 | val buffer = StringBuilder() 81 | var toSkip = 0 82 | for ((index, c) in str.withIndex()) { 83 | if(toSkip > 0) { 84 | toSkip-- 85 | continue 86 | } 87 | if(escaped) { 88 | when(c) { 89 | '\\', '"', '\'' -> { 90 | buffer.append(c) 91 | escaped = false 92 | } 93 | 'n' -> { 94 | buffer.append('\n') 95 | escaped = false 96 | } 97 | 'r' -> { 98 | buffer.append('\r') 99 | escaped = false 100 | } 101 | 'b' -> { 102 | buffer.append('\b') 103 | escaped = false 104 | } 105 | 'u' -> { 106 | if(index+4 >= str.length) { 107 | error("Not enough characters for Unicode escape sequence") 108 | } else { 109 | buffer.append(Character.toChars(Integer.parseInt(str.substring(index+1..index+4), 16))) 110 | toSkip = 4 111 | escaped = false 112 | } 113 | } 114 | else -> error("Unrecognized escape sequence: \\$c") 115 | } 116 | } else if(c == '\\') { 117 | escaped = true; 118 | } else { 119 | buffer.append(c) 120 | } 121 | } 122 | return buffer.toString() 123 | } 124 | 125 | override fun visitStringNBT(ctx: SNBTParser.StringNBTContext): NBT { 126 | return when { 127 | ctx.DoubleQuoteText() != null -> NBT.String(unescape(ctx.DoubleQuoteText().text.drop(1).dropLast(1))) 128 | ctx.SingleQuoteText() != null -> NBT.String(unescape(ctx.SingleQuoteText().text.drop(1).dropLast(1))) 129 | else -> NBT.String(ctx.text) 130 | } 131 | } 132 | 133 | override fun visitIntNBT(ctx: SNBTParser.IntNBTContext): NBT { 134 | return NBT.Int(ctx.INTEGER().text.toInt()) 135 | } 136 | 137 | override fun visitIdentifier(ctx: SNBTParser.IdentifierContext?): NBT? { 138 | error("Should not access this rule directly") 139 | } 140 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/collections/ImmutableByteArray.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.collections 2 | 3 | /** 4 | * Immutable object of [ByteArray]. 5 | * 6 | * Can not be modified -- if needed, use [copyArray] to create a mutable [ByteArray] copy. 7 | */ 8 | class ImmutableByteArray constructor(private vararg val numbers: Byte): Iterable { 9 | 10 | constructor(length: Int, generator: (Int) -> Byte) : this(*ByteArray(length).apply { 11 | repeat(length) { 12 | this[it] = generator(it) 13 | } 14 | }) 15 | 16 | fun copyInto(destination: ByteArray, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = size): ByteArray { 17 | System.arraycopy(this.numbers, startIndex, destination, destinationOffset, endIndex - startIndex) 18 | return destination 19 | } 20 | 21 | fun copyArray(): ByteArray = ByteArray(numbers.size).also { copyInto(it) } 22 | 23 | val size get() = numbers.size 24 | 25 | operator fun get(index: Int): Byte = numbers[index] 26 | 27 | infix fun contentEquals(other: ImmutableByteArray?) = numbers.contentEquals(other?.numbers) 28 | 29 | override fun hashCode() = numbers.contentHashCode() 30 | 31 | fun joinToString(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((Byte) -> CharSequence)? = null): String { 32 | return numbers.joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() 33 | } 34 | 35 | override fun toString() = "[${joinToString(", ")}]" 36 | 37 | override fun equals(other: Any?): Boolean { 38 | if (this === other) return true 39 | if (javaClass != other?.javaClass) return false 40 | 41 | other as ImmutableByteArray 42 | 43 | if (!numbers.contentEquals(other.numbers)) return false 44 | 45 | return true 46 | } 47 | 48 | companion object { 49 | @JvmStatic 50 | val EMPTY = ImmutableByteArray() 51 | 52 | @JvmStatic 53 | fun from(vararg numbers: Int) = ImmutableByteArray(*numbers.map { it.toByte() }.toByteArray()) 54 | } 55 | 56 | override fun iterator(): Iterator = object : Iterator { 57 | 58 | var index = 0 59 | 60 | override fun hasNext() = index < numbers.size 61 | 62 | override fun next() = numbers[index.also { index++ }] 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/collections/ImmutableIntArray.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.collections 2 | 3 | /** 4 | * Immutable alternative of [IntArray] 5 | * 6 | * Can not be modified -- if needed, use [copyArray] to create a mutable [IntArray] copy. 7 | */ 8 | class ImmutableIntArray(internal vararg val numbers: Int): Iterable { 9 | 10 | constructor(size: Int, generator: (Int) -> Int) : this(*IntArray(size).apply { 11 | repeat(size) { 12 | this[it] = generator(it) 13 | } 14 | }) 15 | 16 | fun copyInto(destination: IntArray, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = size): IntArray { 17 | System.arraycopy(this.numbers, startIndex, destination, destinationOffset, endIndex - startIndex) 18 | return destination 19 | } 20 | 21 | fun copyArray(): IntArray = IntArray(numbers.size).also { copyInto(it) } 22 | 23 | val size get() = numbers.size 24 | 25 | operator fun get(index: Int) = numbers[index] 26 | 27 | infix fun contentEquals(other: ImmutableIntArray?) = numbers.contentEquals(other?.numbers) 28 | 29 | override fun hashCode() = numbers.contentHashCode() 30 | 31 | fun joinToString(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((Int) -> CharSequence)? = null): String { 32 | return numbers.joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() 33 | } 34 | 35 | override fun toString(): String { 36 | return joinToString(prefix = "[", postfix = "]") 37 | } 38 | 39 | override fun equals(other: Any?): Boolean { 40 | if (this === other) return true 41 | if (javaClass != other?.javaClass) return false 42 | 43 | other as ImmutableIntArray 44 | 45 | if (!numbers.contentEquals(other.numbers)) return false 46 | 47 | return true 48 | } 49 | 50 | override fun iterator(): Iterator = object : Iterator { 51 | 52 | var index = 0 53 | 54 | override fun hasNext() = index < numbers.size 55 | 56 | override fun next() = numbers[index.also { index++ }] 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/collections/ImmutableLongArray.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.collections 2 | 3 | /** 4 | * Immutable alternative of [LongArray] 5 | * 6 | * Can not be modified -- if needed, use [copyArray] to create a mutable [LongArray] copy. 7 | */ 8 | class ImmutableLongArray(internal vararg val numbers: Long): Iterable { 9 | 10 | constructor(length: Int, generator: (Int) -> Long) : this(*LongArray(length).apply { 11 | repeat(length) { 12 | this[it] = generator(it) 13 | } 14 | }) 15 | 16 | fun copyInto(destination: LongArray, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = size): LongArray { 17 | System.arraycopy(this.numbers, startIndex, destination, destinationOffset, endIndex - startIndex) 18 | return destination 19 | } 20 | 21 | fun copyArray(): LongArray = LongArray(numbers.size).also { copyInto(it) } 22 | 23 | val size get() = numbers.size 24 | 25 | operator fun get(index: Int): Long = numbers[index] 26 | 27 | infix fun contentEquals(other: ImmutableLongArray?) = numbers.contentEquals(other?.numbers) 28 | 29 | override fun hashCode() = numbers.contentHashCode() 30 | 31 | fun joinToString(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((Long) -> CharSequence)? = null): String { 32 | return numbers.joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() 33 | } 34 | 35 | override fun toString() = "[${joinToString(", ")}]" 36 | 37 | override fun equals(other: Any?): Boolean { 38 | if (this === other) return true 39 | if (javaClass != other?.javaClass) return false 40 | 41 | other as ImmutableLongArray 42 | 43 | if (!numbers.contentEquals(other.numbers)) return false 44 | 45 | return true 46 | } 47 | 48 | override fun iterator(): Iterator = object : Iterator { 49 | 50 | var index = 0 51 | 52 | override fun hasNext() = index < numbers.size 53 | 54 | override fun next() = numbers[index.also { index++ }] 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/data/DataSource.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.data 2 | 3 | import java.io.Closeable 4 | import java.io.DataInput 5 | import java.io.DataOutput 6 | import java.io.IOException 7 | 8 | interface DataSource: DataInput, DataOutput, Closeable { 9 | 10 | @Throws(IOException::class) 11 | fun seek(position: Long) 12 | 13 | @Throws(IOException::class) 14 | fun length(): Long 15 | 16 | @Throws(IOException::class) 17 | fun setLength(newLength: Long) 18 | 19 | @Throws(IOException::class) 20 | fun writeByte(pos: Long, b: Byte) 21 | 22 | @Throws(IOException::class) 23 | fun writeBytes(pos: Long, bytes: ByteArray) 24 | 25 | @Throws(IOException::class) 26 | fun writeInt(pos: Long, int: Int) 27 | 28 | @Throws(IOException::class) 29 | fun readBytes(pos: Long, destination: ByteArray) 30 | 31 | @Throws(IOException::class) 32 | fun readByte(pos: Long): Byte 33 | 34 | @Throws(IOException::class) 35 | fun readInt(pos: Long): Int 36 | 37 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/data/RandomAccessFileSource.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.data 2 | 3 | import java.io.Closeable 4 | import java.io.DataInput 5 | import java.io.DataOutput 6 | import java.io.RandomAccessFile 7 | import java.nio.ByteBuffer 8 | 9 | class RandomAccessFileSource(val file: RandomAccessFile): DataSource, DataInput by file, DataOutput by file, Closeable by file { 10 | override fun seek(position: Long) { 11 | file.seek(position) 12 | } 13 | 14 | override fun length(): Long { 15 | return file.length() 16 | } 17 | 18 | override fun setLength(newLength: Long) { 19 | file.setLength(newLength) 20 | } 21 | 22 | override fun writeByte(pos: Long, b: Byte) { 23 | file.channel.write(ByteBuffer.allocate(1).put(0, b), pos) 24 | } 25 | 26 | override fun writeBytes(pos: Long, bytes: ByteArray) { 27 | file.channel.write(ByteBuffer.wrap(bytes), pos) 28 | } 29 | 30 | override fun writeInt(pos: Long, int: Int) { 31 | file.channel.write(ByteBuffer.allocate(4).putInt(0, int), pos) 32 | } 33 | 34 | override fun readBytes(pos: Long, destination: ByteArray) { 35 | val buf = ByteBuffer.allocateDirect(destination.size) 36 | file.channel.read(buf, pos) 37 | for (i in destination.indices) destination[i] = buf[i] 38 | } 39 | 40 | override fun readByte(pos: Long): Byte { 41 | val buf = ByteBuffer.allocate(1) 42 | file.channel.read(buf, pos) 43 | return buf[0] 44 | } 45 | 46 | override fun readInt(pos: Long): Int { 47 | val buf = ByteBuffer.allocate(4) 48 | file.channel.read(buf, pos) 49 | return buf.getInt(0) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/AnvilException.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mca 2 | 3 | import java.lang.Exception 4 | 5 | /** 6 | * Exception thrown when: 7 | * 1. An Anvil file is malformed (missing fields for example) 8 | * 2. Trying to access data not available in a RegionFile (like chunks that are not inside the file) 9 | */ 10 | class AnvilException(message: String, cause: Throwable?): Exception(message, cause) { 11 | 12 | constructor(message: String): this(message, null) 13 | 14 | companion object { 15 | fun missing(name: String): Nothing = throw AnvilException("Missing field named '$name' (or of wrong type)") 16 | } 17 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/BlockState.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mca 2 | 3 | import org.jglrxavpok.hephaistos.mca.AnvilException.Companion.missing 4 | import org.jglrxavpok.hephaistos.nbt.NBT 5 | import org.jglrxavpok.hephaistos.nbt.NBTCompound 6 | import org.jglrxavpok.hephaistos.nbt.NBTString 7 | 8 | /** 9 | * Represents a block state in a section NBT 10 | */ 11 | data class BlockState @JvmOverloads constructor(val name: String, val properties: Map = HashMap()) { 12 | companion object { 13 | @JvmField 14 | val AIR = BlockState(NBT.Kompound { 15 | this["Name"] = NBT.String("minecraft:air") 16 | this["Properties"] = NBT.EMPTY 17 | }) 18 | } 19 | 20 | private var hashCode = 0 21 | 22 | /** 23 | * Constructs a BlockState from the given TAG_Compound 24 | * @throws AnvilException if the NBT is malformed 25 | */ 26 | @Throws(AnvilException::class) 27 | constructor(nbt: NBTCompound): this(nbt.getString("Name") ?: missing("Name"), loadProperties(nbt.getCompound("Properties") ?: NBT.EMPTY)) 28 | 29 | /** 30 | * Converts this BlockState to a TAG_Compound 31 | */ 32 | fun toNBT(): NBTCompound = NBT.Kompound { 33 | this["Name"] = NBT.String(name) 34 | this["Properties"] = NBT.Kompound { 35 | for((name, value) in properties) { 36 | this[name] = NBT.String(value) 37 | } 38 | } 39 | } 40 | 41 | // Ironically, IntelliJ's generator for these produces 42 | // faster code than the Kotlin compiler does 43 | override fun equals(other: Any?): Boolean { 44 | if (this === other) return true 45 | if (other !is BlockState) return false 46 | 47 | if (name != other.name) return false 48 | if (properties != other.properties) return false 49 | 50 | return true 51 | } 52 | 53 | override fun hashCode(): Int { 54 | if (hashCode == 0) { 55 | var result = name.hashCode() 56 | result = 31 * result + properties.hashCode() 57 | hashCode = result 58 | } 59 | return hashCode 60 | } 61 | } 62 | 63 | private fun loadProperties(properties: NBTCompound): Map { 64 | val result = HashMap() 65 | for ((name, value) in properties) { 66 | result[name] = (value as NBTString).value 67 | } 68 | return result 69 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/Coordinates.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mca 2 | 3 | import kotlin.math.floor 4 | 5 | fun Int.chunkToRegion() = floor(this / 32.0).toInt() 6 | fun Int.regionToChunk() = this * 32 7 | fun Int.blockToChunk() = floor(this / 16.0).toInt() 8 | fun Int.chunkToBlock() = this shl 4 9 | fun Int.chunkInsideRegion() = this and 31 10 | fun Int.blockInsideChunk() = this and 15 11 | fun Int.blockInsideSection() = this and 15 12 | 13 | fun Int.blockToSection() = floor(this / 16.0).toInt().toByte() 14 | fun Int.sectionToBlock() = this * 16 -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/Heightmap.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mca 2 | 3 | import org.jglrxavpok.hephaistos.collections.ImmutableLongArray 4 | 5 | /** 6 | * Represents a heightmap for a 16x16 area. 7 | */ 8 | class Heightmap() { 9 | 10 | private var heights = IntArray(16*16) 11 | 12 | /** 13 | * Constructs the heightmap from a compressed heightmap (36 longs long) 14 | */ 15 | @JvmOverloads 16 | constructor(compactVersion: ImmutableLongArray, version: SupportedVersion = SupportedVersion.Latest): this() { 17 | when { 18 | version == SupportedVersion.MC_1_15 -> { 19 | if(compactVersion.size != 36) { // 16x16 20 | throw AnvilException("Wrong length for 1.15 compacted heightmap (found ${compactVersion.size}, expected 36)") 21 | } 22 | heights = decompress(compactVersion, 9) 23 | } 24 | 25 | version >= SupportedVersion.MC_1_16 -> { 26 | if(compactVersion.size != 37) { // 16x16 27 | throw AnvilException("Wrong length for 1.16 compacted heightmap (found ${compactVersion.size}, expected 37)") 28 | } 29 | heights = unpack(compactVersion, 9) 30 | } 31 | } 32 | } 33 | 34 | /** 35 | * Gets the height saved at the given coordinates. Coordinates must be in 16x16 square 36 | * 37 | * @throws IllegalArgumentException if the coordinates are not inside the heightmap 38 | */ 39 | operator fun get(x: Int, z: Int): Int { 40 | checkBounds(x, z) 41 | return heights[z*16+x] 42 | } 43 | 44 | /** 45 | * Sets the height saved at the given coordinates. Coordinates must be in 16x16 square 46 | * 47 | * @throws IllegalArgumentException if the coordinates are not inside the heightmap 48 | */ 49 | operator fun set(x: Int, z: Int, height: Int) { 50 | checkBounds(x, z) 51 | heights[z*16+x] = height 52 | } 53 | 54 | /** 55 | * Creates a compressed version of this heightmap, to be transferred over network, or saved to disk. 56 | * (9bit heights saved in longs, resulting in a long array of 36 longs) 57 | */ 58 | @JvmOverloads 59 | fun compact(version: SupportedVersion = SupportedVersion.Latest): ImmutableLongArray { 60 | return when { 61 | version == SupportedVersion.MC_1_15 -> compress(heights, 9) 62 | version >= SupportedVersion.MC_1_16 -> pack(heights, 9) 63 | 64 | else -> throw AnvilException("Unsupported data version for heightmap: $version") 65 | } 66 | } 67 | 68 | private fun checkBounds(x: Int, z: Int) { 69 | if(x !in 0..15) 70 | throw IllegalArgumentException("x must be in 0..15") 71 | if(z !in 0..15) 72 | throw IllegalArgumentException("z must be in 0..15") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/LongCompactor.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mca 2 | 3 | import org.jglrxavpok.hephaistos.collections.ImmutableLongArray 4 | import kotlin.math.ceil 5 | import kotlin.math.floor 6 | 7 | /** 8 | * Compresses the given ints into a long array, at the given lengthInBits bitrate. 9 | * 10 | * lengthInBits must be at least 1 11 | */ 12 | fun compress(data: IntArray, lengthInBits: Int): ImmutableLongArray { 13 | val compacted = LongArray(ceil(data.size*lengthInBits/64.0).toInt()) 14 | var bitIndex = 0 15 | val maxMask = (1L shl lengthInBits) -1 16 | 17 | for(i in data.indices) { 18 | val value = data[i] and 0b11111_1111 19 | 20 | val beginLongIndex = bitIndex / 64 21 | val endLongIndex = (bitIndex + lengthInBits-1) / 64 22 | 23 | if(beginLongIndex != endLongIndex) { // split over two longs 24 | val overshoot = (bitIndex+lengthInBits) % 64 25 | val highMask = (1 shl overshoot) -1 26 | val mask = ((1 shl lengthInBits-overshoot)-1).toLong() 27 | compacted[beginLongIndex] = compacted[beginLongIndex] and (maxMask shl bitIndex%64).inv() or (value.toLong() and mask shl (bitIndex%64)) 28 | compacted[endLongIndex] = compacted[endLongIndex] or ((value.toLong() and maxMask shr (64-(bitIndex%64))) and highMask.toLong()) 29 | } else { 30 | compacted[beginLongIndex] = compacted[beginLongIndex] and (maxMask shl bitIndex%64).inv() or (value.toLong() and maxMask shl (bitIndex%64)) 31 | } 32 | bitIndex += lengthInBits 33 | } 34 | return ImmutableLongArray(*compacted) 35 | } 36 | 37 | /** 38 | * Decompresses compressed 'data' into a int array, with lengthInBits bits per int. 39 | */ 40 | fun decompress(data: ImmutableLongArray, lengthInBits: Int): IntArray { 41 | var bitIndex = 0 42 | val count = (data.size.toLong()*64 / lengthInBits).toInt() 43 | val result = IntArray(count) 44 | val maxMask = (1L shl lengthInBits) -1 45 | 46 | for(i in result.indices) { 47 | val beginLongIndex = bitIndex / 64 48 | val endLongIndex = (bitIndex + lengthInBits-1) / 64 49 | 50 | val value: Int = if(beginLongIndex != endLongIndex) { // split over two longs 51 | val overshoot = (bitIndex+lengthInBits) % 64 52 | val lowMask = (1L shl lengthInBits-overshoot) -1 53 | val highMask = (1L shl overshoot) -1 54 | val low = data[beginLongIndex] shr (bitIndex % 64) and lowMask 55 | val high = data[endLongIndex] and highMask 56 | (low or (high shl lengthInBits-overshoot)).toInt() 57 | } else { 58 | ((data[beginLongIndex] shr (bitIndex%64)) and maxMask).toInt() 59 | } 60 | 61 | result[bitIndex/lengthInBits] = value 62 | 63 | bitIndex += lengthInBits 64 | } 65 | return result 66 | } 67 | 68 | /** 69 | * Unpacks int values of 'lengthInBits' bits from a long array. 70 | * Contrary to decompress, this method will produce unused bits and do not overflow remaining bits to the next long. 71 | * 72 | * (ie 2 >32 bit long values will produce two longs, but the highest bits of each long will be unused) 73 | */ 74 | fun unpack(longs: ImmutableLongArray, lengthInBits: Int): IntArray { 75 | val intPerLong = floor(64.0 / lengthInBits) 76 | val intCount = ceil(longs.size * intPerLong).toInt() 77 | val ints = IntArray(intCount) 78 | val intPerLongCeil = ceil(intPerLong).toInt() 79 | val mask = (1 shl lengthInBits)-1L 80 | for(i in ints.indices) { 81 | val longIndex = i / intPerLongCeil 82 | val subIndex = i % intPerLongCeil 83 | val value = ((longs[longIndex] shr (subIndex*lengthInBits)) and mask).toInt() 84 | ints[i] = value 85 | } 86 | return ints 87 | } 88 | 89 | /** 90 | * Packs ints into a long array. Produces unused bits and does not partially overflow to next long on boundaries. 91 | */ 92 | fun pack(ints: IntArray, lengthInBits: Int): ImmutableLongArray { 93 | val intPerLong = floor(64.0 / lengthInBits).toInt() 94 | val longCount = ceil(ints.size / intPerLong.toDouble()).toInt() 95 | val longs = LongArray(longCount) 96 | val mask = (1 shl lengthInBits)-1L 97 | for(i in longs.indices) { 98 | var long = 0L 99 | for(intIndex in 0 until intPerLong) { 100 | val bitIndex = intIndex * lengthInBits 101 | val intActualIndex = intIndex+i*intPerLong 102 | if(intActualIndex < ints.size) { 103 | val value = (ints[intActualIndex].toLong() and mask) shl bitIndex 104 | long = long or (value) 105 | } 106 | } 107 | longs[i] = long 108 | } 109 | return ImmutableLongArray(*longs) 110 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/Palette.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mca 2 | 3 | import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap 4 | import org.jglrxavpok.hephaistos.collections.ImmutableLongArray 5 | import org.jglrxavpok.hephaistos.mcdata.Biome 6 | import org.jglrxavpok.hephaistos.nbt.NBT 7 | import org.jglrxavpok.hephaistos.nbt.NBTCompound 8 | import org.jglrxavpok.hephaistos.nbt.NBTList 9 | import org.jglrxavpok.hephaistos.nbt.NBTString 10 | import org.jglrxavpok.hephaistos.nbt.NBTType 11 | import kotlin.math.ceil 12 | import kotlin.math.log2 13 | 14 | /** 15 | * Represents the palette of elements used in a chunk section. This palette allows to save space when saving to disk or transferring over network, 16 | * as it lowers the required number of bits used to represent an element, by remapping global IDs to local IDs, with fewer bits per entry. 17 | */ 18 | sealed class Palette(private val nbtType: NBTType, private val defaultValue: ElementType, private val writer: (ElementType) -> NBT) { 19 | val elements = arrayListOf() 20 | 21 | internal val referenceCounts = Object2IntOpenHashMap() 22 | 23 | internal fun loadReferences(states: Array) { 24 | val visited = mutableSetOf() 25 | for(element in states) { 26 | if(element in visited) continue 27 | if(element !in elements) { 28 | throw IllegalArgumentException("Tried to add a reference counter to $element which is not in this palette") 29 | } 30 | visited.add(element) 31 | } 32 | 33 | for(state in states) referenceCounts.addTo(state, 1) 34 | } 35 | 36 | /** 37 | * Increases the reference count of the given element. 38 | * If the element was not referenced, it is added to this palette and its reference becomes 1. 39 | */ 40 | fun increaseReference(block: ElementType) { 41 | if(referenceCounts.addTo(block, 1) == 0) { 42 | elements.add(block) 43 | } 44 | } 45 | 46 | /** 47 | * Decreases the number of references to the given element. 48 | * If the reference becomes <= 0, the element is removed from this palette, and its reference becomes 0. 49 | * 50 | * We optionally don't throw an exception if the reference doesn't exist anymore as that check 51 | * removes an expensive calculation which significantly slows down palette functionality. 52 | */ 53 | fun decreaseReference(block: ElementType) { 54 | if(!referenceCounts.containsKey(block)) { 55 | throw IllegalArgumentException("Tried to remove a reference counter to $block which is not in this palette") 56 | } 57 | 58 | val old = referenceCounts.addTo(block, -1) 59 | if(old - 1 <= 0) { 60 | elements.remove(block) 61 | referenceCounts.removeInt(block) 62 | } 63 | } 64 | 65 | /** 66 | * Converts this Palette into its NBT representation 67 | */ 68 | fun toNBT(): NBTList = 69 | NBT.List(nbtType, elements.map { writer(it) }) 70 | 71 | /** 72 | * Index of the given element inside the palette. Returns -1 if none 73 | */ 74 | fun getPaletteIndex(obj: ElementType): Int = elements.indexOf(obj) 75 | 76 | /** 77 | * Produces a long array with the compacted IDs based on this palette. The 'indices' array is supposed to be the states already paletted (ie a value inside the 'indices' array is an index into the palette already) 78 | * Bit length is selected on the size of this palette (`ceil(log2(size))`), ID correspond to the index inside this palette 79 | */ 80 | @JvmOverloads 81 | fun compactPreProcessedIDs(indices: IntArray, version: SupportedVersion = SupportedVersion.Latest, minimumBitSize: Int = 1): ImmutableLongArray { 82 | check(minimumBitSize > 0) { "Minimum bit size cannot be 0 or negative" } 83 | 84 | // convert state list into uncompressed data 85 | val bitLength = ceil(log2(elements.size.toFloat())).toInt().coerceAtLeast(minimumBitSize) // at least one bit 86 | return when { 87 | version == SupportedVersion.MC_1_15 -> compress(indices, bitLength) 88 | version >= SupportedVersion.MC_1_16 -> pack(indices, bitLength) 89 | 90 | else -> throw AnvilException("Unsupported version for compacting palette: $version") 91 | } 92 | } 93 | 94 | /** 95 | * Produces a long array with the compacted IDs based on this palette. 96 | * Bit length is selected on the size of this palette (`ceil(log2(size))`), ID correspond to the index inside this palette 97 | */ 98 | @JvmOverloads 99 | fun compactIDs(states: Array, version: SupportedVersion = SupportedVersion.Latest, minimumBitSize: Int = 1): ImmutableLongArray { 100 | check(minimumBitSize > 0) { "Minimum bit size cannot be 0 or negative" } 101 | 102 | // convert state list into uncompressed data 103 | val indices = states.map(elements::indexOf).toIntArray() 104 | val bitLength = ceil(log2(elements.size.toFloat())).toInt().coerceAtLeast(minimumBitSize) // at least one bit 105 | return when { 106 | version == SupportedVersion.MC_1_15 -> compress(indices, bitLength) 107 | version >= SupportedVersion.MC_1_16 -> pack(indices, bitLength) 108 | 109 | else -> throw AnvilException("Unsupported version for compacting palette: $version") 110 | } 111 | 112 | } 113 | 114 | /** 115 | * Returns true iif the only referenced block inside this palette is "minecraft:air" 116 | */ 117 | fun isEmpty(): Boolean { 118 | return elements.size == 1 && elements[0] == defaultValue 119 | } 120 | } 121 | 122 | class BlockPalette(): Palette(NBTType.TAG_Compound, BlockState.AIR, BlockState::toNBT) { 123 | constructor(elements: NBTList): this() { 124 | for(b in elements) { 125 | this.elements += BlockState(b) 126 | } 127 | } 128 | } 129 | class BiomePalette(): Palette(NBTType.TAG_String, Biome.UnknownBiome, { str -> NBT.String(str) }) { 130 | constructor(elements: NBTList): this() { 131 | elements.forEach { this.elements += it.value } 132 | } 133 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/SupportedVersion.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mca 2 | 3 | enum class SupportedVersion(val lowestDataVersion: Int) { 4 | 5 | MC_1_15(2225), 6 | MC_1_16(2504), 7 | MC_1_17_0(2724), 8 | MC_1_18_PRE_4(2850), 9 | MC_1_20(3463), 10 | ; 11 | 12 | companion object { 13 | 14 | val Latest: SupportedVersion = MC_1_20 15 | 16 | /** 17 | * Returns the highest version known for the given data version 18 | * Because Hephaistos does not know about version below MC 1.14 (at the time of writing this comment), it will default to 19 | * MC_1_15 if the data version is lower than the MC_1_15 data version 20 | */ 21 | fun closest(dataVersion: Int): SupportedVersion { 22 | var closestFound = MC_1_15 // default 23 | for (v in values()) { 24 | if(v.lowestDataVersion <= dataVersion) { 25 | closestFound = v 26 | } 27 | } 28 | return closestFound 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/readers/ChunkVersionedFields.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mca.readers 2 | 3 | import org.jglrxavpok.hephaistos.mca.SupportedVersion 4 | 5 | fun SectionName(version: SupportedVersion) = if(version < SupportedVersion.MC_1_18_PRE_4) "Sections" else "sections" 6 | fun EntitiesName(version: SupportedVersion) = if(version < SupportedVersion.MC_1_18_PRE_4) "Entities" else "entities" 7 | fun BlockEntitiesName(version: SupportedVersion) = if(version < SupportedVersion.MC_1_18_PRE_4) "TileEntities" else "block_entities" 8 | fun StructuresName(version: SupportedVersion) = if(version < SupportedVersion.MC_1_18_PRE_4) "Structures" else "structures" 9 | fun BlockTicksName(version: SupportedVersion) = if(version < SupportedVersion.MC_1_18_PRE_4) "TileTicks" else "block_ticks" 10 | fun FluidTicksName(version: SupportedVersion) = if(version < SupportedVersion.MC_1_18_PRE_4) "LiquidTicks" else "fluid_ticks" -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/readers/SectionBiomeInformation.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mca.readers 2 | 3 | /** 4 | * Holds the information about biomes for a single section. 5 | * There are 3 possible scenarii: 6 | * 1. biomes = null and baseBiome = null 7 | * This means 8 | * either the section is from before 1.18, so no biome information exists in the section 9 | * OR 10 | * you accessed the biomes via ChunkReader directly, and no biome information is available for the given section. 11 | * 2. biomes = null and baseBiome != null 12 | * This means the section has biome information, but the entire section is filled with baseBiome. 13 | * Technically this means there is a palette with at least 1 element but no data. 14 | * 3. biomes != null 15 | * The biomes array holds the biomes for this section. 16 | */ 17 | data class SectionBiomeInformation(val biomes: Array?, val baseBiome: String?) { 18 | constructor(): this(null, null) 19 | 20 | /** 21 | * False iif both biomes and baseBiome are null 22 | */ 23 | fun hasBiomeInformation() = biomes != null || baseBiome != null 24 | 25 | /** 26 | * Is the entire section filled with a single biome? 27 | * Note that this can return false even if only a single biome is present inside the 'biomes' array, 28 | * as this denotes an optimisation when saving worlds to reduce file size. 29 | * 30 | * Technically, this means there is a biome palette with at least 1 element inside the section, but no data. 31 | */ 32 | fun isFilledWithSingleBiome() = biomes == null && baseBiome != null 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/writer/ChunkSectionWriter.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mca.writer 2 | 3 | import org.jglrxavpok.hephaistos.mca.* 4 | import org.jglrxavpok.hephaistos.nbt.NBT 5 | import org.jglrxavpok.hephaistos.nbt.NBTCompound 6 | 7 | /** 8 | * Writes section data to a NBTCompound. 9 | * Allows to save section without having to endure the overhead of ChunkSection (& BlockState) 10 | */ 11 | class ChunkSectionWriter(val version: SupportedVersion, val y: Byte) { 12 | 13 | /** 14 | * See ChunkSection#empty 15 | */ 16 | private val empty: Boolean get() = blockStatePalette == null 17 | 18 | private var blockLights = ByteArray(0) 19 | private var skyLights = ByteArray(0) 20 | 21 | private var biomePalette: BiomePalette? = null 22 | private var biomeIndices: IntArray? = null 23 | 24 | private var blockStatePalette: BlockPalette? = null 25 | private var blockStateIndices: IntArray? = null 26 | 27 | /** 28 | * Sets the block lights of this section. Does not copy the input array 29 | */ 30 | fun setBlockLights(lights: ByteArray) { 31 | blockLights = lights 32 | } 33 | 34 | /** 35 | * Sets the sky lights of this section. Does not copy the input array 36 | */ 37 | fun setSkyLights(lights: ByteArray) { 38 | skyLights = lights 39 | } 40 | 41 | /** 42 | * Use this to set biomes directly. Is less performant than setPalettedBiomes 43 | */ 44 | fun setAllBiomes(biomes: Array) { 45 | check(biomes.size == ChunkSection.BiomeArraySize) { "biomes.size (${biomes.size}) != ChunkSection.BiomeArraySize (${ChunkSection.BiomeArraySize})" } 46 | val biomePalette = BiomePalette() 47 | for(b in biomes) { 48 | biomePalette.increaseReference(b) 49 | } 50 | 51 | setPalettedBiomes(biomePalette, IntArray(ChunkSection.BiomeArraySize) { index -> 52 | biomePalette.elements.indexOf(biomes[index]) 53 | }) 54 | } 55 | 56 | /** 57 | * Use this to set biomes directly. Is less performant than setPalettedBiomes 58 | */ 59 | fun setAllBlockStates(blockStates: Array) { 60 | check(blockStates.size == 16*16*16) { "blockStates.size (${blockStates.size}) != 16x16x16 (${16*16*16})" } 61 | val blockPalette = BlockPalette() 62 | for(b in blockStates) { 63 | blockPalette.increaseReference(b) 64 | } 65 | 66 | setPalettedBlockStates(blockPalette, IntArray(16*16*16) { index -> 67 | blockPalette.elements.indexOf(blockStates[index]) 68 | }) 69 | } 70 | 71 | /** 72 | * Use this to set the biome palette and biome data directly. More performant than setAllBiomes 73 | */ 74 | fun setPalettedBiomes(biomePalette: BiomePalette, palettedBiomes: IntArray) { 75 | this.biomePalette = biomePalette 76 | this.biomeIndices = palettedBiomes 77 | } 78 | 79 | /** 80 | * Use this to set the block state palette and block state data directly. More performant than setAllBlockStates 81 | */ 82 | fun setPalettedBlockStates(blockPalette: BlockPalette, palettedBlockStates: IntArray) { 83 | this.blockStatePalette = blockPalette 84 | this.blockStateIndices = palettedBlockStates 85 | } 86 | 87 | fun toNBT(): NBTCompound = NBT.Kompound { 88 | this["Y"] = NBT.Byte(y) 89 | if(blockLights.isNotEmpty()) { 90 | this["BlockLight"] = NBT.ByteArray(*blockLights) 91 | } 92 | if(skyLights.isNotEmpty()) { 93 | this["SkyLight"] = NBT.ByteArray(*skyLights) 94 | } 95 | if(!empty) { 96 | if(version < SupportedVersion.MC_1_18_PRE_4) { 97 | this["Palette"] = blockStatePalette!!.toNBT() 98 | this["BlockStates"] = NBT.LongArray(blockStatePalette!!.compactPreProcessedIDs(blockStateIndices!!, version, 4)) 99 | } else { 100 | this["block_states"] = NBT.Kompound { 101 | this["palette"] = blockStatePalette!!.toNBT() 102 | this["data"] = NBT.LongArray(blockStatePalette!!.compactPreProcessedIDs(blockStateIndices!!, version, 4)) 103 | } 104 | 105 | if(biomePalette != null) { 106 | this["biomes"] = NBT.Kompound { 107 | this["palette"] = biomePalette!!.toNBT() 108 | this["data"] = NBT.LongArray(biomePalette!!.compactPreProcessedIDs(biomeIndices!!, version)) 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mcdata/Biome.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mcdata 2 | 3 | /** 4 | * Allows to convert pre 1.18 biomes to 1.18+ ids 5 | */ 6 | enum class Biome(val namespaceID: String, val numericalID: Int) { 7 | 8 | Ocean("minecraft:ocean", 0), 9 | Plains("minecraft:plains", 1), 10 | Desert("minecraft:desert", 2), 11 | Mountains("minecraft:mountains", 3), 12 | Forest("minecraft:forest", 4), 13 | Taiga("minecraft:taiga", 5), 14 | Swamp("minecraft:swamp", 6), 15 | River("minecraft:river", 7), 16 | NetherWastes("minecraft:nether_wastes", 8), 17 | TheEnd("minecraft:the_end", 9), 18 | FrozenOcean("minecraft:frozen_ocean", 10), 19 | FrozenRiver("minecraft:frozen_river", 11), 20 | SnowyTundra("minecraft:snowy_tundra", 12), 21 | SnowyMountains("minecraft:snowy_mountains", 13), 22 | MushroomFields("minecraft:mushroom_fields", 14), 23 | MushroomFieldShore("minecraft:mushroom_field_shore", 15), 24 | Beach("minecraft:beach", 16), 25 | DesertHills("minecraft:desert_hills", 17), 26 | WoodedHills("minecraft:wooded_hills", 18), 27 | TaigaHills("minecraft:taiga_hills", 19), 28 | MountainEdge("minecraft:mountain_edge", 20), 29 | Jungle("minecraft:jungle", 21), 30 | JungleHills("minecraft:jungle_hills", 22), 31 | JungleEdge("minecraft:jungle_edge", 23), 32 | DeepOcean("minecraft:deep_ocean", 24), 33 | StoneShore("minecraft:stone_shore", 25), 34 | SnowyBeach("minecraft:snowy_beach", 26), 35 | BirchForest("minecraft:birch_forest", 27), 36 | BirchForestHills("minecraft:birch_forest_hills", 28), 37 | DarkForest("minecraft:dark_forest", 29), 38 | SnowyTaiga("minecraft:snowy_taiga", 30), 39 | SnowyTaigaHills("minecraft:snowy_taiga_hills", 31), 40 | GiantTreeTaiga("minecraft:giant_tree_taiga", 32), 41 | GiantTreeTaigaHills("minecraft:giant_tree_taiga_hills", 33), 42 | WoodedMountains("minecraft:wooded_mountains", 34), 43 | Savanna("minecraft:savanna", 35), 44 | SavannaPlateau("minecraft:savanna_plateau", 36), 45 | Badlands("minecraft:badlands", 37), 46 | WoodedBadlandsPlateau("minecraft:wooded_badlands_plateau", 38), 47 | BadlandsPlateau("minecraft:badlands_plateau", 39), 48 | SmallEndIslands("minecraft:small_end_islands", 40), 49 | EndMidlands("minecraft:end_midlands", 41), 50 | EndHighlands("minecraft:end_highlands", 42), 51 | EndBarrens("minecraft:end_barrens", 43), 52 | WarmOcean("minecraft:warm_ocean", 44), 53 | LukewarmOcean("minecraft:lukewarm_ocean", 45), 54 | ColdOcean("minecraft:cold_ocean", 46), 55 | DeepWarmOcean("minecraft:deep_warm_ocean", 47), 56 | DeepLukewarmOcean("minecraft:deep_lukewarm_ocean", 48), 57 | DeepColdOcean("minecraft:deep_cold_ocean", 49), 58 | DeepFrozenOcean("minecraft:deep_frozen_ocean", 50), 59 | TheVoid("minecraft:the_void", 127), 60 | SunflowerPlains("minecraft:sunflower_plains", 129), 61 | DesertLakes("minecraft:desert_lakes", 130), 62 | GravellyMountains("minecraft:gravelly_mountains", 131), 63 | FlowerForest("minecraft:flower_forest", 132), 64 | TaigaMountains("minecraft:taiga_mountains", 133), 65 | SwampHills("minecraft:swamp_hills", 134), 66 | IceSpikes("minecraft:ice_spikes", 140), 67 | ModifiedJungle("minecraft:modified_jungle", 149), 68 | ModifiedJungleEdge("minecraft:modified_jungle_edge", 151), 69 | TallBirchForest("minecraft:tall_birch_forest", 155), 70 | TallBirchHills("minecraft:tall_birch_hills", 156), 71 | DarkForestHills("minecraft:dark_forest_hills", 157), 72 | SnowyTaigaMountains("minecraft:snowy_taiga_mountains", 158), 73 | GiantSpruceTaiga("minecraft:giant_spruce_taiga", 160), 74 | GiantSpruceTaigaHills("minecraft:giant_spruce_taiga_hills", 161), 75 | ModifiedGravellyMountains("minecraft:modified_gravelly_mountains", 162), 76 | ShatteredSavanna("minecraft:shattered_savanna", 163), 77 | ShatteredSavannaPlateau("minecraft:shattered_savanna_plateau", 164), 78 | ErodedBadlands("minecraft:eroded_badlands", 165), 79 | ModifiedWoodedBadlandsPlateau("minecraft:modified_wooded_badlands_plateau", 166), 80 | ModifiedBadlandsPlateau("minecraft:modified_badlands_plateau", 167), 81 | BambooJungle("minecraft:bamboo_jungle", 168), 82 | BambooJungleHills("minecraft:bamboo_jungle_hills", 169), 83 | SoulSandValley("minecraft:soul_sand_valley", 170), 84 | CrimsonForest("minecraft:crimson_forest", 171), 85 | WarpedForest("minecraft:warped_forest", 172), 86 | BasaltDeltas("minecraft:basalt_deltas", 173), 87 | DripstoneCaves("minecraft:dripstone_caves", 174), 88 | LushCaves("minecraft:lush_caves", 175); 89 | 90 | companion object { 91 | val UnknownBiome = TheVoid.namespaceID 92 | 93 | /** 94 | * Converts a pre 1.18 ID to its namespace ID. If the index is not recognized, "hephaistos:unknown_{index}" will be returned 95 | */ 96 | fun numericalIDToNamespaceID(index: Int): String { 97 | return values().find { it.numericalID == index }?.namespaceID ?: "hephaistos:unknown_$index" 98 | } 99 | 100 | /** 101 | * Tries to get the biome corresponding to the given id 102 | * If none match, returns 'Void' 103 | */ 104 | fun fromNamespaceID(id: String): Biome { 105 | return values().find { it.namespaceID == id } ?: TheVoid 106 | } 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/mcdata/Sizes.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.mcdata 2 | 3 | val VanillaMinY = -64 4 | val VanillaMaxY = 319 -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/CompressedProcesser.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.InputStream 4 | import java.io.OutputStream 5 | import java.util.zip.* 6 | 7 | /** 8 | * Processes an [InputStream] or an [OutputStream] into a compressed stream. 9 | */ 10 | open class CompressedProcesser { 11 | 12 | /** 13 | * Generates a compressed input stream from the [originalInputStream] 14 | * 15 | * @param originalInputStream The input stream to generate the new input stream from. 16 | * 17 | * @return A new compressed input stream. 18 | */ 19 | open fun generateInputStream(originalInputStream: InputStream) = originalInputStream 20 | 21 | /** 22 | * Generates a compressed output stream from the [originalOutputStream] 23 | * 24 | * @param originalOutputStream The output stream to generate the new output stream from. 25 | * 26 | * @return A new compressed output stream. 27 | */ 28 | open fun generateOutputStream(originalOutputStream: OutputStream) = originalOutputStream 29 | 30 | companion object { 31 | /** 32 | * No compression. Data in and out will remain the same. 33 | */ 34 | @JvmField 35 | val NONE = object : CompressedProcesser() { } 36 | 37 | /** GZIP compression. Buffer size is 512 */ 38 | @JvmField 39 | val GZIP = ParameterizedGzip() 40 | 41 | /** 42 | * ZLIP compression. 43 | * 44 | * Buffer size is 512 and the inflater/deflater 45 | * used is [ParameterizedZlib.DEFAULT_INFLATER] / [ParameterizedZlib.DEFAULT_DEFLATER] 46 | */ 47 | @JvmField 48 | val ZLIB = ParameterizedZlib() 49 | } 50 | 51 | class ParameterizedGzip @JvmOverloads constructor(val bufferSize: Int = 512) : CompressedProcesser() { 52 | override fun generateInputStream(originalInputStream: InputStream) = GZIPInputStream(originalInputStream, bufferSize) 53 | override fun generateOutputStream(originalOutputStream: OutputStream) = GZIPOutputStream(originalOutputStream, bufferSize) 54 | } 55 | 56 | class ParameterizedZlib @JvmOverloads constructor( 57 | val inflater: Inflater = DEFAULT_INFLATER, 58 | val deflater: Deflater = DEFAULT_DEFLATER, 59 | val bufferSize: Int = 512 60 | ) : CompressedProcesser() { 61 | 62 | override fun generateInputStream(originalInputStream: InputStream) = if (inflater == DEFAULT_INFLATER) { 63 | InflaterInputStream(originalInputStream) 64 | } else { 65 | InflaterInputStream(originalInputStream, inflater, bufferSize) 66 | } 67 | 68 | override fun generateOutputStream(originalOutputStream: OutputStream) = if (deflater == DEFAULT_DEFLATER) { 69 | DeflaterOutputStream(originalOutputStream) 70 | } else { 71 | DeflaterOutputStream(originalOutputStream, deflater, bufferSize) 72 | } 73 | 74 | companion object { 75 | /** Represents the default inflater. Using `new Inflater()` instead of `DEFAULT_INFLATER` causes issues. */ 76 | val DEFAULT_INFLATER = Inflater() 77 | 78 | /** Represents the default deflater. Using `new Deflater()` instead of `DEFAULT_DEFLATER` causes issues. */ 79 | val DEFAULT_DEFLATER = Deflater() 80 | } 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/Extensions.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | import java.io.IOException 6 | 7 | /** 8 | * Reads a full tag (ID, name and value when applicable) from this input stream 9 | */ 10 | @Throws(IOException::class, NBTException::class) 11 | fun DataInputStream.readFullyFormedTag(): Pair { 12 | val id = readByte().toInt() 13 | if(id == NBTType.TAG_End.ordinal) { 14 | return "" to NBTEnd 15 | } 16 | val name = readUTF() 17 | 18 | return name to readTag(id) 19 | } 20 | 21 | /** 22 | * Reads a tag (value only) from this input stream 23 | */ 24 | @Throws(IOException::class, NBTException::class) 25 | fun DataInputStream.readTag(id: Int): NBT { 26 | val readerCompanion = NBTType.byIndex(id).readerCompanion 27 | return readerCompanion.readContents(this) 28 | } 29 | 30 | /** 31 | * Writes a full tag (ID, name and value when applicable) to this output stream 32 | */ 33 | @Throws(IOException::class) 34 | fun DataOutputStream.writeFullyFormedTag(name: String, tag: NBT) { 35 | writeByte(tag.ID.ordinal) 36 | writeUTF(name) 37 | tag.writeContents(this) 38 | } 39 | 40 | /** 41 | * Writes a TAG_End to this output stream 42 | */ 43 | @Throws(IOException::class) 44 | fun DataOutputStream.writeEndTag() { 45 | writeByte(NBTType.TAG_End.ordinal) 46 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTByte.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | 6 | class NBTByte constructor(value: Byte) : NBTNumber(value) { 7 | 8 | override val ID = NBTType.TAG_Byte 9 | 10 | constructor(value: Boolean): this(if(value) 1 else 0) 11 | 12 | // help Java compiler to find the correct type (boxed vs primitive types) 13 | fun getValue(): Byte = value 14 | 15 | /** 16 | * Returns true iif the value is not 0 17 | */ 18 | fun asBoolean(): Boolean = value != 0.toByte() 19 | 20 | override fun writeContents(destination: DataOutputStream) { 21 | destination.writeByte(value.toInt()) 22 | } 23 | 24 | override fun toSNBT(): String { 25 | return "${value}B" 26 | } 27 | 28 | companion object: NBTReaderCompanion { 29 | override fun readContents(source: DataInputStream): NBTByte { 30 | return NBTByte(source.readByte()) 31 | } 32 | 33 | @JvmField 34 | val ONE = NBTByte(1) 35 | 36 | @JvmField 37 | val ZERO = NBTByte(0) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTByteArray.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import org.jglrxavpok.hephaistos.collections.ImmutableByteArray 4 | import java.io.DataInputStream 5 | import java.io.DataOutputStream 6 | 7 | class NBTByteArray constructor(override val value: ImmutableByteArray) : NBT, Iterable { 8 | 9 | val size get() = value.size 10 | 11 | override val ID = NBTType.TAG_Byte_Array 12 | 13 | constructor(vararg numbers: Byte): this(ImmutableByteArray(*numbers)) 14 | 15 | override fun writeContents(destination: DataOutputStream) { 16 | destination.writeInt(size) 17 | destination.write(value.copyArray()) 18 | } 19 | 20 | operator fun get(index: Int) = value[index] 21 | 22 | override fun toSNBT(): String { 23 | val list = value.joinToString(",") { "${it}B" } 24 | return "[B;$list]" 25 | } 26 | 27 | override fun toString() = toSNBT() 28 | 29 | override fun equals(other: Any?): Boolean { 30 | if (this === other) return true 31 | if (javaClass != other?.javaClass) return false 32 | 33 | other as NBTByteArray 34 | 35 | if (!(value contentEquals other.value)) return false 36 | 37 | return true 38 | } 39 | 40 | override fun hashCode() = value.hashCode() 41 | 42 | override fun iterator() = value.iterator() 43 | 44 | companion object : NBTReaderCompanion { 45 | @JvmField 46 | val EMPTY = NBTByteArray(ImmutableByteArray.EMPTY) 47 | 48 | override fun readContents(source: DataInputStream): NBTByteArray { 49 | val length = source.readInt() 50 | val value = ImmutableByteArray(*source.readNBytes(length)) 51 | return NBTByteArray(value) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTCompound.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import org.jetbrains.annotations.Contract 4 | import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound 5 | import java.io.DataInputStream 6 | import java.io.DataOutputStream 7 | 8 | class NBTCompound @JvmOverloads constructor(_tags: Map = mapOf()): NBT, NBTCompoundLike { 9 | 10 | private val tags: Map = java.util.Map.copyOf(_tags) 11 | 12 | override val ID = NBTType.TAG_Compound 13 | 14 | override val value: Map = tags 15 | 16 | override fun writeContents(destination: DataOutputStream) { 17 | for(entry in tags.entries) { 18 | val name = entry.key 19 | val tag = entry.value 20 | destination.writeFullyFormedTag(name, tag) 21 | } 22 | destination.writeEndTag() 23 | } 24 | 25 | override fun toSNBT(): String { 26 | val tagStr = asMapView().map { entry -> 27 | "\"${entry.key.replace("\"", "\\\"")}\":${entry.value.toSNBT()}" 28 | }.joinToString(",") 29 | return "{$tagStr}" 30 | } 31 | 32 | override fun toString() = toSNBT() 33 | 34 | override fun equals(other: Any?): Boolean { 35 | if(other === this) return true 36 | 37 | if(other == null) return false 38 | 39 | if(other !is NBTCompoundLike) return false 40 | 41 | return tags == other.asMapView() 42 | } 43 | 44 | override fun hashCode(): Int { 45 | return tags.hashCode() 46 | } 47 | 48 | @Contract(pure = true) 49 | fun modify(lambda: CompoundBuilder) = MutableNBTCompound(tags.toMutableMap()).also { lambda.run(it) }.toCompound() 50 | 51 | @Contract(pure = true) 52 | fun kmodify(lambda: MutableNBTCompound.() -> Unit) = modify(lambda) 53 | 54 | @Contract(pure = true) 55 | fun withRemovedKeys(vararg keys: String) = kmodify { keys.forEach { remove(it) } } 56 | 57 | @Contract(pure = true) 58 | fun withEntries(vararg entries: CompoundEntry) = kmodify { entries.forEach { this[it.key] = it.value } } 59 | 60 | companion object : NBTReaderCompanion { 61 | 62 | override fun readContents(source: DataInputStream) = NBT.Kompound { 63 | do { 64 | val tag = source.readFullyFormedTag() 65 | if(tag.second !is NBTEnd) { 66 | this[tag.first] = tag.second 67 | } 68 | } while(tag.second !is NBTEnd) 69 | } 70 | 71 | @Contract(pure = true) 72 | internal fun entry(key: String, value: NBT) = object: CompoundEntry { 73 | override val key: String 74 | get() = key 75 | override val value: NBT 76 | get() = value 77 | } 78 | 79 | @JvmField 80 | val EMPTY = NBTCompound() 81 | } 82 | 83 | override fun toCompound() = this 84 | 85 | override fun asMapView() = tags 86 | 87 | override fun toMutableCompound() = MutableNBTCompound(this) 88 | 89 | override fun plus(other: NBTCompoundLike): NBTCompound = modify { nbt -> 90 | nbt += other 91 | } 92 | } 93 | 94 | fun interface CompoundBuilder { 95 | fun run(map: MutableNBTCompound) 96 | } 97 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTCompoundLike.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import org.jetbrains.annotations.Contract 4 | import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound 5 | import java.util.function.BiConsumer 6 | 7 | typealias CompoundEntry = Map.Entry 8 | typealias MutableCompoundEntry = MutableMap.MutableEntry 9 | 10 | interface NBTCompoundLike: NBTCompoundGetters, Iterable { 11 | 12 | /** 13 | * Creates a NBTCompound. This will be immutable and copied, 14 | * regardless if the original object is immutable or mutable. 15 | */ 16 | @Contract(pure = true) 17 | fun toCompound(): NBTCompound 18 | 19 | /** 20 | * Same semantics than NBT#toSNBT 21 | */ 22 | fun toSNBT(): String { 23 | val tagStr = asMapView().map { entry -> 24 | "\"${entry.key.replace("\"", "\\\"")}\":${entry.value.toSNBT()}" 25 | }.joinToString(",") 26 | return "{$tagStr}" 27 | } 28 | 29 | /** 30 | * Returns a Map representing this compound. The map is not modifiable if this is a NBTCompound, which is immutable. 31 | */ 32 | fun asMapView(): Map 33 | 34 | /** 35 | * Returns a new mutable map containing all key-value pairs from the original compound. 36 | */ 37 | fun toMutableCompound(): MutableNBTCompound 38 | 39 | // ======================== 40 | // Map-like interface 41 | // ======================== 42 | /** 43 | * Returns a read-only Set of all key/value pairs in this compound. 44 | */ 45 | val entries: Set get()= asMapView().entries 46 | 47 | /** 48 | * Returns a read-only Set of all keys in this compound. 49 | */ 50 | val keys: Set get()= asMapView().keys 51 | 52 | /** 53 | * Returns the number of key/value pairs in the compound. 54 | */ 55 | val size: Int get()= asMapView().size 56 | 57 | /** 58 | * Returns a read-only Collection of all values in this compound. Note that this collection may contain duplicate values. 59 | */ 60 | val values: Collection get()= asMapView().values 61 | 62 | /** 63 | * Returns true if the compound maps one or more keys to the specified value. 64 | */ 65 | fun containsValue(value: NBT): Boolean = asMapView().containsValue(value) 66 | 67 | /** 68 | * Returns true if the compound contains the specified key. 69 | */ 70 | fun containsKey(key: String): Boolean = asMapView().containsKey(key) 71 | 72 | /** 73 | * Returns true if the compound is empty (contains no elements), false otherwise. 74 | */ 75 | fun isEmpty(): Boolean = asMapView().isEmpty() 76 | 77 | /** 78 | * Checks if the compound contains the given key. 79 | */ 80 | operator fun contains(key: String) = asMapView().contains(key) 81 | 82 | /** 83 | * Performs the given action on each entry. 84 | */ 85 | fun forEach(action: BiConsumer) = asMapView().forEach(action) 86 | 87 | /** 88 | * Performs the given action on each entry. 89 | */ 90 | fun forEach(action: (CompoundEntry) -> Unit) = asMapView().forEach(action) 91 | 92 | /** 93 | * Returns an Iterator over the entries in the compound. 94 | */ 95 | override operator fun iterator(): Iterator = asMapView().iterator() 96 | 97 | /** 98 | * Returns the value corresponding to the given key, or null if such a key is not present in the compound. 99 | */ 100 | override fun get(key: String) = asMapView()[key] 101 | 102 | /** 103 | * Creates a new NBTCompoundLike by replacing or adding entries from this compound with entries from 'other' 104 | */ 105 | operator fun plus(other: NBTCompoundLike): NBTCompoundLike 106 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTDouble.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | 6 | class NBTDouble constructor(value: Double) : NBTNumber(value) { 7 | 8 | override val ID = NBTType.TAG_Double 9 | 10 | // help Java compiler to find the correct type (boxed vs primitive types) 11 | fun getValue(): Double = value 12 | 13 | override fun writeContents(destination: DataOutputStream) { 14 | destination.writeDouble(value) 15 | } 16 | 17 | override fun toSNBT(): String { 18 | return "${value}D" 19 | } 20 | 21 | companion object: NBTReaderCompanion { 22 | override fun readContents(source: DataInputStream): NBTDouble { 23 | return NBTDouble(source.readDouble()) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTEnd.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | 6 | object NBTEnd: NBT, NBTReaderCompanion { 7 | override val ID = NBTType.TAG_End 8 | 9 | override val value: Any get() = throw UnsupportedOperationException("This tag has no value") 10 | 11 | override fun readContents(source: DataInputStream): NBTEnd = this 12 | 13 | override fun writeContents(destination: DataOutputStream) {} 14 | 15 | override fun toSNBT() = "" 16 | 17 | override fun toString() = "" 18 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTException.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.lang.Exception 4 | 5 | class NBTException @JvmOverloads constructor(message: String, cause: Throwable? = null) : Exception(message, cause) -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTFloat.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | 6 | class NBTFloat constructor(value: Float) : NBTNumber(value) { 7 | override val ID = NBTType.TAG_Float 8 | 9 | // help Java compiler to find the correct type (boxed vs primitive types) 10 | fun getValue(): Float = value 11 | 12 | override fun writeContents(destination: DataOutputStream) { 13 | destination.writeFloat(value) 14 | } 15 | 16 | override fun toSNBT(): String { 17 | return "${value}F" 18 | } 19 | 20 | companion object: NBTReaderCompanion { 21 | override fun readContents(source: DataInputStream): NBTFloat { 22 | return NBTFloat(source.readFloat()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTInt.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | 6 | class NBTInt constructor(value: Int) : NBTNumber(value) { 7 | override val ID = NBTType.TAG_Int 8 | 9 | // help Java compiler to find the correct type (boxed vs primitive types) 10 | fun getValue(): Int = value 11 | 12 | override fun writeContents(destination: DataOutputStream) { 13 | destination.writeInt(value) 14 | } 15 | 16 | override fun toSNBT(): String { 17 | return "$value" 18 | } 19 | 20 | companion object: NBTReaderCompanion { 21 | override fun readContents(source: DataInputStream): NBTInt { 22 | return NBTInt(source.readInt()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTIntArray.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import org.jglrxavpok.hephaistos.collections.ImmutableIntArray 4 | import java.io.DataInputStream 5 | import java.io.DataOutputStream 6 | 7 | class NBTIntArray constructor(override val value: ImmutableIntArray) : NBT, Iterable { 8 | 9 | val size get() = value.size 10 | 11 | override val ID = NBTType.TAG_Int_Array 12 | 13 | constructor(vararg numbers: Int): this(ImmutableIntArray(*numbers)) 14 | 15 | override fun writeContents(destination: DataOutputStream) { 16 | destination.writeInt(size) 17 | value.numbers.forEach(destination::writeInt) 18 | } 19 | 20 | operator fun get(index: Int) = value[index] 21 | 22 | override fun toSNBT(): String { 23 | val list = value.joinToString(",") { "$it" } 24 | return "[I;$list]" 25 | } 26 | 27 | override fun toString() = toSNBT() 28 | 29 | override fun equals(other: Any?): Boolean { 30 | if (this === other) return true 31 | if (javaClass != other?.javaClass) return false 32 | 33 | other as NBTIntArray 34 | 35 | if (!(value contentEquals other.value)) return false 36 | 37 | return true 38 | } 39 | 40 | override fun hashCode() = value.hashCode() 41 | 42 | override fun iterator() = value.iterator() 43 | 44 | companion object : NBTReaderCompanion { 45 | @JvmField 46 | val EMPTY = NBTIntArray() 47 | 48 | override fun readContents(source: DataInputStream): NBTIntArray { 49 | val length = source.readInt() 50 | val inArray = source.readNBytes(length * 4) 51 | val outArray = IntArray(length) 52 | 53 | for(i in 0 until length) { 54 | val index = i * 4 55 | outArray[i] = (inArray[index].toInt() and 0xFF shl 24) or 56 | (inArray[index + 1].toInt() and 0xFF shl 16) or 57 | (inArray[index + 2].toInt() and 0xFF shl 8) or 58 | (inArray[index + 3].toInt() and 0xFF) 59 | } 60 | 61 | val value = ImmutableIntArray(*outArray) 62 | return NBTIntArray(value) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTList.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | import java.io.IOException 6 | 7 | class NBTList @JvmOverloads constructor(val subtagType: NBTType, _tags: List = listOf()): NBT, Iterable { 8 | 9 | private val tags: List = java.util.List.copyOf(_tags) 10 | 11 | override val ID = NBTType.TAG_List 12 | 13 | override val value: List = tags 14 | 15 | /** 16 | * Writes the contents of the list, WITH for the subtag ID 17 | * @see NBT.writeContents 18 | */ 19 | override fun writeContents(destination: DataOutputStream) { 20 | destination.writeByte(subtagType.ordinal) 21 | destination.writeInt(size) 22 | 23 | tags.forEach { it.writeContents(destination) } 24 | } 25 | 26 | override fun toSNBT(): String { 27 | return "[${tags.joinToString(",") { it.toSNBT() }}]" 28 | } 29 | 30 | override fun toString() = toSNBT() 31 | 32 | /** 33 | * Returns the tag at the given index 34 | */ 35 | operator fun get(index: Int) = tags[index] 36 | 37 | /** 38 | * Casts this list to another list type. Can throw a ClassCastException, so be careful 39 | */ 40 | @Suppress("UNCHECKED_CAST") // if that throws, it is the user's fault 41 | fun asListOf() = this as NBTList 42 | 43 | /** 44 | * Returns a List representing this compound. The list is not modifiable 45 | */ 46 | fun asListView(): List = tags 47 | 48 | override fun equals(other: Any?): Boolean { 49 | if (this === other) return true 50 | if (javaClass != other?.javaClass) return false 51 | 52 | other as NBTList<*> 53 | 54 | if (subtagType != other.subtagType) return false 55 | if (size != other.size) return false 56 | for (i in 0 until size) { 57 | if(this[i] != other[i]) { 58 | return false 59 | } 60 | } 61 | 62 | return true 63 | } 64 | 65 | override fun hashCode(): Int { 66 | var result = subtagType.ordinal 67 | result = 31 * result + run { 68 | var hashCodeResult = 1 69 | 70 | tags.forEach { hashCodeResult = 31 * hashCodeResult + it.hashCode() } 71 | 72 | return@run hashCodeResult 73 | } 74 | return result 75 | } 76 | 77 | companion object : NBTReaderCompanion> { 78 | /** 79 | * Reads the contents of the list, except for the subtag ID, which is supposed to be already read 80 | * @see NBTReaderCompanion.readContents 81 | */ 82 | @Throws(IOException::class) 83 | override fun readContents(source: DataInputStream): NBTList { 84 | val subtagType = source.readByte().toInt() 85 | val length = source.readInt() 86 | 87 | return NBT.List(NBTType.byIndex(subtagType), List(length) { 88 | source.readTag(subtagType) 89 | }) 90 | } 91 | } 92 | 93 | // =============== 94 | // List-like interface 95 | // =============== 96 | /** 97 | * Returns the size of the list. 98 | */ 99 | val size get()= tags.size 100 | 101 | /** 102 | * Checks if the specified element is contained in this list. 103 | */ 104 | operator fun contains(element: Tag) = element in tags 105 | 106 | /** 107 | * Checks if all elements in the specified collection are contained in this list. 108 | */ 109 | fun containsAll(elements: Collection) = tags.containsAll(elements) 110 | 111 | /** 112 | * Returns the index of the first occurrence of the specified element in the list, or -1 if the specified element is not contained in the list. 113 | */ 114 | fun indexOf(element: Tag) = tags.indexOf(element) 115 | 116 | /** 117 | * Returns true if the collection is empty (contains no elements), false otherwise. 118 | */ 119 | fun isEmpty() = tags.isEmpty() 120 | 121 | /** 122 | * Returns false if the collection is empty (contains no elements), true otherwise. 123 | */ 124 | fun isNotEmpty() = tags.isNotEmpty() 125 | 126 | /** 127 | * Returns an iterator over the elements of this object. 128 | */ 129 | override operator fun iterator() = tags.iterator() 130 | 131 | /** 132 | * Returns a list iterator over the elements in this list (in proper sequence), starting at the specified index. 133 | */ 134 | fun listIterator(index: Int = 0) = tags.listIterator(index) 135 | 136 | /** 137 | * Returns a view of the portion of this list between the specified fromIndex (inclusive) and toIndex (exclusive). The returned list is backed by this list. 138 | */ 139 | fun subList(fromIndex: Int, toIndex: Int) = NBTList(subtagType, tags.subList(fromIndex, toIndex)) 140 | 141 | /** 142 | * Performs the given action on each element. 143 | */ 144 | fun forEach(action: (Tag) -> Unit) = tags.forEach(action) 145 | } 146 | 147 | fun interface NBTListGenerator { 148 | fun run(index: Int): T 149 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTLong.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | 6 | class NBTLong constructor(value: Long) : NBTNumber(value) { 7 | override val ID = NBTType.TAG_Long 8 | 9 | // help Java compiler to find the correct type (boxed vs primitive types) 10 | fun getValue(): Long = value 11 | 12 | override fun writeContents(destination: DataOutputStream) { 13 | destination.writeLong(value) 14 | } 15 | 16 | override fun toSNBT(): String { 17 | return "${value}L" 18 | } 19 | 20 | companion object: NBTReaderCompanion { 21 | override fun readContents(source: DataInputStream): NBTLong { 22 | return NBTLong(source.readLong()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTLongArray.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import org.jglrxavpok.hephaistos.collections.ImmutableLongArray 4 | import java.io.DataInputStream 5 | import java.io.DataOutputStream 6 | 7 | class NBTLongArray constructor(override val value: ImmutableLongArray) : NBT, Iterable { 8 | 9 | val size get() = value.size 10 | 11 | override val ID = NBTType.TAG_Long_Array 12 | 13 | constructor(vararg numbers: Long): this(ImmutableLongArray(*numbers)) 14 | 15 | override fun writeContents(destination: DataOutputStream) { 16 | destination.writeInt(size) 17 | value.numbers.forEach(destination::writeLong) 18 | } 19 | 20 | operator fun get(index: Int) = value[index] 21 | 22 | override fun toSNBT(): String { 23 | val list = value.joinToString(",") { "${it}L" } 24 | return "[L;$list]" 25 | } 26 | 27 | override fun toString() = toSNBT() 28 | 29 | override fun equals(other: Any?): Boolean { 30 | if (this === other) return true 31 | if (javaClass != other?.javaClass) return false 32 | 33 | other as NBTLongArray 34 | 35 | if (!(value contentEquals other.value)) return false 36 | 37 | return true 38 | } 39 | 40 | override fun hashCode() = value.hashCode() 41 | 42 | override fun iterator() = value.iterator() 43 | 44 | companion object : NBTReaderCompanion { 45 | @JvmField 46 | val EMPTY = NBTLongArray() 47 | 48 | override fun readContents(source: DataInputStream): NBTLongArray { 49 | val length = source.readInt() 50 | val inArray = source.readNBytes(length * 8) 51 | val outArray = LongArray(length) 52 | 53 | for(i in 0 until length) { 54 | val index = i * 8 55 | outArray[i] = (inArray[index].toLong() shl 56) or 56 | ((inArray[index+1].toLong() and 0xFF) shl 48) or 57 | ((inArray[index+2].toLong() and 0xFF) shl 40) or 58 | ((inArray[index+3].toLong() and 0xFF) shl 32) or 59 | ((inArray[index+4].toLong() and 0xFF) shl 24) or 60 | ((inArray[index+5].toLong() and 0xFF) shl 16) or 61 | ((inArray[index+6].toLong() and 0xFF) shl 8) or 62 | (inArray[index+7].toLong() and 0xFF) 63 | } 64 | 65 | return NBTLongArray(*outArray) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTNumber.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.util.* 4 | 5 | abstract class NBTNumber(override val value: Type): NBT { 6 | 7 | override fun hashCode(): Int { 8 | return Objects.hash(value) 9 | } 10 | 11 | override fun toString() = toSNBT() 12 | 13 | override fun equals(other: Any?): Boolean { 14 | if (this === other) return true 15 | 16 | other as NBTNumber 17 | 18 | if (value != other.value) return false 19 | 20 | return true 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTReader.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.* 4 | import java.nio.file.Files 5 | import java.nio.file.Path 6 | 7 | /** 8 | * Reads NBT Data from a given input stream. 9 | * Once the input stream is passed to a NBTReader, use NBTReader#close to close the stream. 10 | */ 11 | class NBTReader @JvmOverloads constructor( 12 | source: InputStream, 13 | compressedProcesser: CompressedProcesser<*, *> = CompressedProcesser.GZIP 14 | ): AutoCloseable, Closeable { 15 | 16 | private val reader = DataInputStream( 17 | compressedProcesser.generateInputStream(source) 18 | ) 19 | 20 | /** 21 | * Constructs a [NBTReader] from a file (convenience method, equivalent to `NBTReader(BufferedInputStream(FileInputStream(file)), compressedProcesser)`) 22 | */ 23 | @Throws(IOException::class) 24 | @JvmOverloads constructor(file: File, compressedProcesser: CompressedProcesser<*, *> = CompressedProcesser.GZIP): 25 | this(BufferedInputStream(FileInputStream(file)), compressedProcesser) 26 | 27 | /** 28 | * Constructs a [NBTReader] from a path (convenience method, equivalent to `NBTReader(BufferedOutputStream(Files.newOutputStream(path)), compressedProcesser)`) 29 | */ 30 | @Throws(IOException::class) 31 | @JvmOverloads constructor(path: Path, compressedProcesser: CompressedProcesser<*, *> = CompressedProcesser.GZIP): 32 | this(BufferedInputStream(Files.newInputStream(path)), compressedProcesser) 33 | 34 | /** 35 | * Constructs a [NBTReader] from a byte array (convenience method, equivalent to `NBTReader(ByteArrayInputStream(array), compressedProcesser)`) 36 | */ 37 | @Throws(IOException::class) 38 | @JvmOverloads constructor(array: ByteArray, compressedProcesser: CompressedProcesser<*, *> = CompressedProcesser.GZIP): 39 | this(ByteArrayInputStream(array), compressedProcesser) 40 | 41 | /** 42 | * Reads a single named tag from the source. 'first' will hold the name, 'second' the tag 43 | * @throws IOException if an error occurred during reading 44 | * @throws NBTException if the file does not follow NBT format 45 | */ 46 | @Throws(IOException::class, NBTException::class) 47 | fun readNamed(): Pair { 48 | return reader.readFullyFormedTag() 49 | } 50 | 51 | /** 52 | * Reads a single tag from the source. 53 | * @throws IOException if an error occurred during reading 54 | * @throws NBTException if the file does not follow NBT format 55 | */ 56 | @Throws(IOException::class, NBTException::class) 57 | fun read(): NBT { 58 | return readNamed().second 59 | } 60 | 61 | /** 62 | * Reads a single tag from the source. 63 | * @throws IOException if an error occurred during reading 64 | * @throws NBTException if the file does not follow NBT format 65 | */ 66 | @Throws(IOException::class, NBTException::class) 67 | fun readRaw(id: Int): NBT { 68 | return reader.readTag(id) 69 | } 70 | 71 | override fun close() { 72 | reader.close() 73 | } 74 | 75 | companion object { 76 | /** 77 | * Creates an [NBTReader] from a [ByteArray] 78 | * 79 | * @param array The byte array to create it from. 80 | * 81 | * @return The created [NBTReader] from the [ByteArray]. 82 | */ 83 | @JvmStatic 84 | @JvmOverloads 85 | fun fromArray(array: ByteArray, compressedProcesser: CompressedProcesser<*, *> = CompressedProcesser.NONE) = 86 | NBTReader(ByteArrayInputStream(array), compressedProcesser) 87 | } 88 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTReaderCompanion.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.IOException 5 | 6 | /** 7 | * Companion object for all final NBT types 8 | * that allows creation of said [NBT] object from a [DataInputStream]. 9 | */ 10 | sealed interface NBTReaderCompanion { 11 | 12 | /** 13 | * Reads the contents of the tag from the given source. The tag ID is supposed to be already read. 14 | * 15 | * For NBTLists, it assumes the subtag type ID as already been read 16 | * @throws IOException if an error occurred during reading 17 | * @throws NBTException if the data stream does not respect NBT specs 18 | */ 19 | @Throws(IOException::class, NBTException::class) 20 | fun readContents(source: DataInputStream): T 21 | 22 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTShort.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | 6 | class NBTShort constructor(value: Short) : NBTNumber(value) { 7 | override val ID = NBTType.TAG_Short 8 | 9 | // help Java compiler to find the correct type (boxed vs primitive types) 10 | fun getValue(): Short = value 11 | 12 | override fun writeContents(destination: DataOutputStream) { 13 | destination.writeShort(value.toInt()) 14 | } 15 | 16 | override fun toSNBT(): String { 17 | return "${value}S" 18 | } 19 | 20 | companion object: NBTReaderCompanion { 21 | override fun readContents(source: DataInputStream): NBTShort { 22 | return NBTShort(source.readShort()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTString.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.DataInputStream 4 | import java.io.DataOutputStream 5 | import java.util.* 6 | 7 | class NBTString constructor(override val value: String): NBT { 8 | 9 | override val ID = NBTType.TAG_String 10 | 11 | override fun writeContents(destination: DataOutputStream) { 12 | destination.writeUTF(value) 13 | } 14 | 15 | override fun toSNBT(): String { 16 | val escaped = value.replace("\"", "\\\"") 17 | return "\"$escaped\"" 18 | } 19 | 20 | override fun toString() = toSNBT() 21 | 22 | override fun equals(other: Any?): Boolean { 23 | if (this === other) return true 24 | if (javaClass != other?.javaClass) return false 25 | 26 | other as NBTString 27 | 28 | if (value != other.value) return false 29 | 30 | return true 31 | } 32 | 33 | override fun hashCode(): Int { 34 | return Objects.hash(value) 35 | } 36 | 37 | companion object: NBTReaderCompanion { 38 | override fun readContents(source: DataInputStream): NBTString { 39 | return NBTString(source.readUTF()) 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTType.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import kotlin.reflect.KClass 4 | 5 | open class NBTType( 6 | val nbtClass: KClass, 7 | val readerCompanion: NBTReaderCompanion, 8 | val readableName: String, 9 | val ordinal: Int 10 | ) { 11 | 12 | companion object { 13 | 14 | @JvmField 15 | val TAG_End = object 16 | : NBTType(NBTEnd::class, NBTEnd, "TAG_End", 0) {} 17 | 18 | @JvmField 19 | val TAG_Byte = object 20 | : NBTType(NBTByte::class, NBTByte, "TAG_Byte", 1) {} 21 | 22 | @JvmField 23 | val TAG_Short = object 24 | : NBTType(NBTShort::class, NBTShort, "TAG_Short", 2) {} 25 | 26 | @JvmField 27 | val TAG_Int = object 28 | : NBTType(NBTInt::class, NBTInt, "TAG_Int", 3) {} 29 | 30 | @JvmField 31 | val TAG_Long = object 32 | : NBTType(NBTLong::class, NBTLong, "TAG_Long", 4) {} 33 | 34 | @JvmField 35 | val TAG_Float = object 36 | : NBTType(NBTFloat::class, NBTFloat, "TAG_Float", 5) {} 37 | 38 | @JvmField 39 | val TAG_Double = object 40 | : NBTType(NBTDouble::class, NBTDouble, "TAG_Double", 6) {} 41 | 42 | @JvmField 43 | val TAG_Byte_Array = object 44 | : NBTType(NBTByteArray::class, NBTByteArray, "TAG_Byte_Array", 7) {} 45 | 46 | @JvmField 47 | val TAG_String = object 48 | : NBTType(NBTString::class, NBTString, "TAG_String", 8) {} 49 | 50 | @JvmField 51 | val TAG_List = object 52 | : NBTType>(NBTList::class, NBTList as NBTReaderCompanion>, "TAG_List", 9) {} 53 | 54 | @JvmField 55 | val TAG_Compound = object 56 | : NBTType(NBTCompound::class, NBTCompound, "Tag_Compound", 10) {} 57 | 58 | @JvmField 59 | val TAG_Int_Array = object 60 | : NBTType(NBTIntArray::class, NBTIntArray, "TAG_Int_Array", 11) {} 61 | 62 | @JvmField 63 | val TAG_Long_Array = object 64 | : NBTType(NBTLongArray::class, NBTLongArray, "TAG_Long_Array", 12) {} 65 | 66 | val values = arrayOf( 67 | TAG_End, 68 | TAG_Byte, 69 | TAG_Short, 70 | TAG_Int, 71 | TAG_Long, 72 | TAG_Float, 73 | TAG_Double, 74 | TAG_Byte_Array, 75 | TAG_String, 76 | TAG_List, 77 | TAG_Compound, 78 | TAG_Int_Array, 79 | TAG_Long_Array 80 | ) 81 | 82 | @JvmStatic 83 | fun byIndex(index: Int): NBTType<*> { 84 | 85 | require(index >= 0) { "The index must be greater than 0!" } 86 | require(index < values.size) { "The index must be smaller than ${values.size}! "} 87 | 88 | return values[index] 89 | } 90 | 91 | inline fun byClass(): NBTType? = byClass(T::class) 92 | fun byClass(clazz: KClass): NBTType? = values.firstOrNull { it.nbtClass == clazz } as? NBTType 93 | 94 | @JvmStatic 95 | fun byClass(clazz: Class): NBTType? = byClass(clazz.kotlin) 96 | } 97 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTWriter.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.nbt 2 | 3 | import java.io.* 4 | import java.nio.file.Files 5 | import java.nio.file.Path 6 | 7 | /** 8 | * Writes NBT data to a given destination 9 | * Once the output stream is passed to a NBTWriter, use NBTWriter#close to close the stream. 10 | */ 11 | class NBTWriter @JvmOverloads constructor( 12 | destination: OutputStream, 13 | compressedProcesser: CompressedProcesser<*, *> = CompressedProcesser.GZIP 14 | ): AutoCloseable, Closeable { 15 | 16 | private val writer = DataOutputStream( 17 | compressedProcesser.generateOutputStream(destination) 18 | ) 19 | 20 | /** 21 | * Constructs a NBTWriter from a file (convenience method, equivalent to `NBTWriter(BufferedOutputStream(FileOutputStream(file)))`) 22 | */ 23 | @Throws(IOException::class) 24 | @JvmOverloads constructor(file: File, compressedProcesser: CompressedProcesser<*, *> = CompressedProcesser.GZIP): this(BufferedOutputStream(FileOutputStream(file)), compressedProcesser) 25 | 26 | /** 27 | * Constructs a NBTWriter from a path (convenience method, equivalent to `NBTWriter(BufferedOutputStream(Files.newOutputStream(path)))`) 28 | */ 29 | @Throws(IOException::class) 30 | @JvmOverloads constructor(path: Path, compressedProcesser: CompressedProcesser<*, *> = CompressedProcesser.GZIP): this(BufferedOutputStream(Files.newOutputStream(path)), compressedProcesser) 31 | 32 | /** 33 | * Write a tag with a name inside the destination 34 | */ 35 | @Throws(IOException::class) 36 | fun writeNamed(name: String, tag: NBT) { 37 | writer.writeFullyFormedTag(name, tag) 38 | } 39 | 40 | /** 41 | * Writes the tag contents directly to the destination 42 | */ 43 | @Throws(IOException::class) 44 | fun writeRaw(tag: NBT) { 45 | tag.writeContents(writer) 46 | } 47 | 48 | override fun close() { 49 | writer.close() 50 | } 51 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/org/jglrxavpok/hephaistos/parser/SNBTParser.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.parser 2 | 3 | import org.antlr.v4.runtime.* 4 | import org.jglrxavpok.hephaistos.antlr.SNBTLexer 5 | import org.jglrxavpok.hephaistos.SNBTParsingVisitor 6 | import org.jglrxavpok.hephaistos.nbt.NBT 7 | import org.jglrxavpok.hephaistos.nbt.NBTException 8 | import java.io.Reader 9 | import kotlin.jvm.Throws 10 | import org.jglrxavpok.hephaistos.antlr.SNBTParser as ANTLRParser 11 | 12 | class SNBTParser(val reader: Reader): BaseErrorListener(), AutoCloseable, Cloneable { 13 | 14 | @Throws(NBTException::class) 15 | fun parse(): NBT { 16 | val stream = CharStreams.fromString(reader.readText()) 17 | val lexer = SNBTLexer(stream) 18 | lexer.removeErrorListeners() 19 | lexer.addErrorListener(this) 20 | 21 | val tokens = CommonTokenStream(lexer) 22 | val parser = ANTLRParser(tokens) 23 | parser.removeErrorListeners() 24 | parser.addErrorListener(this) 25 | 26 | return parser.snbt().accept(SNBTParsingVisitor) 27 | } 28 | 29 | override fun close() { 30 | reader.close() 31 | } 32 | 33 | override fun syntaxError( 34 | recognizer: Recognizer<*, *>?, 35 | offendingSymbol: Any?, 36 | line: Int, 37 | charPositionInLine: Int, 38 | msg: String?, 39 | e: RecognitionException? 40 | ) { 41 | throw NBTException("Failed to parse SNBT: Line $line, column $charPositionInLine $msg", e) 42 | } 43 | } -------------------------------------------------------------------------------- /common/src/test/java/LoadTest.java: -------------------------------------------------------------------------------- 1 | import org.jglrxavpok.hephaistos.mca.RegionFile; 2 | 3 | import java.io.RandomAccessFile; 4 | 5 | /** 6 | * Used when Minecraft updates or Hephaistos has errors 7 | * On git because it's easier for me :) 8 | */ 9 | public class LoadTest { 10 | 11 | public static void main(String[] args) { 12 | try { 13 | RegionFile regionFile = new RegionFile(new RandomAccessFile("C:\\Users\\jglrxavpok\\AppData\\Roaming\\.minecraft\\saves\\Test for Hephaistos\\region\\r.0.0.mca", "r"), 0, 0); 14 | regionFile.getChunk(0, 0); 15 | } catch (Exception e) { 16 | e.printStackTrace(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/src/test/java/mca/ChunkMutability.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.data.DataSource; 4 | import org.jglrxavpok.hephaistos.mca.AnvilException; 5 | import org.jglrxavpok.hephaistos.mca.ChunkColumn; 6 | import org.jglrxavpok.hephaistos.mca.CoordinatesKt; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.io.IOException; 11 | 12 | public class ChunkMutability { 13 | 14 | @Test 15 | public void biomesAreMutable() { 16 | ChunkColumn chunk = new ChunkColumn(0, 0); 17 | final int biomeGranularity = 4; 18 | for (int y = 0; y < 256; y += biomeGranularity) { 19 | for (int x = 0; x < 16; x += biomeGranularity) { 20 | for (int z = 0; z < 16; z += biomeGranularity) { 21 | String biomeID = "hephaistos:" + (x + y * 16 + z * 16 * 16); 22 | chunk.setBiome(x, y, z, biomeID); 23 | } 24 | } 25 | } 26 | 27 | 28 | for (int y = 0; y < 256; y += biomeGranularity) { 29 | for (int x = 0; x < 16; x += biomeGranularity) { 30 | for (int z = 0; z < 16; z += biomeGranularity) { 31 | String expectedBiomeID = "hephaistos:" + (x + y * 16 + z * 16 * 16); 32 | String actualBiomeID = chunk.getBiome(x, y, z); 33 | Assertions.assertEquals(expectedBiomeID, actualBiomeID); 34 | } 35 | } 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /common/src/test/java/mca/DataSourceProvider.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.data.RandomAccessFileSource; 4 | import org.jglrxavpok.hephaistos.data.DataSource; 5 | import org.jglrxavpok.hephaistos.data.GrowableSource; 6 | import org.junit.jupiter.api.extension.ExtensionContext; 7 | import org.junit.jupiter.params.provider.Arguments; 8 | import org.junit.jupiter.params.provider.ArgumentsProvider; 9 | 10 | import java.io.File; 11 | import java.io.RandomAccessFile; 12 | import java.nio.file.Files; 13 | import java.util.stream.Stream; 14 | 15 | public class DataSourceProvider implements ArgumentsProvider { 16 | 17 | @Override 18 | public Stream provideArguments(ExtensionContext context) throws Exception { 19 | DataSource growable = new GrowableSource(); 20 | File target = new File(""+context.getUniqueId().hashCode()+".mca"); 21 | Files.deleteIfExists(target.toPath()); 22 | target.createNewFile(); 23 | target.deleteOnExit(); 24 | DataSource raf = new RandomAccessFileSource(new RandomAccessFile(target, "rw")); 25 | 26 | return Stream.of( 27 | Arguments.of(raf), 28 | Arguments.of(growable) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/src/test/java/mca/HeightmapCompression.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.collections.ImmutableLongArray; 4 | import org.jglrxavpok.hephaistos.mca.Heightmap; 5 | import org.jglrxavpok.hephaistos.mca.SupportedVersion; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | public class HeightmapCompression { 11 | 12 | @Test 13 | public void loadAndSave1_15() { 14 | Heightmap map = new Heightmap(); 15 | for (int x = 0; x < 16; x++) { 16 | for (int z = 0; z < 16; z++) { 17 | map.set(x, z, 255-x+z*16); 18 | } 19 | } 20 | ImmutableLongArray compacted = map.compact(SupportedVersion.MC_1_15); 21 | assertEquals(36, compacted.getSize()); 22 | Heightmap loaded = new Heightmap(compacted, SupportedVersion.MC_1_15); 23 | for (int z = 0; z < 16; z++) { 24 | for (int x = 0; x < 16; x++) { 25 | int height = loaded.get(x, z); 26 | assertEquals(255-x+z*16, height); 27 | } 28 | } 29 | } 30 | 31 | @Test 32 | public void loadAndSave1_16() { 33 | Heightmap map = new Heightmap(); 34 | for (int x = 0; x < 16; x++) { 35 | for (int z = 0; z < 16; z++) { 36 | map.set(x, z, 255-x+z*16); 37 | } 38 | } 39 | ImmutableLongArray compacted = map.compact(SupportedVersion.MC_1_16); 40 | assertEquals(37, compacted.getSize()); 41 | Heightmap loaded = new Heightmap(compacted, SupportedVersion.MC_1_16); 42 | for (int z = 0; z < 16; z++) { 43 | for (int x = 0; x < 16; x++) { 44 | int height = loaded.get(x, z); 45 | assertEquals(255-x+z*16, height); 46 | } 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /common/src/test/java/mca/MCACoordinates.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.mca.AnvilException; 4 | import org.jglrxavpok.hephaistos.mca.BlockState; 5 | import org.jglrxavpok.hephaistos.mca.ChunkColumn; 6 | import org.jglrxavpok.hephaistos.mca.RegionFile; 7 | import org.junit.jupiter.api.*; 8 | 9 | import java.io.IOException; 10 | import java.io.RandomAccessFile; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | 15 | import static org.junit.jupiter.api.Assertions.*; 16 | 17 | public class MCACoordinates { 18 | 19 | private RegionFile region; 20 | private RegionFile region2; 21 | 22 | @BeforeEach 23 | void init() throws IOException, AnvilException { 24 | Path target = Path.of("./tmp_coords_r.0.0.mca"); 25 | Path target2 = Path.of("./tmp_coords_r2.0.0.mca"); 26 | Files.createFile(target); 27 | Files.createFile(target2); 28 | RandomAccessFile file = new RandomAccessFile(target.toFile(), "rw"); 29 | RandomAccessFile file2 = new RandomAccessFile(target2.toFile(), "rw"); 30 | region = new RegionFile(file, 0, 0); 31 | region2 = new RegionFile(file2, 5, -2); 32 | } 33 | 34 | @Test 35 | public void throwOnInvalidCoordsX() { 36 | assertThrows(AnvilException.class, () -> 37 | region.getChunk(-1, 0) 38 | ); 39 | } 40 | 41 | @Test 42 | public void throwOnInvalidCoordsZ() { 43 | assertThrows(AnvilException.class, () -> 44 | region.getChunk(0, -1) 45 | ); 46 | } 47 | 48 | @Test 49 | public void throwOnInvalidCoordsX_2() { 50 | assertThrows(AnvilException.class, () -> 51 | region.getChunk(32, 0) 52 | ); 53 | } 54 | 55 | @Test 56 | public void throwOnInvalidCoordsZ_2() { 57 | assertThrows(AnvilException.class, () -> 58 | region.getChunk(0, 32) 59 | ); 60 | } 61 | 62 | @Test 63 | public void throwOnInvalidChunkLocalPositionXNegative() { 64 | assertThrows(IllegalArgumentException.class, () -> { 65 | ChunkColumn column; 66 | column = region2.getOrCreateChunk(5 * 32 + 5, -2 * 32 + 5); 67 | column.setBlockState(-1, 0, 0, BlockState.AIR); 68 | }, "Chunk has a valid position inside the RegionFile, should not throw"); 69 | } 70 | 71 | @Test 72 | public void throwOnInvalidChunkLocalPositionXOverMax() { 73 | assertThrows(IllegalArgumentException.class, () -> { 74 | ChunkColumn column; 75 | column = region2.getOrCreateChunk(5*32+5, -2*32+5); 76 | column.setBlockState(16, 0, 0, BlockState.AIR); 77 | }, "Chunk has a valid position inside the RegionFile, should not throw"); 78 | } 79 | 80 | @Test 81 | public void validChunkLocalPositionX() throws IOException { 82 | ChunkColumn column; 83 | try { 84 | column = region2.getOrCreateChunk(5*32+5, -2*32+5); 85 | column.setBlockState(8, 0, 0, BlockState.AIR); 86 | } catch (AnvilException e) { 87 | fail("Chunk has a valid position inside the RegionFile, should not throw"); 88 | } 89 | } 90 | 91 | @Test 92 | public void validChunkLocalPositionZ() throws IOException { 93 | ChunkColumn column; 94 | try { 95 | column = region2.getOrCreateChunk(5*32+5, -2*32+5); 96 | column.setBlockState(0, 0, 8, BlockState.AIR); 97 | } catch (AnvilException e) { 98 | fail("Chunk has a valid position inside the RegionFile, should not throw"); 99 | } 100 | } 101 | 102 | @Test 103 | public void throwOnInvalidChunkLocalPositionZNegative() { 104 | assertThrows(IllegalArgumentException.class, () -> { 105 | ChunkColumn column; 106 | column = region2.getOrCreateChunk(5*32+5, -2*32+5); 107 | column.setBlockState(0, 0, -1, BlockState.AIR); 108 | }); 109 | } 110 | 111 | @Test 112 | public void throwOnInvalidChunkLocalPositionZOverMax() { 113 | assertThrows(IllegalArgumentException.class, () -> { 114 | ChunkColumn column; 115 | column = region2.getOrCreateChunk(5*32+5, -2*32+5); 116 | column.setBlockState(0, 0, 16, BlockState.AIR); 117 | }, "Chunk has a valid position inside the RegionFile, should not throw"); 118 | } 119 | 120 | @Test 121 | public void validChunkLocalPositionY() throws IOException { 122 | ChunkColumn column; 123 | try { 124 | column = region2.getOrCreateChunk(5*32+5, -2*32+5); 125 | column.setBlockState(0, 8, 0, BlockState.AIR); 126 | } catch (AnvilException e) { 127 | fail("Chunk has a valid position inside the RegionFile, should not throw"); 128 | } 129 | } 130 | 131 | @Test 132 | public void throwOnInvalidChunkLocalPositionYBelowMinY() { 133 | assertThrows(IllegalArgumentException.class, () -> { 134 | ChunkColumn column; 135 | column = region2.getOrCreateChunk(5*32+5, -2*32+5); 136 | column.setBlockState(0, column.getMinY()-1, 0, BlockState.AIR); 137 | }); 138 | } 139 | 140 | 141 | @Test 142 | public void throwOnInvalidChunkLocalPositionYOverMax() { 143 | assertThrows(IllegalArgumentException.class, () -> { 144 | ChunkColumn column; 145 | column = region2.getOrCreateChunk(5*32+5, -2*32+5); 146 | column.setBlockState(0, column.getMaxY()+1, 0, BlockState.AIR); 147 | }, "Chunk has a valid position inside the RegionFile, should not throw"); 148 | } 149 | 150 | @Test 151 | public void setBlockStateInNon0_0Region() throws AnvilException, IOException { 152 | BlockState stone = new BlockState("minecraft:stone"); 153 | region2.setBlockState((32*5)*16+16, 8, (-2*32)*16+16, stone); 154 | assertEquals(stone, region2.getBlockState((32*5)*16+16, 8, (-2*32)*16+16)); 155 | ChunkColumn chunk = region2.getChunk(32*5+1, -2*32+1); 156 | assertNotNull(chunk); 157 | assertEquals(stone, chunk.getBlockState(0, 8, 0)); 158 | assertEquals(BlockState.AIR, chunk.getBlockState(0, 0, 0)); 159 | } 160 | 161 | @AfterEach 162 | public void clean() throws IOException { 163 | region.close(); 164 | region2.close(); 165 | Files.delete(Paths.get("tmp_coords_r.0.0.mca")); 166 | Files.delete(Paths.get("tmp_coords_r2.0.0.mca")); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /common/src/test/java/mca/MCALoadAndSave.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.data.DataSource; 4 | import org.jglrxavpok.hephaistos.data.GrowableSource; 5 | import org.jglrxavpok.hephaistos.data.RandomAccessFileSource; 6 | import org.jglrxavpok.hephaistos.mca.AnvilException; 7 | import org.jglrxavpok.hephaistos.mca.ChunkColumn; 8 | import org.jglrxavpok.hephaistos.mca.RegionFile; 9 | import org.jglrxavpok.hephaistos.mca.readers.ChunkReader; 10 | import org.jglrxavpok.hephaistos.nbt.*; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.ExtensionContext; 13 | import org.junit.jupiter.params.ParameterizedTest; 14 | import org.junit.jupiter.params.provider.Arguments; 15 | import org.junit.jupiter.params.provider.ArgumentsProvider; 16 | import org.junit.jupiter.params.provider.ArgumentsSource; 17 | 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.io.RandomAccessFile; 22 | import java.nio.file.Files; 23 | import java.nio.file.Path; 24 | import java.nio.file.Paths; 25 | import java.nio.file.attribute.FileAttribute; 26 | import java.util.stream.Stream; 27 | 28 | import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 29 | import static org.junit.jupiter.api.Assertions.assertEquals; 30 | import static org.junit.jupiter.api.Assertions.assertNotNull; 31 | 32 | public class MCALoadAndSave { 33 | 34 | @ParameterizedTest 35 | @ArgumentsSource(PathProvider.class) 36 | public void loadAndSaveNoExceptions(Path path) throws IOException, AnvilException { 37 | Path tmpFile = Files.createTempFile("tmp_save_r", ".mca"); 38 | Files.copy(path, tmpFile, REPLACE_EXISTING); 39 | RandomAccessFile file = new RandomAccessFile(tmpFile.toFile(), "rw"); 40 | try(RegionFile r = new RegionFile(file, 0, 0)) { 41 | ChunkColumn chunk0_0 = r.getChunk(0,0); 42 | assertNotNull(chunk0_0); 43 | r.writeColumn(chunk0_0); 44 | } 45 | Files.delete(tmpFile); 46 | } 47 | 48 | @ParameterizedTest 49 | @ArgumentsSource(PathProvider.class) 50 | public void loadAndSaveCheckEquality(Path path) throws IOException, AnvilException { 51 | Path tmpFile = Files.createTempFile("tmp_save_r", ".mca"); 52 | Files.copy(path, tmpFile, REPLACE_EXISTING); 53 | RandomAccessFile file = new RandomAccessFile(tmpFile.toFile(), "rw"); 54 | try(RegionFile r = new RegionFile(file, 0, 0)) { 55 | ChunkColumn c = r.getChunk(0, 0); 56 | r.forget(c); // clear from cache 57 | r.writeColumn(c, c.getVersion()); // save to region file again (without upgrading to latest version) 58 | ChunkColumn reloaded = r.getChunk(0, 0); // reload 59 | 60 | assertEquals(c.toNBT(), reloaded.toNBT()); 61 | } 62 | Files.delete(tmpFile); 63 | } 64 | 65 | @ParameterizedTest 66 | @ArgumentsSource(PathProvider.class) 67 | public void loadAndSaveCheckBiomeEquality(Path path) throws IOException, AnvilException { 68 | Path tmpFile = Files.createTempFile("tmp_save_r", ".mca"); 69 | Files.copy(path, tmpFile, REPLACE_EXISTING); 70 | RandomAccessFile file = new RandomAccessFile(tmpFile.toFile(), "rw"); 71 | try(RegionFile r = new RegionFile(file, 0, 0)) { 72 | NBTCompound cData = r.getChunkData(0, 0); 73 | ChunkColumn c = new ChunkColumn(cData); 74 | NBTCompound reloadedNBT = c.toNBT(c.getVersion()); 75 | 76 | ChunkReader readerOriginal = new ChunkReader(cData); 77 | ChunkReader readerReloaded = new ChunkReader(reloadedNBT); 78 | assertEquals(readerOriginal.getOldBiomes(), readerReloaded.getOldBiomes()); 79 | } 80 | Files.delete(tmpFile); 81 | } 82 | 83 | @Test 84 | public void saveAlmostFullChunk() throws IOException, AnvilException { 85 | Path tmpFile = Files.createTempFile("tmp_save_r", ".mca"); 86 | RandomAccessFile file = new RandomAccessFile(tmpFile.toFile(), "rw"); 87 | 88 | try (RegionFile r = new RegionFile(file, 0, 0)) { 89 | int bigChunkIndex = 0; 90 | int overlappedChunkIndex = 1; 91 | // allocate one sector for chunk which will have big data size 92 | ChunkColumn emptyBigChunk = r.getOrCreateChunk(bigChunkIndex, bigChunkIndex); 93 | r.writeColumn(emptyBigChunk); 94 | r.forget(emptyBigChunk); 95 | 96 | // allocate one sector for chunk which will be overlapped by another chunk 97 | ChunkColumn initialChunk = r.getOrCreateChunk(overlappedChunkIndex, overlappedChunkIndex); 98 | r.writeColumn(initialChunk); 99 | r.forget(initialChunk); 100 | 101 | // write some NBT data so that dataSize is in [4092, 4096] range (4094 in this case) 102 | NBTCompound chunkData = NBT.Compound(builder -> { 103 | int[] intData = new int[2230]; 104 | for (int i = 0; i < intData.length; i++) { 105 | intData[i] = i; 106 | } 107 | NBTIntArray data = NBT.IntArray(intData); 108 | builder.put("v", data); 109 | }); 110 | ByteArrayOutputStream dataOut = new ByteArrayOutputStream(); 111 | try (NBTWriter writer = new NBTWriter(dataOut, CompressedProcesser.ZLIB)) { 112 | writer.writeNamed("", chunkData); 113 | } 114 | 115 | // free previous big chunk position (which is before overlapped chunk) 116 | r.writeColumnData(chunkData, bigChunkIndex, bigChunkIndex); 117 | // write big chunk before overlapped chunk 118 | r.writeColumnData(chunkData, bigChunkIndex, bigChunkIndex); 119 | 120 | // load chunk after possible overlapping (should not happen) 121 | ChunkColumn oldChunk = r.getChunk(overlappedChunkIndex, overlappedChunkIndex); 122 | assertNotNull(oldChunk); 123 | assertEquals(oldChunk.toNBT(), initialChunk.toNBT()); 124 | } finally { 125 | Files.delete(tmpFile); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /common/src/test/java/mca/MCALoading.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.mca.AnvilException; 4 | import org.jglrxavpok.hephaistos.mca.ChunkColumn; 5 | import org.jglrxavpok.hephaistos.mca.RegionFile; 6 | import org.junit.jupiter.api.*; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.ArgumentsSource; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.io.RandomAccessFile; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | import java.nio.file.Paths; 16 | 17 | import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertNotNull; 20 | 21 | public class MCALoading { 22 | 23 | @ParameterizedTest 24 | @ArgumentsSource(PathProvider.class) 25 | public void loadAndSave(Path path) throws IOException, AnvilException { 26 | Path tmpFile = Files.createTempFile("r", ".mca"); 27 | Files.copy(path, tmpFile, REPLACE_EXISTING); 28 | RandomAccessFile file = new RandomAccessFile(tmpFile.toFile(), "rw"); 29 | RegionFile region = new RegionFile(file, 0, 0); 30 | ChunkColumn column0_0 = region.getChunk(0, 0); 31 | assertNotNull(column0_0); 32 | assertEquals(0, column0_0.getX()); 33 | assertEquals(0, column0_0.getZ()); 34 | assertEquals(ChunkColumn.GenerationStatus.Full, column0_0.getGenerationStatus()); 35 | 36 | for (int z = 0; z < 16; z++) { 37 | for (int x = 0; x < 16; x++) { 38 | assertEquals("minecraft:bedrock", column0_0.getBlockState(x, column0_0.getMinY(), z).getName()); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/src/test/java/mca/PaletteTests.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.collections.ImmutableLongArray; 4 | import org.jglrxavpok.hephaistos.mca.*; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.util.HashMap; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | public class PaletteTests { 14 | 15 | private BlockPalette palette; 16 | private static final BlockState STONE = new BlockState("minecraft:stone", new HashMap<>()); 17 | 18 | @BeforeEach 19 | public void init() { 20 | palette = new BlockPalette(); 21 | } 22 | 23 | @Test 24 | public void increaseReferenceOfNonExistentBlock() { 25 | assertEquals(0, palette.getElements().size()); 26 | palette.increaseReference(BlockState.AIR); 27 | assertEquals(1, palette.getElements().size()); 28 | palette.increaseReference(BlockState.AIR); 29 | assertEquals(1, palette.getElements().size()); 30 | } 31 | 32 | @Test 33 | public void decreaseReferenceOfNonExistentBlock() { 34 | assertThrows(IllegalArgumentException.class, () -> 35 | palette.decreaseReference(BlockState.AIR) 36 | ); 37 | 38 | } 39 | 40 | @Test 41 | public void decreaseReferenceOfExistentBlock() { 42 | assertEquals(0, palette.getElements().size()); 43 | palette.increaseReference(BlockState.AIR); 44 | palette.increaseReference(STONE); 45 | assertEquals(2, palette.getElements().size()); 46 | palette.decreaseReference(BlockState.AIR); 47 | assertEquals(1, palette.getElements().size()); 48 | assertEquals(STONE, palette.getElements().get(0)); 49 | palette.decreaseReference(STONE); 50 | assertEquals(0, palette.getElements().size()); 51 | } 52 | 53 | @Test 54 | public void testSimpleCompression1_15() { 55 | palette.increaseReference(BlockState.AIR); 56 | palette.increaseReference(STONE); 57 | BlockState[] states = new BlockState[16]; 58 | for (int i = 0; i < states.length; i++) { 59 | if(i % 3 == 0) { 60 | states[i] = BlockState.AIR; 61 | } else { 62 | states[i] = STONE; 63 | } 64 | } 65 | int[] ids = new int[states.length]; 66 | for (int i = 0; i < states.length; i++) { 67 | // the id assigned to a state is dependent to the order in which the states have been added to the palette 68 | ids[i] = states[i] == BlockState.AIR ? 0 : 1; 69 | } 70 | 71 | // 64 bits / 16 entries = 4 bits required per entry. 72 | // But we only have 2 blocks, so a single bit is enough. 73 | // Finally, a long has 64 bits, which is enough to store 16 entries at 1 bit/entry 74 | ImmutableLongArray expected = LongCompactorKt.compress(ids, 1); 75 | assertEquals(expected, palette.compactIDs(states, SupportedVersion.MC_1_15)); 76 | } 77 | 78 | @Test 79 | public void testSimpleCompression1_16() { 80 | palette.increaseReference(BlockState.AIR); 81 | palette.increaseReference(STONE); 82 | BlockState[] states = new BlockState[16]; 83 | for (int i = 0; i < states.length; i++) { 84 | if(i % 3 == 0) { 85 | states[i] = BlockState.AIR; 86 | } else { 87 | states[i] = STONE; 88 | } 89 | } 90 | int[] ids = new int[states.length]; 91 | for (int i = 0; i < states.length; i++) { 92 | // the id assigned to a state is dependent to the order in which the states have been added to the palette 93 | ids[i] = states[i] == BlockState.AIR ? 0 : 1; 94 | } 95 | 96 | // 64 bits / 16 entries = 4 bits required per entry. 97 | // But we only have 2 blocks, so a single bit is enough. 98 | // Finally, a long has 64 bits, which is enough to store 16 entries at 1 bit/entry 99 | ImmutableLongArray expected = LongCompactorKt.pack(ids, 1); 100 | assertEquals(expected, palette.compactIDs(states, SupportedVersion.MC_1_16)); 101 | } 102 | 103 | @AfterEach 104 | public void clean() { 105 | palette = null; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /common/src/test/java/mca/ParallelizationTests.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.mca.AnvilException; 4 | import org.jglrxavpok.hephaistos.mca.BlockState; 5 | import org.jglrxavpok.hephaistos.mca.ChunkColumn; 6 | import org.jglrxavpok.hephaistos.mca.RegionFile; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.io.RandomAccessFile; 13 | import java.nio.file.Files; 14 | import java.nio.file.Paths; 15 | import java.util.concurrent.ExecutionException; 16 | import java.util.concurrent.ExecutorService; 17 | import java.util.concurrent.Executors; 18 | import java.util.concurrent.Future; 19 | 20 | import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 21 | import static org.junit.jupiter.api.Assertions.assertEquals; 22 | 23 | /** 24 | * While these tests are far from perfect and can clearly report false positives, the sheer amount of chunks to try to save/load at the same time 25 | * amounts to such probabilities of corruption that a false positive is extremely unlikely. 26 | * 27 | * Run these tests multiple times if you are suspicious. 28 | * 29 | * These tests are very basic, and by no means guarantee 100% thread-safety 30 | */ 31 | public class ParallelizationTests { 32 | 33 | @BeforeEach 34 | public void init() throws IOException { 35 | if(Files.deleteIfExists(Paths.get("tmp_parallel_empty_r.0.0.mca"))) 36 | System.out.println("deleted"); 37 | Files.deleteIfExists(Paths.get("tmp_parallel_r.0.0.mca")); 38 | } 39 | 40 | @Test 41 | public void parallelLoad() throws InterruptedException, IOException, AnvilException { 42 | Files.copy(Paths.get("src/test/resources/r.0.0.mca"), Paths.get("tmp_parallel_r.0.0.mca"), REPLACE_EXISTING); 43 | 44 | RegionFile region = new RegionFile(new RandomAccessFile(new File("tmp_parallel_r.0.0.mca"), "rw"), 0, 0, 0, 255); 45 | 46 | ExecutorService pool = Executors.newFixedThreadPool(4); 47 | Future[] chunks = new Future[1024]; 48 | for (int i = 0; i < 1024; i++) { 49 | final int chunkID = i; 50 | chunks[i] = pool.submit(() -> region.getChunk(chunkID % 32, chunkID / 32)); 51 | } 52 | pool.shutdown(); 53 | for (Future chunk : chunks) { 54 | try { 55 | chunk.get(); 56 | } catch (ExecutionException e) { 57 | throw new AssertionError("Chunk loading failed (cause may look weird if filepointer 'corruptions' happen)", e); 58 | } 59 | } 60 | // if parallel reading works and throws no exception, chunks should have loaded with no issue 61 | 62 | region.close(); 63 | Files.deleteIfExists(Paths.get("tmp_parallel_r.0.0.mca")); 64 | } 65 | 66 | private void fill(ChunkColumn column, int id) { 67 | BlockState state = new BlockState(String.valueOf(id)); 68 | for (int z = 0; z < 16; z++) { 69 | for (int x = 0; x < 16; x++) { 70 | for (int y = 0; y < 256; y++) { 71 | column.setBlockState(x, y, z, state); 72 | } 73 | } 74 | } 75 | column.setGenerationStatus(ChunkColumn.GenerationStatus.Full); 76 | } 77 | 78 | private void verify(ChunkColumn column, int id) { 79 | assertEquals(ChunkColumn.GenerationStatus.Full, column.getGenerationStatus(), "Mismatch in generation status: "+column.getX()+"; "+column.getZ()); 80 | BlockState state = new BlockState(String.valueOf(id)); 81 | for (int z = 0; z < 16; z++) { 82 | for (int x = 0; x < 16; x++) { 83 | for (int y = 0; y < 256; y++) { 84 | assertEquals(state, column.getBlockState(x, y, z), "Failed at Chunk("+column.getX()+"; "+column.getZ()+") at X="+x+"; Y="+y+"; Z="+z); 85 | } 86 | } 87 | } 88 | } 89 | 90 | @Test 91 | public void parallelSave() throws InterruptedException, IOException, AnvilException { 92 | RandomAccessFile raf = new RandomAccessFile(new File("tmp_parallel_empty_r.0.0.mca"), "rw"); 93 | raf.setLength(0); 94 | RegionFile region = new RegionFile(raf, 0, 0); 95 | 96 | ExecutorService pool = Executors.newFixedThreadPool(4); 97 | Future[] chunks = new Future[1024]; 98 | System.out.println("SAVE"); 99 | for (int i = 0; i < 1024; i++) { 100 | final int chunkID = i; 101 | RegionFile finalRegion = region; 102 | chunks[i] = pool.submit(() -> { 103 | try { 104 | ChunkColumn column = finalRegion.getOrCreateChunk(chunkID % 32, chunkID / 32); 105 | fill(column, chunkID); 106 | finalRegion.writeColumn(column); 107 | return column; 108 | } catch (Exception e) { 109 | e.printStackTrace(); 110 | throw e; 111 | } 112 | }); 113 | } 114 | 115 | System.out.println("CHECK SAVING WITH NO EXCEPTION"); 116 | for (int i = 0; i < 1024; i++) { 117 | try { 118 | chunks[i].get(); 119 | } catch (ExecutionException e) { 120 | throw new AssertionError("Chunk loading failed (cause may look weird if filepointer 'corruptions' happen)", e); 121 | } 122 | } 123 | // if parallel writes works and throws no exception, chunks should have saved with no issue 124 | // now, we check their contents 125 | region.close(); 126 | 127 | System.out.println("LOAD TO CHECK CONTENTS"); 128 | region = new RegionFile(new RandomAccessFile(new File("tmp_parallel_empty_r.0.0.mca"), "rw"), 0, 0); 129 | for (int chunkID = 0; chunkID < 1024; chunkID++) { 130 | ChunkColumn column = region.getOrCreateChunk(chunkID % 32, chunkID / 32); 131 | verify(column, chunkID); 132 | region.forget(column); 133 | } 134 | 135 | region.close(); 136 | Files.deleteIfExists(Paths.get("tmp_parallel_empty_r.0.0.mca")); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /common/src/test/java/mca/PathProvider.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.junit.jupiter.api.extension.ExtensionContext; 4 | import org.junit.jupiter.params.provider.Arguments; 5 | import org.junit.jupiter.params.provider.ArgumentsProvider; 6 | 7 | import java.nio.file.Paths; 8 | import java.util.stream.Stream; 9 | 10 | public class PathProvider implements ArgumentsProvider { 11 | @Override 12 | public Stream provideArguments(ExtensionContext context) { 13 | return Stream.of( 14 | Arguments.of(Paths.get("src/test/resources/1.18-pre4regions/r.0.0.mca")), 15 | Arguments.of(Paths.get("src/test/resources/r.0.0.mca")), 16 | Arguments.of(Paths.get("src/test/resources/1.20/r.0.0.mca")) 17 | ); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /common/src/test/java/mca/ReadLevelDat.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.nbt.CompressedProcesser; 4 | import org.jglrxavpok.hephaistos.nbt.NBTCompound; 5 | import org.jglrxavpok.hephaistos.nbt.NBTException; 6 | import org.jglrxavpok.hephaistos.nbt.NBTReader; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.io.IOException; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | public class ReadLevelDat { 14 | 15 | @Test 16 | public void readLevelDatFiles() throws IOException, NBTException { 17 | String[] files = new String[] { 18 | "level.1.16.dat", 19 | "level.1.18.dat", 20 | }; 21 | for(String f : files) { 22 | try(var reader = new NBTReader(ReadLevelDat.class.getResourceAsStream("/"+f), CompressedProcesser.GZIP)) { 23 | final NBTCompound tag = (NBTCompound) reader.read(); 24 | assertTrue(tag.contains("Data")); 25 | } catch (IOException | NBTException e) { 26 | throw e; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/test/java/mca/SectionTests.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.mca.BlockState; 4 | import org.jglrxavpok.hephaistos.mca.ChunkSection; 5 | import org.jglrxavpok.hephaistos.mca.SupportedVersion; 6 | import org.jglrxavpok.hephaistos.nbt.NBTCompound; 7 | import org.jglrxavpok.hephaistos.nbt.NBTList; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.util.HashMap; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | public class SectionTests { 15 | 16 | @Test 17 | public void paletteAfterSetBlockState() { 18 | ChunkSection section = new ChunkSection((byte)0); 19 | section.set(0,0,0, new BlockState("minecraft:stone", new HashMap<>())); 20 | NBTCompound nbt = section.toNBT(SupportedVersion.MC_1_17_0); 21 | NBTList palette = nbt.getList("Palette"); 22 | assertEquals(2, palette.getSize()); 23 | 24 | assertEquals(BlockState.AIR.getName(), palette.get(1).getString("Name")); 25 | assertEquals("minecraft:stone", palette.get(0).getString("Name")); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /common/src/test/java/mca/SupportedVersionProvider.java: -------------------------------------------------------------------------------- 1 | package mca; 2 | 3 | import org.jglrxavpok.hephaistos.mca.SupportedVersion; 4 | import org.junit.jupiter.api.extension.ExtensionContext; 5 | import org.junit.jupiter.params.provider.Arguments; 6 | import org.junit.jupiter.params.provider.ArgumentsProvider; 7 | 8 | import java.util.stream.Stream; 9 | 10 | public class SupportedVersionProvider implements ArgumentsProvider { 11 | 12 | @Override 13 | public Stream provideArguments(ExtensionContext context) throws Exception { 14 | return Stream.of( 15 | Arguments.of(SupportedVersion.MC_1_16), 16 | Arguments.of(SupportedVersion.MC_1_17_0), 17 | Arguments.of(SupportedVersion.MC_1_18_PRE_4) 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/src/test/java/nbt/CompressedModeProvider.java: -------------------------------------------------------------------------------- 1 | package nbt; 2 | 3 | import org.jglrxavpok.hephaistos.nbt.CompressedProcesser; 4 | import org.junit.jupiter.api.extension.ExtensionContext; 5 | import org.junit.jupiter.params.provider.Arguments; 6 | import org.junit.jupiter.params.provider.ArgumentsProvider; 7 | 8 | import java.util.stream.Stream; 9 | 10 | class CompressedModeProvider implements ArgumentsProvider { 11 | 12 | @Override 13 | public Stream provideArguments(ExtensionContext context) { 14 | return Stream.of( 15 | Arguments.of(CompressedProcesser.NONE), 16 | Arguments.of(CompressedProcesser.GZIP), 17 | Arguments.of(CompressedProcesser.ZLIB) 18 | ); 19 | } 20 | } -------------------------------------------------------------------------------- /common/src/test/java/nbt/Misc.java: -------------------------------------------------------------------------------- 1 | package nbt; 2 | 3 | import org.jglrxavpok.hephaistos.nbt.*; 4 | import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | public class Misc { 10 | 11 | @Test 12 | public void snbtArrays() { 13 | NBT array = NBT.ByteArray(1, 2, 3); 14 | assertEquals("[B;1B,2B,3B]", array.toSNBT()); 15 | 16 | array = NBT.IntArray(1, 2, 3); 17 | assertEquals("[I;1,2,3]", array.toSNBT()); 18 | 19 | array = NBT.LongArray(1, 2, 3); 20 | assertEquals("[L;1L,2L,3L]", array.toSNBT()); 21 | } 22 | 23 | @Test 24 | public void truthness() { 25 | NBTByte shouldBeFalse = NBT.Byte(0); 26 | assertFalse(shouldBeFalse.asBoolean()); 27 | 28 | NBTByte shouldBeTrue = NBT.Byte(1); 29 | assertTrue(shouldBeTrue.asBoolean()); 30 | 31 | NBTByte shouldBeTrue2 = NBT.Byte(-1); 32 | assertTrue(shouldBeTrue2.asBoolean()); 33 | 34 | NBTByte shouldBeTrueToo = NBT.Byte(42); 35 | assertTrue(shouldBeTrueToo.asBoolean()); 36 | } 37 | 38 | @Test 39 | public void equalityBetweenMutableAndNonMutable() { 40 | NBTCompound myCompound = NBT.Compound(n -> { 41 | n.setString("test", "aaa"); 42 | }); 43 | 44 | MutableNBTCompound mutableCopy = new MutableNBTCompound(myCompound); 45 | assertEquals(myCompound, mutableCopy); 46 | assertEquals(mutableCopy, myCompound); 47 | mutableCopy.setString("test", "bbb"); 48 | assertNotEquals(myCompound, mutableCopy); 49 | assertNotEquals(mutableCopy, myCompound); 50 | } 51 | 52 | @Test 53 | public void mutableNBTConstructorShouldCopyNBTCompound() { 54 | NBTCompound myCompound = NBT.Compound(n -> { 55 | n.setString("test", "aaa"); 56 | }); 57 | 58 | MutableNBTCompound mutableCopy = new MutableNBTCompound(myCompound); 59 | assertEquals("aaa", mutableCopy.getString("test")); 60 | mutableCopy.setString("test", "bbb"); 61 | assertEquals("bbb", mutableCopy.getString("test")); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /common/src/test/java/nbt/NBTLoading.java: -------------------------------------------------------------------------------- 1 | package nbt; 2 | 3 | import kotlin.Pair; 4 | import org.jglrxavpok.hephaistos.collections.ImmutableByteArray; 5 | import org.jglrxavpok.hephaistos.nbt.*; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.io.IOException; 9 | import java.util.Objects; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | public class NBTLoading { 14 | 15 | @Test 16 | public void helloWorld() throws IOException, NBTException { 17 | try(NBTReader reader = new NBTReader(NBTLoading.class.getResourceAsStream("/hello_world.nbt"), CompressedProcesser.NONE)) { 18 | Pair namedTag = reader.readNamed(); 19 | assertEquals("hello world", namedTag.getFirst()); 20 | NBT tag = namedTag.getSecond(); 21 | if(tag instanceof NBTCompound) { 22 | NBTCompound compound = (NBTCompound)tag; 23 | assertEquals(1, compound.getSize()); 24 | assertTrue(compound.containsKey("name"), "Must contain tag 'name'"); 25 | NBT subTag = compound.get("name"); 26 | assertNotNull(subTag); 27 | assertTrue(subTag instanceof NBTString); 28 | assertEquals("Bananrama", ((NBTString)subTag).getValue()); 29 | } else { 30 | fail("Root tag is not TAG_Compound"); 31 | } 32 | } 33 | } 34 | 35 | @Test 36 | public void bigtest() throws IOException, NBTException { 37 | try(NBTReader reader = new NBTReader(NBTLoading.class.getResourceAsStream("/bigtest.nbt"), CompressedProcesser.GZIP)) { 38 | Pair namedTag = reader.readNamed(); 39 | assertEquals("Level", namedTag.getFirst()); 40 | NBT tag = namedTag.getSecond(); 41 | assertTrue(tag instanceof NBTCompound, "root must be TAG_Compound"); 42 | NBTCompound level = (NBTCompound) tag; 43 | assertEquals(11, level.getSize()); 44 | 45 | NBTCompound nestedCompoundTest = Objects.requireNonNull(level.getCompound("nested compound test")); 46 | { 47 | assertEquals(2, nestedCompoundTest.getSize()); 48 | NBTCompound egg = Objects.requireNonNull(nestedCompoundTest.getCompound("egg")); 49 | { 50 | assertEquals(2, egg.getSize()); 51 | assertEquals("Eggbert", egg.getString("name")); 52 | assertEquals(0.5, egg.getFloat("value"), 10e-16); 53 | } 54 | NBTCompound ham = Objects.requireNonNull(nestedCompoundTest.getCompound("ham")); 55 | { 56 | assertEquals(2, ham.getSize()); 57 | assertEquals("Hampus", ham.getString("name")); 58 | assertEquals(0.75, ham.getFloat("value"), 10e-16); 59 | } 60 | } 61 | 62 | assertEquals(2147483647, level.getInt("intTest").intValue()); 63 | assertEquals(127, level.getByte("byteTest").byteValue()); 64 | assertEquals(32767, level.getShort("shortTest").shortValue()); 65 | assertEquals(0.49312871321823148, level.getDouble("doubleTest"), 10e-16); 66 | assertEquals(0.49823147058486938, level.getFloat("floatTest"), 10e-16); 67 | assertEquals(9223372036854775807L, level.getLong("longTest").longValue()); 68 | 69 | // not actually testing the individuals non-ASCII characters, we'll let the standard library handle it 70 | assertTrue(level.getString("stringTest").startsWith("HELLO WORLD THIS IS A TEST STRING ")); 71 | assertTrue(level.getString("stringTest").endsWith("!")); 72 | 73 | NBTList listTestLong = level.getList("listTest (long)").asListOf(); 74 | { 75 | assertEquals(5, listTestLong.getSize()); 76 | assertEquals(11, listTestLong.get(0).getValue()); 77 | assertEquals(12, listTestLong.get(1).getValue()); 78 | assertEquals(13, listTestLong.get(2).getValue()); 79 | assertEquals(14, listTestLong.get(3).getValue()); 80 | assertEquals(15, listTestLong.get(4).getValue()); 81 | } 82 | 83 | NBTList listTestCompound = level.getList("listTest (compound)").asListOf(); 84 | { 85 | assertEquals(2, listTestCompound.getSize()); 86 | NBTCompound compound0 = Objects.requireNonNull(listTestCompound.get(0)); 87 | { 88 | assertEquals(2, compound0.getSize()); 89 | assertEquals(1264099775885L, compound0.getLong("created-on").longValue()); 90 | assertEquals("Compound tag #0", compound0.getString("name")); 91 | } 92 | 93 | NBTCompound compound1 = Objects.requireNonNull(listTestCompound.get(1)); 94 | { 95 | assertEquals(2, compound1.getSize()); 96 | assertEquals(1264099775885L, compound1.getLong("created-on").longValue()); 97 | assertEquals("Compound tag #1", compound1.getString("name")); 98 | } 99 | } 100 | 101 | ImmutableByteArray bytes = Objects.requireNonNull(level.getByteArray("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))")); 102 | assertEquals(1000, bytes.getSize()); 103 | for (int n = 0; n < 1000; n++) { 104 | assertEquals((n*n*255+n*7) % 100, bytes.get(n)); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /common/src/test/java/nbt/NBTSaving.java: -------------------------------------------------------------------------------- 1 | package nbt; 2 | 3 | import kotlin.Pair; 4 | import org.jglrxavpok.hephaistos.nbt.*; 5 | import org.junit.jupiter.api.*; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.ArgumentsSource; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | public class NBTSaving { 15 | 16 | private ByteArrayOutputStream byteArrayOutputStream; 17 | 18 | @ParameterizedTest 19 | @ArgumentsSource(CompressedModeProvider.class) 20 | public void saveByte(CompressedProcesser compressedProcesser) throws IOException, NBTException { 21 | NBTByte nbt = NBT.Byte(42); 22 | test(nbt, compressedProcesser); 23 | } 24 | 25 | @ParameterizedTest 26 | @ArgumentsSource(CompressedModeProvider.class) 27 | public void saveByteArray(CompressedProcesser compressedProcesser) throws IOException, NBTException { 28 | NBTByteArray nbt = NBT.ByteArray(42, 53, -56); 29 | test(nbt, compressedProcesser); 30 | } 31 | 32 | @ParameterizedTest 33 | @ArgumentsSource(CompressedModeProvider.class) 34 | public void saveIntArray(CompressedProcesser compressedProcesser) throws IOException, NBTException { 35 | NBTIntArray nbt = NBT.IntArray(-42, -53, 56, 0xFFC0C0C0); 36 | test(nbt, compressedProcesser); 37 | } 38 | 39 | @ParameterizedTest 40 | @ArgumentsSource(CompressedModeProvider.class) 41 | public void saveLongArray(CompressedProcesser compressedProcesser) throws IOException, NBTException { 42 | NBTLongArray nbt = NBT.LongArray(0xDEADBEEF, 0xCAFEBABE, 0xAAAAAAA); 43 | test(nbt, compressedProcesser); 44 | } 45 | 46 | @ParameterizedTest 47 | @ArgumentsSource(CompressedModeProvider.class) 48 | public void saveShort(CompressedProcesser compressedProcesser) throws IOException, NBTException { 49 | NBTShort nbt = NBT.Short(1); 50 | test(nbt, compressedProcesser); 51 | } 52 | 53 | @ParameterizedTest 54 | @ArgumentsSource(CompressedModeProvider.class) 55 | public void saveInt(CompressedProcesser compressedProcesser) throws IOException, NBTException { 56 | NBTInt nbt = NBT.Int(0x42); 57 | test(nbt, compressedProcesser); 58 | } 59 | 60 | @ParameterizedTest 61 | @ArgumentsSource(CompressedModeProvider.class) 62 | public void saveLong(CompressedProcesser compressedProcesser) throws IOException, NBTException { 63 | NBTLong nbt = NBT.Long(0xCAFEBABEL); 64 | test(nbt, compressedProcesser); 65 | } 66 | 67 | @ParameterizedTest 68 | @ArgumentsSource(CompressedModeProvider.class) 69 | public void saveDouble(CompressedProcesser compressedProcesser) throws IOException, NBTException { 70 | NBTDouble nbt = NBT.Double(0.25); 71 | test(nbt, compressedProcesser); 72 | } 73 | 74 | @ParameterizedTest 75 | @ArgumentsSource(CompressedModeProvider.class) 76 | public void saveFloat(CompressedProcesser compressedProcesser) throws IOException, NBTException { 77 | NBTFloat nbt = NBT.Float(0.5f); 78 | test(nbt, compressedProcesser); 79 | } 80 | 81 | @ParameterizedTest 82 | @ArgumentsSource(CompressedModeProvider.class) 83 | public void saveString(CompressedProcesser compressedProcesser) throws IOException, NBTException { 84 | NBTString nbt = NBT.String("AAA"); 85 | test(nbt, compressedProcesser); 86 | } 87 | 88 | @ParameterizedTest 89 | @ArgumentsSource(CompressedModeProvider.class) 90 | public void saveList(CompressedProcesser compressedProcesser) throws IOException, NBTException { 91 | NBTList nbt = NBT.List( 92 | NBTType.TAG_String, 93 | NBT.String("A"), NBT.String("B"), NBT.String("C"), NBT.String("D") 94 | ); 95 | 96 | test(nbt, compressedProcesser); 97 | } 98 | 99 | @ParameterizedTest 100 | @ArgumentsSource(CompressedModeProvider.class) 101 | public void saveCompound(CompressedProcesser compressedProcesser) throws IOException, NBTException { 102 | var compound = NBT.Compound((root) -> { 103 | root.put("byteArray", NBT.ByteArray(1, 2, 3)); 104 | root.put("byte", NBT.Byte(0x42)); 105 | root.put("double", NBT.Double(0.5)); 106 | root.put("string", NBT.String("ABC")); 107 | root.put("float", NBT.Float(0.25f)); 108 | root.put("int", NBT.Int(4567)); 109 | root.put("intarray", NBT.IntArray(42, 42, 25464, 454, -10)); 110 | root.put("long", NBT.Long(30000000000L)); 111 | root.put("longarray", NBT.LongArray(30000000000L, -30000000000L, 130000000000L)); 112 | root.put("short", NBT.Short(-10)); 113 | }); 114 | 115 | test(compound, compressedProcesser); 116 | } 117 | 118 | private void test(T nbt, CompressedProcesser compressedProcesser) throws IOException, NBTException { 119 | T saved = saveAndRead(nbt, compressedProcesser); 120 | assertEquals(nbt, saved); 121 | } 122 | 123 | 124 | private T saveAndRead(NBT tag, CompressedProcesser compressedProcesser) throws IOException, NBTException { 125 | NBTWriter output = output(compressedProcesser); 126 | output.writeNamed("a", tag); 127 | output.close(); 128 | Pair namedTag = input(compressedProcesser).readNamed(); 129 | assertEquals("a", namedTag.getFirst()); 130 | assertEquals(tag.getClass(), namedTag.getSecond().getClass()); 131 | return (T) namedTag.getSecond(); 132 | } 133 | 134 | 135 | private NBTReader input(CompressedProcesser compressedProcesser) { 136 | return NBTReader.fromArray(byteArrayOutputStream.toByteArray(), compressedProcesser); 137 | } 138 | 139 | private NBTWriter output(CompressedProcesser compressedProcesser) { 140 | return new NBTWriter(byteArrayOutputStream, compressedProcesser); 141 | } 142 | 143 | @BeforeEach 144 | public void init() { 145 | byteArrayOutputStream = new ByteArrayOutputStream(); 146 | } 147 | 148 | @AfterEach 149 | public void clean() { 150 | byteArrayOutputStream = null; 151 | } 152 | 153 | 154 | } 155 | -------------------------------------------------------------------------------- /common/src/test/java/nbt/NBTValue.java: -------------------------------------------------------------------------------- 1 | package nbt; 2 | 3 | import org.jglrxavpok.hephaistos.collections.ImmutableByteArray; 4 | import org.jglrxavpok.hephaistos.collections.ImmutableIntArray; 5 | import org.jglrxavpok.hephaistos.collections.ImmutableLongArray; 6 | import org.jglrxavpok.hephaistos.nbt.NBT; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.util.Map; 11 | 12 | public class NBTValue { 13 | 14 | @Test 15 | public void runTime() { 16 | assertType(Byte.class, NBT.Byte(0)); 17 | assertType(Short.class, NBT.Short(0)); 18 | assertType(Integer.class, NBT.Int(0)); 19 | assertType(Long.class, NBT.Long(0)); 20 | assertType(Float.class, NBT.Float(0)); 21 | assertType(Double.class, NBT.Double(0)); 22 | assertType(String.class, NBT.String("")); 23 | 24 | assertType(ImmutableByteArray.class, NBT.ByteArray()); 25 | assertType(ImmutableIntArray.class, NBT.IntArray()); 26 | assertType(ImmutableLongArray.class, NBT.LongArray()); 27 | 28 | assertType(Map.class, NBT.Compound(Map.of())); 29 | } 30 | 31 | @Test 32 | public void compileTime() { 33 | byte v1 = NBT.Byte(0).getValue(); 34 | Byte v2 = NBT.Byte(0).getValue(); 35 | 36 | short v3 = NBT.Short(0).getValue(); 37 | Short v4 = NBT.Short(0).getValue(); 38 | 39 | int v5 = NBT.Int(0).getValue(); 40 | Integer v6 = NBT.Int(0).getValue(); 41 | 42 | long v7 = NBT.Long(0).getValue(); 43 | Long v8 = NBT.Long(0).getValue(); 44 | 45 | float v9 = NBT.Float(0).getValue(); 46 | Float v10 = NBT.Float(0).getValue(); 47 | 48 | double v11 = NBT.Double(0).getValue(); 49 | Double v12 = NBT.Double(0).getValue(); 50 | 51 | String v13 = NBT.String("").getValue(); 52 | 53 | ImmutableByteArray v14 = NBT.ByteArray().getValue(); 54 | ImmutableIntArray v15 = NBT.IntArray().getValue(); 55 | ImmutableLongArray v16 = NBT.LongArray().getValue(); 56 | 57 | String v17 = NBT.String("").getValue(); 58 | 59 | NBT nbt = NBT.Byte(0); 60 | Object v18 = nbt.getValue(); 61 | 62 | Map v19 = NBT.Compound(Map.of()).getValue(); 63 | } 64 | 65 | void assertType(Class expected, NBT nbt) { 66 | Assertions.assertInstanceOf(expected, nbt.getValue()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /common/src/test/java/regression/Issue17.java: -------------------------------------------------------------------------------- 1 | package regression; 2 | 3 | import org.jglrxavpok.hephaistos.nbt.NBTCompound; 4 | import org.jglrxavpok.hephaistos.nbt.NBTException; 5 | import org.jglrxavpok.hephaistos.nbt.NBTString; 6 | import org.jglrxavpok.hephaistos.parser.SNBTParser; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.io.StringReader; 10 | import java.nio.charset.StandardCharsets; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | public class Issue17 { 15 | @Test 16 | public void unescapeUnicode() throws NBTException { 17 | String tag = "'\\\" hi \\\" \\u00e9'"; 18 | 19 | SNBTParser parser = new SNBTParser(new StringReader(tag)); 20 | NBTString parsed = (NBTString) parser.parse(); 21 | assertEquals("\" hi \" \u00e9", parsed.getValue()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/src/test/java/regression/Issue29.java: -------------------------------------------------------------------------------- 1 | package regression; 2 | 3 | import org.jglrxavpok.hephaistos.nbt.*; 4 | import org.jglrxavpok.hephaistos.parser.SNBTParser; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.io.StringReader; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | public class Issue29 { 13 | @Test 14 | public void bigFloat() throws NBTException { 15 | NBTFloat myFloat = new NBTFloat(10000000F); 16 | 17 | String floatSNBT = myFloat.toSNBT(); 18 | 19 | NBT result = new SNBTParser(new StringReader(floatSNBT)).parse(); 20 | Assertions.assertEquals(myFloat, result); 21 | 22 | result = new SNBTParser(new StringReader("1e7F")).parse(); 23 | Assertions.assertEquals(myFloat, result); 24 | } 25 | 26 | @Test 27 | public void bigDouble() throws NBTException { 28 | NBTDouble myDouble = new NBTDouble(10000000.0); 29 | 30 | String doubleSNBT = myDouble.toSNBT(); 31 | 32 | NBT result = new SNBTParser(new StringReader(doubleSNBT)).parse(); 33 | Assertions.assertEquals(myDouble, result); 34 | 35 | result = new SNBTParser(new StringReader("1e7D")).parse(); 36 | Assertions.assertEquals(myDouble, result); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/src/test/java/regression/Issue3.java: -------------------------------------------------------------------------------- 1 | package regression; 2 | 3 | import org.jglrxavpok.hephaistos.mca.AnvilException; 4 | import org.jglrxavpok.hephaistos.mca.ChunkColumn; 5 | import org.jglrxavpok.hephaistos.mca.RegionFile; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.io.IOException; 9 | import java.io.RandomAccessFile; 10 | 11 | public class Issue3 { 12 | 13 | @Test 14 | public void loadAllChunks() throws IOException, AnvilException { 15 | String name = "src/test/resources/issue_3/r.0.0.mca"; 16 | RegionFile file = new RegionFile(new RandomAccessFile(name, "rw"), 0, 0, 0, 255); 17 | for (int chunkX = 0; chunkX < 32; chunkX++) { 18 | for (int chunkZ = 0; chunkZ < 32; chunkZ++) { 19 | if(file.hasChunk(chunkX, chunkZ)) { 20 | ChunkColumn column = file.getChunk(chunkX, chunkZ); 21 | file.forget(column); 22 | } 23 | } 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /common/src/test/java/regression/Issue8.java: -------------------------------------------------------------------------------- 1 | package regression; 2 | 3 | import org.jglrxavpok.hephaistos.mca.AnvilException; 4 | import org.jglrxavpok.hephaistos.mca.ChunkColumn; 5 | import org.jglrxavpok.hephaistos.mca.RegionFile; 6 | import org.jglrxavpok.hephaistos.nbt.NBTCompound; 7 | import org.jglrxavpok.hephaistos.nbt.NBTException; 8 | import org.jglrxavpok.hephaistos.parser.SNBTParser; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.io.IOException; 12 | import java.io.RandomAccessFile; 13 | import java.io.StringReader; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | 17 | public class Issue8 { 18 | 19 | @Test 20 | public void escapeString() { 21 | String input = "{\"t\":\"h \\\" hihi \\\" i\"}"; 22 | NBTCompound compound; 23 | try { 24 | compound = (NBTCompound) new SNBTParser(new StringReader(input)).parse(); 25 | } catch (final NBTException e) { 26 | throw new RuntimeException(e); 27 | } 28 | 29 | assertEquals("h \" hihi \" i", 30 | compound.getString("t") 31 | ); 32 | } 33 | 34 | /* Removed test. Reason: tag-order dependent, and redundant with test above 35 | @Test 36 | public void escapeString() throws IOException, AnvilException { 37 | String input = "{\"Unbreakable\":1B,\"CustomModelData\":22,\"item_id\":\"test_back\",\"display\":{\"Lore\":[\"{\\\"text\\\":\\\"This is a test backpack! Used for wearing and testing! Put it on your back slot!\\\"}\"],\"Name\":\"{\\\"text\\\":\\\"Test Backpack\\\"}\"}}"; 38 | NBTCompound compound; 39 | try { 40 | compound = (NBTCompound) new SNBTParser(new StringReader(input)).parse(); 41 | } catch (final NBTException e) { 42 | throw new RuntimeException(e); 43 | } 44 | 45 | assertEquals( 46 | "{\"Unbreakable\":1B,\"CustomModelData\":22,\"item_id\":\"test_back\",\"display\":{\"Lore\":[\"{\\\"text\\\":\\\"This is a test backpack! Used for wearing and testing! Put it on your back slot!\\\"}\"],\"Name\":\"{\\\"text\\\":\\\"Test Backpack\\\"}\"}}", 47 | compound.toString() 48 | ); 49 | } 50 | */ 51 | 52 | } 53 | -------------------------------------------------------------------------------- /common/src/test/java/snbt/SNBTCoherencyTests.java: -------------------------------------------------------------------------------- 1 | package snbt; 2 | 3 | import org.jglrxavpok.hephaistos.nbt.*; 4 | import org.jglrxavpok.hephaistos.parser.SNBTParser; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.StringReader; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class SNBTCoherencyTests { 12 | 13 | @Test 14 | public void testCoherence() throws NBTException { 15 | 16 | NBTList testList = NBT.List(NBTType.TAG_String, 10, i -> NBT.String("Test#" + i)); 17 | 18 | NBTCompound c = NBT.Compound(root -> { 19 | root.put("inside", NBT.Compound(inside -> { 20 | inside.put("AAA", NBT.Byte(42)); 21 | inside.put("BBB", NBT.Short(23)); 22 | inside.put("CCC", NBT.Int(-50)); 23 | inside.put("DDD", NBT.Long(123456789123456L)); 24 | inside.put("myByteArray", NBT.ByteArray(1, 2, 3, -9)); 25 | inside.put("myIntArray", NBT.IntArray(1, 2, 3, 9)); 26 | inside.put("myLongArray", NBT.LongArray(10468463464L, 2489679874L, 3549876415L, 9489674L)); 27 | inside.put("EEE", NBT.Double(0.25)); 28 | inside.put("FFF", NBT.Float(0.125f)); 29 | inside.put("some text", NBT.String("KSOKPDOK")); 30 | })); 31 | root.put("list", testList); 32 | root.put("timestamp", NBT.Long(-914312L)); 33 | }); 34 | 35 | String snbt = c.toSNBT(); 36 | try(SNBTParser parser = new SNBTParser(new StringReader(snbt))) { 37 | assertEquals(c, parser.parse()); 38 | } 39 | } 40 | 41 | @Test 42 | public void syntaxError() { 43 | String snbt = "{display:{Lore:[\"text here\"]}"; 44 | assertThrows(NBTException.class, () -> { 45 | try (SNBTParser parser = new SNBTParser(new StringReader(snbt))) { 46 | parser.parse(); 47 | } 48 | }, "Missing bracket, should not parse"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /common/src/test/java/snbt/WhitespaceParsing.java: -------------------------------------------------------------------------------- 1 | package snbt; 2 | 3 | import org.jglrxavpok.hephaistos.nbt.NBTCompound; 4 | import org.jglrxavpok.hephaistos.nbt.NBTException; 5 | import org.jglrxavpok.hephaistos.nbt.NBTType; 6 | import org.jglrxavpok.hephaistos.parser.SNBTParser; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.io.StringReader; 11 | 12 | public class WhitespaceParsing { 13 | 14 | @Test 15 | public void readChatRegistry() throws NBTException { 16 | // From Minestom 17 | NBTCompound chatRegistry = (NBTCompound) new SNBTParser(new StringReader("{\n" + 18 | " \"type\": \"minecraft:chat_type\",\n" + 19 | " \"value\": [\n" + 20 | " {\n" + 21 | " \"name\": \"minecraft:system\",\n" + 22 | " \"id\": 0,\n" + 23 | " \"element\": {\n" + 24 | " \"chat\": {},\n" + 25 | " \"narration\": {\n" + 26 | " \"priority\": \"system\"\n" + 27 | " }\n" + 28 | " }\n" + 29 | " },\n" + 30 | " {\n" + 31 | " \"name\": \"minecraft:game_info\",\n" + 32 | " \"id\": 1,\n" + 33 | " \"element\": {\n" + 34 | " \"overlay\": {}\n" + 35 | " }\n" + 36 | " }\n" + 37 | " ]\n" + 38 | "}\n")).parse(); 39 | 40 | // whitespace in from of id values are NOT part of the value 41 | Assertions.assertEquals(NBTType.TAG_Int, chatRegistry.getList("value").get(0).get("id").getID()); 42 | Assertions.assertEquals(0, chatRegistry.getList("value").get(0).getAsInt("id")); 43 | Assertions.assertEquals(1, chatRegistry.getList("value").get(1).getAsInt("id")); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /common/src/test/kotlin/GrowableSourceTests.kt: -------------------------------------------------------------------------------- 1 | 2 | import org.jglrxavpok.hephaistos.data.GrowableSource 3 | import org.junit.jupiter.api.Assertions.assertEquals 4 | import org.junit.jupiter.api.Test 5 | 6 | class GrowableSourceTests { 7 | 8 | @Test 9 | fun updateSize() { 10 | val source = GrowableSource() 11 | source.setLength(1000) 12 | assertEquals(1000, source.length()) 13 | } 14 | 15 | @Test 16 | fun updateSizeAfterWrite() { 17 | val source = GrowableSource() 18 | source.writeByte(42) 19 | assertEquals(1, source.length()) 20 | } 21 | 22 | @Test 23 | fun updateSizeAfterWrite2() { 24 | val source = GrowableSource() 25 | source.writeInt(-999) 26 | assertEquals(4, source.length()) 27 | } 28 | } -------------------------------------------------------------------------------- /common/src/test/resources/1.18-pre4regions/r.0.0.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minestom/Hephaistos/ddcb64d5d8e6854290350a288543655903500dca/common/src/test/resources/1.18-pre4regions/r.0.0.mca -------------------------------------------------------------------------------- /common/src/test/resources/1.20/r.0.0.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minestom/Hephaistos/ddcb64d5d8e6854290350a288543655903500dca/common/src/test/resources/1.20/r.0.0.mca -------------------------------------------------------------------------------- /common/src/test/resources/bigtest.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minestom/Hephaistos/ddcb64d5d8e6854290350a288543655903500dca/common/src/test/resources/bigtest.nbt -------------------------------------------------------------------------------- /common/src/test/resources/hello_world.nbt: -------------------------------------------------------------------------------- 1 | 2 | hello worldname Bananrama -------------------------------------------------------------------------------- /common/src/test/resources/issue_3/README.md: -------------------------------------------------------------------------------- 1 | Issue #3 -------------------------------------------------------------------------------- /common/src/test/resources/issue_3/r.0.0.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minestom/Hephaistos/ddcb64d5d8e6854290350a288543655903500dca/common/src/test/resources/issue_3/r.0.0.mca -------------------------------------------------------------------------------- /common/src/test/resources/issue_4/r.0.0.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minestom/Hephaistos/ddcb64d5d8e6854290350a288543655903500dca/common/src/test/resources/issue_4/r.0.0.mca -------------------------------------------------------------------------------- /common/src/test/resources/level.1.16.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minestom/Hephaistos/ddcb64d5d8e6854290350a288543655903500dca/common/src/test/resources/level.1.16.dat -------------------------------------------------------------------------------- /common/src/test/resources/level.1.18.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minestom/Hephaistos/ddcb64d5d8e6854290350a288543655903500dca/common/src/test/resources/level.1.18.dat -------------------------------------------------------------------------------- /common/src/test/resources/r.0.0.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minestom/Hephaistos/ddcb64d5d8e6854290350a288543655903500dca/common/src/test/resources/r.0.0.mca -------------------------------------------------------------------------------- /convention-plugins/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` // Is needed to turn our build logic written in Kotlin into Gralde Plugin 3 | } 4 | 5 | repositories { 6 | gradlePluginPortal() // To use 'maven-publish' and 'signing' plugins in our own plugin 7 | } 8 | -------------------------------------------------------------------------------- /convention-plugins/src/main/kotlin/convention.publication.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.util.Properties 2 | 3 | plugins { 4 | `maven-publish` 5 | signing 6 | } 7 | 8 | // Stub secrets to let the project sync and build without the publication values set up 9 | ext["signing.keyId"] = null 10 | ext["signing.password"] = null 11 | ext["signing.secretKeyRingFile"] = null 12 | ext["ossrhUsername"] = null 13 | ext["ossrhPassword"] = null 14 | 15 | // Grabbing secrets from local.properties file or from environment variables, which could be used on CI 16 | val secretPropsFile = project.rootProject.file("secrets.properties") 17 | if (secretPropsFile.exists()) { 18 | secretPropsFile.reader().use { 19 | Properties().apply { 20 | load(it) 21 | } 22 | }.onEach { (name, value) -> 23 | ext[name.toString()] = value 24 | } 25 | } else { 26 | ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") 27 | ext["signing.password"] = System.getenv("SIGNING_PASSWORD") 28 | ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") 29 | ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") 30 | ext["ossrhPassword"] = System.getenv("OSSRH_PASSWORD") 31 | } 32 | 33 | /*val sourcesJar by tasks.registering(Jar::class) { 34 | archiveClassifier.set("sources") 35 | } 36 | val javadocJar by tasks.registering(Jar::class) { 37 | archiveClassifier.set("javadoc") 38 | }*/ 39 | 40 | val javadocJar = tasks.named("javadocJar") 41 | 42 | fun getExtraString(name: String) = ext[name]?.toString() 43 | 44 | publishing { 45 | // Configure maven central repository 46 | repositories { 47 | maven { 48 | name = "sonatype" 49 | setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") 50 | credentials { 51 | username = getExtraString("ossrhUsername") 52 | password = getExtraString("ossrhPassword") 53 | } 54 | } 55 | } 56 | 57 | // Configure all publications 58 | publications.withType { 59 | 60 | artifact(javadocJar.get()) 61 | 62 | // Provide artifacts information requited by Maven Central 63 | pom { 64 | name.set("Hephaistos") 65 | description.set("NBT and MCA library") 66 | url.set("https://github.com/jglrxavpok/Hephaistos") 67 | 68 | licenses { 69 | license { 70 | name.set("MIT") 71 | url.set("https://opensource.org/licenses/MIT") 72 | } 73 | } 74 | developers { 75 | developer { 76 | id.set("jglrxavpok") 77 | name.set("Xavier Niochaut") 78 | email.set("jglrxavpok@gmail.com") 79 | } 80 | } 81 | scm { 82 | url.set("https://github.com/jglrxavpok/Hephaistos") 83 | } 84 | 85 | } 86 | } 87 | } 88 | 89 | // Signing artifacts. Signing.* extra properties values will be used 90 | 91 | // Gradle will throw an exception if the signing key is not found 92 | if (ext["signing.keyId"] != null) { 93 | signing { 94 | sign(publishing.publications) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /docs/migration_guide_v1_v2.md: -------------------------------------------------------------------------------- 1 | # Upgrading guide from Hephaistos v1 to v2 2 | 3 | This guide was made during all changes made to [Minestom](https://github.com/Minestom/Minestom), while I updated its Hephaistos to version v2.1.2. I hope this guide will be clear enough to provide an easy migration. 4 | 5 | The guide will give hints with how to apply the changes via IntelliJ IDEA, but any IDE would work. 6 | 7 | * `org.jglrxavpok.hephaistos.nbt.SNBTParser` becomes `org.jglrxavpok.hephaistos.parser.SNBTParser`. 'Replace in Files' (Control+Shift+R) is enough. 8 | * `org.jglrxavpok.hephaistos.nbt.NBTTypes` become `org.jglrxavpok.hephaistos.nbt.NBTType`. 'Replace in Files' (Control+Shift+R) is enough. 9 | * `NBTEnd` is now a Kotlin `object`. This means `NBTEnd` is now a singleton, creating new instances is not allowed. Use `NBTEnd.INSTANCE` instead. 10 | * `NBTWriter` and `NBTReader` constructors no longer take a `boolean` as a second parameter to determine whether the contents are compressed or not. 11 | If you used no compression (`false` second argument), you can simply drop the second argument and only provide the output. 12 | If you did use compression, select a `CompressedProcessor`. Three are available by default: `CompressedProcessor.NONE`, `CompressedProcessor.GZIP` and `CompressedProcessor.ZLIB`. 13 | 14 | * `NBT#deepClone` no longer exists, because tags are now immutable. 15 | 16 | * `NBTCompound` is no longer mutable. `NBT.Compound` is used to build a NBTCompound now. 17 | If you are writing a compound from scratch, use this example as a base: 18 | 19 | Old version: 20 | ```java 21 | @Override 22 | public @NotNull Component serializeShowEntity(HoverEvent.@NotNull ShowEntity input, Codec.Encoder componentEncoder) throws IOException { 23 | final NBTCompound tag = new NBTCompound(); 24 | tag.setString(ENTITY_ID, input.id().toString()); 25 | tag.setString(ENTITY_TYPE, input.type().asString()); 26 | 27 | final Component name = input.name(); 28 | if (name != null) { 29 | tag.setString(ENTITY_NAME, componentEncoder.encode(name)); 30 | } 31 | 32 | return Component.text(MinestomAdventure.NBT_CODEC.encode(tag)); 33 | } 34 | ``` 35 | 36 | New version: 37 | ```java 38 | @Override 39 | public @NotNull Component serializeShowEntity(HoverEvent.@NotNull ShowEntity input, Codec.Encoder componentEncoder) throws IOException { 40 | final NBTCompound tag = NBT.Compound(t -> { 41 | t.setString(ENTITY_ID, input.id().toString()); 42 | t.setString(ENTITY_TYPE, input.type().asString()); 43 | 44 | final Component name = input.name(); 45 | if (name != null) { 46 | t.setString(ENTITY_NAME, componentEncoder.encode(name)); 47 | } 48 | }); 49 | 50 | return Component.text(MinestomAdventure.NBT_CODEC.encode(tag)); 51 | } 52 | ``` 53 | 54 | * `NBTList` is no longer mutable. Use `NBT.List` to create a list: 55 | 56 | Old: 57 | ```java 58 | List<@NotNull Component> lore; // your list 59 | // ... 60 | final NBTList loreNBT = NBT.List<>(NBTType.TAG_String); 61 | for (Component line : lore) { 62 | loreNBT.add(new NBTString(GsonComponentSerializer.gson().serialize(line))); 63 | } 64 | ``` 65 | 66 | New: 67 | ```java 68 | List<@NotNull Component> lore; // your list 69 | // ... 70 | final NBTList loreNBT = NBT.List(NBTType.TAG_String, 71 | lore.stream() 72 | .map(line -> new NBTString(GsonComponentSerializer.gson().serialize(line))) 73 | .collect(Collectors.toList()) 74 | ); 75 | ``` 76 | 77 | * `NBTCompound#getByteArray`, `NBTCompound#getIntArray`, `NBTCompound#getLongArray`, now respectively return an `ImmutableByteArray`, `ImmutableIntArray`, or an `ImmutableLongArray`. 78 | 79 | * `ChunkColumn#getSections` no longer returns an array, but a map due to negative Y now being allowed (1.17+). 80 | This kind of code no longer compiles: 81 | ```java 82 | ChunkColumn chunk; 83 | for (var section : chunk.getSections()) { 84 | // do something 85 | } 86 | ``` 87 | You will need to use `.values()` to make it work: 88 | ```java 89 | ChunkColumn chunk; 90 | for (var section : chunk.getSections().values()) { 91 | // do something 92 | } 93 | ``` 94 | 95 | 96 | ## Additionnal notes 97 | 98 | * `NBTCompound#removeTag` no longer exists (NBTCompound is now immutable). `MutableNBTCompound` exposes `remove(String)` instead. -------------------------------------------------------------------------------- /example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | 3 | } 4 | 5 | dependencies { 6 | implementation(project(":common")) 7 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | 3 | # Fix for Dokka crashing with an OOM: 4 | org.gradle.jvmargs=-XX:MaxMetaspaceSize=2G -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minestom/Hephaistos/ddcb64d5d8e6854290350a288543655903500dca/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jul 02 14:38:26 CEST 2020 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /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 Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /gson/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `maven-publish` 3 | id("convention.publication") 4 | } 5 | 6 | dependencies { 7 | implementation("com.google.code.gson:gson:2.9.1") 8 | 9 | implementation(project(":common")) 10 | } 11 | 12 | publishing { 13 | publications { 14 | create("maven") { 15 | from(components["java"]) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /gson/src/main/kotlin/org/jglrxavpok/hephaistos/json/NBTGsonReader.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.json 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonElement 5 | import com.google.gson.JsonObject 6 | import org.jglrxavpok.hephaistos.nbt.* 7 | import java.io.Closeable 8 | import java.io.Reader 9 | 10 | class NBTGsonReader(private val reader: Reader): AutoCloseable, Closeable { 11 | 12 | private companion object { 13 | val GsonInstance = Gson() 14 | 15 | /** 16 | * If nbtType is TAG_List, this method will attempt to guess the type of the list elements. 17 | * The guess is done by getting the first element and guessing its type via #guessType. 18 | * If the list is empty, this method will always guess that the subtype is TAG_String 19 | */ 20 | @JvmStatic 21 | fun parse(nbtType: NBTType, element: JsonElement): Tag { 22 | try { 23 | val result = when (nbtType) { 24 | NBTType.TAG_End -> NBTEnd 25 | NBTType.TAG_Byte -> NBT.Byte(element.asByte) 26 | NBTType.TAG_Short -> NBT.Short(element.asShort) 27 | NBTType.TAG_Int -> NBT.Int(element.asInt) 28 | NBTType.TAG_Long -> NBT.Long(element.asLong) 29 | NBTType.TAG_Float -> NBT.Float(element.asFloat) 30 | NBTType.TAG_Double -> NBT.Double(element.asDouble) 31 | NBTType.TAG_Byte_Array -> NBT.ByteArray(*element.asJsonArray.map { it.asByte }.toByteArray()) 32 | NBTType.TAG_String -> if(element.isJsonNull) NBT.String("") else NBT.String(element.asString) 33 | 34 | NBTType.TAG_Compound -> toCompound(element.asJsonObject) 35 | 36 | NBTType.TAG_Int_Array -> NBT.IntArray(*element.asJsonArray.map { it.asInt }.toIntArray()) 37 | NBTType.TAG_Long_Array -> NBT.LongArray(*element.asJsonArray.map { it.asLong }.toLongArray()) 38 | 39 | NBTType.TAG_List -> { 40 | if (!element.isJsonArray) 41 | throw NBTException("Expected a list, but was: $element") 42 | val elements = element.asJsonArray 43 | 44 | if (elements.size() == 0) { // guess strings 45 | NBT.List(NBTType.TAG_String) 46 | } else { 47 | val firstElement = parse(guessType(elements[0]), elements[0]) 48 | val list = NBT.List(firstElement.ID, elements.map { parse(firstElement.ID, it) }) 49 | list 50 | } 51 | } 52 | 53 | else -> throw IllegalArgumentException("$nbtType is not a valid/supported NBT type") 54 | } 55 | return result as Tag 56 | } catch (e: IllegalStateException) { 57 | throw NBTException("Failed to load NBT from json", e) 58 | } 59 | } 60 | 61 | @JvmStatic 62 | private fun toCompound(jsonObject: JsonObject) = NBT.Kompound { 63 | for((key, value) in jsonObject.entrySet()) { 64 | val nbt = parse(guessType(value), value) 65 | this[key] = nbt 66 | } 67 | } 68 | 69 | @JvmStatic 70 | private fun guessType(element: JsonElement): NBTType { 71 | return when { 72 | element.isJsonObject -> NBTType.TAG_Compound 73 | element.isJsonNull -> NBTType.TAG_String 74 | element.isJsonPrimitive -> { 75 | val primitive = element.asJsonPrimitive 76 | when { 77 | primitive.isBoolean -> NBTType.TAG_Byte 78 | primitive.isString -> NBTType.TAG_String 79 | primitive.isNumber -> 80 | if(primitive.asLong.toDouble() == primitive.asDouble) { 81 | if('.' in primitive.asString) { 82 | NBTType.TAG_Double 83 | } else { 84 | NBTType.TAG_Long 85 | } 86 | } else { 87 | NBTType.TAG_Double 88 | } 89 | else -> error("Primitive that is neither a boolean, a string, nor a number?") 90 | } 91 | } 92 | 93 | element.isJsonArray -> { 94 | val array = element.asJsonArray 95 | if(array.size() == 0) { 96 | NBTType.TAG_List 97 | } else { 98 | val firstElement = element.asJsonArray.get(0) 99 | val guessedType = guessType(firstElement) 100 | when(guessedType) { 101 | NBTType.TAG_Long -> NBTType.TAG_Long_Array 102 | NBTType.TAG_Byte -> NBTType.TAG_Byte_Array 103 | NBTType.TAG_Int -> NBTType.TAG_Int_Array 104 | 105 | else -> NBTType.TAG_List 106 | } 107 | } 108 | } 109 | 110 | else -> error("Unknown json element type $element") 111 | } 112 | } 113 | } 114 | 115 | inline fun read(): Tag { 116 | return read(NBTType.byClass() ?: error("Invalid NBTType: ${Tag::class.qualifiedName}")) as Tag 117 | } 118 | 119 | fun read(nbtClass: Class): Tag = read(NBTType.byClass(nbtClass) ?: error("Invalid NBTType: ${nbtClass.canonicalName}")) as Tag 120 | 121 | 122 | fun read(nbtType: NBTType): NBT { 123 | return parse(nbtType, GsonInstance.fromJson(reader, JsonElement::class.java)) 124 | } 125 | 126 | fun readWithGuess(): NBT { 127 | val element = GsonInstance.fromJson(reader, JsonElement::class.java) 128 | return parse(guessType(element), element) 129 | } 130 | 131 | override fun close() { 132 | reader.close() 133 | } 134 | } -------------------------------------------------------------------------------- /gson/src/main/kotlin/org/jglrxavpok/hephaistos/json/NBTGsonWriter.kt: -------------------------------------------------------------------------------- 1 | package org.jglrxavpok.hephaistos.json 2 | 3 | import com.google.gson.* 4 | import org.jglrxavpok.hephaistos.nbt.* 5 | 6 | class NBTGsonWriter internal constructor(val gson: Gson = GsonInstance) { 7 | 8 | companion object { 9 | val GsonInstance = Gson() 10 | 11 | @JvmStatic 12 | @JvmOverloads 13 | fun writer(gson: Gson = GsonInstance) = NBTGsonWriter(gson) 14 | 15 | } 16 | 17 | fun write(element: NBT): JsonElement { 18 | return when(element) { 19 | is NBTNumber -> JsonPrimitive(element.value) 20 | is NBTString -> JsonPrimitive(element.value) 21 | is NBTByteArray -> JsonArray().apply { element.forEach { add(it) } } 22 | is NBTLongArray -> JsonArray().apply { element.forEach { add(it) } } 23 | is NBTIntArray -> JsonArray().apply { element.forEach { add(it) } } 24 | is NBTList -> JsonArray().apply { element.forEach { add(write(it)) } } 25 | is NBTCompound -> JsonObject().apply { 26 | for((name, value) in element) { 27 | add(name, write(value)) 28 | } 29 | } 30 | 31 | is NBTEnd -> throw NBTException("Cannot convert TAG_End to a JsonElement") 32 | else -> error("Cannot serialize type ${element.ID}, not supported by this writer") 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /gson/src/test/java/TestReaderCompound.java: -------------------------------------------------------------------------------- 1 | import com.google.gson.JsonObject; 2 | import org.jglrxavpok.hephaistos.json.NBTGsonReader; 3 | import org.jglrxavpok.hephaistos.nbt.NBTCompound; 4 | import org.jglrxavpok.hephaistos.nbt.NBTType; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.StringReader; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | public class TestReaderCompound { 12 | 13 | @Test 14 | public void readSimpleCompound() { 15 | String json = "{\n" + 16 | "\t\"anInt\": 45,\n" + 17 | "\t\"a float\": 45.5,\n" + 18 | "\t\"text\": \"hello it is me, example text!\"\n" + 19 | "}"; 20 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader(json))) { 21 | NBTCompound compound = reader.read(NBTCompound.class); 22 | assertEquals(3, compound.getSize()); 23 | assertEquals(45, compound.getAsInt("anInt").intValue()); 24 | assertEquals(45.5f, compound.getAsFloat("a float"), 10e-6); 25 | assertEquals("hello it is me, example text!", compound.getString("text")); 26 | } 27 | } 28 | 29 | @Test 30 | public void readCompoundWithInnerCompound() { 31 | String json = "{\n" + 32 | "\t\"anInt\": 45,\n" + 33 | "\t\"a float\": 45.5,\n" + 34 | "\t\"text\": \"hello it is me, example text!\",\n" + 35 | "\t\"testObject\": {\n" + 36 | "\t\t\"a\": 0,\n" + 37 | "\t\t\"b\": 1,\n" + 38 | "\t\t\"c\": 2\n" + 39 | "\t}\n" + 40 | "}"; 41 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader(json))) { 42 | NBTCompound compound = reader.read(NBTCompound.class); 43 | assertEquals(4, compound.getSize()); 44 | assertEquals(45, compound.getAsInt("anInt")); 45 | assertEquals(45.5f, compound.getAsFloat("a float")); 46 | assertEquals("hello it is me, example text!", compound.getString("text")); 47 | 48 | NBTCompound innerObject = compound.getCompound("testObject"); 49 | assertEquals(3, innerObject.getSize()); 50 | assertEquals(0, innerObject.getAsInt("a")); 51 | assertEquals(1, innerObject.getAsInt("b")); 52 | assertEquals(2, innerObject.getAsInt("c")); 53 | } 54 | } 55 | 56 | @Test 57 | public void readCompoundFromGsonElement() { 58 | JsonObject object = new JsonObject(); 59 | object.addProperty("a string", "hello"); 60 | object.addProperty("a float", 1F); 61 | 62 | NBTCompound compound = NBTGsonReader.parse(NBTType.TAG_Compound, object); 63 | 64 | assertEquals(2, compound.getSize()); 65 | assertEquals("hello", compound.getString("a string")); 66 | assertEquals(1f, compound.getAsFloat("a float")); 67 | } 68 | 69 | @Test 70 | public void readCompoundFromGsonElementWithInnerElement() { 71 | JsonObject object = new JsonObject(); 72 | object.addProperty("a string", "hello"); 73 | object.addProperty("a float", 1F); 74 | JsonObject inner = new JsonObject(); 75 | inner.addProperty("inner", "yes"); 76 | object.add("inner", inner); 77 | 78 | NBTCompound compound = NBTGsonReader.parse(NBTType.TAG_Compound, object); 79 | 80 | assertEquals(3, compound.getSize()); 81 | assertEquals("hello", compound.getString("a string")); 82 | assertEquals(1f, compound.getAsFloat("a float")); 83 | 84 | NBTCompound innerCompound = compound.getCompound("inner"); 85 | 86 | assertEquals("yes", innerCompound.getString("inner")); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /gson/src/test/java/TestReaderPrimitives.java: -------------------------------------------------------------------------------- 1 | import org.jglrxavpok.hephaistos.json.NBTGsonReader; 2 | import org.jglrxavpok.hephaistos.nbt.*; 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.StringReader; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | public class TestReaderPrimitives { 11 | 12 | @Test 13 | public void readString() { 14 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("\"AAA\""))) { 15 | NBTString string = reader.read(NBTString.class); 16 | assertEquals("AAA", string.getValue()); 17 | } 18 | } 19 | 20 | @Test 21 | public void guessString() { 22 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("\"AAA\""))) { 23 | NBT string = reader.readWithGuess(); 24 | assertTrue(string instanceof NBTString, "Must be a NBTString"); 25 | assertEquals("AAA", ((NBTString)string).getValue()); 26 | } 27 | } 28 | 29 | @Test 30 | public void readFloat() { 31 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("42.5"))) { 32 | NBTFloat element = reader.read(NBTFloat.class); 33 | assertEquals(42.5, element.getValue(), 10e-6); 34 | } 35 | } 36 | 37 | @Test 38 | public void readDouble() { 39 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("41.5"))) { 40 | NBTDouble element = reader.read(NBTDouble.class); 41 | assertEquals(41.5, element.getValue(), 10e-6); 42 | } 43 | } 44 | 45 | @Test 46 | public void guessDouble() { 47 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("42.5"))) { 48 | NBT element = reader.readWithGuess(); 49 | assertTrue(element instanceof NBTDouble, "Must be a NBTDouble"); 50 | assertEquals(42.5, ((NBTDouble)element).getValue(), 10e-6); 51 | } 52 | } 53 | 54 | @Test 55 | public void readLong() { 56 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("425"))) { 57 | NBTLong element = reader.read(NBTLong.class); 58 | assertEquals(425, element.getValue()); 59 | } 60 | } 61 | 62 | @Test 63 | public void readInt() { 64 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("425"))) { 65 | NBTInt element = reader.read(NBTInt.class); 66 | assertEquals(425, element.getValue()); 67 | } 68 | } 69 | 70 | @Test 71 | public void readShort() { 72 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("425"))) { 73 | NBTShort element = reader.read(NBTShort.class); 74 | assertEquals(425, element.getValue()); 75 | } 76 | } 77 | 78 | @Test 79 | public void readByte() { 80 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("42"))) { 81 | NBTByte element = reader.read(NBTByte.class); 82 | assertEquals(42, element.getValue()); 83 | } 84 | } 85 | 86 | @Test 87 | public void guessLong() { 88 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("425"))) { 89 | NBT element = reader.readWithGuess(); 90 | assertTrue(element instanceof NBTLong, "Must be a NBTLong"); 91 | assertEquals(425, ((NBTLong)element).getValue()); 92 | } 93 | } 94 | @Test 95 | public void nullShouldBeGuessedAsEmptyString() { 96 | try(NBTGsonReader reader = new NBTGsonReader(new StringReader("null"))) { 97 | NBT element = reader.readWithGuess(); 98 | assertTrue(element instanceof NBTString, "Must be a NBTString"); 99 | assertEquals("", ((NBTString)element).getValue()); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /gson/src/test/java/TestWriterCompound.java: -------------------------------------------------------------------------------- 1 | import com.google.gson.Gson; 2 | import com.google.gson.JsonObject; 3 | import org.jglrxavpok.hephaistos.json.NBTGsonWriter; 4 | import org.jglrxavpok.hephaistos.nbt.NBT; 5 | import org.jglrxavpok.hephaistos.nbt.NBTCompound; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | 13 | public class TestWriterCompound { 14 | 15 | private NBTGsonWriter writer; 16 | 17 | @BeforeEach 18 | public void init() { 19 | writer = NBTGsonWriter.writer(); 20 | } 21 | 22 | @Test 23 | public void innerCompound() { 24 | NBTCompound nbt = NBT.Compound(root -> { 25 | root.put("timestamp", NBT.Long(1564864468L)); 26 | root.put("458", NBT.Byte(0x0F)); 27 | 28 | root.put("inside", NBT.Compound(inside -> 29 | inside.put("doubledouble", NBT.Double(0.5)) 30 | )); 31 | 32 | root.put("aaa", NBT.ByteArray(1, 2)); 33 | }); 34 | 35 | assertEquals(new Gson().fromJson("{\"timestamp\":1564864468,\"458\":15,\"inside\":{\"doubledouble\":0.5},\"aaa\":[1,2]}", JsonObject.class), writer.write(nbt)); 36 | } 37 | 38 | @AfterEach 39 | public void clean() { 40 | writer = null; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /gson/src/test/java/TestWriterLists.java: -------------------------------------------------------------------------------- 1 | import com.google.gson.JsonElement; 2 | import org.jglrxavpok.hephaistos.json.NBTGsonWriter; 3 | import org.jglrxavpok.hephaistos.nbt.*; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | public class TestWriterLists { 10 | 11 | private NBTGsonWriter writer; 12 | 13 | @BeforeEach 14 | public void init() { 15 | writer = NBTGsonWriter.writer(); 16 | } 17 | 18 | @Test 19 | public void writeByteArray() { 20 | JsonElement element = writer.write(NBT.ByteArray(0xCA, 0xFE, 0xBA, 0xBE)); 21 | assertEquals("[-54,-2,-70,-66]", element.toString()); 22 | } 23 | 24 | @Test 25 | public void writeIntArray() { 26 | JsonElement element = writer.write(NBT.IntArray(0xDEAD, 0xBEEF)); 27 | assertEquals("[57005,48879]", element.toString()); 28 | } 29 | 30 | @Test 31 | public void writeLongArray() { 32 | JsonElement element = writer.write(NBT.LongArray(132456789L, 987654321L, 14785239L, 9871236540L, -1379468250549L)); 33 | assertEquals("[132456789,987654321,14785239,9871236540,-1379468250549]", element.toString()); 34 | } 35 | 36 | @Test 37 | public void writeList() { 38 | NBTList list = NBT.List( 39 | NBTType.TAG_String, 40 | NBT.String("My"), NBT.String("Super"), NBT.String("Awesome"), NBT.String("List") 41 | ); 42 | JsonElement element = writer.write(list); 43 | assertEquals("[\"My\",\"Super\",\"Awesome\",\"List\"]", element.toString()); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /gson/src/test/java/TestWriterPrimitives.java: -------------------------------------------------------------------------------- 1 | import com.google.gson.JsonElement; 2 | import org.jglrxavpok.hephaistos.json.NBTGsonWriter; 3 | import org.jglrxavpok.hephaistos.nbt.*; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | public class TestWriterPrimitives { 10 | 11 | private NBTGsonWriter writer; 12 | 13 | @BeforeEach 14 | public void init() { 15 | writer = NBTGsonWriter.writer(); 16 | } 17 | 18 | @Test 19 | public void writeByte() { 20 | JsonElement element = writer.write(NBT.Byte(42)); 21 | assertEquals("42", element.toString()); 22 | } 23 | 24 | @Test 25 | public void writeShort() { 26 | JsonElement element = writer.write(NBT.Short(-40)); 27 | assertEquals("-40", element.toString()); 28 | } 29 | 30 | @Test 31 | public void writeLong() { 32 | JsonElement element = writer.write(NBT.Long(9876543210L)); 33 | assertEquals("9876543210", element.toString()); 34 | } 35 | 36 | @Test 37 | public void writeString() { 38 | JsonElement element = writer.write(NBT.String("Some text")); 39 | assertEquals("\"Some text\"", element.toString()); 40 | } 41 | 42 | @Test 43 | public void writeFloat() { 44 | JsonElement element = writer.write(NBT.Float(0.5f)); 45 | assertEquals("0.5", element.toString()); 46 | } 47 | 48 | @Test 49 | public void writeDouble() { 50 | JsonElement element = writer.write(NBT.Double(-0.25)); 51 | assertEquals("-0.25", element.toString()); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "Hephaistos" 2 | 3 | include(":example") 4 | include(":gson") 5 | include(":common") 6 | include(":antlr") 7 | include(":viewer") 8 | includeBuild("convention-plugins") 9 | -------------------------------------------------------------------------------- /viewer/build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | mavenCentral() 3 | } 4 | 5 | dependencies { 6 | implementation("io.github.spair:imgui-java-app:1.86.0") 7 | 8 | implementation(project(":common")) 9 | } -------------------------------------------------------------------------------- /viewer/src/main/kotlin/io/github/jglrxavpok/ViewerApp.kt: -------------------------------------------------------------------------------- 1 | import imgui.ImGui 2 | import imgui.ImVec2 3 | import imgui.app.Application 4 | import imgui.app.Configuration 5 | import imgui.flag.ImGuiWindowFlags 6 | import org.jglrxavpok.hephaistos.mca.RegionFile 7 | import org.jglrxavpok.hephaistos.nbt.* 8 | import java.awt.Desktop 9 | import java.io.File 10 | import java.io.RandomAccessFile 11 | import javax.swing.JFileChooser 12 | 13 | class ViewerApp : Application() { 14 | companion object { 15 | @JvmStatic 16 | fun main(args: Array) { 17 | launch(ViewerApp()) 18 | } 19 | 20 | private val WhichChunkPopupID = "Which chunk inside region?" 21 | } 22 | 23 | private var showChunkPopup = false 24 | private var tryingToOpen = File("") 25 | private var nbt = NBTCompound() 26 | 27 | override fun configure(config: Configuration) { 28 | config.title = "Hephaistos NBT Viewer" 29 | } 30 | 31 | private fun open(f: File) { 32 | if("mca" == f.extension) { 33 | tryingToOpen = f 34 | showChunkPopup = true 35 | return 36 | } 37 | 38 | TODO() 39 | } 40 | 41 | private fun drawPopups() { 42 | if(showChunkPopup) { 43 | ImGui.openPopup(WhichChunkPopupID) 44 | } 45 | 46 | if(ImGui.beginPopupModal(WhichChunkPopupID)) { 47 | val array by lazy { IntArray(2) } 48 | ImGui.dragInt2("Chunk coordinates", array) 49 | 50 | ImGui.separator() 51 | if(ImGui.button("Cancel")) { 52 | showChunkPopup = false 53 | ImGui.closeCurrentPopup() 54 | } 55 | ImGui.sameLine(); 56 | if(ImGui.button("OK")) { 57 | val region = RegionFile(RandomAccessFile(tryingToOpen, "r"), 0, 0) 58 | nbt = region.getChunkData(array[0], array[1]) ?: NBTCompound() 59 | 60 | showChunkPopup = false 61 | ImGui.closeCurrentPopup() 62 | } 63 | 64 | ImGui.endPopup() 65 | } 66 | } 67 | 68 | private fun drawMainBar() { 69 | if(ImGui.beginMainMenuBar()) { 70 | if(ImGui.beginMenu("File")) { 71 | if(ImGui.menuItem("Open")) { 72 | val fileChooser = JFileChooser(File(".")) 73 | val returnValue = fileChooser.showOpenDialog(null) 74 | if(returnValue == JFileChooser.APPROVE_OPTION) { 75 | open(fileChooser.selectedFile) 76 | } 77 | } 78 | 79 | ImGui.endMenu(); 80 | } 81 | 82 | ImGui.endMainMenuBar(); 83 | } 84 | } 85 | 86 | private fun drawEntry(name: String, nbt: NBT) { 87 | when (nbt.ID) { 88 | NBTType.TAG_Compound -> { 89 | if(ImGui.treeNode(name)) { 90 | drawTree(nbt as NBTCompound) 91 | ImGui.treePop() 92 | } 93 | } 94 | NBTType.TAG_List -> { 95 | if(ImGui.treeNode(name)) { 96 | val asList = nbt as NBTList 97 | for ((i, entry) in asList.withIndex()) { 98 | drawEntry("[$i]", entry) 99 | } 100 | ImGui.treePop() 101 | } 102 | } 103 | NBTType.TAG_Byte -> { 104 | ImGui.bulletText(name) 105 | val value = IntArray(1) 106 | value[0] = (nbt as NBTByte).value.toInt() 107 | 108 | ImGui.sameLine() 109 | ImGui.setNextItemWidth(100f); 110 | ImGui.dragInt("##value", value, 1.0f, -128f, 128f) 111 | // TODO: make it modifiable 112 | } 113 | NBTType.TAG_Int_Array -> { 114 | val asArray = nbt as NBTIntArray 115 | if(ImGui.treeNode(name + " Int[${asArray.size}]")) { 116 | for ((i, entry) in asArray.withIndex()) { 117 | ImGui.bulletText("[$i] = $entry") 118 | } 119 | ImGui.treePop() 120 | } 121 | } 122 | NBTType.TAG_Long_Array -> { 123 | val asArray = nbt as NBTLongArray 124 | if(ImGui.treeNode(name + " Long[${asArray.size}]")) { 125 | for ((i, entry) in asArray.withIndex()) { 126 | ImGui.bulletText("[$i] = $entry") 127 | } 128 | ImGui.treePop() 129 | } 130 | } 131 | NBTType.TAG_Byte_Array -> { 132 | val asArray = nbt as NBTByteArray 133 | if(ImGui.treeNode(name + " Byte[${asArray.size}]")) { 134 | for ((i, entry) in asArray.withIndex()) { 135 | ImGui.bulletText("[$i] = $entry") 136 | } 137 | ImGui.treePop() 138 | } 139 | } 140 | else -> { 141 | ImGui.bulletText(name) 142 | ImGui.sameLine() 143 | 144 | // TODO: 145 | ImGui.text(nbt.toSNBT()) 146 | } 147 | } 148 | } 149 | 150 | private fun drawTree(compound: NBTCompound) { 151 | ImGui.pushID(compound.toSNBT()) 152 | for((name, entry) in compound) { 153 | drawEntry(name, entry) 154 | } 155 | ImGui.popID() 156 | } 157 | 158 | override fun process() { 159 | val size = ImGui.getMainViewport().size 160 | 161 | drawMainBar(); 162 | val mainBarHeight = ImGui.getCursorPos().y 163 | ImGui.setNextWindowPos(0f, mainBarHeight) 164 | ImGui.setNextWindowSize(size.x, size.y-mainBarHeight) 165 | 166 | if(ImGui.begin("main window", ImGuiWindowFlags.NoDecoration or ImGuiWindowFlags.NoMove)) { 167 | drawTree(nbt) 168 | } 169 | 170 | drawPopups() 171 | ImGui.end(); 172 | } 173 | 174 | } --------------------------------------------------------------------------------