All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.minecraftforge.gradle.common.util.MinecraftRepo Maven / Gradle / Ivy

Go to download

Minecraft mod development framework used by Forge and FML for the gradle build system adapted for mohist api.

The newest version!
/*
 * ForgeGradle
 * Copyright (C) 2018 Forge Development LLC
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

package net.minecraftforge.gradle.common.util;

import net.minecraftforge.artifactural.api.artifact.ArtifactIdentifier;
import net.minecraftforge.artifactural.api.repository.ArtifactProvider;
import net.minecraftforge.artifactural.api.repository.Repository;
import net.minecraftforge.artifactural.base.repository.ArtifactProviderBuilder;
import net.minecraftforge.artifactural.base.repository.SimpleRepository;
import net.minecraftforge.artifactural.gradle.GradleRepositoryAdapter;
import net.minecraftforge.gradle.common.config.MCPConfigV1;
import net.minecraftforge.gradle.common.config.MCPConfigV2;
import net.minecraftforge.gradle.common.util.VersionJson.Download;
import net.minecraftforge.gradle.common.util.VersionJson.OS;
import net.minecraftforge.srgutils.IMappingFile;
import net.minecraftforge.srgutils.MinecraftVersion;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;

import com.google.common.base.Joiner;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import javax.annotation.Nullable;

public class MinecraftRepo extends BaseRepo {
    private static MinecraftRepo INSTANCE;
    private static final String GROUP = "net.minecraft";
    public static final String MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
    public static final String CURRENT_OS = OS.getCurrent().getName();
    private static int CACHE_BUSTER = 1;
    private static final MinecraftVersion v1_14_4 = MinecraftVersion.from("1.14.4");
    public static final Pattern MCP_CONFIG_VERSION = Pattern.compile("\\d{8}\\.\\d{6}"); //Timestamp: YYYYMMDD.HHMMSS

    private final Repository repo;
    private final boolean offline;
    private MinecraftRepo(File cache, Logger log, boolean offline) {
        super(cache, log);
        this.repo = SimpleRepository.of(ArtifactProviderBuilder.begin(ArtifactIdentifier.class)
            .filter(ArtifactIdentifier.groupEquals(GROUP))
            .filter(ArtifactIdentifier.nameMatches("^(client|server)$"))
            .provide(this)
        );
        this.offline = offline;
    }

    private static MinecraftRepo getInstance(Project project) {
        if (INSTANCE == null)
            INSTANCE = new MinecraftRepo(Utils.getCache(project, "minecraft_repo"), project.getLogger(), project.getGradle().getStartParameter().isOffline());
        return INSTANCE;
    }

    public static void attach(Project project) {
        MinecraftRepo instance = getInstance(project);
        GradleRepositoryAdapter.add(project.getRepositories(), "MINECRAFT_DYNAMIC", instance.getCacheRoot(), instance.repo);
    }

    public static ArtifactProvider create(Project project) {
        return getInstance(project);
    }

    @Nullable
    protected String getMappings(String version) {
        if (!version.contains("_mapped_")) {
            return null;
        }
        return version.split("_mapped_")[0];
    }

    private HashStore commonCache(File file) throws IOException {
        HashStore ret = new HashStore(this.getCacheRoot()).load(new File(file.getAbsolutePath() + ".input"));
        ret.bust(CACHE_BUSTER);
        return ret;
    }

    @Override
    @Nullable
    public File findFile(ArtifactIdentifier artifact) throws IOException {
        String side = artifact.getName();

        if (!artifact.getGroup().equals(GROUP) || (!"client".equals(side) && !"server".equals(side)))
            return null;

        String version = artifact.getVersion();
        boolean forceStable = version.endsWith("-stable");
        if (forceStable)
            version = version.substring(0, version.length() - 7);
        String mappings = getMappings(version);
        if (mappings != null)
            return null; //We do not support mappings
        String classifier = artifact.getClassifier() == null ? "" : artifact.getClassifier();
        String ext = artifact.getExtension();

        File json = findVersion(getMCVersion(version));
        if (json == null)
            return null; //Not a vanilla version, MCP?

        debug("  " + REPO_NAME + " Request: " + artifact.getGroup() + ":" + side + ":" + version + ":" + classifier + "@" + ext);

        if ("pom".equals(ext)) {
            return null; //findPom(side, version, json); //MCP Repo will take care and add the extra libraries.
        } else if ("json".equals(ext)) {
            if ("".equals(classifier))
                return findVersion(getMCVersion(version));
        } else if ("jar".equals(ext)) {
            switch (classifier) {
                case "":         return findRaw(side, version, json);
                case "slim":     return findSlim(side, version, forceStable, json); //Deprecated - Use MCP Specific version
                case "data":     return findData(side, version, forceStable, json); //Deprecated - Use extra
                case "extra":    return findExtra(side, version, forceStable, json); //Deprecated - Use MCP Specific version
            }
        } else if ("txt".equals(ext)) {
            if ("mappings".equals(classifier))
                return findMappings(side, version, json);
        }
        return null;
    }

    private File findMcp(String version) throws IOException {
        Artifact mcp = Artifact.from("de.oceanlabs.mcp:mcp_config:" + version + "@zip");
        File zip = cache("versions", version, "mcp.zip");
        if (!zip.exists()) {
            FileUtils.copyURLToFile(new URL(Utils.FORGE_MAVEN + mcp.getPath()), zip);
            Utils.updateHash(zip);
        }
        return zip;
    }

    @Nullable
    private File findMcpMappings(String version) throws IOException {
        File mcp = findMcp(version);
        if (mcp == null)
            return null;

        File mappings = cache("versions", version, "mcp_mappings.tsrg");
        HashStore cache = commonCache(cache("versions", version, "mcp_mappings.tsrg"));
        cache.add(mcp);

        if (!cache.isSame() || !mappings.exists()) {
            MCPWrapperSlim wrapper = new MCPWrapperSlim(mcp);
            wrapper.extractData(mappings, "mappings");
            cache.save();
            Utils.updateHash(mappings);
        }

        return mappings;
    }

    public static String getMCVersion(String version) {
        int idx = version.lastIndexOf('-');
        if (idx != -1 && MCP_CONFIG_VERSION.matcher(version.substring(idx + 1)).matches()) {
            return version.substring(version.lastIndexOf('-', idx - 1) + 1, idx);
        }
        return version.substring(idx + 1);
    }

    @Nullable
    private File findVersion(String version) throws IOException {
        File manifest = cache("versions/manifest.json");
        if (!DownloadUtils.downloadEtag(new URL(MANIFEST_URL), manifest, offline))
            return null;
        Utils.updateHash(manifest, HashFunction.SHA1);

        File json = cache("versions", version, "version.json");
        URL url =  Utils.loadJson(manifest, ManifestJson.class).getUrl(version);
        if (url == null)
            throw new RuntimeException("Missing version from manifest: " + version);

        if (!DownloadUtils.downloadEtag(url, json, offline))
            return null;
        Utils.updateHash(json, HashFunction.SHA1);
        return json;
    }

    @Nullable
    protected File findPom(String side, String version, File json) throws IOException {
        File pom = cache("versions", version, side + ".pom");
        HashStore cache = commonCache(cache("versions", version, side + ".pom"));

        if ("client".equals(side)) {
            cache.add(json);
        }

        //log("POM Cache: " + cache.isSame()  + " " + pom.exists());
        if (!cache.isSame() || !pom.exists()) {
            POMBuilder builder = new POMBuilder(GROUP, side, version);
            if ("client".equals(side)) {
                VersionJson meta = Utils.loadJson(json, VersionJson.class);
                for (VersionJson.Library lib : meta.libraries) {
                    if (lib.isAllowed()) {
                        if (lib.downloads.artifact != null)
                            builder.dependencies().add(lib.name, "compile");
                        if (lib.downloads.classifiers != null) {
                            if (lib.downloads.classifiers.containsKey("test")) {
                                builder.dependencies().add(lib.name, "test").withClassifier("test");
                            }
                            if (lib.natives != null && lib.natives.containsKey(CURRENT_OS) && !lib.getArtifact().getName().contains("java-objc-bridge")) {
                                builder.dependencies().add(lib.name, "runtime").withClassifier(lib.natives.get(CURRENT_OS));
                            }
                        }
                    }
                }
            }
            builder.dependencies().add(GROUP + ":" + side + ":" + version, "compile").withClassifier("extra");
            builder.dependencies().add("com.google.code.findbugs:jsr305:3.0.1", "compile"); //TODO: Pull this from MCPConfig.

            String ret = builder.tryBuild();
            if (ret == null) {
                return null;
            }
            FileUtils.writeByteArrayToFile(pom, ret.getBytes());
            cache.save();
            Utils.updateHash(pom);
        }

        return pom;
    }

    private File findMappings(String side, String version, File json_file) throws IOException {
        return findDownloadEntry(side + "_mappings", cache("versions", getMCVersion(version), side + "_mappings.txt"), getMCVersion(version), json_file);
    }

    private File findRaw(String side, String version, File json_file) throws IOException {
        return findDownloadEntry(side, cache("versions", getMCVersion(version), side + ".jar"), getMCVersion(version), json_file);
    }

    private File findDownloadEntry(String key, File target, String version, File json_file) throws IOException {
        VersionJson json = Utils.loadJson(json_file, VersionJson.class);
        if (json.downloads == null || !json.downloads.containsKey(key))
            throw new IllegalStateException(version + ".json missing download for " + key);

        Download dl = json.downloads.get(key);
        if (!target.exists() || !HashFunction.SHA1.hash(target).equals(dl.sha1)) {
            FileUtils.copyURLToFile(dl.url, target);
            Utils.updateHash(target, HashFunction.SHA1);
        }
        return target;
    }

    private File findExtra(String side, String version, boolean forceStable, File json) throws IOException {
        boolean stable = v1_14_4.compareTo(MinecraftVersion.from(getMCVersion(version))) < 0;
        File raw = findRaw(side, version, json);
        File mappings = findMcpMappings(version);
        File extra = cache("versions", version, side + "-extra" + (forceStable && !stable ? "-stable" : "") + ".jar");
        HashStore cache = commonCache(cache("versions", version, side + "-extra" + (forceStable && !stable ? "-stable" : "") + ".jar"))
                .add("raw", raw)
                .add("mappings", mappings)
                .add("codever", "1");

        if (!cache.isSame() || !extra.exists()) {
            splitJar(raw, mappings, extra, false, stable || forceStable);
            cache.save();
        }

        return extra;
    }

    private File findSlim(String side, String version, boolean forceStable, File json) throws IOException {
        boolean stable = v1_14_4.compareTo(MinecraftVersion.from(getMCVersion(version))) < 0;
        File raw = findRaw(side, version, json);
        File mappings = findMcpMappings(version);
        File extra = cache("versions", version, side + "-slim" + (forceStable && !stable ? "-stable" : "") + ".jar");
        HashStore cache = commonCache(cache("versions", version, side + "-slim" + (forceStable && !stable ? "-stable" : "") + ".jar"))
                .add("raw", raw)
                .add("mappings", mappings)
                .add("codever", "1");

        if (!cache.isSame() || !extra.exists()) {
            splitJar(raw, mappings, extra, true, stable || forceStable);
            cache.save();
        }


        return extra;
    }

    private static void splitJar(File raw, File mappings, File output, boolean slim, boolean stable) throws IOException {
        try (FileInputStream input = new FileInputStream(mappings)) {
            splitJar(raw, input, output, slim, stable);
        }
    }

    public static void splitJar(File raw, InputStream mappings, File output, boolean slim, boolean stable) throws IOException {
        try (ZipFile zin = new ZipFile(raw);
             FileOutputStream fos = new FileOutputStream(output);
             ZipOutputStream out = new ZipOutputStream(fos)) {

            Set whitelist = IMappingFile.load(mappings).getClasses().stream()
                    .map(IMappingFile.IClass::getOriginal)
                    .collect(Collectors.toSet());

            for (Enumeration entries = zin.entries(); entries.hasMoreElements();) {
                ZipEntry entry = entries.nextElement();
                String name = entry.getName();
                if (name.endsWith(".class")) {
                    boolean isNotch = whitelist.contains(name.substring(0, name.length() - 6 /*.class*/));
                    if (slim == isNotch) {
                        ZipEntry _new = Utils.getStableEntry(name, stable ? Utils.ZIPTIME : 0);
                        out.putNextEntry(_new);
                        try (InputStream ein = zin.getInputStream(entry)) {
                            IOUtils.copy(ein, out);
                        }
                        out.closeEntry();
                    }
                } else {
                    if (!slim) {
                        ZipEntry _new = Utils.getStableEntry(name, stable ? Utils.ZIPTIME : 0);
                        out.putNextEntry(_new);
                        try (InputStream ein = zin.getInputStream(entry)) {
                            IOUtils.copy(ein, out);
                        }
                        out.closeEntry();
                    }
                }
            }
        }
        Utils.updateHash(output);
    }

    private File findData(String side, String version, boolean forceStable, File json) throws IOException {
        boolean stable = v1_14_4.compareTo(MinecraftVersion.from(getMCVersion(version))) < 0 || forceStable;
        File raw = findRaw(side, version, json);
        File extra = cache("versions", version, side + "-data" + (forceStable && !stable ? "-stable" : "") + ".jar");
        HashStore cache = commonCache(cache("versions", version, side + "-data" + (forceStable && !stable ? "-stable" : "") + ".jar"))
                .add("raw", raw)
                .add("codever", "1");

        if (!cache.isSame() || !extra.exists()) {
            try (ZipFile zin = new ZipFile(raw);
                 FileOutputStream fos = new FileOutputStream(extra);
                 ZipOutputStream out = new ZipOutputStream(fos)) {

                for (Enumeration entries = zin.entries(); entries.hasMoreElements();) {
                    ZipEntry entry = entries.nextElement();
                    String name = entry.getName();
                    if (!name.endsWith(".class")) {
                        ZipEntry _new = Utils.getStableEntry(name, stable || forceStable ? Utils.ZIPTIME : 0);
                        out.putNextEntry(_new);
                        try (InputStream ein = zin.getInputStream(entry)) {
                            IOUtils.copy(ein, out);
                        }
                        out.closeEntry();
                    }
                }
            }

            cache.save();
            Utils.updateHash(extra);
        }
        return extra;
    }


    private static class MCPWrapperSlim {
        private final File data;
        private final MCPConfigV1 config;
        public MCPWrapperSlim(File data) throws IOException {
            this.data = data;
            this.config = MCPConfigV2.getFromArchive(data);
        }

        public void extractData(File target, String... path) throws IOException {
            String name = config.getData(path);
            if (name == null)
                throw new IOException("Unknown MCP Entry: " + Joiner.on("/").join(path));

            try (ZipFile zip = new ZipFile(data)) {
                Utils.extractFile(zip, name, target);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy