From 7f0c65c0d1869acfabb4d3df2d99b7ef1731c577 Mon Sep 17 00:00:00 2001
From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com>
Date: Thu, 29 Oct 2026 11:41:29 +0100
Subject: [PATCH] fix(tools): restore extracted resource pack definitions
The Gradle tooling refactor in #413 removed the __brarchive folder support
introduced by #415. Normal brax output still uses that layout, so pack
generation succeeds while omitting biome, fog and render-controller data.
Merge direct and extracted definition folders into the generated pack.
Preserve direct files on duplicate paths, close directory streams and
propagate copy failures instead of silently producing incomplete output.
Exercise the actual ZIP writer with extracted, mixed and direct layouts.
Restore schema guidance for compiled MCB resources in the update process.
Validate on the update/r26u6 branch with a complete build and three tests.
Removing the extracted-folder source makes two regressions fail. Running
the generator against the verified 1.26.60.5 GDK extraction restores 344
biome, 249 fog and 214 render-controller files across the bundled packs.
The generated vanilla skin archive retains its existing file contents.
---
Update Process.md | 3 +
build.gradle | 12 +++
.../client/BedrockPacksGeneratorTest.java | 91 +++++++++++++++++++
.../client/BedrockPacksGenerator.java | 50 +++++-----
4 files changed, 133 insertions(+), 23 deletions(-)
create mode 100644 src/test/java/net/raphimc/viabedrock/tool/generator/client/BedrockPacksGeneratorTest.java
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();
}
- });
+ }
}
}