1
0
mirror of https://github.com/GeyserMC/PackConverter.git synced 2026-01-06 15:41:51 +00:00

Improve access to the vanilla pack, allow some fallback textures in fonts and overlay transformer

This commit is contained in:
Aurora
2025-07-15 16:28:34 +01:00
parent 4e795ea670
commit e2d623f19c
14 changed files with 156 additions and 39 deletions

View File

@@ -31,10 +31,7 @@ import org.geysermc.pack.bedrock.resource.BedrockResourcePack;
import org.geysermc.pack.converter.converter.ActionListener;
import org.geysermc.pack.converter.converter.Converter;
import org.geysermc.pack.converter.data.ConversionData;
import org.geysermc.pack.converter.util.DefaultLogListener;
import org.geysermc.pack.converter.util.LogListener;
import org.geysermc.pack.converter.util.NioDirectoryFileTreeReader;
import org.geysermc.pack.converter.util.ZipUtils;
import org.geysermc.pack.converter.util.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import team.unnamed.creative.ResourcePack;
@@ -43,6 +40,7 @@ import team.unnamed.creative.serialize.minecraft.MinecraftResourcePackReader;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
@@ -56,6 +54,8 @@ public final class PackConverter {
private Path input;
private Path output;
private Path vanillaPackPath = Paths.get("vanilla-pack.zip");
private String textureSubdirectory;
private boolean compressed;
@@ -121,6 +121,17 @@ public final class PackConverter {
return this;
}
/**
* Sets the path where the vanilla pack is downloaded to
*
* @param vanillaPackPath the vanilla pack location
* @return this instance
*/
public PackConverter vanillaPackPath(@NotNull Path vanillaPackPath) {
this.vanillaPackPath = vanillaPackPath;
return this;
}
/**
* Sets the texture subdirectory.
* <p>
@@ -245,9 +256,18 @@ public final class PackConverter {
throw new NullPointerException("Output cannot be null");
}
if (this.vanillaPackPath == null) {
throw new NullPointerException("Vanilla Pack Path cannot be null");
}
// Load any image plugins
ImageIO.scanForPlugins();
// Need to download the client jar, then use the
// client jar to get the vanilla models and textures, so we can
// ensure all parent models exist to convert them to Bedrock.
VanillaPackProvider.create(vanillaPackPath, this.logListener);
ZipUtils.openFileSystem(this.input, this.compressed, input -> {
this.tmpDir = this.output.toAbsolutePath().getParent().resolve(this.output.getFileName() + "_mcpack/");
@@ -256,10 +276,11 @@ public final class PackConverter {
}
ResourcePack javaResourcePack = this.compressed ? MinecraftResourcePackReader.minecraft().readFromZipFile(this.input) : MinecraftResourcePackReader.minecraft().read(NioDirectoryFileTreeReader.read(this.input));
ResourcePack vanillaResourcePack = MinecraftResourcePackReader.minecraft().readFromZipFile(vanillaPackPath);
BedrockResourcePack bedrockResourcePack = new BedrockResourcePack(this.tmpDir);
final Converter.ConversionDataCreationContext conversionDataCreationContext = new Converter.ConversionDataCreationContext(
this, logListener, input, this.tmpDir, javaResourcePack
this, logListener, input, this.tmpDir, javaResourcePack, vanillaResourcePack
);
int errors = 0;

View File

@@ -33,6 +33,6 @@ public abstract class BaseConverter implements Converter<BaseConversionData> {
@Override
public BaseConversionData createConversionData(@NotNull ConversionDataCreationContext context) {
return new BaseConversionData(context.inputDirectory(), context.outputDirectory());
return new BaseConversionData(context.inputDirectory(), context.outputDirectory(), context.vanillaResourcePack());
}
}

View File

@@ -50,7 +50,8 @@ public interface Converter<T extends ConversionData> {
@NotNull LogListener logListener,
@NotNull Path inputDirectory,
@NotNull Path outputDirectory,
@NotNull ResourcePack javaResourcePack
@NotNull ResourcePack javaResourcePack,
@NotNull ResourcePack vanillaResourcePack
) {
}
}

View File

@@ -185,7 +185,8 @@ public class ModelConverter implements Converter<ModelConversionData> {
public ModelConversionData createConversionData(@NotNull ConversionDataCreationContext context) {
return new ModelConversionData(
context.inputDirectory(), context.outputDirectory(),
ModelStitcher.vanillaProvider(context.javaResourcePack(), context.logListener())
ModelStitcher.vanillaProvider(context.javaResourcePack(), context.logListener(), context.vanillaResourcePack()),
context.vanillaResourcePack()
);
}

View File

@@ -195,19 +195,11 @@ public class ModelStitcher {
return pack::model;
}
public static Provider vanillaProvider(@NotNull ResourcePack pack, @NotNull LogListener log) {
// Need to download the client jar, then use the
// client jar to get the vanilla models, so we can
// ensure all parents exist to convert them to Bedrock.
// TODO Make the location of this configurable
Path vanillaPackPath = Paths.get("vanilla-pack.zip");
VanillaPackProvider.create(vanillaPackPath, log);
ResourcePack vanillaResourcePack = MinecraftResourcePackReader.minecraft().readFromZipFile(vanillaPackPath);
public static Provider vanillaProvider(@NotNull ResourcePack pack, @NotNull LogListener log, @NotNull ResourcePack vanillaPack) {
return key -> {
Model model = pack.model(key);
if (model == null) {
return vanillaResourcePack.model(key);
return vanillaPack.model(key);
}
return model;

View File

@@ -74,7 +74,12 @@ public class TextureConverter implements Converter<TextureConversionData> {
List<Texture> textures = new ArrayList<>(context.javaResourcePack().textures());
context.info("Transforming textures...");
TransformContext transformContext = new TransformContext(context, mappings, textures, context.bedrockResourcePack());
TransformContext transformContext = new TransformContext(
context,
mappings,
textures,
context.bedrockResourcePack()
);
for (TextureTransformer transformer : this.transformers) {
transformer.transform(transformContext);
}
@@ -186,6 +191,11 @@ public class TextureConverter implements Converter<TextureConversionData> {
@Override
public TextureConversionData createConversionData(@NotNull ConversionDataCreationContext context) {
return new TextureConversionData(context.inputDirectory(), context.outputDirectory(), context.converter().textureSubdirectory());
return new TextureConversionData(
context.inputDirectory(),
context.outputDirectory(),
context.converter().textureSubdirectory(),
context.vanillaResourcePack()
);
}
}

View File

@@ -34,6 +34,7 @@ import org.geysermc.pack.converter.data.TextureConversionData;
import org.geysermc.pack.converter.util.ImageUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import team.unnamed.creative.ResourcePack;
import team.unnamed.creative.base.Writable;
import team.unnamed.creative.texture.Texture;
@@ -97,6 +98,56 @@ public class TransformContext {
return this.byKey.get(key);
}
/**
* Removes the texture from the list of textures and returns it.
* Or will peek the vanilla texture if it does not exist in the pack.
* If it still does not exist, we return null
*
* @param key the key of the texture to remove
* @return the texture that was removed, or the vanilla one if it didn't exist, or null
*/
@Nullable
public Texture pollOrPeekVanilla(@NotNull Key key) {
Texture remove = this.byKey.remove(key);
if (remove == null) {
// This *shouldn't* be null, but if a bad key is inputted, it is possible this value is null
return this.conversionContext.data().vanillaPack().texture(key);
}
this.textures.remove(remove);
return remove;
}
/**
* Gets the texture from the list of textures and returns it.
* Or will peek the vanilla texture if it does not exist in the pack.
* If it still does not exist, we return null
*
* @param key the key of the texture to get
* @return the texture that was removed, or the vanilla one if it didn't exist, or null
*/
@Nullable
public Texture peekOrVanilla(@NotNull Key key) {
Texture texture = this.byKey.get(key);
if (texture == null) {
// This *shouldn't* be null, but if a bad key is inputted, it is possible this value is null
return this.conversionContext.data().vanillaPack().texture(key);
}
return texture;
}
/**
* Returns whether the pack has a certain texture
*
* @param key the key of the texture to check
* @return true if the texture is present, else false
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted") // It just doesn't make sense.
public boolean isTexturePresent(@NotNull Key key) {
return this.byKey.containsKey(key);
}
/**
* Adds the given texture to the list of textures.
*

View File

@@ -41,7 +41,8 @@ import java.util.List;
@AutoService(TextureTransformer.class)
public class OverlayTransformer implements TextureTransformer {
private static final List<OverlayData> OVERLAYS = List.of(
// This is used in VanillaPackProvider in order to get textures if one is missing out of the two
public static final List<OverlayData> OVERLAYS = List.of(
// Cat
new OverlayData("entity/cat/cat_collar.png", "entity/cat/all_black.png", "entity/cat/allblackcat_tame.png", true, true),
new OverlayData("entity/cat/cat_collar.png", "entity/cat/british_shorthair.png", "entity/cat/britishshorthair_tame.png", true, true),
@@ -101,18 +102,23 @@ public class OverlayTransformer implements TextureTransformer {
boolean noReplace = overlay.noReplace();
boolean keep = overlay.keep();
Texture texture = context.peek(Key.key(Key.MINECRAFT_NAMESPACE, javaName));
if (texture == null) {
context.debug(String.format("Base overlay texture %s not found", javaName)); // TODO only skip if both the base and overlay are not present, if one is present, use the vanilla texture as the other
Key javaKey = Key.key(Key.MINECRAFT_NAMESPACE, javaName);
Key overlayKey = Key.key(Key.MINECRAFT_NAMESPACE, overlayName);
// We don't have either textures, skip this
if (!context.isTexturePresent(javaKey) && !context.isTexturePresent(overlayKey)) continue;
Texture texture = context.peekOrVanilla(javaKey);
if (texture == null) { // This ideally, shouldn't happen anymore
context.info(String.format("Base overlay texture %s not found", javaName));
continue;
}
BufferedImage image = this.readImage(texture);
Key overlayKey = Key.key(Key.MINECRAFT_NAMESPACE, overlayName);
Texture overlayTexture = keep ? context.peek(overlayKey) : context.poll(overlayKey);
if (overlayTexture == null) {
context.debug(String.format("Overlay texture %s not found", overlayName));
Texture overlayTexture = keep ? context.peekOrVanilla(overlayKey) : context.pollOrPeekVanilla(overlayKey);
if (overlayTexture == null) { // This ideally, shouldn't happen anymore
context.info(String.format("Overlay texture %s not found", overlayName));
context.offer(Key.key(Key.MINECRAFT_NAMESPACE, bedrockName), image, "png");
continue;
}
@@ -121,6 +127,11 @@ public class OverlayTransformer implements TextureTransformer {
BufferedImage imageOverlay = this.readImage(overlayTexture);
// Scale to the base image so ensure we don't go out of bounds in the image
int resizeX = image.getWidth() / imageOverlay.getWidth();
int resizeY = image.getHeight() / imageOverlay.getHeight();
imageOverlay = ImageUtil.scale(imageOverlay, resizeX, resizeY);
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
if (noReplace) {
@@ -143,7 +154,7 @@ public class OverlayTransformer implements TextureTransformer {
}
}
record OverlayData(@NotNull String javaName, @NotNull String overlay, @NotNull String bedrockName, boolean noReplace, boolean keep) {
public record OverlayData(@NotNull String javaName, @NotNull String overlay, @NotNull String bedrockName, boolean noReplace, boolean keep) {
public OverlayData(@NotNull String javaName, @NotNull String overlay, @NotNull String bedrockName, boolean noReplace) {
this(javaName, overlay, bedrockName, noReplace, false);
}

View File

@@ -56,21 +56,21 @@ public class FontTransformer implements TextureTransformer {
Map<String, BufferedImage> imgs = new HashMap<>();
Map<String, Integer> scales = new HashMap<>();
Texture ascii = context.poll(Key.key(Key.MINECRAFT_NAMESPACE, "font/ascii.png"));
Texture ascii = context.pollOrPeekVanilla(Key.key(Key.MINECRAFT_NAMESPACE, "font/ascii.png"));
if (ascii != null) {
BufferedImage image = this.readImage(ascii);
imgs.put("ascii", image);
scales.put("ascii", image.getWidth() / 128);
}
Texture accented = context.poll(Key.key(Key.MINECRAFT_NAMESPACE, "font/accented.png"));
Texture accented = context.pollOrPeekVanilla(Key.key(Key.MINECRAFT_NAMESPACE, "font/accented.png"));
if (accented != null) {
BufferedImage image = this.readImage(accented);
imgs.put("accented", image);
scales.put("accented", image.getWidth() / 128);
}
Texture nonlatin_european = context.poll(Key.key(Key.MINECRAFT_NAMESPACE, "font/nonlatin_european.png"));
Texture nonlatin_european = context.pollOrPeekVanilla(Key.key(Key.MINECRAFT_NAMESPACE, "font/nonlatin_european.png"));
if (nonlatin_european != null) {
BufferedImage image = this.readImage(nonlatin_european);
imgs.put("nonlatin_european", image);

View File

@@ -27,16 +27,19 @@
package org.geysermc.pack.converter.data;
import org.jetbrains.annotations.NotNull;
import team.unnamed.creative.ResourcePack;
import java.nio.file.Path;
public class BaseConversionData implements ConversionData {
private final Path inputDirectory;
private final Path outputDirectory;
private final ResourcePack vanillaPack;
public BaseConversionData(@NotNull Path inputDirectory, @NotNull Path outputDirectory) {
public BaseConversionData(@NotNull Path inputDirectory, @NotNull Path outputDirectory, @NotNull ResourcePack vanillaPack) {
this.inputDirectory = inputDirectory;
this.outputDirectory = outputDirectory;
this.vanillaPack = vanillaPack;
}
@NotNull
@@ -50,4 +53,10 @@ public class BaseConversionData implements ConversionData {
public Path outputDirectory() {
return this.outputDirectory;
}
@NotNull
@Override
public ResourcePack vanillaPack() {
return this.vanillaPack;
}
}

View File

@@ -27,6 +27,7 @@
package org.geysermc.pack.converter.data;
import org.jetbrains.annotations.NotNull;
import team.unnamed.creative.ResourcePack;
import java.nio.file.Path;
@@ -37,4 +38,7 @@ public interface ConversionData {
@NotNull
Path outputDirectory();
@NotNull
ResourcePack vanillaPack();
}

View File

@@ -30,6 +30,7 @@ import lombok.Getter;
import net.kyori.adventure.key.Key;
import org.geysermc.pack.converter.converter.model.ModelStitcher;
import org.jetbrains.annotations.NotNull;
import team.unnamed.creative.ResourcePack;
import team.unnamed.creative.model.Model;
import java.nio.file.Path;
@@ -41,8 +42,8 @@ public class ModelConversionData extends BaseConversionData {
@Getter
private final ModelStitcher.Provider modelProvider;
public ModelConversionData(@NotNull Path inputDirectory, @NotNull Path outputDirectory, ModelStitcher.Provider modelProvider) {
super(inputDirectory, outputDirectory);
public ModelConversionData(@NotNull Path inputDirectory, @NotNull Path outputDirectory, ModelStitcher.Provider modelProvider, @NotNull ResourcePack vanillaPack) {
super(inputDirectory, outputDirectory, vanillaPack);
this.modelProvider = modelProvider;
}

View File

@@ -29,6 +29,7 @@ package org.geysermc.pack.converter.data;
import org.geysermc.pack.converter.converter.texture.transformer.TransformedTexture;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import team.unnamed.creative.ResourcePack;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -38,8 +39,8 @@ public class TextureConversionData extends BaseConversionData {
private final List<TransformedTexture> transformedTextures = new ArrayList<>();
private final String textureSubdirectory;
public TextureConversionData(@NotNull Path inputDirectory, @NotNull Path outputDirectory, @Nullable String textureSubdirectory) {
super(inputDirectory, outputDirectory);
public TextureConversionData(@NotNull Path inputDirectory, @NotNull Path outputDirectory, @Nullable String textureSubdirectory, @NotNull ResourcePack vanillaPack) {
super(inputDirectory, outputDirectory, vanillaPack);
this.textureSubdirectory = textureSubdirectory;
}

View File

@@ -28,6 +28,7 @@ package org.geysermc.pack.converter.util;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.file.PathUtils;
import org.geysermc.pack.converter.converter.texture.transformer.type.OverlayTransformer;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
@@ -36,11 +37,13 @@ import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public final class VanillaPackProvider {
private static final String JAR_DOWNLOAD = "https://piston-data.mojang.com/v1/objects/%s/client.jar";
private static final String JAR_HASH = "0c3ec587af28e5a785c0b4a7b8a30f9a8f78f838";
private static final String JAR_HASH = "a2db1ea98c37b2d00c83f6867fb8bb581a593e07";
/**
* Downloads the vanilla jar from Mojang's servers.
@@ -94,9 +97,21 @@ public final class VanillaPackProvider {
return;
}
List<String> validPaths = new ArrayList<>();
for (OverlayTransformer.OverlayData overlayData : OverlayTransformer.OVERLAYS) {
validPaths.add("/assets/minecraft/textures/" + overlayData.javaName());
validPaths.add("/assets/minecraft/textures/" + overlayData.overlay());
}
// At the moment, we only care about models and blockstate info from vanilla.
String pathName = path.toString();
if (!pathName.startsWith("/assets/minecraft/models") && !pathName.startsWith("/assets/minecraft/blockstates")) {
if (
!pathName.startsWith("/assets/minecraft/models") &&
!pathName.startsWith("/assets/minecraft/blockstates") &&
!pathName.startsWith("/assets/minecraft/textures/font") &&
!validPaths.contains(pathName)
) {
PathUtils.delete(path);
return;
}