diff --git a/converter/src/main/java/org/geysermc/pack/converter/converter/texture/transformer/type/ui/FontTransformer.java b/converter/src/main/java/org/geysermc/pack/converter/converter/texture/transformer/type/ui/FontTransformer.java index 5a1126c..36bb119 100644 --- a/converter/src/main/java/org/geysermc/pack/converter/converter/texture/transformer/type/ui/FontTransformer.java +++ b/converter/src/main/java/org/geysermc/pack/converter/converter/texture/transformer/type/ui/FontTransformer.java @@ -118,6 +118,9 @@ public class FontTransformer implements TextureTransformer { } static { + // These mappings were mostly found by manually work and checking if the unicodes match correctly + // Some characters were found just by seeing they completely match between default8 (bedrock) and ascii (java) + // Manual stuff... MAPPINGS.add(new FontMapping("accented", 0, 0)); MAPPINGS.add(new FontMapping("accented", 1, 0)); diff --git a/converter/src/main/java/org/geysermc/pack/converter/util/VanillaPackProvider.java b/converter/src/main/java/org/geysermc/pack/converter/util/VanillaPackProvider.java index 96b4df2..c1c106d 100644 --- a/converter/src/main/java/org/geysermc/pack/converter/util/VanillaPackProvider.java +++ b/converter/src/main/java/org/geysermc/pack/converter/util/VanillaPackProvider.java @@ -26,6 +26,8 @@ package org.geysermc.pack.converter.util; +import com.google.gson.*; +import lombok.Getter; import org.apache.commons.io.IOUtils; import org.apache.commons.io.file.PathUtils; import org.geysermc.pack.converter.converter.texture.transformer.type.OverlayTransformer; @@ -37,13 +39,16 @@ 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.*; 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 = "a2db1ea98c37b2d00c83f6867fb8bb581a593e07"; + private static final Gson GSON = new GsonBuilder() + .setPrettyPrinting().create(); + + private static final Map ASSET_MAP = new HashMap<>(); + + private static final List REQUIRED_ASSETS = List.of(); // While not used yet, it's possible we will need other assets as some point /** * Downloads the vanilla jar from Mojang's servers. @@ -59,12 +64,56 @@ public final class VanillaPackProvider { try { // Download vanilla jar - log.info("Downloading vanilla jar..."); - PathUtils.copyFile(new URL(String.format(JAR_DOWNLOAD, JAR_HASH)), path); + log.info("Fetching vanilla jar file download..."); + // Get the version manifest from Mojang + VersionManifest versionManifest = GSON.fromJson( + WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); + + // Get the url for the latest version of the games manifest + String latestInfoURL = ""; + for (Version version : versionManifest.getVersions()) { + if (version.getId().equals("1.21.7")) { // TODO De-hardcode this + latestInfoURL = version.getUrl(); + break; + } + } + + // Make sure we definitely got a version + if (latestInfoURL.isEmpty()) { + throw new IOException("Unable to find a valid version!"); + } + + // Get the individual version manifest + VersionInfo versionInfo = GSON.fromJson(WebUtils.getBody(latestInfoURL), VersionInfo.class); + + // Get the client jar for use when downloading the en_us locale + log.debug(GSON.toJson(versionInfo.getDownloads())); + VersionDownload clientJarInfo = versionInfo.getDownloads().get("client"); + log.debug(GSON.toJson(clientJarInfo)); + + JsonObject assets = JsonParser.parseString(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).getAsJsonObject().get("objects").getAsJsonObject(); + + // Put each asset into an array for use later + for (Map.Entry entry : assets.entrySet()) { + if (!REQUIRED_ASSETS.contains(entry.getKey())) { + // No need to cache this asset, we don't use it + continue; + } + + Asset asset = GSON.fromJson(entry.getValue(), Asset.class); + ASSET_MAP.put(entry.getKey(), asset); + } + + log.info("Vanilla jar file found! Downloading vanilla jar..."); + + PathUtils.copyFile(new URL(clientJarInfo.url), path); + log.info("Downloaded vanilla jar successfully!"); + log.info("Removing unneeded assets from the vanilla jar..."); // Clean the jar clean(path, log); + log.info("Removed unneeded assets from the vanilla jar!"); } catch (IOException e) { log.error("Error downloading vanilla jar", e); } @@ -136,6 +185,94 @@ public final class VanillaPackProvider { } }); } + + log.info("Applying assets..."); + + for (Map.Entry asset : ASSET_MAP.entrySet()) { + String bytes2 = asset.getValue().hash.substring(0, 2); + + PathUtils.copyFile( + new URL("https://resources.download.minecraft.net/%s/%s" + .formatted(bytes2, asset.getValue().hash)), + rootPath.resolve("assets/" + asset.getKey()) + ); + + Files.write( + rootPath.resolve("assets/" + asset.getKey()), + IOUtils.toByteArray(builtinEntity) + ); + } }); } + + @Getter + static class VersionManifest { + private LatestVersion latest; + + private List versions; + } + + @Getter + static class LatestVersion { + private String release; + + private String snapshot; + } + + @Getter + static class Version { + private String id; + + private String type; + + private String url; + + private String time; + + private String releaseTime; + } + + @Getter + static class VersionInfo { + private String id; + + private String type; + + private String time; + + private String releaseTime; + + private AssetIndex assetIndex; + + private Map downloads; + } + + @Getter + static class VersionDownload { + private String sha1; + + private int size; + + private String url; + } + + @Getter + static class AssetIndex { + private String id; + + private String sha1; + + private int size; + + private int totalSize; + + private String url; + } + + @Getter + public static class Asset { + private String hash; + + private int size; + } } diff --git a/converter/src/main/java/org/geysermc/pack/converter/util/WebUtils.java b/converter/src/main/java/org/geysermc/pack/converter/util/WebUtils.java new file mode 100644 index 0000000..f1438b3 --- /dev/null +++ b/converter/src/main/java/org/geysermc/pack/converter/util/WebUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * 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: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/PackConverter + * + */ + +package org.geysermc.pack.converter.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.UnknownHostException; + +public class WebUtils { + /** + * Makes a web request to the given URL and returns the body as a string + * + * @param reqURL URL to fetch + * @return body content or + * @throws IOException / a wrapped UnknownHostException for nicer errors. + */ + public static String getBody(String reqURL) throws IOException { + try { + URL url = new URL(reqURL); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("User-Agent", getUserAgent()); // Otherwise Java 8 fails on checking updates + con.setConnectTimeout(10000); + con.setReadTimeout(10000); + + return connectionToString(con); + } catch (UnknownHostException e) { + throw new IllegalStateException("Unable to resolve requested url (%s)! Are you offline?".formatted(reqURL), e); + } + } + + /** + * Get the string output from the passed {@link HttpURLConnection} + * + * @param con The connection to get the string from + * @return The body of the returned page + * @throws IOException If the request fails + */ + private static String connectionToString(HttpURLConnection con) throws IOException { + // Send the request (we dont use this but its required for getErrorStream() to work) + con.getResponseCode(); + + // Read the error message if there is one if not just read normally + InputStream inputStream = con.getErrorStream(); + if (inputStream == null) { + inputStream = con.getInputStream(); + } + + StringBuilder content = new StringBuilder(); + try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) { + String inputLine; + + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + content.append("\n"); + } + + con.disconnect(); + } + + return content.toString(); + } + + public static String getUserAgent() { + return "Geyser-PackConverter/3.3.2-SNAPSHOT"; // TODO Pull this from buildscript, BuildConstants? + } +}