From 410031a86f888b65670a4a5de303a60727aa2954 Mon Sep 17 00:00:00 2001 From: Henry Le Grys Date: Tue, 1 Dec 2020 18:41:44 +0000 Subject: [PATCH] Initial 1.16 update oh lord okay - Update to new launchermeta endpoint for version manifests - Fix library handling (libraries now seem to give us the URL most of the time) - Write entire new system for handling the forge installer process - Fix asset handling, mostly (still TODO is remove all traces of assetSource, it's given to us by launchermeta now) --- .../launcher/builder/BuilderUtils.java | 2 +- .../launcher/builder/PackageBuilder.java | 250 ++++++++++++------ .../java/com/skcraft/launcher/AssetsRoot.java | 4 +- .../java/com/skcraft/launcher/Launcher.java | 12 + .../skcraft/launcher/install/FileCopy.java | 3 +- .../skcraft/launcher/install/FileMover.java | 3 +- .../launcher/install/InstallLogFileMover.java | 3 +- .../skcraft/launcher/install/InstallTask.java | 3 +- .../skcraft/launcher/install/Installer.java | 75 ++++-- .../launcher/install/ProcessorTask.java | 129 +++++++++ .../com/skcraft/launcher/launch/Runner.java | 2 +- .../model/loader/InstallProcessor.java | 41 +++ .../launcher/model/loader/InstallProfile.java | 22 +- .../launcher/model/loader/LoaderManifest.java | 17 ++ .../model/loader/LoaderSubResolver.java | 68 +++++ .../launcher/model/loader/ProcessorEntry.java | 39 +++ .../launcher/model/loader/SidedData.java | 28 ++ .../launcher/model/loader/VersionInfo.java | 7 +- .../launcher/model/minecraft/Library.java | 110 ++++---- .../model/minecraft/MinecraftArguments.java | 15 ++ .../launcher/model/minecraft/Side.java | 21 ++ .../launcher/model/minecraft/Version.java | 5 + .../model/minecraft/VersionManifest.java | 39 ++- .../model/modpack/DownloadableFile.java | 32 +++ .../launcher/model/modpack/Manifest.java | 6 +- .../launcher/model/modpack/ManifestEntry.java | 4 +- .../launcher/selfupdate/SelfUpdater.java | 2 +- .../skcraft/launcher/update/BaseUpdater.java | 2 +- .../com/skcraft/launcher/update/Updater.java | 10 +- .../com/skcraft/launcher/util/FileUtils.java | 43 +++ .../skcraft/launcher/lang/Launcher.properties | 1 + .../com/skcraft/launcher/launcher.properties | 2 +- .../com/skcraft/launcher/maven_repos.json | 1 + 33 files changed, 819 insertions(+), 182 deletions(-) create mode 100644 launcher/src/main/java/com/skcraft/launcher/install/ProcessorTask.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProcessor.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/loader/LoaderManifest.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/loader/LoaderSubResolver.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/loader/ProcessorEntry.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/loader/SidedData.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/minecraft/MinecraftArguments.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/minecraft/Side.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/modpack/DownloadableFile.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/util/FileUtils.java diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java index 091e63f..ce2f278 100644 --- a/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java @@ -42,7 +42,7 @@ public final class BuilderUtils { } public static List getCompressors(String repoUrl) { - if (repoUrl.matches("^https?://files.minecraftforge.net/maven/")) { + if (repoUrl.matches("^https?://files.minecraftforge.net/maven/?")) { return Lists.newArrayList( new Compressor("xz", CompressorStreamFactory.XZ), new Compressor("pack", CompressorStreamFactory.PACK200)); diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java index 303c63d..1340cc0 100644 --- a/launcher-builder/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java @@ -12,6 +12,7 @@ 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.Joiner; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; @@ -21,24 +22,32 @@ 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.loader.LoaderManifest; +import com.skcraft.launcher.model.loader.SidedData; +import com.skcraft.launcher.model.loader.VersionInfo; 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.DownloadableFile; import com.skcraft.launcher.model.modpack.Manifest; import com.skcraft.launcher.util.Environment; +import com.skcraft.launcher.util.FileUtils; 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.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; @@ -61,8 +70,13 @@ public class PackageBuilder { private final PropertiesApplicator applicator; @Getter private boolean prettyPrint = false; + + @Getter @Setter + private File baseDir; + private List loaderLibraries = Lists.newArrayList(); private List mavenRepos; + private List jarMavens = Lists.newArrayList(); /** * Create a new package builder. @@ -144,67 +158,101 @@ public class PackageBuilder { Closer closer = Closer.create(); try { - ZipEntry profileEntry = BuilderUtils.getZipEntry(jarFile, "install_profile.json"); + ZipEntry manifestEntry = BuilderUtils.getZipEntry(jarFile, "version.json"); + String loaderName = file.getName(); - if (profileEntry != null) { - InputStream stream = jarFile.getInputStream(profileEntry); + if (manifestEntry != null) { + InputStream stream = jarFile.getInputStream(manifestEntry); // 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); + VersionInfo info = mapper.readValue(data, VersionInfo.class); VersionManifest version = manifest.getVersionManifest(); + if (version.getId() != null) { + loaderName = version.getId(); + } + // Copy tweak class arguments - String args = profile.getVersionInfo().getMinecraftArguments(); - if (args != null) { + List gameArguments = info.getMinecraftArguments().getGameArguments(); + if (gameArguments != null) { + String args = Joiner.on(' ').join(gameArguments); 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"); - } + version.setMinecraftArguments(Joiner.on(' ').join(existingArgs, args)); } // Add libraries - List libraries = profile.getVersionInfo().getLibraries(); + List libraries = info.getLibraries(); if (libraries != null) { for (Library library : libraries) { - if (!version.getLibraries().contains(library)) { - loaderLibraries.add(library); - } + loaderLibraries.add(library); + log.info("Adding loader library " + library.getName()); } } // Copy main class - String mainClass = profile.getVersionInfo().getMainClass(); + String mainClass = info.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?"); + "version.json file inside -- is it actually an installer for a mod loader?"); + } + + 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*\\}", "}"); + + InstallProfile profile = mapper.readValue(data, InstallProfile.class); + + // Import the libraries for the installer + loaderLibraries.addAll(profile.getLibraries()); + + // Extract the data files + List extraFiles = Lists.newArrayList(); + ZipEntry clientBinpatch = BuilderUtils.getZipEntry(jarFile, "data/client.lzma"); + if (clientBinpatch != null) { + DownloadableFile entry = FileUtils.saveStreamToObjectsDir( + closer.register(jarFile.getInputStream(clientBinpatch)), + new File(baseDir, manifest.getObjectsLocation())); + + entry.setName("client.lzma"); + 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)), + new File(baseDir, manifest.getObjectsLocation())); + + entry.setName("server.lzma"); + extraFiles.add(entry); + profile.getData().get("BINPATCH").setServer("&" + 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.getData(), extraFiles)); + + // Add processors + manifest.getTasks().addAll(profile.toProcessorEntries(loaderName)); + } + + ZipEntry mavenEntry = BuilderUtils.getZipEntry(jarFile, "maven/"); + if (mavenEntry != null) { + URL jarUrl = new URL("jar:file:" + file.getAbsolutePath() + "!/"); + jarMavens.add(new URL(jarUrl, "/maven/")); } } finally { closer.close(); @@ -219,53 +267,31 @@ public class PackageBuilder { Environment env = Environment.getInstance(); for (Library library : loaderLibraries) { - File outputPath = new File(librariesDir, library.getPath(env)); + Library.Artifact artifact = library.getArtifact(env); + File outputPath = new File(librariesDir, artifact.getPath()); 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()); + if (!artifact.getUrl().isEmpty()) { + found = tryDownloadLibrary(library, artifact, artifact.getUrl(), outputPath); } - 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); + // Look inside the loader JARs + if (!found) { + for (URL base : jarMavens) { + found = tryFetchLibrary(library, new URL(base, artifact.getPath()), outputPath); + if (found) break; } + } - 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; + // Try each repository if not found yet + if (!found) { + for (String baseUrl : mavenRepos) { + found = tryDownloadLibrary(library, artifact, baseUrl + artifact.getPath(), outputPath); + if (found) break; } - - // 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) { @@ -275,6 +301,65 @@ public class PackageBuilder { } } + private boolean tryDownloadLibrary(Library library, Library.Artifact artifact, String baseUrl, File outputPath) + throws IOException, InterruptedException { + URL url = new URL(baseUrl); + File tempFile = File.createTempFile("launcherlib", null); + + // Some repositories compress their files + List compressors = BuilderUtils.getCompressors(baseUrl); + for (Compressor compressor : Lists.reverse(compressors)) { + url = new URL(url, compressor.transformPathname(artifact.getPath())); + } + + 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()); + 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.emptyList()); + return true; + } + + private void writeLibraryToFile(File outputPath, File inputFile, List 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"); @@ -297,18 +382,24 @@ public class PackageBuilder { log.info("Loaded version manifest from " + path.getAbsolutePath()); } else { - URL url = url(String.format( - properties.getProperty("versionManifestUrl"), - manifest.getGameVersion())); + URL url = url(properties.getProperty("versionManifestUrl")); log.info("Fetching version manifest from " + url + "..."); - manifest.setVersionManifest(HttpRequest - .get(url) + ReleaseList releases = HttpRequest.get(url) .execute() .expectResponseCode(200) .returnContent() - .asJson(VersionManifest.class)); + .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); } } @@ -379,6 +470,7 @@ public class PackageBuilder { // From config builder.readConfig(options.getConfigPath()); builder.readVersionManifest(options.getVersionManifestPath()); + builder.setBaseDir(options.getOutputPath()); // From options manifest.updateName(options.getName()); diff --git a/launcher/src/main/java/com/skcraft/launcher/AssetsRoot.java b/launcher/src/main/java/com/skcraft/launcher/AssetsRoot.java index 4127172..2bf64c7 100644 --- a/launcher/src/main/java/com/skcraft/launcher/AssetsRoot.java +++ b/launcher/src/main/java/com/skcraft/launcher/AssetsRoot.java @@ -50,7 +50,7 @@ public class AssetsRoot { * @return the file, which may not exist */ public File getIndexPath(VersionManifest versionManifest) { - return new File(dir, "indexes/" + versionManifest.getAssetsIndex() + ".json"); + return new File(dir, "indexes/" + versionManifest.getAssetId() + ".json"); } /** @@ -75,7 +75,7 @@ public class AssetsRoot { * @throws LauncherException */ public AssetsTreeBuilder createAssetsBuilder(@NonNull VersionManifest versionManifest) throws LauncherException { - String indexId = versionManifest.getAssetsIndex(); + String indexId = versionManifest.getAssetId(); File path = getIndexPath(versionManifest); AssetsIndex index = Persistence.read(path, AssetsIndex.class, true); if (index == null || index.getObjects() == null) { diff --git a/launcher/src/main/java/com/skcraft/launcher/Launcher.java b/launcher/src/main/java/com/skcraft/launcher/Launcher.java index f6d5cce..8b8acca 100644 --- a/launcher/src/main/java/com/skcraft/launcher/Launcher.java +++ b/launcher/src/main/java/com/skcraft/launcher/Launcher.java @@ -16,10 +16,12 @@ import com.skcraft.launcher.auth.AccountList; import com.skcraft.launcher.auth.LoginService; import com.skcraft.launcher.auth.YggdrasilLoginService; import com.skcraft.launcher.launch.LaunchSupervisor; +import com.skcraft.launcher.model.minecraft.Library; import com.skcraft.launcher.model.minecraft.VersionManifest; import com.skcraft.launcher.persistence.Persistence; import com.skcraft.launcher.swing.SwingHelper; import com.skcraft.launcher.update.UpdateManager; +import com.skcraft.launcher.util.Environment; import com.skcraft.launcher.util.HttpRequest; import com.skcraft.launcher.util.SharedLocale; import com.skcraft.launcher.util.SimpleLogFormatter; @@ -264,6 +266,16 @@ public final class Launcher { return new File(getCommonDataDir(), "libraries"); } + /** + * Fetch a library file. + * @param library Library to fetch + * @return File pointing to the library on disk. + */ + public File getLibraryFile(Library library) { + Environment env = Environment.getInstance(); + return new File(getLibrariesDir(), library.getPath(env)); + } + /** * Get the directory to store versions. * diff --git a/launcher/src/main/java/com/skcraft/launcher/install/FileCopy.java b/launcher/src/main/java/com/skcraft/launcher/install/FileCopy.java index eec2be6..49eb68b 100644 --- a/launcher/src/main/java/com/skcraft/launcher/install/FileCopy.java +++ b/launcher/src/main/java/com/skcraft/launcher/install/FileCopy.java @@ -7,6 +7,7 @@ package com.skcraft.launcher.install; import com.google.common.io.Files; +import com.skcraft.launcher.Launcher; import lombok.NonNull; import lombok.extern.java.Log; @@ -28,7 +29,7 @@ public class FileCopy implements InstallTask { } @Override - public void execute() throws IOException { + public void execute(Launcher launcher) throws IOException { log.log(Level.INFO, "Copying to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()}); to.getParentFile().mkdirs(); Files.copy(from, to); diff --git a/launcher/src/main/java/com/skcraft/launcher/install/FileMover.java b/launcher/src/main/java/com/skcraft/launcher/install/FileMover.java index c1331b8..6943129 100644 --- a/launcher/src/main/java/com/skcraft/launcher/install/FileMover.java +++ b/launcher/src/main/java/com/skcraft/launcher/install/FileMover.java @@ -6,6 +6,7 @@ package com.skcraft.launcher.install; +import com.skcraft.launcher.Launcher; import lombok.NonNull; import lombok.extern.java.Log; @@ -27,7 +28,7 @@ public class FileMover implements InstallTask { } @Override - public void execute() throws IOException { + public void execute(Launcher launcher) throws IOException { log.log(Level.INFO, "Moving to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()}); to.getParentFile().mkdirs(); to.delete(); diff --git a/launcher/src/main/java/com/skcraft/launcher/install/InstallLogFileMover.java b/launcher/src/main/java/com/skcraft/launcher/install/InstallLogFileMover.java index e26e858..5464c2a 100644 --- a/launcher/src/main/java/com/skcraft/launcher/install/InstallLogFileMover.java +++ b/launcher/src/main/java/com/skcraft/launcher/install/InstallLogFileMover.java @@ -6,6 +6,7 @@ package com.skcraft.launcher.install; +import com.skcraft.launcher.Launcher; import lombok.NonNull; import lombok.extern.java.Log; @@ -29,7 +30,7 @@ public class InstallLogFileMover implements InstallTask { } @Override - public void execute() throws IOException { + public void execute(Launcher launcher) throws IOException { InstallLogFileMover.log.log(Level.INFO, "Installing to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()}); to.getParentFile().mkdirs(); to.delete(); diff --git a/launcher/src/main/java/com/skcraft/launcher/install/InstallTask.java b/launcher/src/main/java/com/skcraft/launcher/install/InstallTask.java index 8988002..2f7d5ab 100644 --- a/launcher/src/main/java/com/skcraft/launcher/install/InstallTask.java +++ b/launcher/src/main/java/com/skcraft/launcher/install/InstallTask.java @@ -7,9 +7,10 @@ package com.skcraft.launcher.install; import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.Launcher; public interface InstallTask extends ProgressObservable { - void execute() throws Exception; + void execute(Launcher launcher) throws Exception; } diff --git a/launcher/src/main/java/com/skcraft/launcher/install/Installer.java b/launcher/src/main/java/com/skcraft/launcher/install/Installer.java index ccfa522..22e224a 100644 --- a/launcher/src/main/java/com/skcraft/launcher/install/Installer.java +++ b/launcher/src/main/java/com/skcraft/launcher/install/Installer.java @@ -7,6 +7,7 @@ package com.skcraft.launcher.install; import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.Launcher; import com.skcraft.launcher.util.SharedLocale; import lombok.Getter; import lombok.NonNull; @@ -26,11 +27,11 @@ public class Installer implements ProgressObservable { @Getter private final File tempDir; private final HttpDownloader downloader; - private InstallTask running; - private int count = 0; - private int finished = 0; - private List queue = new ArrayList(); + private TaskQueue mainQueue = new TaskQueue(); + private TaskQueue lateQueue = new TaskQueue(); + + private transient TaskQueue activeQueue; public Installer(@NonNull File tempDir) { this.tempDir = tempDir; @@ -38,27 +39,27 @@ public class Installer implements ProgressObservable { } public synchronized void queue(@NonNull InstallTask runnable) { - queue.add(runnable); - count++; + mainQueue.queue(runnable); + } + + public synchronized void queueLate(@NonNull InstallTask runnable) { + lateQueue.queue(runnable); } public void download() throws IOException, InterruptedException { downloader.execute(); } - public synchronized void execute() throws Exception { - queue = Collections.unmodifiableList(queue); + public synchronized void execute(Launcher launcher) throws Exception { + activeQueue = mainQueue; + mainQueue.execute(launcher); + activeQueue = null; + } - try { - for (InstallTask runnable : queue) { - checkInterrupted(); - running = runnable; - runnable.execute(); - finished++; - } - } finally { - running = null; - } + public synchronized void executeLate(Launcher launcher) throws Exception { + activeQueue = lateQueue; + lateQueue.execute(launcher); + activeQueue = null; } public Downloader getDownloader() { @@ -67,20 +68,50 @@ public class Installer implements ProgressObservable { @Override public double getProgress() { - return finished / (double) count; + if (activeQueue == null) return 0.0; + + return activeQueue.finished / (double) activeQueue.count; } @Override public String getStatus() { - InstallTask running = this.running; - if (running != null) { + if (activeQueue != null && activeQueue.running != null) { + InstallTask running = activeQueue.running; String status = running.getStatus(); if (status == null) { status = running.toString(); } - return tr("installer.executing", count - finished) + "\n" + status; + return tr("installer.executing", activeQueue.count - activeQueue.finished) + "\n" + status; } else { return SharedLocale.tr("installer.installing"); } } + + public static class TaskQueue { + private List queue = new ArrayList(); + + private int count = 0; + private int finished = 0; + private InstallTask running; + + public synchronized void queue(@NonNull InstallTask runnable) { + queue.add(runnable); + count++; + } + + public synchronized void execute(Launcher launcher) throws Exception { + queue = Collections.unmodifiableList(queue); + + try { + for (InstallTask runnable : queue) { + checkInterrupted(); + running = runnable; + runnable.execute(launcher); + finished++; + } + } finally { + running = null; + } + } + } } diff --git a/launcher/src/main/java/com/skcraft/launcher/install/ProcessorTask.java b/launcher/src/main/java/com/skcraft/launcher/install/ProcessorTask.java new file mode 100644 index 0000000..0cc89b1 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/ProcessorTask.java @@ -0,0 +1,129 @@ +package com.skcraft.launcher.install; + +import com.google.common.collect.Lists; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.model.loader.InstallProcessor; +import com.skcraft.launcher.model.loader.LoaderManifest; +import com.skcraft.launcher.model.loader.LoaderSubResolver; +import com.skcraft.launcher.model.loader.SidedData; +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.Environment; +import com.skcraft.launcher.util.FileUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.JarFile; + +import static com.skcraft.launcher.util.SharedLocale.tr; + +@RequiredArgsConstructor +@Log +public class ProcessorTask implements InstallTask { + private final InstallProcessor processor; + private final LoaderManifest loaderManifest; + private final Manifest manifest; + private final HashMap localFiles; + + private transient String message = ""; + private transient double progress = 0; + + @Override + public void execute(Launcher launcher) throws Exception { + VersionManifest versionManifest = manifest.getVersionManifest(); + loaderManifest.getSidedData().put("MINECRAFT_JAR", SidedData.of(launcher.getJarPath(versionManifest).getAbsolutePath())); + + LoaderSubResolver resolver = new LoaderSubResolver(manifest, loaderManifest, + Environment.getInstance(), Side.CLIENT, launcher.getBaseDir(), localFiles); + + message = "Resolving parameters"; + List programArgs = processor.resolveArgs(resolver); + Map outputs = processor.resolveOutputs(resolver); + + message = "Finding libraries"; + Library execFile = versionManifest.findLibrary(processor.getJar()); + File jar = launcher.getLibraryFile(execFile); + + JarFile jarFile = new JarFile(jar); + String mainClass = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS); + jarFile.close(); + + if (mainClass == null || mainClass.isEmpty()) { + throw new RuntimeException(String.format("Processor jar file '%s' has no main class!", processor.getJar())); + } + + List classpath = Lists.newArrayList(jar.toURI().toURL()); + int i = 0; + int total = processor.getClasspath().size(); + for (String libraryName : processor.getClasspath()) { + message = "Adding library " + libraryName; + File libraryFile = launcher.getLibraryFile(versionManifest.findLibrary(libraryName)); + if (!libraryFile.exists()) { + throw new RuntimeException(String.format("Missing library '%s' for processor '%s'", + libraryName, processor.getJar())); + } + + classpath.add(libraryFile.toURI().toURL()); + i++; + progress = (double) i / total; + } + + progress = 0.0; + message = "Executing"; + + log.info(String.format("Running processor '%s' with %d args", processor.getJar(), programArgs.size())); + + ClassLoader cl = new URLClassLoader(classpath.toArray(new URL[0]), null); + try { + Class mainClazz = Class.forName(mainClass, true, cl); + Method main = mainClazz.getDeclaredMethod("main", String[].class); + main.invoke(null, (Object) programArgs.toArray(new String[0])); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + message = "Verifying"; + progress = 1.0; + + if (!outputs.isEmpty()) { + progress = 0.0; + i = 0; + total = outputs.size(); + for (Map.Entry output : outputs.entrySet()) { + File artifact = new File(output.getKey()); + + if (!artifact.exists()) { + throw new RuntimeException(String.format("Artifact '%s' missing", output.getKey())); + } + + if (!FileUtils.getShaHash(artifact).equals(output.getValue())) { + throw new RuntimeException(String.format("Artifact '%s' has invalid hash!", output.getKey())); + } + + i++; + progress = (double) i / total; + } + } + } + + @Override + public double getProgress() { + return progress; + } + + @Override + public String getStatus() { + return tr("installer.runningProcessor", processor.getJar(), message); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java b/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java index 833d733..7ffe672 100644 --- a/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java +++ b/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java @@ -371,7 +371,7 @@ public class Runner implements Callable, ProgressObservable { map.put("game_directory", instance.getContentDir().getAbsolutePath()); map.put("game_assets", virtualAssetsDir.getAbsolutePath()); map.put("assets_root", launcher.getAssets().getDir().getAbsolutePath()); - map.put("assets_index_name", versionManifest.getAssetsIndex()); + map.put("assets_index_name", versionManifest.getAssetId()); return map; } diff --git a/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProcessor.java b/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProcessor.java new file mode 100644 index 0000000..c23ed16 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProcessor.java @@ -0,0 +1,41 @@ +package com.skcraft.launcher.model.loader; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import lombok.Data; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class InstallProcessor { + private String jar; + private List classpath; + private List args; + private Map outputs; + + public List resolveArgs(final LoaderSubResolver resolver) { + return Lists.transform(getArgs(), new Function() { + @Override + public String apply(String input) { + return resolver.transform(input); + } + }); + } + + public Map resolveOutputs(final LoaderSubResolver resolver) { + if (getOutputs() == null) return Collections.emptyMap(); + + HashMap result = new HashMap(); + + for (Map.Entry entry : getOutputs().entrySet()) { + result.put(resolver.transform(entry.getKey()), resolver.transform(entry.getValue())); + } + + return result; + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java b/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java index 6205501..78d2cf4 100644 --- a/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java +++ b/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java @@ -7,15 +7,27 @@ package com.skcraft.launcher.model.loader; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.skcraft.launcher.model.minecraft.Library; import lombok.Data; +import java.util.List; +import java.util.Map; + @Data @JsonIgnoreProperties(ignoreUnknown = true) public class InstallProfile { + private List libraries; + private List processors; + private Map data; - @JsonProperty("install") - private InstallData installData; - private VersionInfo versionInfo; - + public List toProcessorEntries(final String loaderName) { + return Lists.transform(getProcessors(), new Function() { + @Override + public ProcessorEntry apply(InstallProcessor input) { + return new ProcessorEntry(loaderName, input); + } + }); + } } diff --git a/launcher/src/main/java/com/skcraft/launcher/model/loader/LoaderManifest.java b/launcher/src/main/java/com/skcraft/launcher/model/loader/LoaderManifest.java new file mode 100644 index 0000000..d565c8f --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/loader/LoaderManifest.java @@ -0,0 +1,17 @@ +package com.skcraft.launcher.model.loader; + +import com.skcraft.launcher.model.modpack.DownloadableFile; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class LoaderManifest { + private Map sidedData; + private List downloadableFiles; +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/loader/LoaderSubResolver.java b/launcher/src/main/java/com/skcraft/launcher/model/loader/LoaderSubResolver.java new file mode 100644 index 0000000..ed16f31 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/loader/LoaderSubResolver.java @@ -0,0 +1,68 @@ +package com.skcraft.launcher.model.loader; + +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.Environment; +import lombok.RequiredArgsConstructor; + +import java.io.File; +import java.util.HashMap; + +@RequiredArgsConstructor +public class LoaderSubResolver { + private final Manifest manifest; + private final LoaderManifest loader; + private final Environment env; + private final Side side; + private final File baseDir; + private final HashMap localFiles; + + public String getPathOf(String... rest) { + File file = baseDir; + for (String part : rest) { + file = new File(file, part); + } + + return file.getAbsolutePath(); + } + + public String transform(String arg) { + VersionManifest version = manifest.getVersionManifest(); + + while (true) { + char start = arg.charAt(0); + int bound = arg.length() - 1; + char end = arg.charAt(bound); + + if (start == '{' && end == '}') { + SidedData sidedData = loader.getSidedData().get(arg.substring(1, bound)); + if (sidedData != null) { + arg = sidedData.resolveFor(side); + } + } else if (start == '[' && end == ']') { + String libraryName = arg.substring(1, bound); + Library library = version.findLibrary(libraryName); + if (library != null) { + arg = getPathOf(manifest.getLibrariesLocation(), library.getPath(env)); + } else { + arg = getPathOf(manifest.getLibrariesLocation(), Library.mavenNameToPath(libraryName)); + } + } else if (start == '&' && end == '&') { + String localFileName = arg.substring(1, bound); + + if (localFiles.containsKey(localFileName)) { + arg = localFiles.get(localFileName).getLocation().getAbsolutePath(); + } else { + arg = localFileName; + } + } else if (start == '\'' && end == '\'') { + arg = arg.substring(1, bound); + } else { + return arg; + } + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/loader/ProcessorEntry.java b/launcher/src/main/java/com/skcraft/launcher/model/loader/ProcessorEntry.java new file mode 100644 index 0000000..0be41eb --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/loader/ProcessorEntry.java @@ -0,0 +1,39 @@ +package com.skcraft.launcher.model.loader; + +import com.google.common.collect.Maps; +import com.skcraft.launcher.install.InstallLog; +import com.skcraft.launcher.install.Installer; +import com.skcraft.launcher.install.ProcessorTask; +import com.skcraft.launcher.install.UpdateCache; +import com.skcraft.launcher.model.modpack.DownloadableFile; +import com.skcraft.launcher.model.modpack.ManifestEntry; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.io.File; +import java.util.HashMap; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class ProcessorEntry extends ManifestEntry { + private String loaderName; + private InstallProcessor processor; + + @Override + public void install(Installer installer, InstallLog log, UpdateCache cache, File contentDir) throws Exception { + LoaderManifest loaderManifest = getManifest().getLoaders().get(loaderName); + + HashMap localFilesMap = Maps.newHashMap(); + for (DownloadableFile downloadableFile : loaderManifest.getDownloadableFiles()) { + DownloadableFile.LocalFile localFile = downloadableFile.download(installer, getManifest()); + + localFilesMap.put(localFile.getName(), localFile); + } + + installer.queueLate(new ProcessorTask(processor, loaderManifest, getManifest(), localFilesMap)); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/loader/SidedData.java b/launcher/src/main/java/com/skcraft/launcher/model/loader/SidedData.java new file mode 100644 index 0000000..807b444 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/loader/SidedData.java @@ -0,0 +1,28 @@ +package com.skcraft.launcher.model.loader; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.skcraft.launcher.model.minecraft.Side; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor(staticName = "create") +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class SidedData { + private String client; + private String server; + + public String resolveFor(Side side) { + switch (side) { + case CLIENT: return client; + case SERVER: return server; + default: return null; + } + } + + public static SidedData of(String singleValue) { + return new SidedData(singleValue, singleValue); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java b/launcher/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java index f9872c4..84a31b3 100644 --- a/launcher/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java +++ b/launcher/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java @@ -7,7 +7,9 @@ package com.skcraft.launcher.model.loader; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.skcraft.launcher.model.minecraft.Library; +import com.skcraft.launcher.model.minecraft.MinecraftArguments; import lombok.Data; import java.util.List; @@ -15,8 +17,9 @@ import java.util.List; @Data @JsonIgnoreProperties(ignoreUnknown = true) public class VersionInfo { - - private String minecraftArguments; + private String id; + @JsonProperty("arguments") + private MinecraftArguments minecraftArguments; private String mainClass; private List libraries; diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Library.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Library.java index ccb13de..363b226 100644 --- a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Library.java +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Library.java @@ -6,9 +6,14 @@ package com.skcraft.launcher.model.minecraft; -import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; import com.skcraft.launcher.util.Environment; import com.skcraft.launcher.util.Platform; import lombok.Data; @@ -22,11 +27,7 @@ import java.util.regex.Pattern; public class Library { private String name; - private transient String group; - private transient String artifact; - private transient String version; - @JsonProperty("url") - private String baseUrl; + private Downloads downloads; private Map natives; private Extract extract; private List rules; @@ -37,21 +38,6 @@ public class Library { // Custom private boolean locallyAvailable; - public void setName(String name) { - this.name = name; - - if (name != null) { - String[] parts = name.split(":"); - this.group = parts[0]; - this.artifact = parts[1]; - this.version = parts[2]; - } else { - this.group = null; - this.artifact = null; - this.version = null; - } - } - public boolean matches(Environment environment) { boolean allow = false; @@ -68,21 +54,6 @@ public class Library { return allow; } - @JsonIgnore - public String getGroup() { - return group; - } - - @JsonIgnore - public String getArtifact() { - return artifact; - } - - @JsonIgnore - public String getVersion() { - return version; - } - public String getNativeString(Platform platform) { if (getNatives() != null) { switch (platform) { @@ -100,28 +71,18 @@ public class Library { } } - public String getFilename(Environment environment) { + public Artifact getArtifact(Environment environment) { String nativeString = getNativeString(environment.getPlatform()); - if (nativeString != null) { - return String.format("%s-%s-%s.jar", - getArtifact(), getVersion(), nativeString); - } - return String.format("%s-%s.jar", getArtifact(), getVersion()); + if (nativeString != null) { + return getDownloads().getClassifiers().get(nativeString); + } else { + return getDownloads().getArtifact(); + } } public String getPath(Environment environment) { - StringBuilder builder = new StringBuilder(); - builder.append(getGroup().replace('.', '/')); - builder.append("/"); - builder.append(getArtifact()); - builder.append("/"); - builder.append(getVersion()); - builder.append("/"); - builder.append(getFilename(environment)); - String path = builder.toString(); - path = path.replace("${arch}", environment.getArchBits()); - return path; + return getArtifact(environment).getPath(); } @Override @@ -179,6 +140,21 @@ public class Library { private List exclude; } + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Artifact { + private String path; + private String url; + private String sha1; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Downloads { + private Artifact artifact; + private Map classifiers; + } + private enum Action { ALLOW, DISALLOW; @@ -194,4 +170,32 @@ public class Library { } } + public static String mavenNameToPath(String mavenName) { + List split = Splitter.on(':').splitToList(mavenName); + int size = split.size(); + + String group = split.get(0); + String name = split.get(1); + String version = split.get(2); + String extension = "jar"; + + String fileName = name + "-" + version; + + if (size > 3) { + String classifier = split.get(3); + + if (classifier.indexOf("@") != -1) { + List parts = Splitter.on('@').splitToList(classifier); + + classifier = parts.get(0); + extension = parts.get(1); + } + + fileName += "-" + classifier; + } + + fileName += "." + extension; + + return Joiner.on('/').join(group.replace('.', '/'), name, version, fileName); + } } diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/MinecraftArguments.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/MinecraftArguments.java new file mode 100644 index 0000000..ad5bf34 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/MinecraftArguments.java @@ -0,0 +1,15 @@ +package com.skcraft.launcher.model.minecraft; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class MinecraftArguments { + @JsonProperty("game") + private List gameArguments; +} + diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Side.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Side.java new file mode 100644 index 0000000..94079b2 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Side.java @@ -0,0 +1,21 @@ +package com.skcraft.launcher.model.minecraft; + +public enum Side { + CLIENT("client"), + SERVER("server"); + + private String name; + + Side(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Version.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Version.java index c01f6cb..a580df2 100644 --- a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Version.java +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Version.java @@ -20,6 +20,11 @@ public class Version { @NonNull private String id; + @Getter + @Setter + @NonNull + private String url; + public Version() { } diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java index 283bb8a..6fc8384 100644 --- a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java @@ -6,12 +6,14 @@ package com.skcraft.launcher.model.minecraft; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.Date; +import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.Map; @Data @JsonIgnoreProperties(ignoreUnknown = true) @@ -21,16 +23,45 @@ public class VersionManifest { private Date time; private Date releaseTime; private String assets; + private AssetIndex assetIndex; private String type; private String processArguments; private String minecraftArguments; private String mainClass; private int minimumLauncherVersion; private LinkedHashSet libraries; + private Map downloads = new HashMap(); - @JsonIgnore - public String getAssetsIndex() { - return getAssets() != null ? getAssets() : "legacy"; + public String getAssetId() { + return getAssetIndex() != null + ? getAssetIndex().getId() + : "legacy"; } + public Library findLibrary(String name) { + for (Library library : getLibraries()) { + if (library.getName().equals(name)) { + return library; + } + } + + return null; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Artifact { + private String url; + private int size; + + @JsonProperty("sha1") + private String hash; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class AssetIndex { + private String id; + private String url; + } } diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/DownloadableFile.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/DownloadableFile.java new file mode 100644 index 0000000..f275ebf --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/DownloadableFile.java @@ -0,0 +1,32 @@ +package com.skcraft.launcher.model.modpack; + +import com.skcraft.launcher.install.Installer; +import lombok.Data; +import lombok.NonNull; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import static com.skcraft.launcher.LauncherUtils.concat; + +@Data +public class DownloadableFile { + private String name; + private String hash; + private String location; + private int size; + + public LocalFile download(@NonNull Installer installer, Manifest manifest) throws MalformedURLException { + URL url = concat(manifest.getObjectsUrl(), getLocation()); + + File local = installer.getDownloader().download(url, hash, size, name); + return new LocalFile(local, name); + } + + @Data + public static class LocalFile { + private final File location; + private final String name; + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/Manifest.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/Manifest.java index cac0f7f..772d15d 100644 --- a/launcher/src/main/java/com/skcraft/launcher/model/modpack/Manifest.java +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/Manifest.java @@ -12,8 +12,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Strings; import com.skcraft.launcher.Instance; import com.skcraft.launcher.LauncherUtils; -import com.skcraft.launcher.model.minecraft.VersionManifest; import com.skcraft.launcher.install.Installer; +import com.skcraft.launcher.model.loader.LoaderManifest; +import com.skcraft.launcher.model.minecraft.VersionManifest; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -22,7 +23,9 @@ import lombok.Setter; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Data @EqualsAndHashCode(callSuper = true) @@ -43,6 +46,7 @@ public class Manifest extends BaseManifest { @Getter @Setter @JsonIgnore private Installer installer; private VersionManifest versionManifest; + private Map loaders = new HashMap(); @JsonIgnore public URL getLibrariesUrl() { diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestEntry.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestEntry.java index 8d06fad..e4f503a 100644 --- a/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestEntry.java +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestEntry.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.skcraft.launcher.install.InstallLog; import com.skcraft.launcher.install.Installer; import com.skcraft.launcher.install.UpdateCache; +import com.skcraft.launcher.model.loader.ProcessorEntry; import lombok.Data; import lombok.ToString; @@ -23,7 +24,8 @@ import java.io.File; property = "type", defaultImpl = FileInstall.class) @JsonSubTypes({ - @JsonSubTypes.Type(value = FileInstall.class, name = "file") + @JsonSubTypes.Type(value = FileInstall.class, name = "file"), + @JsonSubTypes.Type(value = ProcessorEntry.class, name = "process") }) @Data @ToString(exclude = "manifest") diff --git a/launcher/src/main/java/com/skcraft/launcher/selfupdate/SelfUpdater.java b/launcher/src/main/java/com/skcraft/launcher/selfupdate/SelfUpdater.java index 0557004..4155df6 100644 --- a/launcher/src/main/java/com/skcraft/launcher/selfupdate/SelfUpdater.java +++ b/launcher/src/main/java/com/skcraft/launcher/selfupdate/SelfUpdater.java @@ -48,7 +48,7 @@ public class SelfUpdater implements Callable, ProgressObservable { installer.queue(new FileMover(tempFile, file)); progress = installer; - installer.execute(); + installer.execute(launcher); return file; } finally { diff --git a/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java b/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java index 61f3dd1..da923da 100644 --- a/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java +++ b/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java @@ -224,8 +224,8 @@ public abstract class BaseUpdater { File tempFile = installer.getDownloader().download(urls, "", LIBRARY_SIZE_ESTIMATE, library.getName() + ".jar"); - installer.queue(new FileMover( tempFile, targetFile)); log.info("Fetching " + path + " from " + urls); + installer.queue(new FileMover( tempFile, targetFile)); } } } diff --git a/launcher/src/main/java/com/skcraft/launcher/update/Updater.java b/launcher/src/main/java/com/skcraft/launcher/update/Updater.java index 70fa583..412021b 100644 --- a/launcher/src/main/java/com/skcraft/launcher/update/Updater.java +++ b/launcher/src/main/java/com/skcraft/launcher/update/Updater.java @@ -152,7 +152,7 @@ public class Updater extends BaseUpdater implements Callable, Progress // Install the .jar File jarPath = launcher.getJarPath(version); - URL jarSource = launcher.propUrl("jarUrl", version.getId()); + URL jarSource = url(version.getDownloads().get("client").getUrl()); log.info("JAR at " + jarPath.getAbsolutePath() + ", fetched from " + jarSource); installJar(installer, jarPath, jarSource); @@ -162,7 +162,7 @@ public class Updater extends BaseUpdater implements Callable, Progress URL url = manifest.getLibrariesUrl(); if (url != null) { log.info("Added library source: " + url); - librarySources.add(url); + librarySources.add(0, url); } progress = new DefaultProgress(-1, SharedLocale.tr("instanceUpdater.collectingLibraries")); @@ -171,7 +171,7 @@ public class Updater extends BaseUpdater implements Callable, Progress // Download assets log.info("Enumerating assets to download..."); progress = new DefaultProgress(-1, SharedLocale.tr("instanceUpdater.collectingAssets")); - installAssets(installer, version, launcher.propUrl("assetsIndexUrl", version.getAssetsIndex()), assetsSources); + installAssets(installer, version, url(version.getAssetIndex().getUrl()), assetsSources); log.info("Executing download phase..."); progress = ProgressFilter.between(installer.getDownloader(), 0, 0.98); @@ -179,7 +179,9 @@ public class Updater extends BaseUpdater implements Callable, Progress log.info("Executing install phase..."); progress = ProgressFilter.between(installer, 0.98, 1); - installer.execute(); + installer.execute(launcher); + + installer.executeLate(launcher); log.info("Completing..."); complete(); diff --git a/launcher/src/main/java/com/skcraft/launcher/util/FileUtils.java b/launcher/src/main/java/com/skcraft/launcher/util/FileUtils.java new file mode 100644 index 0000000..35a9227 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/FileUtils.java @@ -0,0 +1,43 @@ +package com.skcraft.launcher.util; + +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteStreams; +import com.google.common.io.Files; +import com.skcraft.launcher.model.modpack.DownloadableFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class FileUtils { + public static DownloadableFile saveStreamToObjectsDir(InputStream stream, File outputDir) throws IOException { + byte[] input = ByteStreams.toByteArray(stream); + HashFunction hf = Hashing.sha1(); + + String fileHash = hf.hashBytes(input).toString(); + String filePath = fileHash.substring(0, 2) + "/" + fileHash.substring(2, 4) + "/" + fileHash; + + File dest = new File(outputDir, filePath); + dest.getParentFile().mkdirs(); + + Files.write(input, dest); + + DownloadableFile entry = new DownloadableFile(); + entry.setLocation(filePath); + entry.setHash(fileHash); + entry.setSize(input.length); + + return entry; + } + + public static String getShaHash(File file) throws IOException { + FileInputStream stream = new FileInputStream(file); + byte[] input = ByteStreams.toByteArray(stream); + String res = Hashing.sha1().hashBytes(input).toString(); + + stream.close(); + return res; + } +} diff --git a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties index 99c31e4..23b54eb 100644 --- a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties +++ b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties @@ -146,6 +146,7 @@ installer.installing=Installing... installer.executing=Executing tasks... ({0} remaining) installer.copyingFile=Copying from {0} to {1} installer.movingFile=Moving {0} to {1} +installer.runningProcessor=Running processor {0}: {1} updater.updating=Updating launcher... updater.updateRequiredButOffline=An update is required but you need to be in online mode. diff --git a/launcher/src/main/resources/com/skcraft/launcher/launcher.properties b/launcher/src/main/resources/com/skcraft/launcher/launcher.properties index f434d2c..f748bde 100644 --- a/launcher/src/main/resources/com/skcraft/launcher/launcher.properties +++ b/launcher/src/main/resources/com/skcraft/launcher/launcher.properties @@ -8,7 +8,7 @@ version=${project.version} agentName=Minecraft offlinePlayerName=Player -versionManifestUrl=https://s3.amazonaws.com/Minecraft.Download/versions/%1$s/%1$s.json +versionManifestUrl=https://launchermeta.mojang.com/mc/game/version_manifest.json librariesSource=https://libraries.minecraft.net/ jarUrl=https://s3.amazonaws.com/Minecraft.Download/versions/%1$s/%1$s.jar assetsIndexUrl=https://s3.amazonaws.com/Minecraft.Download/indexes/%s.json diff --git a/launcher/src/main/resources/com/skcraft/launcher/maven_repos.json b/launcher/src/main/resources/com/skcraft/launcher/maven_repos.json index e7ad706..fda4311 100644 --- a/launcher/src/main/resources/com/skcraft/launcher/maven_repos.json +++ b/launcher/src/main/resources/com/skcraft/launcher/maven_repos.json @@ -1,5 +1,6 @@ [ "https://libraries.minecraft.net/", "https://repo1.maven.org/maven2/", + "https://files.minecraftforge.net/maven/", "http://maven.apache.org/" ]