diff --git a/Update Process.md b/Update Process.md index 29e060fca..7ae6ac535 100644 --- a/Update Process.md +++ b/Update Process.md @@ -92,6 +92,9 @@ Proposals come from three strategies, and the report says which one produced eac 3. Update manual enums. 4. Extract the client resource packs with [brarchive-extractor](https://github.com/LPaicen/brarchive-extractor). The tools read the extracted packs from `run/client-data/resource_packs_unpacked` by default. + Use matching [BDS schemas](https://github.com/bedrock-apis/bds-docs) or a client schema export to decode compiled MCB resources. + Pass the schema directory with `--schema=/path/to/schemas`. + Keep the extracted `__brarchive` directories inside each pack. The pack generator merges their definitions into the pack root. 5. Update `assets/viabedrock/data` (see `Data Asset Sources.md`) and `BedrockMappingData`. `./gradlew generateBedrockSoundLists` and `./gradlew generateBedrockParticleList` generate the sound and particle files from the client. New block state upgrade schemas are applied to the mappings with diff --git a/build.gradle b/build.gradle index 8a16054d7..1ff718496 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,10 @@ sourceSets { tool.runtimeClasspath += tool.compileClasspath } +configurations { + testRuntimeOnly.extendsFrom compileOnly +} + repositories { //mavenLocal() // Uncomment when updating maven { @@ -101,6 +105,14 @@ dependencies { toolImplementation "org.jsoup:jsoup:1.23.2" toolImplementation "org.ow2.asm:asm-tree:9.11.1" toolImplementation "net.lenni0451.commons:asm:1.9.2" + + testImplementation sourceSets.tool.output + testImplementation "org.junit.jupiter:junit-jupiter:6.1.3" + testRuntimeOnly "org.junit.platform:junit-platform-launcher:6.1.3" +} + +test { + useJUnitPlatform() } def registerToolTask(String name, String toolMainClass, String toolDescription) { diff --git a/src/test/java/net/raphimc/viabedrock/tool/generator/client/BedrockPacksGeneratorTest.java b/src/test/java/net/raphimc/viabedrock/tool/generator/client/BedrockPacksGeneratorTest.java new file mode 100644 index 000000000..bb2405bd7 --- /dev/null +++ b/src/test/java/net/raphimc/viabedrock/tool/generator/client/BedrockPacksGeneratorTest.java @@ -0,0 +1,91 @@ +/* + * This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock + * Copyright (C) 2023-2026 RK_01/RaphiMC and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.viabedrock.tool.generator.client; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.ZipFile; + +import static org.junit.jupiter.api.Assertions.*; + +final class BedrockPacksGeneratorTest { + + @TempDir + Path directory; + + @Test + void includesExtractedDefinitionsAtThePackRoot() throws Exception { + final Path input = Files.createDirectory(this.directory.resolve("input")); + final byte[] payload = {1, 2, 3}; + for (String folder : new String[]{"biomes", "fogs", "render_controllers"}) { + write(input.resolve("__brarchive/" + folder + "/nested/definition.json"), payload); + } + final Path output = this.directory.resolve("output.mcpack"); + + BedrockPacksGenerator.writeResourcePack(input.toFile(), output); + + try (ZipFile pack = new ZipFile(output.toFile())) { + for (String folder : new String[]{"biomes", "fogs", "render_controllers"}) { + assertArrayEquals(payload, pack.getInputStream(pack.getEntry(folder + "/nested/definition.json")).readAllBytes()); + assertNull(pack.getEntry("__brarchive/" + folder + "/nested/definition.json")); + } + } + } + + @Test + void mergesBothLayoutsAndPreservesDirectFilesOnConflicts() throws Exception { + final Path input = Files.createDirectory(this.directory.resolve("input")); + final byte[] direct = {1, 2, 3}; + final byte[] extracted = {4, 5, 6}; + write(input.resolve("biomes/direct.json"), direct); + write(input.resolve("__brarchive/biomes/extracted.json"), extracted); + write(input.resolve("__brarchive/biomes/direct.json"), extracted); + final Path output = this.directory.resolve("output.mcpack"); + + BedrockPacksGenerator.writeResourcePack(input.toFile(), output); + + try (ZipFile pack = new ZipFile(output.toFile())) { + assertArrayEquals(direct, pack.getInputStream(pack.getEntry("biomes/direct.json")).readAllBytes()); + assertArrayEquals(extracted, pack.getInputStream(pack.getEntry("biomes/extracted.json")).readAllBytes()); + } + } + + @Test + void preservesDirectDefinitionsWithoutAnExtractedDirectory() throws Exception { + final Path input = Files.createDirectory(this.directory.resolve("input")); + final byte[] payload = {7, 8, 9}; + write(input.resolve("fogs/direct.json"), payload); + final Path output = this.directory.resolve("output.mcpack"); + + BedrockPacksGenerator.writeResourcePack(input.toFile(), output); + + try (ZipFile pack = new ZipFile(output.toFile())) { + assertArrayEquals(payload, pack.getInputStream(pack.getEntry("fogs/direct.json")).readAllBytes()); + assertNull(pack.getEntry("biomes")); + } + } + + private static void write(final Path path, final byte[] payload) throws Exception { + Files.createDirectories(path.getParent()); + Files.write(path, payload); + } + +} diff --git a/src/tool/java/net/raphimc/viabedrock/tool/generator/client/BedrockPacksGenerator.java b/src/tool/java/net/raphimc/viabedrock/tool/generator/client/BedrockPacksGenerator.java index 7dc6be460..7963784f8 100644 --- a/src/tool/java/net/raphimc/viabedrock/tool/generator/client/BedrockPacksGenerator.java +++ b/src/tool/java/net/raphimc/viabedrock/tool/generator/client/BedrockPacksGenerator.java @@ -65,21 +65,7 @@ public static void main(final String[] args) throws Throwable { packKeys.add(readPackKey(new File(packDir, "manifest.json"))); final File outputFile = new File(resourcePacksOutputDir, packDir.getName() + ".mcpack"); - - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + outputFile.toURI()), Map.of("create", "true"))) { - final Path fsRoot = fs.getRootDirectories().iterator().next(); - - addLicense(fsRoot); - copyFileIfExists(packDir, fsRoot, "manifest.json"); - copyFileIfExists(packDir, fsRoot, "texts/en_US.lang"); - copyFileIfExists(packDir, fsRoot, "font/glyph_E0.png"); - copyFileIfExists(packDir, fsRoot, "font/glyph_E1.png"); - copyFolder(packDir, fsRoot, "biomes"); - copyFolder(packDir, fsRoot, "fogs"); - copyFolder(packDir, fsRoot, "render_controllers"); - - removeTimestamps(fsRoot); - } + writeResourcePack(packDir, outputFile.toPath()); } final File skinPacksOutputDir = ToolPaths.SKIN_PACKS.toFile(); @@ -95,6 +81,23 @@ public static void main(final String[] args) throws Throwable { reportMissingPackKeys(packKeys); } + static void writeResourcePack(final File packDir, final Path outputFile) throws IOException { + try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + outputFile.toUri()), Map.of("create", "true"))) { + final Path fsRoot = fs.getRootDirectories().iterator().next(); + + addLicense(fsRoot); + copyFileIfExists(packDir, fsRoot, "manifest.json"); + copyFileIfExists(packDir, fsRoot, "texts/en_US.lang"); + copyFileIfExists(packDir, fsRoot, "font/glyph_E0.png"); + copyFileIfExists(packDir, fsRoot, "font/glyph_E1.png"); + copyFolder(packDir, fsRoot, "biomes"); + copyFolder(packDir, fsRoot, "fogs"); + copyFolder(packDir, fsRoot, "render_controllers"); + + removeTimestamps(fsRoot); + } + } + /** * The load order of the vanilla packs is hand maintained, so new packs are only reported instead of being added blindly. */ @@ -153,22 +156,23 @@ private static void copyFileIfExists(final File packDir, final Path targetRoot, } private static void copyFolder(final File packDir, final Path targetRoot, final String folderPath) throws IOException { - final File folder = new File(packDir, folderPath); - if (folder.exists()) { + // Direct definitions take precedence when both layouts contain the same path. + for (File folder : List.of(new File(packDir, folderPath), new File(packDir, "__brarchive/" + folderPath))) { + if (!folder.isDirectory()) { + continue; + } final Path sourcePath = folder.toPath(); final Path targetPath = targetRoot.resolve(folderPath); - Files.walk(sourcePath).forEach(path -> { - try { + try (Stream paths = Files.walk(sourcePath)) { + for (Path path : paths.toList()) { final Path resolvedTargetPath = targetPath.resolve(sourcePath.relativize(path).toString()); if (Files.isDirectory(path)) { Files.createDirectories(resolvedTargetPath); - } else { + } else if (!Files.exists(resolvedTargetPath)) { Files.copy(path, resolvedTargetPath); } - } catch (final IOException e) { - e.printStackTrace(); } - }); + } } }