diff --git a/pom.xml b/pom.xml
index ac6ebc9..cf35f51 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,6 +44,16 @@
jcommander
1.32
+
+ org.tukaani
+ xz
+ 1.0
+
+
+ org.apache.commons
+ commons-compress
+ 1.9
+
diff --git a/src/main/java/com/skcraft/launcher/builder/BuilderOptions.java b/src/main/java/com/skcraft/launcher/builder/BuilderOptions.java
index f0672d6..793312e 100644
--- a/src/main/java/com/skcraft/launcher/builder/BuilderOptions.java
+++ b/src/main/java/com/skcraft/launcher/builder/BuilderOptions.java
@@ -7,6 +7,7 @@
package com.skcraft.launcher.builder;
import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
import lombok.Data;
import java.io.File;
@@ -15,14 +16,6 @@
public class BuilderOptions {
// Configuration
- @Parameter(names = "--config")
- private File configPath;
- @Parameter(names = "--version-file")
- private File versionManifestPath;
- @Parameter(names = "--libs-url")
- private String librariesLocation;
- @Parameter(names = "--objects-url")
- private String objectsLocation;
// Override config
@Parameter(names = "--name")
@@ -35,17 +28,82 @@
// Required
@Parameter(names = "--version", required = true)
private String version;
-
- // Paths
- @Parameter(names = "--files", required = true)
- private File filesDir;
@Parameter(names = "--manifest-dest", required = true)
private File manifestPath;
- @Parameter(names = "--objects-dest", required = true)
+
+ // 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, "modpack.json");
+ }
+
+ if (versionManifestPath == null) {
+ requireInputPath("--version");
+ versionManifestPath = new File(inputPath, "version.json");
+ }
+
+ if (filesDir == null) {
+ requireInputPath("--files");
+ filesDir = new File(inputPath, "src");
+ }
+
+ if (loadersDir == null) {
+ requireInputPath("--loaders");
+ loadersDir = new File(inputPath, "loaders");
+ }
+
+ 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");
+ }
+ }
+
}
diff --git a/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java b/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java
new file mode 100644
index 0000000..8f2e722
--- /dev/null
+++ b/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java
@@ -0,0 +1,52 @@
+/*
+ * SK's Minecraft Launcher
+ * Copyright (C) 2010-2014 Albert Pham and contributors
+ * Please see LICENSE.txt for license information.
+ */
+
+package com.skcraft.launcher.builder;
+
+import com.beust.jcommander.internal.Lists;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public final class BuilderUtils {
+
+ 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 List 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();
+ }
+ }
+
+}
diff --git a/src/main/java/com/skcraft/launcher/builder/Compressor.java b/src/main/java/com/skcraft/launcher/builder/Compressor.java
new file mode 100644
index 0000000..40958b1
--- /dev/null
+++ b/src/main/java/com/skcraft/launcher/builder/Compressor.java
@@ -0,0 +1,48 @@
+/*
+ * SK's Minecraft Launcher
+ * Copyright (C) 2010-2014 Albert Pham 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);
+ }
+ }
+
+}
diff --git a/src/main/java/com/skcraft/launcher/builder/JarFileFilter.java b/src/main/java/com/skcraft/launcher/builder/JarFileFilter.java
new file mode 100644
index 0000000..31ca410
--- /dev/null
+++ b/src/main/java/com/skcraft/launcher/builder/JarFileFilter.java
@@ -0,0 +1,19 @@
+/*
+ * SK's Minecraft Launcher
+ * Copyright (C) 2010-2014 Albert Pham 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");
+ }
+
+}
diff --git a/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java b/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java
index c7e836d..be0dfcb 100644
--- a/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java
+++ b/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java
@@ -7,21 +7,43 @@
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.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CharStreams;
+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.model.loader.InstallProfile;
+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.Environment;
+import com.skcraft.launcher.util.HttpRequest;
import com.skcraft.launcher.util.SimpleLogFormatter;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.java.Log;
-import java.io.File;
-import java.io.IOException;
+import java.io.*;
+import java.net.URL;
+import java.util.List;
+import java.util.Properties;
+import java.util.jar.JarFile;
+import java.util.logging.Level;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+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.
@@ -29,12 +51,17 @@
@Log
public class PackageBuilder {
+ private static final Pattern TWEAK_CLASS_ARG = Pattern.compile("--tweakClass\\s+([^\\s]+)");
+
+ private final Properties properties;
private final ObjectMapper mapper;
private ObjectWriter writer;
private final Manifest manifest;
private final PropertiesApplicator applicator;
@Getter
private boolean prettyPrint = false;
+ private List loaderLibraries = Lists.newArrayList();
+ private List mavenRepos;
/**
* Create a new package builder.
@@ -42,11 +69,22 @@
* @param mapper the mapper
* @param manifest the manifest
*/
- public PackageBuilder(@NonNull ObjectMapper mapper, @NonNull Manifest 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>() {
+ });
+ } finally {
+ closer.close();
+ }
}
public void setPrettyPrint(boolean prettyPrint) {
@@ -71,6 +109,156 @@
collector.walk(dir);
}
+ public void addLoaders(File dir, File librariesDir) {
+ logSection("Checking for mod loaders to install...");
+
+ File[] files = dir.listFiles(new JarFileFilter());
+ if (files != null) {
+ for (File file : files) {
+ try {
+ processLoader(file, librariesDir);
+ } catch (IOException e) {
+ log.log(Level.WARNING, "Failed to add the loader at " + file.getAbsolutePath(), e);
+ }
+ }
+ }
+ }
+
+ private void processLoader(File file, File librariesDir) throws IOException {
+ log.info("Installing " + file.getName() + "...");
+
+ JarFile jarFile = new JarFile(file);
+ 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 = CharStreams.toString(closer.register(new InputStreamReader(stream)));
+ data = data.replaceAll(",\\s*\\}", "}"); // Fix issues with trailing commas
+
+ InstallProfile profile = mapper.readValue(data, InstallProfile.class);
+ VersionManifest version = manifest.getVersionManifest();
+
+ // Copy tweak class arguments
+ String args = profile.getVersionInfo().getMinecraftArguments();
+ if (args != null) {
+ String existingArgs = Strings.nullToEmpty(version.getMinecraftArguments());
+
+ Matcher m = TWEAK_CLASS_ARG.matcher(args);
+ while (m.find()) {
+ version.setMinecraftArguments(existingArgs + " " + m.group());
+ log.info("Adding " + m.group() + " to launch arguments");
+ }
+ }
+
+ // Add libraries
+ List libraries = profile.getVersionInfo().getLibraries();
+ if (libraries != null) {
+ version.getLibraries().addAll(libraries);
+ loaderLibraries.addAll(libraries);
+ }
+
+ // 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) {
+ Library library = new Library();
+ library.setName(libraryPath);
+ File extractPath = new File(librariesDir, library.getPath(Environment.getInstance()));
+ Files.createParentDirs(extractPath);
+ ByteStreams.copy(closer.register(jarFile.getInputStream(libraryEntry)), Files.newOutputStreamSupplier(extractPath));
+ } else {
+ log.warning("Could not find the file '" + filePath + "' in " + file.getAbsolutePath() + ", which means that this mod loader will not work correctly");
+ }
+ }
+ } else {
+ log.warning("The file at " + file.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();
+ }
+ }
+
+ public void downloadLibraries(File librariesDir) throws IOException, InterruptedException {
+ logSection("Downloading libraries...");
+
+ // TODO: Download libraries for different environments -- As of writing, this is not an issue
+ Environment env = Environment.getInstance();
+
+ for (Library library : loaderLibraries) {
+ File outputPath = new File(librariesDir, library.getPath(env));
+
+ if (!outputPath.exists()) {
+ Files.createParentDirs(outputPath);
+ boolean found = false;
+
+ // Gather a list of repositories to download from
+ List sources = Lists.newArrayList();
+ if (library.getBaseUrl() != null) {
+ sources.add(library.getBaseUrl());
+ }
+ sources.addAll(mavenRepos);
+
+ // Try each repository
+ for (String baseUrl : sources) {
+ String pathname = library.getPath(env);
+
+ // Some repositories compress their files
+ List compressors = BuilderUtils.getCompressors(baseUrl);
+ for (Compressor compressor : Lists.reverse(compressors)) {
+ pathname = compressor.transformPathname(pathname);
+ }
+
+ URL url = new URL(baseUrl + pathname);
+ File tempFile = File.createTempFile("launcherlib", null);
+
+ try {
+ log.info("Downloading library " + library.getName() + " from " + url + "...");
+ HttpRequest.get(url).execute().expectResponseCode(200).saveContent(tempFile);
+ } catch (IOException e) {
+ log.info("Could not get file from " + url + ": " + e.getMessage());
+ continue;
+ }
+
+ // Decompress (if needed) and write to file
+ Closer closer = Closer.create();
+ InputStream inputStream = closer.register(new FileInputStream(tempFile));
+ inputStream = closer.register(new BufferedInputStream(inputStream));
+ for (Compressor compressor : compressors) {
+ inputStream = closer.register(compressor.createInputStream(inputStream));
+ }
+ ByteStreams.copy(inputStream, closer.register(new FileOutputStream(outputPath)));
+
+ tempFile.delete();
+
+ found = true;
+ break;
+ }
+
+ if (!found) {
+ log.warning("!! Failed to download the library " + library.getName() + " -- this means your copy of the libraries will lack this file");
+ }
+ }
+ }
+ }
+
public void validateManifest() {
checkNotNull(emptyToNull(manifest.getName()), "Package name is not defined");
checkNotNull(emptyToNull(manifest.getGameVersion()), "Game version is not defined");
@@ -84,14 +272,33 @@
}
}
- public void readVersionManifest(File path) throws IOException {
- if (path != null) {
+ 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(String.format(
+ properties.getProperty("versionManifestUrl"),
+ manifest.getGameVersion()));
+
+ log.info("Fetching version manifest from " + url + "...");
+
+ manifest.setVersionManifest(HttpRequest
+ .get(url)
+ .execute()
+ .expectResponseCode(200)
+ .returnContent()
+ .asJson(VersionManifest.class));
}
}
public void writeManifest(@NonNull File path) throws IOException {
+ logSection("Writing manifest...");
+
manifest.setFeatures(applicator.getFeaturesInUse());
VersionManifest versionManifest = manifest.getVersionManifest();
if (versionManifest != null) {
@@ -100,11 +307,14 @@
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, args);
+ options.choosePaths();
return options;
}
@@ -127,9 +337,18 @@
*
* @param args arguments
* @throws IOException thrown on I/O error
+ * @throws InterruptedException on interruption
*/
- public static void main(String[] args) throws IOException {
- BuilderOptions options = parseArgs(args);
+ 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();
@@ -155,10 +374,18 @@
builder.scan(options.getFilesDir());
builder.addFiles(options.getFilesDir(), options.getObjectsDir());
+ builder.addLoaders(options.getLoadersDir(), options.getLibrariesDir());
+ builder.downloadLibraries(options.getLibrariesDir());
builder.writeManifest(options.getManifestPath());
- log.info("Wrote manifest to " + options.getManifestPath().getAbsolutePath());
- log.info("Done.");
+ 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 + " ---");
}
}
diff --git a/src/main/java/com/skcraft/launcher/model/loader/InstallData.java b/src/main/java/com/skcraft/launcher/model/loader/InstallData.java
new file mode 100644
index 0000000..c346dc6
--- /dev/null
+++ b/src/main/java/com/skcraft/launcher/model/loader/InstallData.java
@@ -0,0 +1,19 @@
+/*
+ * SK's Minecraft Launcher
+ * Copyright (C) 2010-2014 Albert Pham and contributors
+ * Please see LICENSE.txt for license information.
+ */
+
+package com.skcraft.launcher.model.loader;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class InstallData {
+
+ private String path;
+ private String filePath;
+
+}
diff --git a/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java b/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java
new file mode 100644
index 0000000..6205501
--- /dev/null
+++ b/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java
@@ -0,0 +1,21 @@
+/*
+ * SK's Minecraft Launcher
+ * Copyright (C) 2010-2014 Albert Pham and contributors
+ * Please see LICENSE.txt for license information.
+ */
+
+package com.skcraft.launcher.model.loader;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class InstallProfile {
+
+ @JsonProperty("install")
+ private InstallData installData;
+ private VersionInfo versionInfo;
+
+}
diff --git a/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java b/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java
new file mode 100644
index 0000000..f9872c4
--- /dev/null
+++ b/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java
@@ -0,0 +1,23 @@
+/*
+ * SK's Minecraft Launcher
+ * Copyright (C) 2010-2014 Albert Pham and contributors
+ * Please see LICENSE.txt for license information.
+ */
+
+package com.skcraft.launcher.model.loader;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.skcraft.launcher.model.minecraft.Library;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class VersionInfo {
+
+ private String minecraftArguments;
+ private String mainClass;
+ private List libraries;
+
+}
diff --git a/src/main/java/com/skcraft/launcher/model/minecraft/Library.java b/src/main/java/com/skcraft/launcher/model/minecraft/Library.java
index 677d1cc..ccb13de 100644
--- a/src/main/java/com/skcraft/launcher/model/minecraft/Library.java
+++ b/src/main/java/com/skcraft/launcher/model/minecraft/Library.java
@@ -25,6 +25,8 @@
private transient String group;
private transient String artifact;
private transient String version;
+ @JsonProperty("url")
+ private String baseUrl;
private Map natives;
private Extract extract;
private List rules;
@@ -122,6 +124,24 @@
return path;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Library library = (Library) o;
+
+ if (name != null ? !name.equals(library.name) : library.name != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return name != null ? name.hashCode() : 0;
+ }
+
@Data
public static class Rule {
private Action action;
diff --git a/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java b/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java
index eee01d4..283bb8a 100644
--- a/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java
+++ b/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java
@@ -11,7 +11,7 @@
import lombok.Data;
import java.util.Date;
-import java.util.List;
+import java.util.LinkedHashSet;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -26,7 +26,7 @@
private String minecraftArguments;
private String mainClass;
private int minimumLauncherVersion;
- private List libraries;
+ private LinkedHashSet libraries;
@JsonIgnore
public String getAssetsIndex() {
diff --git a/src/main/resources/com/skcraft/launcher/maven_repos.json b/src/main/resources/com/skcraft/launcher/maven_repos.json
new file mode 100644
index 0000000..eecbdb4
--- /dev/null
+++ b/src/main/resources/com/skcraft/launcher/maven_repos.json
@@ -0,0 +1,5 @@
+[
+ "https://libraries.minecraft.net/",
+ "https://central.maven.org/maven2/",
+ "http://maven.apache.org/"
+]
\ No newline at end of file