Add the base files to the Launcher Git.
This commit is contained in:
parent
d1b3d2168c
commit
7242015c76
376 changed files with 27499 additions and 2 deletions
28
launcher-builder/build.gradle
Normal file
28
launcher-builder/build.gradle
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
plugins {
|
||||
id 'application'
|
||||
id 'java-library'
|
||||
id "com.github.johnrengelman.shadow"
|
||||
id 'io.freefair.lombok'
|
||||
}
|
||||
|
||||
application {
|
||||
mainClassName = "com.skcraft.launcher.builder.PackageBuilder"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':launcher')
|
||||
implementation 'org.apache.commons:commons-compress:1.21'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
dependsOn ':launcher:shadowJar'
|
||||
archiveClassifier.set("")
|
||||
}
|
||||
|
||||
// Work around gradle shadow bug
|
||||
// see https://github.com/johnrengelman/shadow/issues/713
|
||||
startScripts.dependsOn(':launcher:shadowJar')
|
||||
distTar.dependsOn(':launcher:shadowJar')
|
||||
distZip.dependsOn(':launcher:shadowJar')
|
||||
|
||||
build.dependsOn(shadowJar)
|
||||
2
launcher-builder/lombok.config
Normal file
2
launcher-builder/lombok.config
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# This file is generated by the 'io.freefair.lombok' Gradle plugin
|
||||
config.stopBubbling = true
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.skcraft.launcher.model.modpack.LaunchModifier;
|
||||
import com.skcraft.launcher.model.modpack.Manifest;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.emptyToNull;
|
||||
|
||||
@Data
|
||||
public class BuilderConfig {
|
||||
|
||||
private String name;
|
||||
private String title;
|
||||
private String gameVersion;
|
||||
@JsonProperty("launch")
|
||||
private LaunchModifier launchModifier = new LaunchModifier();
|
||||
private List<FeaturePattern> features = Lists.newArrayList();
|
||||
private FnPatternList userFiles = new FnPatternList();
|
||||
|
||||
public void setLaunchModifier(LaunchModifier launchModifier) {
|
||||
this.launchModifier = launchModifier != null ? launchModifier : new LaunchModifier();
|
||||
}
|
||||
|
||||
public void setFeatures(List<FeaturePattern> features) {
|
||||
this.features = features != null ? features : Lists.<FeaturePattern>newArrayList();
|
||||
}
|
||||
|
||||
public void setUserFiles(FnPatternList userFiles) {
|
||||
this.userFiles = userFiles != null ? userFiles : new FnPatternList();
|
||||
}
|
||||
|
||||
public void update(Manifest manifest) {
|
||||
manifest.updateName(getName());
|
||||
manifest.updateTitle(getTitle());
|
||||
manifest.updateGameVersion(getGameVersion());
|
||||
manifest.setLaunchModifier(getLaunchModifier());
|
||||
}
|
||||
|
||||
public void registerProperties(PropertiesApplicator applicator) {
|
||||
if (features != null) {
|
||||
for (FeaturePattern feature : features) {
|
||||
checkNotNull(emptyToNull(feature.getFeature().getName()),
|
||||
"Empty feature name found");
|
||||
applicator.register(feature);
|
||||
}
|
||||
}
|
||||
|
||||
applicator.setUserFiles(userFiles);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Data
|
||||
public class BuilderOptions {
|
||||
|
||||
public static final String DEFAULT_CONFIG_FILENAME = "modpack.json";
|
||||
public static final String DEFAULT_VERSION_FILENAME = "version.json";
|
||||
public static final String DEFAULT_SRC_DIRNAME = "src";
|
||||
public static final String DEFAULT_LOADERS_DIRNAME = "loaders";
|
||||
|
||||
// Configuration
|
||||
|
||||
// Override config
|
||||
@Parameter(names = "--name")
|
||||
private String name;
|
||||
@Parameter(names = "--title")
|
||||
private String title;
|
||||
@Parameter(names = "--mc-version")
|
||||
private String gameVersion;
|
||||
|
||||
// Required
|
||||
@Parameter(names = "--version", required = true)
|
||||
private String version;
|
||||
@Parameter(names = "--manifest-dest", required = true)
|
||||
private File manifestPath;
|
||||
|
||||
// Overall paths
|
||||
@Parameter(names = {"--input", "-i"})
|
||||
private File inputPath;
|
||||
@Parameter(names = {"--output", "-o"})
|
||||
private File outputPath;
|
||||
|
||||
// Input paths
|
||||
@Parameter(names = "--config")
|
||||
private File configPath;
|
||||
@Parameter(names = "--version-file")
|
||||
private File versionManifestPath;
|
||||
@Parameter(names = "--files")
|
||||
private File filesDir;
|
||||
@Parameter(names = "--loaders")
|
||||
private File loadersDir;
|
||||
|
||||
// Output paths
|
||||
@Parameter(names = "--objects-dest")
|
||||
private File objectsDir;
|
||||
@Parameter(names = "--libraries-dest")
|
||||
private File librariesDir;
|
||||
|
||||
@Parameter(names = "--libs-url")
|
||||
private String librariesLocation = "libraries";
|
||||
@Parameter(names = "--objects-url")
|
||||
private String objectsLocation = "objects";
|
||||
|
||||
// Misc
|
||||
@Parameter(names = "--pretty-print")
|
||||
private boolean prettyPrinting;
|
||||
|
||||
public void choosePaths() throws ParameterException {
|
||||
if (configPath == null) {
|
||||
requireInputPath("--config");
|
||||
configPath = new File(inputPath, DEFAULT_CONFIG_FILENAME);
|
||||
}
|
||||
|
||||
if (versionManifestPath == null) {
|
||||
requireInputPath("--version");
|
||||
versionManifestPath = new File(inputPath, DEFAULT_VERSION_FILENAME);
|
||||
}
|
||||
|
||||
if (filesDir == null) {
|
||||
requireInputPath("--files");
|
||||
filesDir = new File(inputPath, DEFAULT_SRC_DIRNAME);
|
||||
}
|
||||
|
||||
if (loadersDir == null) {
|
||||
requireInputPath("--loaders");
|
||||
loadersDir = new File(inputPath, DEFAULT_LOADERS_DIRNAME);
|
||||
}
|
||||
|
||||
if (objectsDir == null) {
|
||||
requireOutputPath("--objects-dest");
|
||||
objectsDir = new File(outputPath, objectsLocation);
|
||||
}
|
||||
|
||||
if (librariesDir == null) {
|
||||
requireOutputPath("--libs-dest");
|
||||
librariesDir = new File(outputPath, librariesLocation);
|
||||
}
|
||||
}
|
||||
|
||||
private void requireOutputPath(String name) throws ParameterException {
|
||||
if (outputPath == null) {
|
||||
throw new ParameterException("Because " + name + " was not specified, --output needs to be specified as the output directory and then " + name + " will be default to a pre-set path within the output directory");
|
||||
}
|
||||
}
|
||||
|
||||
private void requireInputPath(String name) throws ParameterException {
|
||||
if (inputPath == null) {
|
||||
throw new ParameterException("Because " + name + " was not specified, --input needs to be specified as the project directory and then " + name + " will be default to a pre-set path within the project directory");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.CharStreams;
|
||||
import org.apache.commons.compress.compressors.CompressorStreamFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public final class BuilderUtils {
|
||||
|
||||
private static final DateFormat VERSION_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
|
||||
|
||||
private BuilderUtils() {
|
||||
}
|
||||
|
||||
public static String normalizePath(String path) {
|
||||
return path.replaceAll("^[/\\\\]*", "").replaceAll("[/\\\\]+", "/");
|
||||
}
|
||||
|
||||
public static ZipEntry getZipEntry(ZipFile jarFile, String path) {
|
||||
Enumeration<? extends ZipEntry> entries = jarFile.entries();
|
||||
String expected = normalizePath(path);
|
||||
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
String test = normalizePath(entry.getName());
|
||||
if (expected.equals(test)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String readStringFromStream(Readable r) throws IOException {
|
||||
String data = CharStreams.toString(r);
|
||||
data = data.replaceAll(",\\s*\\}", "}"); // Fix issues with trailing commas
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static List<Compressor> getCompressors(String repoUrl) {
|
||||
if (repoUrl.matches("^https?://files.minecraftforge.net/maven/?")) {
|
||||
return Lists.newArrayList(
|
||||
new Compressor("xz", CompressorStreamFactory.XZ),
|
||||
new Compressor("pack", CompressorStreamFactory.PACK200));
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public static String generateVersionFromDate() {
|
||||
Date today = Calendar.getInstance().getTime();
|
||||
return VERSION_DATE_FORMAT.format(today);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.google.common.hash.HashFunction;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.Files;
|
||||
import com.skcraft.launcher.model.modpack.FileInstall;
|
||||
import com.skcraft.launcher.model.modpack.Manifest;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Walks a path and adds hashed path versions to the given
|
||||
* {@link com.skcraft.launcher.model.modpack.Manifest}.
|
||||
*/
|
||||
@Log
|
||||
public class ClientFileCollector extends DirectoryWalker {
|
||||
|
||||
private final Manifest manifest;
|
||||
private final PropertiesApplicator applicator;
|
||||
private final File destDir;
|
||||
private HashFunction hf = Hashing.sha1();
|
||||
|
||||
/**
|
||||
* Create a new collector.
|
||||
*
|
||||
* @param manifest the manifest
|
||||
* @param applicator applies properties to manifest entries
|
||||
* @param destDir the destination directory to copy the hashed objects
|
||||
*/
|
||||
public ClientFileCollector(@NonNull Manifest manifest, @NonNull PropertiesApplicator applicator,
|
||||
@NonNull File destDir) {
|
||||
this.manifest = manifest;
|
||||
this.applicator = applicator;
|
||||
this.destDir = destDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DirectoryBehavior getBehavior(@NonNull String name) {
|
||||
return getDirectoryBehavior(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFile(File file, String relPath) throws IOException {
|
||||
if (file.getName().endsWith(FileInfoScanner.FILE_SUFFIX)
|
||||
|| file.getName().endsWith(FileUrlScanner.URL_FILE_SUFFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileInstall entry = new FileInstall();
|
||||
String hash = Files.hash(file, hf).toString();
|
||||
String to = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(relPath));
|
||||
|
||||
// url.txt override file
|
||||
File urlFile = new File(file.getAbsoluteFile().getParentFile(),
|
||||
file.getName() + FileUrlScanner.URL_FILE_SUFFIX);
|
||||
String location;
|
||||
boolean copy = true;
|
||||
if (urlFile.exists() && FileUrlScanner.isEnabled()) {
|
||||
FileUrlRedirect redirect = FileUrlRedirect.fromFile(urlFile);
|
||||
|
||||
location = redirect.getUrl().toString();
|
||||
copy = false;
|
||||
} else {
|
||||
location = hash.substring(0, 2) + "/" + hash.substring(2, 4) + "/" + hash;
|
||||
}
|
||||
|
||||
File destPath = new File(destDir, location);
|
||||
entry.setHash(hash);
|
||||
entry.setLocation(location);
|
||||
entry.setTo(to);
|
||||
entry.setSize(file.length());
|
||||
applicator.apply(entry);
|
||||
destPath.getParentFile().mkdirs();
|
||||
ClientFileCollector.log.info(String.format("Adding %s from %s...", relPath, file.getAbsolutePath()));
|
||||
if (copy) {
|
||||
Files.copy(file, destPath);
|
||||
}
|
||||
manifest.getTasks().add(entry);
|
||||
}
|
||||
|
||||
public static DirectoryBehavior getDirectoryBehavior(@NonNull String name) {
|
||||
if (name.startsWith(".")) {
|
||||
return DirectoryBehavior.SKIP;
|
||||
} else if (name.equals("_OPTIONAL")) {
|
||||
return DirectoryBehavior.IGNORE;
|
||||
} else if (name.equals("_SERVER")) {
|
||||
return DirectoryBehavior.SKIP;
|
||||
} else if (name.equals("_CLIENT")) {
|
||||
return DirectoryBehavior.IGNORE;
|
||||
} else {
|
||||
return DirectoryBehavior.CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import org.apache.commons.compress.compressors.CompressorException;
|
||||
import org.apache.commons.compress.compressors.CompressorStreamFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class Compressor {
|
||||
|
||||
private static final CompressorStreamFactory factory = new CompressorStreamFactory();
|
||||
|
||||
private final String extension;
|
||||
private final String format;
|
||||
|
||||
public Compressor(String extension, String format) {
|
||||
this.extension = extension;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public String transformPathname(String filename) {
|
||||
return filename + "." + extension;
|
||||
}
|
||||
|
||||
public InputStream createInputStream(InputStream inputStream) throws IOException {
|
||||
try {
|
||||
return factory.createCompressorInputStream(format, inputStream);
|
||||
} catch (CompressorException e) {
|
||||
throw new IOException("Failed to create decompressor", e);
|
||||
}
|
||||
}
|
||||
|
||||
public OutputStream createOutputStream(OutputStream outputStream) throws IOException {
|
||||
try {
|
||||
return factory.createCompressorOutputStream(format, outputStream);
|
||||
} catch (CompressorException e) {
|
||||
throw new IOException("Failed to create compressor", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Abstract class to recursively walk a directory, keep track of a relative
|
||||
* path (which may be modified by dropping certain directory entries),
|
||||
* and call {@link #onFile(java.io.File, String)} with each file.
|
||||
*/
|
||||
public abstract class DirectoryWalker {
|
||||
|
||||
public enum DirectoryBehavior {
|
||||
/**
|
||||
* Continue and add the given directory to the relative path.
|
||||
*/
|
||||
CONTINUE,
|
||||
/**
|
||||
* Continue but don't add the given directory to the relative path.
|
||||
*/
|
||||
IGNORE,
|
||||
/**
|
||||
* Don't walk this directory.
|
||||
*/
|
||||
SKIP
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the given directory.
|
||||
*
|
||||
* @param dir the directory
|
||||
* @throws IOException thrown on I/O error
|
||||
*/
|
||||
public final void walk(@NonNull File dir) throws IOException {
|
||||
walk(dir, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively walk the given directory and keep track of the relative path.
|
||||
*
|
||||
* @param dir the directory
|
||||
* @param basePath the base path
|
||||
* @throws IOException
|
||||
*/
|
||||
private void walk(@NonNull File dir, @NonNull String basePath) throws IOException {
|
||||
if (!dir.isDirectory()) {
|
||||
throw new IllegalArgumentException(dir.getAbsolutePath() + " is not a directory");
|
||||
}
|
||||
|
||||
File[] files = dir.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
String newPath = basePath;
|
||||
|
||||
switch (getBehavior(file.getName())) {
|
||||
case CONTINUE:
|
||||
newPath += file.getName() + "/";
|
||||
case IGNORE:
|
||||
walk(file, newPath);
|
||||
break;
|
||||
case SKIP: break;
|
||||
}
|
||||
} else {
|
||||
onFile(file, basePath + file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the behavior for the given directory name.
|
||||
*
|
||||
* @param name the directory name
|
||||
* @return the behavor
|
||||
*/
|
||||
protected DirectoryBehavior getBehavior(String name) {
|
||||
return DirectoryBehavior.CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback on each file.
|
||||
*
|
||||
* @param file the file
|
||||
* @param relPath the relative path
|
||||
* @throws IOException thrown on I/O error
|
||||
*/
|
||||
protected abstract void onFile(File file, String relPath) throws IOException;
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.skcraft.launcher.model.modpack.Feature;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FeaturePattern {
|
||||
|
||||
@JsonProperty("properties")
|
||||
private Feature feature;
|
||||
@JsonProperty("files")
|
||||
private FnPatternList filePatterns = new FnPatternList();
|
||||
|
||||
public boolean matches(String path) {
|
||||
return filePatterns != null && filePatterns.matches(path);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.skcraft.launcher.model.modpack.Feature;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FileInfo {
|
||||
|
||||
private Feature feature;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.skcraft.launcher.model.modpack.Feature;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.emptyToNull;
|
||||
import static com.skcraft.launcher.builder.ClientFileCollector.getDirectoryBehavior;
|
||||
import static org.apache.commons.io.FilenameUtils.*;
|
||||
|
||||
@Log
|
||||
public class FileInfoScanner extends DirectoryWalker {
|
||||
|
||||
private static final EnumSet<FnMatch.Flag> MATCH_FLAGS = EnumSet.of(
|
||||
FnMatch.Flag.CASEFOLD, FnMatch.Flag.PERIOD, FnMatch.Flag.PATHNAME);
|
||||
public static final String FILE_SUFFIX = ".info.json";
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
@Getter
|
||||
private final List<FeaturePattern> patterns = new ArrayList<FeaturePattern>();
|
||||
|
||||
public FileInfoScanner(ObjectMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DirectoryBehavior getBehavior(String name) {
|
||||
return getDirectoryBehavior(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFile(File file, String relPath) throws IOException {
|
||||
if (file.getName().endsWith(FILE_SUFFIX)) {
|
||||
String fnPattern =
|
||||
separatorsToUnix(getPath(relPath)) +
|
||||
getBaseName(getBaseName(file.getName())) + "*";
|
||||
|
||||
FileInfo info = mapper.readValue(file, FileInfo.class);
|
||||
Feature feature = info.getFeature();
|
||||
|
||||
if (feature != null) {
|
||||
checkNotNull(emptyToNull(feature.getName()),
|
||||
"Empty component name found in " + file.getAbsolutePath());
|
||||
|
||||
List<String> patterns = new ArrayList<String>();
|
||||
patterns.add(fnPattern);
|
||||
FnPatternList patternList = new FnPatternList();
|
||||
patternList.setInclude(patterns);
|
||||
patternList.setFlags(MATCH_FLAGS);
|
||||
FeaturePattern fp = new FeaturePattern();
|
||||
fp.setFeature(feature);
|
||||
fp.setFilePatterns(patternList);
|
||||
getPatterns().add(fp);
|
||||
|
||||
FileInfoScanner.log.info("Found .info.json file at " + file.getAbsolutePath() +
|
||||
", with pattern " + fnPattern + ", and component " + feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
import static com.skcraft.launcher.util.HttpRequest.url;
|
||||
|
||||
@Data
|
||||
public class FileUrlRedirect {
|
||||
private URL url;
|
||||
private String hash;
|
||||
|
||||
public void readFromFile(File file) throws IOException {
|
||||
List<String> lines = Files.readLines(file, Charset.defaultCharset());
|
||||
this.url = url(lines.get(0));
|
||||
|
||||
if (lines.size() > 1) {
|
||||
String hash = lines.get(1);
|
||||
|
||||
if (!hash.isEmpty()) {
|
||||
this.hash = hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeToFile(File file) throws IOException {
|
||||
String entry = url.toString() + '\n' + hash;
|
||||
|
||||
Files.write(entry, file, Charset.defaultCharset());
|
||||
}
|
||||
|
||||
public static FileUrlRedirect fromFile(File file) throws IOException {
|
||||
FileUrlRedirect entry = new FileUrlRedirect();
|
||||
entry.readFromFile(file);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.skcraft.launcher.util.FileUtils;
|
||||
import com.skcraft.launcher.util.HttpRequest;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Log
|
||||
public class FileUrlScanner extends DirectoryWalker {
|
||||
public static final String URL_FILE_SUFFIX = ".url.txt";
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return !System.getProperty("com.skcraft.builder.ignoreURLOverrides", "false")
|
||||
.equalsIgnoreCase("true");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFile(File file, String relPath) throws IOException {
|
||||
if (!file.getName().endsWith(URL_FILE_SUFFIX)) return;
|
||||
|
||||
log.info("Found URL file " + file.getName());
|
||||
|
||||
File targetFile = new File(file.getAbsoluteFile().getParentFile(),
|
||||
file.getName().replace(URL_FILE_SUFFIX, ""));
|
||||
FileUrlRedirect info = FileUrlRedirect.fromFile(file);
|
||||
|
||||
if (targetFile.exists()) {
|
||||
String localHash = FileUtils.getShaHash(targetFile);
|
||||
if (info.getHash() == null) {
|
||||
// Disabled for now, let's not touch source files
|
||||
// info.setHash(localHash);
|
||||
// info.writeToFile(file);
|
||||
return;
|
||||
}
|
||||
|
||||
// If everything matches, skip this file
|
||||
if (info.getHash().equals(localHash)) return;
|
||||
}
|
||||
|
||||
File tempFile = File.createTempFile("launcherlib", null);
|
||||
|
||||
try {
|
||||
log.info("Downloading file " + targetFile.getName() + " from " + info.getUrl() + "...");
|
||||
HttpRequest.get(info.getUrl())
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.saveContent(tempFile);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
Files.move(tempFile, targetFile);
|
||||
log.info("Updated " + targetFile.getName() + " from " + file.getName());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
/* $OpenBSD: fnmatch.c,v 1.13 2006/03/31 05:34:14 deraadt Exp $ */
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/*
|
||||
* Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
|
||||
* Compares a filename or pathname to a pattern.
|
||||
*/
|
||||
public class FnMatch {
|
||||
|
||||
public static enum Flag {
|
||||
|
||||
/** Disable backslash escaping. */
|
||||
NOESCAPE,
|
||||
/** Slash must be matched by slash. */
|
||||
PATHNAME,
|
||||
/** Period must be matched by period. */
|
||||
PERIOD,
|
||||
/** Ignore /<tail> after Imatch. */
|
||||
LEADING_DIR,
|
||||
/** Case insensitive search. */
|
||||
CASEFOLD
|
||||
}
|
||||
private static final int RANGE_ERROR = -1;
|
||||
private static final int RANGE_NOMATCH = 0;
|
||||
|
||||
public static boolean fnmatch(String pattern, String string, EnumSet<Flag> flags) {
|
||||
return match(pattern, 0, string, 0, flags);
|
||||
}
|
||||
|
||||
public static boolean fnmatch(String pattern, String string, int stringPos, Flag flag) {
|
||||
return match(pattern, 0, string, stringPos, EnumSet.of(flag));
|
||||
}
|
||||
|
||||
public static boolean fnmatch(String pattern, String string, int stringPos) {
|
||||
return match(pattern, 0, string, stringPos, EnumSet.noneOf(Flag.class));
|
||||
}
|
||||
|
||||
public static boolean fnmatch(String pattern, String string) {
|
||||
return fnmatch(pattern, string, 0);
|
||||
}
|
||||
|
||||
private static boolean match(String pattern, int patternPos,
|
||||
String string, int stringPos, EnumSet<Flag> flags) {
|
||||
char c;
|
||||
|
||||
while (true) {
|
||||
if (patternPos >= pattern.length()) {
|
||||
if (flags.contains(Flag.LEADING_DIR) && string.charAt(stringPos) == '/') {
|
||||
return true;
|
||||
}
|
||||
return stringPos == string.length();
|
||||
}
|
||||
c = pattern.charAt(patternPos++);
|
||||
switch (c) {
|
||||
case '?':
|
||||
if (stringPos >= string.length()) {
|
||||
return false;
|
||||
}
|
||||
if (string.charAt(stringPos) == '/' && flags.contains(Flag.PATHNAME)) {
|
||||
return false;
|
||||
}
|
||||
if (hasLeadingPeriod(string, stringPos, flags)) {
|
||||
return false;
|
||||
}
|
||||
++stringPos;
|
||||
continue;
|
||||
case '*':
|
||||
/* Collapse multiple stars. */
|
||||
while (patternPos < pattern.length() &&
|
||||
(c = pattern.charAt(patternPos)) == '*') {
|
||||
patternPos++;
|
||||
}
|
||||
|
||||
if (hasLeadingPeriod(string, stringPos, flags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Optimize for pattern with * at end or before /. */
|
||||
if (patternPos == pattern.length()) {
|
||||
if (flags.contains(Flag.PATHNAME)) {
|
||||
return flags.contains(Flag.LEADING_DIR) ||
|
||||
string.indexOf('/', stringPos) == -1;
|
||||
}
|
||||
return true;
|
||||
} else if (c == '/' && flags.contains(Flag.PATHNAME)) {
|
||||
stringPos = string.indexOf('/', stringPos);
|
||||
if (stringPos == -1) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* General case, use recursion. */
|
||||
while (stringPos < string.length()) {
|
||||
if (flags.contains(Flag.PERIOD)) {
|
||||
flags = EnumSet.copyOf(flags);
|
||||
flags.remove(Flag.PERIOD);
|
||||
}
|
||||
if (match(pattern, patternPos, string, stringPos, flags)) {
|
||||
return true;
|
||||
}
|
||||
if (string.charAt(stringPos) == '/' && flags.contains(Flag.PATHNAME)) {
|
||||
break;
|
||||
}
|
||||
++stringPos;
|
||||
}
|
||||
return false;
|
||||
|
||||
case '[':
|
||||
if (stringPos >= string.length()) {
|
||||
return false;
|
||||
}
|
||||
if (string.charAt(stringPos) == '/' && flags.contains(Flag.PATHNAME)) {
|
||||
return false;
|
||||
}
|
||||
if (hasLeadingPeriod(string, stringPos, flags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = matchRange(pattern, patternPos, string.charAt(stringPos), flags);
|
||||
if (result == RANGE_ERROR) /* not a good range, treat as normal text */ {
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == RANGE_NOMATCH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
patternPos = result;
|
||||
++stringPos;
|
||||
continue;
|
||||
|
||||
case '\\':
|
||||
if (!flags.contains(Flag.NOESCAPE)) {
|
||||
if (patternPos >= pattern.length()) {
|
||||
c = '\\';
|
||||
} else {
|
||||
c = pattern.charAt(patternPos++);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (stringPos >= string.length()) {
|
||||
return false;
|
||||
}
|
||||
if (c != string.charAt(stringPos) &&
|
||||
!(flags.contains(Flag.CASEFOLD) &&
|
||||
Character.toLowerCase(c) == Character.toLowerCase(string.charAt(stringPos)))) {
|
||||
return false;
|
||||
}
|
||||
++stringPos;
|
||||
}
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
private static boolean hasLeadingPeriod(String string, int stringPos, EnumSet<Flag> flags) {
|
||||
if (stringPos > string.length() - 1)
|
||||
return false;
|
||||
return (stringPos == 0
|
||||
|| (flags.contains(Flag.PATHNAME) && string.charAt(stringPos - 1) == '/'))
|
||||
&& string.charAt(stringPos) == '.' && flags.contains(Flag.PERIOD);
|
||||
}
|
||||
|
||||
private static int matchRange(String pattern, int patternPos, char test, EnumSet<Flag> flags) {
|
||||
boolean negate, ok;
|
||||
char c, c2;
|
||||
|
||||
if (patternPos >= pattern.length()) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* A bracket expression starting with an unquoted circumflex
|
||||
* character produces unspecified results (IEEE 1003.2-1992,
|
||||
* 3.13.2). This implementation treats it like '!', for
|
||||
* consistency with the regular expression syntax.
|
||||
* J.T. Conklin (conklin@ngai.kaleida.com)
|
||||
*/
|
||||
c = pattern.charAt(patternPos);
|
||||
negate = c == '!' || c == '^';
|
||||
if (negate) {
|
||||
++patternPos;
|
||||
}
|
||||
|
||||
if (flags.contains(Flag.CASEFOLD)) {
|
||||
test = Character.toLowerCase(test);
|
||||
}
|
||||
|
||||
/*
|
||||
* A right bracket shall lose its special meaning and represent
|
||||
* itself in a bracket expression if it occurs first in the list.
|
||||
* -- POSIX.2 2.8.3.2
|
||||
*/
|
||||
ok = false;
|
||||
while (true) {
|
||||
if (patternPos >= pattern.length()) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
|
||||
c = pattern.charAt(patternPos++);
|
||||
if (c == ']') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '\\' && !flags.contains(Flag.NOESCAPE)) {
|
||||
c = pattern.charAt(patternPos++);
|
||||
}
|
||||
if (c == '/' && flags.contains(Flag.PATHNAME)) {
|
||||
return RANGE_NOMATCH;
|
||||
}
|
||||
if (flags.contains(Flag.CASEFOLD)) {
|
||||
c = Character.toLowerCase(c);
|
||||
}
|
||||
if (pattern.charAt(patternPos) == '-' &&
|
||||
patternPos + 1 < pattern.length() &&
|
||||
(c2 = pattern.charAt(patternPos + 1)) != ']') {
|
||||
patternPos += 2;
|
||||
if (c2 == '\\' && !flags.contains(Flag.NOESCAPE)) {
|
||||
if (patternPos >= pattern.length()) {
|
||||
return RANGE_ERROR;
|
||||
}
|
||||
c = pattern.charAt(patternPos++);
|
||||
}
|
||||
if (flags.contains(Flag.CASEFOLD)) {
|
||||
c2 = Character.toLowerCase(c2);
|
||||
}
|
||||
if (c <= test && test <= c2) {
|
||||
ok = true;
|
||||
}
|
||||
} else if (c == test) {
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ok == negate ? RANGE_NOMATCH : patternPos;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class FnPatternList {
|
||||
|
||||
private static final EnumSet<FnMatch.Flag> DEFAULT_FLAGS = EnumSet.of(
|
||||
FnMatch.Flag.CASEFOLD, FnMatch.Flag.PERIOD);
|
||||
|
||||
private List<String> include = Lists.newArrayList();
|
||||
private List<String> exclude = Lists.newArrayList();
|
||||
@Getter @Setter @JsonIgnore
|
||||
private EnumSet<FnMatch.Flag> flags = DEFAULT_FLAGS;
|
||||
|
||||
public void setInclude(List<String> include) {
|
||||
this.include = include != null ? include : Lists.<String>newArrayList();
|
||||
}
|
||||
|
||||
public void setExclude(List<String> exclude) {
|
||||
this.exclude = exclude != null ? exclude : Lists.<String>newArrayList();
|
||||
}
|
||||
|
||||
public boolean matches(String path) {
|
||||
return include != null && matches(path, include) && (exclude == null || !matches(path, exclude));
|
||||
}
|
||||
|
||||
public boolean matches(String path, Collection<String> patterns) {
|
||||
for (String pattern : patterns) {
|
||||
if (FnMatch.fnmatch(pattern, path, flags)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
|
||||
public class JarFileFilter implements FileFilter {
|
||||
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
return pathname.getName().toLowerCase().endsWith(".jar");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,451 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Closer;
|
||||
import com.google.common.io.Files;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.LauncherUtils;
|
||||
import com.skcraft.launcher.builder.loaders.*;
|
||||
import com.skcraft.launcher.model.loader.BasicInstallProfile;
|
||||
import com.skcraft.launcher.model.minecraft.Library;
|
||||
import com.skcraft.launcher.model.minecraft.ReleaseList;
|
||||
import com.skcraft.launcher.model.minecraft.Version;
|
||||
import com.skcraft.launcher.model.minecraft.VersionManifest;
|
||||
import com.skcraft.launcher.model.modpack.Manifest;
|
||||
import com.skcraft.launcher.util.HttpRequest;
|
||||
import com.skcraft.launcher.util.SimpleLogFormatter;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Level;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.emptyToNull;
|
||||
import static com.skcraft.launcher.util.HttpRequest.url;
|
||||
|
||||
/**
|
||||
* Builds packages for the launcher.
|
||||
*/
|
||||
@Log
|
||||
public class PackageBuilder {
|
||||
private final Properties properties;
|
||||
private final ObjectMapper mapper;
|
||||
private ObjectWriter writer;
|
||||
private final Manifest manifest;
|
||||
private final PropertiesApplicator applicator;
|
||||
@Getter
|
||||
private boolean prettyPrint = false;
|
||||
|
||||
@Getter @Setter
|
||||
private File baseDir;
|
||||
|
||||
private List<Library> loaderLibraries = Lists.newArrayList();
|
||||
private List<Library> installerLibraries = Lists.newArrayList();
|
||||
private List<String> mavenRepos;
|
||||
private List<URL> jarMavens = Lists.newArrayList();
|
||||
|
||||
/**
|
||||
* Create a new package builder.
|
||||
*
|
||||
* @param mapper the mapper
|
||||
* @param manifest the manifest
|
||||
*/
|
||||
public PackageBuilder(@NonNull ObjectMapper mapper, @NonNull Manifest manifest) throws IOException {
|
||||
this.properties = LauncherUtils.loadProperties(Launcher.class,
|
||||
"launcher.properties", "com.skcraft.launcher.propertiesFile");
|
||||
|
||||
this.mapper = mapper;
|
||||
this.manifest = manifest;
|
||||
this.applicator = new PropertiesApplicator(manifest);
|
||||
setPrettyPrint(false); // Set writer
|
||||
|
||||
Closer closer = Closer.create();
|
||||
try {
|
||||
mavenRepos = mapper.readValue(closer.register(Launcher.class.getResourceAsStream("maven_repos.json")), new TypeReference<List<String>>() {
|
||||
});
|
||||
} finally {
|
||||
closer.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPrettyPrint(boolean prettyPrint) {
|
||||
if (prettyPrint) {
|
||||
writer = mapper.writerWithDefaultPrettyPrinter();
|
||||
} else {
|
||||
writer = mapper.writer();
|
||||
}
|
||||
this.prettyPrint = prettyPrint;
|
||||
}
|
||||
|
||||
public void scan(File dir) throws IOException {
|
||||
logSection("Scanning for .info.json files...");
|
||||
|
||||
FileInfoScanner infoScanner = new FileInfoScanner(mapper);
|
||||
infoScanner.walk(dir);
|
||||
for (FeaturePattern pattern : infoScanner.getPatterns()) {
|
||||
applicator.register(pattern);
|
||||
}
|
||||
|
||||
logSection("Scanning for .url.txt files...");
|
||||
FileUrlScanner urlScanner = new FileUrlScanner();
|
||||
urlScanner.walk(dir);
|
||||
}
|
||||
|
||||
public void addFiles(File dir, File destDir) throws IOException {
|
||||
logSection("Adding files to modpack...");
|
||||
|
||||
ClientFileCollector collector = new ClientFileCollector(this.manifest, applicator, destDir);
|
||||
collector.walk(dir);
|
||||
}
|
||||
|
||||
public void addLoaders(File dir, File librariesDir) {
|
||||
logSection("Checking for mod loaders to install...");
|
||||
|
||||
LinkedHashSet<Library> collected = new LinkedHashSet<Library>();
|
||||
|
||||
File[] files = dir.listFiles(new JarFileFilter());
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
try {
|
||||
processLoader(collected, file, librariesDir);
|
||||
} catch (IOException e) {
|
||||
log.log(Level.WARNING, "Failed to add the loader at " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.loaderLibraries.addAll(collected);
|
||||
|
||||
VersionManifest version = manifest.getVersionManifest();
|
||||
collected.addAll(version.getLibraries());
|
||||
version.setLibraries(collected);
|
||||
}
|
||||
|
||||
private void processLoader(LinkedHashSet<Library> loaderLibraries, File file, File librariesDir) throws IOException {
|
||||
log.info("Installing " + file.getName() + "...");
|
||||
|
||||
JarFile jarFile = new JarFile(file);
|
||||
Closer closer = Closer.create();
|
||||
ILoaderProcessor processor = null;
|
||||
|
||||
try {
|
||||
ZipEntry profileEntry = BuilderUtils.getZipEntry(jarFile, "install_profile.json");
|
||||
|
||||
if (profileEntry != null) {
|
||||
InputStream stream = jarFile.getInputStream(profileEntry);
|
||||
InputStreamReader reader = closer.register(new InputStreamReader(stream));
|
||||
|
||||
BasicInstallProfile basicProfile = mapper.readValue(BuilderUtils.readStringFromStream(reader),
|
||||
BasicInstallProfile.class);
|
||||
|
||||
if (basicProfile.isLegacy()) {
|
||||
processor = new OldForgeLoaderProcessor();
|
||||
} else {
|
||||
processor = new ModernForgeLoaderProcessor();
|
||||
}
|
||||
} else if (BuilderUtils.getZipEntry(jarFile, "fabric-installer.json") != null) {
|
||||
processor = new FabricLoaderProcessor(FabricLoaderProcessor.Variant.FABRIC);
|
||||
} else if (BuilderUtils.getZipEntry(jarFile, "quilt_installer.json") != null) {
|
||||
processor = new FabricLoaderProcessor(FabricLoaderProcessor.Variant.QUILT);
|
||||
}
|
||||
} finally {
|
||||
closer.close();
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
if (processor != null) {
|
||||
LoaderResult result = processor.process(file, manifest, mapper, baseDir);
|
||||
|
||||
if (result == null) {
|
||||
log.warning("Loader " + file.getName() + " failed to process.");
|
||||
return;
|
||||
}
|
||||
|
||||
loaderLibraries.addAll(result.getLoaderLibraries());
|
||||
installerLibraries.addAll(result.getProcessorLibraries());
|
||||
jarMavens.addAll(result.getJarMavens());
|
||||
} else {
|
||||
log.warning("Loader " + file.getName() + " was skipped due to missing metadata. " +
|
||||
"Is it really a loader JAR?");
|
||||
}
|
||||
}
|
||||
|
||||
public void downloadLibraries(File librariesDir) throws IOException, InterruptedException {
|
||||
logSection("Downloading libraries...");
|
||||
|
||||
for (Library library : Iterables.concat(loaderLibraries, installerLibraries)) {
|
||||
library.ensureDownloadsExist();
|
||||
|
||||
for (Library.Artifact artifact : library.getDownloads().getAllArtifacts()) {
|
||||
File outputPath = new File(librariesDir, artifact.getPath());
|
||||
|
||||
if (!outputPath.exists()) {
|
||||
Files.createParentDirs(outputPath);
|
||||
boolean found = false;
|
||||
boolean urlEmpty = artifact.getUrl().isEmpty();
|
||||
|
||||
// If URL doesn't end with a /, it might be the direct file
|
||||
if (!urlEmpty && !artifact.getUrl().endsWith("/")) {
|
||||
found = tryDownloadLibrary(library, artifact, artifact.getUrl(), outputPath);
|
||||
}
|
||||
|
||||
// Look inside the loader JARs
|
||||
if (!found) {
|
||||
for (URL base : jarMavens) {
|
||||
found = tryFetchLibrary(library, new URL(base, artifact.getPath()), outputPath);
|
||||
if (found) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Assume artifact URL is a maven repository URL and try that
|
||||
if (!found && !urlEmpty) {
|
||||
URL url = LauncherUtils.concat(url(artifact.getUrl()), artifact.getPath());
|
||||
found = tryDownloadLibrary(library, artifact, url.toString(), outputPath);
|
||||
}
|
||||
|
||||
// Try each repository if not found yet
|
||||
if (!found) {
|
||||
for (String baseUrl : mavenRepos) {
|
||||
found = tryDownloadLibrary(library, artifact, baseUrl + artifact.getPath(),
|
||||
outputPath);
|
||||
if (found) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
log.warning("!! Failed to download the library " + library.getName() +
|
||||
" -- this means your copy of the libraries will lack this file");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryDownloadLibrary(Library library, Library.Artifact artifact, String baseUrl, File outputPath)
|
||||
throws IOException, InterruptedException {
|
||||
URL url = new URL(baseUrl);
|
||||
|
||||
if (url.getPath().isEmpty() || url.getPath().equals("/")) {
|
||||
// empty path, this is probably the first "is this a full URL" try.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some repositories compress their files
|
||||
List<Compressor> compressors = BuilderUtils.getCompressors(baseUrl);
|
||||
for (Compressor compressor : Lists.reverse(compressors)) {
|
||||
url = new URL(compressor.transformPathname(url.toString()));
|
||||
}
|
||||
|
||||
File tempFile = File.createTempFile("launcherlib", null);
|
||||
|
||||
try {
|
||||
log.info("Downloading library " + library.getName() + " from " + url + "...");
|
||||
HttpRequest.get(url).execute().expectResponseCode(200)
|
||||
.expectContentType("application/java-archive", "application/octet-stream", "application/zip")
|
||||
.saveContent(tempFile);
|
||||
} catch (IOException e) {
|
||||
log.info("Could not get file from " + url + ": " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
writeLibraryToFile(outputPath, tempFile, compressors);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean tryFetchLibrary(Library library, URL url, File outputPath)
|
||||
throws IOException {
|
||||
File tempFile = File.createTempFile("launcherlib", null);
|
||||
|
||||
Closer closer = Closer.create();
|
||||
try {
|
||||
log.info("Reading library " + library.getName() + " from " + url.toString());
|
||||
InputStream stream = closer.register(url.openStream());
|
||||
stream = closer.register(new BufferedInputStream(stream));
|
||||
|
||||
ByteStreams.copy(stream, closer.register(new FileOutputStream(tempFile)));
|
||||
} catch (IOException e) {
|
||||
log.info("Could not get file from " + url + ": " + e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
closer.close();
|
||||
}
|
||||
|
||||
writeLibraryToFile(outputPath, tempFile, Collections.<Compressor>emptyList());
|
||||
return true;
|
||||
}
|
||||
|
||||
private void writeLibraryToFile(File outputPath, File inputFile, List<Compressor> compressors) throws IOException {
|
||||
// Decompress (if needed) and write to file
|
||||
Closer closer = Closer.create();
|
||||
InputStream inputStream = closer.register(new FileInputStream(inputFile));
|
||||
inputStream = closer.register(new BufferedInputStream(inputStream));
|
||||
for (Compressor compressor : compressors) {
|
||||
inputStream = closer.register(compressor.createInputStream(inputStream));
|
||||
}
|
||||
ByteStreams.copy(inputStream, closer.register(new FileOutputStream(outputPath)));
|
||||
|
||||
inputFile.delete();
|
||||
closer.close();
|
||||
}
|
||||
|
||||
public void validateManifest() {
|
||||
checkNotNull(emptyToNull(manifest.getName()), "Package name is not defined");
|
||||
checkNotNull(emptyToNull(manifest.getGameVersion()), "Game version is not defined");
|
||||
}
|
||||
|
||||
public void readConfig(File path) throws IOException {
|
||||
if (path != null) {
|
||||
BuilderConfig config = read(path, BuilderConfig.class);
|
||||
config.update(manifest);
|
||||
config.registerProperties(applicator);
|
||||
}
|
||||
}
|
||||
|
||||
public void readVersionManifest(File path) throws IOException, InterruptedException {
|
||||
logSection("Reading version manifest...");
|
||||
|
||||
if (path.exists()) {
|
||||
VersionManifest versionManifest = read(path, VersionManifest.class);
|
||||
manifest.setVersionManifest(versionManifest);
|
||||
|
||||
log.info("Loaded version manifest from " + path.getAbsolutePath());
|
||||
} else {
|
||||
URL url = url(properties.getProperty("versionManifestUrl"));
|
||||
|
||||
log.info("Fetching version manifest from " + url + "...");
|
||||
|
||||
ReleaseList releases = HttpRequest.get(url)
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.asJson(ReleaseList.class);
|
||||
|
||||
Version version = releases.find(manifest.getGameVersion());
|
||||
VersionManifest versionManifest = HttpRequest.get(url(version.getUrl()))
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.asJson(VersionManifest.class);
|
||||
|
||||
manifest.setVersionManifest(versionManifest);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeManifest(@NonNull File path) throws IOException {
|
||||
logSection("Writing manifest...");
|
||||
|
||||
manifest.setFeatures(applicator.getFeaturesInUse());
|
||||
VersionManifest versionManifest = manifest.getVersionManifest();
|
||||
if (versionManifest != null) {
|
||||
versionManifest.setId(manifest.getGameVersion());
|
||||
}
|
||||
validateManifest();
|
||||
path.getAbsoluteFile().getParentFile().mkdirs();
|
||||
writer.writeValue(path, manifest);
|
||||
|
||||
log.info("Wrote manifest to " + path.getAbsolutePath());
|
||||
}
|
||||
|
||||
private static BuilderOptions parseArgs(String[] args) {
|
||||
BuilderOptions options = new BuilderOptions();
|
||||
new JCommander(options).parse(args);
|
||||
options.choosePaths();
|
||||
return options;
|
||||
}
|
||||
|
||||
private <V> V read(File path, Class<V> clazz) throws IOException {
|
||||
try {
|
||||
if (path == null) {
|
||||
return clazz.newInstance();
|
||||
} else {
|
||||
return mapper.readValue(path, clazz);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
throw new IOException("Failed to create " + clazz.getCanonicalName(), e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IOException("Failed to create " + clazz.getCanonicalName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a package given the arguments.
|
||||
*
|
||||
* @param args arguments
|
||||
* @throws IOException thrown on I/O error
|
||||
* @throws InterruptedException on interruption
|
||||
*/
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
BuilderOptions options;
|
||||
try {
|
||||
options = parseArgs(args);
|
||||
} catch (ParameterException e) {
|
||||
new JCommander().usage();
|
||||
System.err.println("error: " + e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize
|
||||
SimpleLogFormatter.configureGlobalLogger();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_ABSENT);
|
||||
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.setMinimumVersion(Manifest.MIN_PROTOCOL_VERSION);
|
||||
PackageBuilder builder = new PackageBuilder(mapper, manifest);
|
||||
builder.setPrettyPrint(options.isPrettyPrinting());
|
||||
|
||||
// From config
|
||||
builder.readConfig(options.getConfigPath());
|
||||
builder.readVersionManifest(options.getVersionManifestPath());
|
||||
builder.setBaseDir(options.getOutputPath());
|
||||
|
||||
// From options
|
||||
manifest.updateName(options.getName());
|
||||
manifest.updateTitle(options.getTitle());
|
||||
manifest.updateGameVersion(options.getGameVersion());
|
||||
manifest.setVersion(options.getVersion());
|
||||
manifest.setLibrariesLocation(options.getLibrariesLocation());
|
||||
manifest.setObjectsLocation(options.getObjectsLocation());
|
||||
|
||||
builder.scan(options.getFilesDir());
|
||||
builder.addFiles(options.getFilesDir(), options.getObjectsDir());
|
||||
builder.addLoaders(options.getLoadersDir(), options.getLibrariesDir());
|
||||
builder.downloadLibraries(options.getLibrariesDir());
|
||||
builder.writeManifest(options.getManifestPath());
|
||||
|
||||
logSection("Done");
|
||||
|
||||
log.info("Now upload the contents of " + options.getOutputPath() + " to your web server or CDN!");
|
||||
}
|
||||
|
||||
private static void logSection(String name) {
|
||||
log.info("");
|
||||
log.info("--- " + name + " ---");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.skcraft.launcher.model.modpack.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class PropertiesApplicator {
|
||||
|
||||
private final Manifest manifest;
|
||||
private final Set<Feature> used = new HashSet<Feature>();
|
||||
private final List<FeaturePattern> features = new ArrayList<FeaturePattern>();
|
||||
@Getter @Setter
|
||||
private FnPatternList userFiles;
|
||||
|
||||
public PropertiesApplicator(Manifest manifest) {
|
||||
this.manifest = manifest;
|
||||
}
|
||||
|
||||
public void apply(ManifestEntry entry) {
|
||||
if (entry instanceof FileInstall) {
|
||||
apply((FileInstall) entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void apply(FileInstall entry) {
|
||||
String path = entry.getTargetPath();
|
||||
entry.setWhen(fromFeature(path));
|
||||
entry.setUserFile(isUserFile(path));
|
||||
}
|
||||
|
||||
public boolean isUserFile(String path) {
|
||||
if (userFiles != null) {
|
||||
return userFiles.matches(path);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Condition fromFeature(String path) {
|
||||
List<Feature> found = new ArrayList<Feature>();
|
||||
for (FeaturePattern pattern : features) {
|
||||
if (pattern.matches(path)) {
|
||||
used.add(pattern.getFeature());
|
||||
found.add(pattern.getFeature());
|
||||
}
|
||||
}
|
||||
|
||||
if (!found.isEmpty()) {
|
||||
return new RequireAny(found);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void register(FeaturePattern component) {
|
||||
features.add(component);
|
||||
}
|
||||
|
||||
public List<Feature> getFeaturesInUse() {
|
||||
return new ArrayList<Feature>(used);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.google.common.io.Files;
|
||||
import com.skcraft.launcher.util.SimpleLogFormatter;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Log
|
||||
public class ServerCopyExport extends DirectoryWalker {
|
||||
|
||||
private final File destDir;
|
||||
|
||||
public ServerCopyExport(@NonNull File destDir) {
|
||||
this.destDir = destDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DirectoryBehavior getBehavior(String name) {
|
||||
if (name.startsWith(".")) {
|
||||
return DirectoryBehavior.SKIP;
|
||||
} else if (name.equals("_SERVER")) {
|
||||
return DirectoryBehavior.IGNORE;
|
||||
} else if (name.equals("_CLIENT")) {
|
||||
return DirectoryBehavior.SKIP;
|
||||
} else {
|
||||
return DirectoryBehavior.CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFile(File file, String relPath) throws IOException {
|
||||
File dest = new File(destDir, relPath);
|
||||
|
||||
log.info("Copying " + file.getAbsolutePath() + " to " + dest.getAbsolutePath());
|
||||
dest.getParentFile().mkdirs();
|
||||
Files.copy(file, dest);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
SimpleLogFormatter.configureGlobalLogger();
|
||||
|
||||
ServerExportOptions options = new ServerExportOptions();
|
||||
new JCommander(options, args);
|
||||
|
||||
log.info("From: " + options.getSourceDir().getAbsolutePath());
|
||||
log.info("To: " + options.getDestDir().getAbsolutePath());
|
||||
ServerCopyExport task = new ServerCopyExport(options.getDestDir());
|
||||
task.walk(options.getSourceDir());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Data
|
||||
public class ServerExportOptions {
|
||||
|
||||
@Parameter(names = "--source", required = true)
|
||||
private File sourceDir;
|
||||
@Parameter(names = "--dest", required = true)
|
||||
private File destDir;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package com.skcraft.launcher.builder.loaders;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.io.Closer;
|
||||
import com.skcraft.launcher.builder.BuilderUtils;
|
||||
import com.skcraft.launcher.model.loader.FabricMod;
|
||||
import com.skcraft.launcher.model.loader.QuiltMod;
|
||||
import com.skcraft.launcher.model.loader.Versionable;
|
||||
import com.skcraft.launcher.model.minecraft.Library;
|
||||
import com.skcraft.launcher.model.minecraft.VersionManifest;
|
||||
import com.skcraft.launcher.model.modpack.Manifest;
|
||||
import com.skcraft.launcher.util.HttpRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
@Log
|
||||
@RequiredArgsConstructor
|
||||
public class FabricLoaderProcessor implements ILoaderProcessor {
|
||||
private final Variant variant;
|
||||
|
||||
@Override
|
||||
public LoaderResult process(File loaderJar, Manifest manifest, ObjectMapper mapper, File baseDir) throws IOException {
|
||||
JarFile jarFile = new JarFile(loaderJar);
|
||||
LoaderResult result = new LoaderResult();
|
||||
Closer closer = Closer.create();
|
||||
|
||||
try {
|
||||
Versionable loaderMod;
|
||||
|
||||
ZipEntry modEntry = BuilderUtils.getZipEntry(jarFile, variant.modJsonName);
|
||||
if (modEntry != null) {
|
||||
InputStreamReader reader = new InputStreamReader(jarFile.getInputStream(modEntry));
|
||||
|
||||
loaderMod = mapper.readValue(
|
||||
BuilderUtils.readStringFromStream(closer.register(reader)), variant.mappedClass);
|
||||
} else {
|
||||
log.warning(String.format("%s loader has no '%s' file, is it really a %s Loader jar?",
|
||||
variant.friendlyName, variant.modJsonName, variant.friendlyName));
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info(String.format("Downloading %s metadata...", variant.friendlyName));
|
||||
URL metaUrl = HttpRequest.url(
|
||||
String.format(variant.metaUrl, manifest.getGameVersion(), loaderMod.getVersion()));
|
||||
VersionManifest fabricManifest = HttpRequest.get(metaUrl)
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.asJson(VersionManifest.class);
|
||||
|
||||
for (Library library : fabricManifest.getLibraries()) {
|
||||
// To quote a famous comment: "And here we come upon a sad state of affairs."
|
||||
// Quilt's meta API returns broken data about how to launch the game. It specifies its own incomplete
|
||||
// and ultimately broken set of intermediary mappings called "hashed". If the loader finds "hashed" in
|
||||
// the classpath it tries to use it and blows up because it doesn't work.
|
||||
// We work around this here by just throwing the hashed library out - they do now at least specify
|
||||
// fabric's intermediary mappings in the library list, which DO work.
|
||||
// Historical note: previously they didn't do this! Every launcher that added Quilt support had to add
|
||||
// a hack that replaced hashed with intermediary! This is a lot of technical debt that is gonna come
|
||||
// back to bite them in the ass later, because it's all still there!
|
||||
// TODO pester Quilt again about fixing this....
|
||||
if (library.getName().startsWith("org.quiltmc:hashed") && loaderMod instanceof QuiltMod) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.getLoaderLibraries().add(library);
|
||||
log.info("Adding loader library " + library.getName());
|
||||
}
|
||||
|
||||
String mainClass = fabricManifest.getMainClass();
|
||||
if (mainClass != null) {
|
||||
manifest.getVersionManifest().setMainClass(mainClass);
|
||||
log.info("Using main class " + mainClass);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.warning(String.format("HTTP request to %s metadata API was interrupted!", variant.friendlyName));
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
closer.close();
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Variant {
|
||||
FABRIC("Fabric", "fabric.mod.json", "https://meta.fabricmc.net/v2/versions/loader/%s/%s/profile/json", FabricMod.class),
|
||||
QUILT("Quilt", "quilt.mod.json", "https://meta.quiltmc.org/v3/versions/loader/%s/%s/profile/json", QuiltMod.class);
|
||||
|
||||
private final String friendlyName;
|
||||
private final String modJsonName;
|
||||
private final String metaUrl;
|
||||
private final Class<? extends Versionable> mappedClass;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.skcraft.launcher.builder.loaders;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.skcraft.launcher.model.modpack.Manifest;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface ILoaderProcessor {
|
||||
LoaderResult process(File loaderJar, Manifest manifest, ObjectMapper mapper, File baseDir) throws IOException;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.skcraft.launcher.builder.loaders;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.skcraft.launcher.model.minecraft.Library;
|
||||
import lombok.Data;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class LoaderResult {
|
||||
private final List<Library> loaderLibraries = Lists.newArrayList();
|
||||
private final List<Library> processorLibraries = Lists.newArrayList();
|
||||
private final List<URL> jarMavens = Lists.newArrayList();
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
package com.skcraft.launcher.builder.loaders;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.io.Closer;
|
||||
import com.skcraft.launcher.builder.BuilderUtils;
|
||||
import com.skcraft.launcher.model.loader.LoaderManifest;
|
||||
import com.skcraft.launcher.model.loader.SidedData;
|
||||
import com.skcraft.launcher.model.loader.VersionInfo;
|
||||
import com.skcraft.launcher.model.loader.profiles.ModernForgeInstallProfile;
|
||||
import com.skcraft.launcher.model.minecraft.GameArgument;
|
||||
import com.skcraft.launcher.model.minecraft.Library;
|
||||
import com.skcraft.launcher.model.minecraft.Side;
|
||||
import com.skcraft.launcher.model.minecraft.VersionManifest;
|
||||
import com.skcraft.launcher.model.modpack.DownloadableFile;
|
||||
import com.skcraft.launcher.model.modpack.Manifest;
|
||||
import com.skcraft.launcher.util.FileUtils;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
@Log
|
||||
public class ModernForgeLoaderProcessor implements ILoaderProcessor {
|
||||
@Override
|
||||
public LoaderResult process(File loaderJar, Manifest manifest, ObjectMapper mapper, File baseDir) throws IOException {
|
||||
JarFile jarFile = new JarFile(loaderJar);
|
||||
Closer closer = Closer.create();
|
||||
LoaderResult result = new LoaderResult();
|
||||
|
||||
try {
|
||||
ZipEntry versionEntry = BuilderUtils.getZipEntry(jarFile, "version.json");
|
||||
String loaderName = jarFile.getName();
|
||||
|
||||
if (versionEntry != null) {
|
||||
InputStream stream = jarFile.getInputStream(versionEntry);
|
||||
|
||||
VersionInfo info = mapper.readValue(
|
||||
BuilderUtils.readStringFromStream(closer.register(new InputStreamReader(stream))),
|
||||
VersionInfo.class);
|
||||
VersionManifest version = manifest.getVersionManifest();
|
||||
|
||||
if (info.getId() != null) {
|
||||
loaderName = info.getId();
|
||||
}
|
||||
|
||||
// Copy game arguments
|
||||
List<GameArgument> gameArguments = info.getArguments().getGameArguments();
|
||||
if (gameArguments != null) {
|
||||
if (info.isOverridingArguments()) {
|
||||
version.getArguments().getGameArguments().clear();
|
||||
}
|
||||
|
||||
version.getArguments().getGameArguments().addAll(gameArguments);
|
||||
}
|
||||
|
||||
// Copy JVM arguments
|
||||
List<GameArgument> jvmArguments = info.getArguments().getJvmArguments();
|
||||
if (jvmArguments != null) {
|
||||
version.getArguments().getJvmArguments().addAll(jvmArguments);
|
||||
}
|
||||
|
||||
// Add libraries
|
||||
List<Library> libraries = info.getLibraries();
|
||||
if (libraries != null) {
|
||||
for (Library library : libraries) {
|
||||
result.getLoaderLibraries().add(library);
|
||||
log.info("Adding loader library " + library.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// Copy logging config
|
||||
SidedData<VersionManifest.LoggingConfig> loggingConfig = info.getLogging();
|
||||
if (loggingConfig != null) {
|
||||
version.setLogging(loggingConfig);
|
||||
}
|
||||
|
||||
// Copy main class
|
||||
String mainClass = info.getMainClass();
|
||||
if (mainClass != null) {
|
||||
version.setMainClass(mainClass);
|
||||
log.info("Using " + mainClass + " as the main class");
|
||||
}
|
||||
} else {
|
||||
log.warning("The loader " + loaderJar.getAbsolutePath() + " does not appear to have a " +
|
||||
"version.json file inside -- is it actually an installer for Forge?");
|
||||
}
|
||||
|
||||
ZipEntry profileEntry = BuilderUtils.getZipEntry(jarFile, "install_profile.json");
|
||||
if (profileEntry != null) {
|
||||
InputStream stream = jarFile.getInputStream(profileEntry);
|
||||
String data = CharStreams.toString(closer.register(new InputStreamReader(stream)));
|
||||
data = data.replace(",\\s*\\}", "}");
|
||||
|
||||
ModernForgeInstallProfile profile = mapper.readValue(data, ModernForgeInstallProfile.class);
|
||||
|
||||
if (!profile.getMinecraft().equals(manifest.getGameVersion())) {
|
||||
// TODO: Ideally this would show up as a Problem when running the check command.
|
||||
// Getting the data from here to there is quite difficult, however.
|
||||
log.warning(String.format("The Forge installer inside loaders/ is for Minecraft version %s; your " +
|
||||
"manifest is set to %s.", profile.getMinecraft(), manifest.getGameVersion()));
|
||||
}
|
||||
|
||||
// Import the libraries for the installer
|
||||
result.getProcessorLibraries().addAll(profile.getLibraries());
|
||||
|
||||
// Extract the data files
|
||||
List<DownloadableFile> extraFiles = Lists.newArrayList();
|
||||
File objectsDir = new File(baseDir, manifest.getObjectsLocation());
|
||||
|
||||
ZipEntry clientBinpatch = BuilderUtils.getZipEntry(jarFile, "data/client.lzma");
|
||||
if (clientBinpatch != null) {
|
||||
DownloadableFile entry = FileUtils.saveStreamToObjectsDir(
|
||||
closer.register(jarFile.getInputStream(clientBinpatch)),
|
||||
objectsDir);
|
||||
|
||||
entry.setName("client.lzma");
|
||||
entry.setSide(Side.CLIENT);
|
||||
extraFiles.add(entry);
|
||||
profile.getData().get("BINPATCH").setClient("&" + entry.getName() + "&");
|
||||
}
|
||||
|
||||
ZipEntry serverBinpatch = BuilderUtils.getZipEntry(jarFile, "data/server.lzma");
|
||||
if (serverBinpatch != null) {
|
||||
DownloadableFile entry = FileUtils.saveStreamToObjectsDir(
|
||||
closer.register(jarFile.getInputStream(serverBinpatch)),
|
||||
objectsDir);
|
||||
|
||||
entry.setName("server.lzma");
|
||||
entry.setSide(Side.SERVER);
|
||||
extraFiles.add(entry);
|
||||
profile.getData().get("BINPATCH").setServer("&" + entry.getName() + "&");
|
||||
}
|
||||
|
||||
// Forge install profile spec version 1 and above.
|
||||
if (profile.getSpec() >= 1) {
|
||||
// Add the installer itself to the extra files.
|
||||
// This is for a server-only task like above, but hey.
|
||||
DownloadableFile entry = FileUtils.saveStreamToObjectsDir(
|
||||
closer.register(new FileInputStream(loaderJar)), objectsDir);
|
||||
|
||||
entry.setName(loaderJar.getName());
|
||||
entry.setSide(Side.SERVER);
|
||||
extraFiles.add(entry);
|
||||
|
||||
profile.getData().put("INSTALLER", SidedData.of("&" + entry.getName() + "&"));
|
||||
}
|
||||
|
||||
// Add extra sided data
|
||||
profile.getData().put("SIDE", SidedData.create("client", "server"));
|
||||
|
||||
// Add loader manifest to the map
|
||||
manifest.getLoaders().put(loaderName, new LoaderManifest(profile.getLibraries(), profile.getData(), extraFiles));
|
||||
|
||||
// Find name of final patched library and mark it as excluded from download
|
||||
// TODO: we should generalize this to all process outputs, really
|
||||
SidedData<String> finalJars = profile.getData().get("PATCHED");
|
||||
if (finalJars != null) {
|
||||
String libraryName = finalJars.getClient();
|
||||
libraryName = libraryName.substring(1, libraryName.length() - 1);
|
||||
|
||||
for (Library lib : result.getLoaderLibraries()) {
|
||||
if (lib.matches(libraryName)) {
|
||||
lib.setGenerated(true);
|
||||
log.info(String.format("Setting generated flag on library '%s'", lib.getName()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add processors
|
||||
manifest.getTasks().addAll(profile.toProcessorEntries(loaderName));
|
||||
}
|
||||
|
||||
ZipEntry mavenEntry = BuilderUtils.getZipEntry(jarFile, "maven/");
|
||||
if (mavenEntry != null) {
|
||||
URL jarUrl = new URL("jar:file:" + loaderJar.getAbsolutePath() + "!/");
|
||||
result.getJarMavens().add(new URL(jarUrl, "/maven/"));
|
||||
}
|
||||
} finally {
|
||||
closer.close();
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package com.skcraft.launcher.builder.loaders;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Closer;
|
||||
import com.google.common.io.Files;
|
||||
import com.skcraft.launcher.builder.BuilderUtils;
|
||||
import com.skcraft.launcher.model.loader.profiles.LegacyInstallProfile;
|
||||
import com.skcraft.launcher.model.minecraft.GameArgument;
|
||||
import com.skcraft.launcher.model.minecraft.Library;
|
||||
import com.skcraft.launcher.model.minecraft.MinecraftArguments;
|
||||
import com.skcraft.launcher.model.minecraft.VersionManifest;
|
||||
import com.skcraft.launcher.model.modpack.Manifest;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
@Log
|
||||
public class OldForgeLoaderProcessor implements ILoaderProcessor {
|
||||
@Override
|
||||
public LoaderResult process(File loaderJar, Manifest manifest, ObjectMapper mapper, File baseDir) throws IOException {
|
||||
JarFile jarFile = new JarFile(loaderJar);
|
||||
LoaderResult result = new LoaderResult();
|
||||
Closer closer = Closer.create();
|
||||
|
||||
try {
|
||||
ZipEntry profileEntry = BuilderUtils.getZipEntry(jarFile, "install_profile.json");
|
||||
|
||||
if (profileEntry != null) {
|
||||
InputStream stream = jarFile.getInputStream(profileEntry);
|
||||
|
||||
// Read file
|
||||
String data = BuilderUtils.readStringFromStream(closer.register(new InputStreamReader(stream)));
|
||||
LegacyInstallProfile profile = mapper.readValue(data, LegacyInstallProfile.class);
|
||||
VersionManifest version = manifest.getVersionManifest();
|
||||
|
||||
// Copy tweak class arguments
|
||||
MinecraftArguments args = profile.getVersionInfo().getArguments();
|
||||
if (args != null) {
|
||||
Iterator<GameArgument> iter = args.getGameArguments().iterator();
|
||||
while (iter.hasNext()) {
|
||||
GameArgument cur = iter.next();
|
||||
if (cur.getValues().contains("--tweakClass")) {
|
||||
String tweakClass = cur.getValues().size() > 1
|
||||
? cur.getValues().get(1)
|
||||
: iter.next().getJoinedValue();
|
||||
|
||||
List<GameArgument> gameArgs = manifest.getVersionManifest().getArguments().getGameArguments();
|
||||
gameArgs.add(new GameArgument("--tweakClass"));
|
||||
gameArgs.add(new GameArgument(tweakClass));
|
||||
|
||||
log.info(String.format("Adding tweak class '%s' to arguments", tweakClass));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add libraries
|
||||
List<Library> libraries = profile.getVersionInfo().getLibraries();
|
||||
if (libraries != null) {
|
||||
for (Library library : libraries) {
|
||||
if (!version.getLibraries().contains(library)) {
|
||||
result.getLoaderLibraries().add(library);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy main class
|
||||
String mainClass = profile.getVersionInfo().getMainClass();
|
||||
if (mainClass != null) {
|
||||
version.setMainClass(mainClass);
|
||||
log.info("Using " + mainClass + " as the main class");
|
||||
}
|
||||
|
||||
// Extract the library
|
||||
String filePath = profile.getInstallData().getFilePath();
|
||||
String libraryPath = profile.getInstallData().getPath();
|
||||
|
||||
if (filePath != null && libraryPath != null) {
|
||||
ZipEntry libraryEntry = BuilderUtils.getZipEntry(jarFile, filePath);
|
||||
|
||||
if (libraryEntry != null) {
|
||||
File librariesDir = new File(baseDir, "libraries");
|
||||
File extractPath = new File(librariesDir, Library.mavenNameToPath(libraryPath));
|
||||
|
||||
Files.createParentDirs(extractPath);
|
||||
ByteStreams.copy(closer.register(jarFile.getInputStream(libraryEntry)),
|
||||
Files.newOutputStreamSupplier(extractPath));
|
||||
} else {
|
||||
log.warning("Could not find the file '" + filePath + "' in "
|
||||
+ loaderJar.getAbsolutePath()
|
||||
+ ", which means that this mod loader will not work correctly");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warning("The file at " + loaderJar.getAbsolutePath() + " did not appear to have an " +
|
||||
"install_profile.json file inside -- is it actually an installer for a mod loader?");
|
||||
}
|
||||
} finally {
|
||||
closer.close();
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue