Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Update Process.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ sourceSets {
tool.runtimeClasspath += tool.compileClasspath
}

configurations {
testRuntimeOnly.extendsFrom compileOnly
}

repositories {
//mavenLocal() // Uncomment when updating
maven {
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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<Path> 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();
}
});
}
}
}

Expand Down