/* * This file is part of FTB Launcher. * * Copyright © 2012-2016, FTB Launcher Contributors <https://github.com/Slowpoke101/FTBLaunch/> * FTB Launcher is licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.ftb.minecraft; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.swing.ProgressMonitor; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Lists; import net.feed_the_beast.launcher.json.JsonFactory; import net.feed_the_beast.launcher.json.assets.AssetIndex; import net.feed_the_beast.launcher.json.versions.Library; import net.feed_the_beast.launcher.json.versions.Version; import net.ftb.data.CommandLineSettings; import net.ftb.data.LauncherStyle; import net.ftb.data.LoginResponse; import net.ftb.data.ModPack; import net.ftb.data.Settings; import net.ftb.download.Locations; import net.ftb.download.info.DownloadInfo; import net.ftb.download.workers.AssetDownloader; import net.ftb.events.EnableObjectsEvent; import net.ftb.gui.LaunchFrame; import net.ftb.gui.panes.OptionsPane; import net.ftb.log.LogEntry; import net.ftb.log.LogLevel; import net.ftb.log.Logger; import net.ftb.log.StreamLogger; import net.ftb.main.Main; import net.ftb.tools.ProcessMonitor; import net.ftb.util.AppUtils; import net.ftb.util.Benchmark; import net.ftb.util.DownloadUtils; import net.ftb.util.ErrorUtils; import net.ftb.util.FTBFileUtils; import net.ftb.util.OSUtils; import net.ftb.util.Parallel; import net.ftb.util.TrackerUtils; public class MCInstaller { private static String packmcversion = new String(); private static String packbasejson = new String(); public static void setupNewStyle (final String installPath, final ModPack pack, final boolean isLegacy, final LoginResponse RESPONSE) { packmcversion = pack.getMcVersion(Settings.getSettings().getPackVer(pack.getDir())); packbasejson = ""; List<DownloadInfo> assets = gatherAssets(new File(installPath), installPath, isLegacy); if (assets != null && assets.size() > 0) { Logger.logInfo("Checking/Downloading " + assets.size() + " assets, this may take a while..."); final ProgressMonitor prog = new ProgressMonitor(LaunchFrame.getInstance(), "Downloading Files...", "", 0, 100); prog.setMaximum(assets.size() * 100); final AssetDownloader downloader = new AssetDownloader(prog, assets) { @Override public void done () { try { prog.close(); if (get()) { Logger.logInfo("Asset downloading complete"); launchMinecraft(installPath, pack, RESPONSE, isLegacy); } else { ErrorUtils.tossError("Error occurred during downloading the assets"); } } catch (CancellationException e) { Logger.logInfo("Asset download interrupted by user"); } catch (Exception e) { ErrorUtils.tossError("Failed to download files.", e); } finally { Main.getEventBus().post(new EnableObjectsEvent()); } } }; downloader.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange (PropertyChangeEvent evt) { if (prog.isCanceled()) { downloader.cancel(false); prog.close(); } else if (!downloader.isCancelled()) { if ("ready".equals(evt.getPropertyName())) { prog.setProgress(downloader.getReady()); } if ("status".equals(evt.getPropertyName())) { prog.setNote(downloader.getStatus()); } } } }); downloader.execute(); } else if (assets == null) { Main.getEventBus().post(new EnableObjectsEvent()); } else { launchMinecraft(installPath, pack, RESPONSE, isLegacy); } } /** * Gather assets to check/download. If force update is enabled this will return all assets * * @return null if failed and MC can't be started, otherwise, ArrayList containing assets to be checked/downloaded * Normally, if offline mode works, setupNewStyle() and gatherAssets() are not called and error situation is impossible * Returning null just in case of network breakge after authentication process */ private static List<DownloadInfo> gatherAssets (final File root, String installDir, boolean isLegacy) { try { Logger.logInfo("Checking local assets file, for MC version " + packmcversion + " Please wait! "); List<DownloadInfo> list = Lists.newArrayList(); Boolean forceUpdate = Settings.getSettings().isForceUpdateEnabled(); File local; // Pack JSON Libraries Logger.logDebug("Checking pack libraries"); ModPack pack = ModPack.getSelectedPack(); File packDir = new File(installDir, pack.getDir()); File gameDir = new File(packDir, "minecraft"); File libDir = new File(installDir, "libraries"); // if (!pack.getDir().equals("mojang_vanilla")) { if (!pack.getDir().equals("mojang_vanilla")) { if (isLegacy) { extractLegacyJson(new File(gameDir, "pack.json")); } } if (new File(gameDir, "pack.json").exists()) { Version packjson = JsonFactory.loadVersion(new File(gameDir, "pack.json")); if (packjson.jar != null && !packjson.jar.isEmpty()) { packmcversion = packjson.jar; } if (packjson.inheritsFrom != null && !packjson.inheritsFrom.isEmpty()) { packbasejson = packjson.inheritsFrom; } Library.Artifact a; for(Library lib : packjson.getLibraries()) { // Logger.logError(new File(libDir, lib.getPath()).getAbsolutePath()); // These files are shipped inside pack.zip, can't do force update check yet local = new File(root, "libraries/" + lib.getPath()); if (!new File(libDir, lib.getPath()).exists() || forceUpdate) { if (lib.checksums != null) { list.add(new DownloadInfo(new URL(lib.getUrl() + lib.getPath()), local, lib.getPath(), lib.checksums, "sha1", DownloadInfo.DLType.NONE, DownloadInfo.DLType.NONE)); } else if (lib.download != null && lib.download) { list.add(new DownloadInfo(new URL(lib.getUrl() + lib.getPath()), local, lib.getPath())); } } a = lib.get_artifact(); if (a.getDomain().equalsIgnoreCase("net.minecraftforge") && (a.getName().equalsIgnoreCase("forge") || a.getName().equalsIgnoreCase("minecraftforge"))) { grabJava8CompatFix(a, pack, packmcversion, installDir + "/" + pack.getDir()); } } // } } else { if (!pack.getDir().equals("mojang_vanilla")) { Logger.logError("pack.json file not found-Forge/Liteloader will not be able to load!"); } else { Logger.logInfo("pack.json not found in vanilla pack(this is expected)"); } // TODO handle vanilla packs w/ tweakers w/ this stuffs !!! } /* * <ftb installation location>/libraries/* */ // check if our copy exists of the version json if not backup to mojang's copy Logger.logDebug("Checking minecraft version json"); if (packbasejson == null || packbasejson.isEmpty()) { packbasejson = packmcversion; } URL url = new URL(DownloadUtils.getStaticCreeperhostLinkOrBackup("mcjsons/versions/{MC_VER}/{MC_VER}.json".replace("{MC_VER}", packbasejson), Locations.mc_dl + "versions/{MC_VER}/{MC_VER}.json".replace("{MC_VER}", packbasejson))); File json = new File(root, "versions/{MC_VER}/{MC_VER}.json".replace("{MC_VER}", packbasejson)); DownloadUtils.downloadToFile(url, json, 3); if (!json.exists()) { Logger.logError("library JSON not found"); return null; } Version version = JsonFactory.loadVersion(json); Logger.logDebug("checking minecraft libraries"); for(Library lib : version.getLibraries()) { if (lib.natives == null) { local = new File(root, "libraries/" + lib.getPath()); if (!local.exists() || forceUpdate) { if (!lib.getUrl().toLowerCase().equalsIgnoreCase(Locations.ftb_maven)) {// DL's shouldn't be coming from maven repos but ours or mojang's list.add(new DownloadInfo(new URL(lib.getUrl() + lib.getPath()), local, lib.getPath())); } else { list.add(new DownloadInfo(new URL(DownloadUtils.getCreeperhostLink(lib.getUrl() + lib.getPath())), local, lib.getPath(), true)); } } } else { local = new File(root, "libraries/" + lib.getPathNatives()); if (!local.exists() || forceUpdate) { list.add(new DownloadInfo(new URL(lib.getUrl() + lib.getPathNatives()), local, lib.getPathNatives())); } } } /* * vanilla minecraft.jar */ local = new File(root, "versions/{MC_VER}/{MC_VER}.jar".replace("{MC_VER}", packmcversion)); if (!local.exists() || forceUpdate) { list.add(new DownloadInfo(new URL(Locations.mc_dl + "versions/{MC_VER}/{MC_VER}.jar".replace("{MC_VER}", packmcversion)), local, local.getName())); } // Move the old format to the new: File test = new File(root, "assets/READ_ME_I_AM_VERY_IMPORTANT.txt"); if (test.exists()) { Logger.logDebug("Moving old format"); File assets = new File(root, "assets"); Set<File> old = FTBFileUtils.listFiles(assets); File objects = new File(assets, "objects"); String[] skip = new String[] {objects.getAbsolutePath(), new File(assets, "indexes").getAbsolutePath(), new File(assets, "virtual").getAbsolutePath()}; for(File f : old) { String path = f.getAbsolutePath(); boolean move = true; for(String prefix : skip) { if (path.startsWith(prefix)) { move = false; } } if (move) { String hash = DownloadUtils.fileSHA(f); File cache = new File(objects, hash.substring(0, 2) + "/" + hash); Logger.logInfo("Caching Asset: " + hash + " - " + f.getAbsolutePath().replace(assets.getAbsolutePath(), "")); if (!cache.exists()) { cache.getParentFile().mkdirs(); f.renameTo(cache); } f.delete(); } } List<File> dirs = FTBFileUtils.listDirs(assets); for(File dir : dirs) { if (dir.listFiles().length == 0) { dir.delete(); } } } /* * assets/* */ Logger.logDebug("Checking minecraft assets"); url = new URL(Locations.mc_dl + "indexes/{INDEX}.json".replace("{INDEX}", version.getAssets())); json = new File(root, "assets/indexes/{INDEX}.json".replace("{INDEX}", version.getAssets())); DownloadUtils.downloadToFile(url, json, 3); if (!json.exists()) { Logger.logError("asset JSON not found"); return null; } AssetIndex index = JsonFactory.loadAssetIndex(json); Benchmark.start("threading"); Collection<DownloadInfo> tmp; Logger.logDebug("Starting TaskHandler to check MC assets"); Parallel.TaskHandler th = new Parallel.ForEach(index.objects.entrySet()).withFixedThreads(2 * OSUtils.getNumCores()) // .configurePoolSize(2*2*OSUtils.getNumCores(), 10) .apply(new Parallel.F<Map.Entry<String, AssetIndex.Asset>, DownloadInfo>() { public DownloadInfo apply (Map.Entry<String, AssetIndex.Asset> e) { try { String name = e.getKey(); AssetIndex.Asset asset = e.getValue(); String path = asset.hash.substring(0, 2) + "/" + asset.hash; final File local = new File(root, "assets/objects/" + path); if (local.exists() && !asset.hash.equals(DownloadUtils.fileSHA(local))) { local.delete(); } if (!local.exists()) { return (new DownloadInfo(new URL(Locations.mc_res + path), local, name, Lists.newArrayList(asset.hash), "sha1")); } } catch (Exception ex) { Logger.logError("Asset hash check failed", ex); } // values() will drop null entries return null; } }); tmp = th.values(); list.addAll(tmp); // kill executorservice th.shutdown(); Benchmark.logBenchAs("threading", "parallel asset check"); return list; } catch (Exception e) { Logger.logError("Error while gathering assets", e); } return null; } public static void launchMinecraft (String installDir, ModPack pack, LoginResponse resp, boolean isLegacy) { try { File packDir = new File(installDir, pack.getDir()); String gameFolder = installDir + File.separator + pack.getDir() + File.separator + "minecraft"; File gameDir = new File(packDir, "minecraft"); File assetDir = new File(installDir, "assets"); File libDir = new File(installDir, "libraries"); File natDir = new File(packDir, "natives"); final String packVer = Settings.getSettings().getPackVer(pack.getDir()); Logger.logInfo("Setting up native libraries for " + pack.getName() + " v " + packVer + " MC " + packmcversion); if (!gameDir.exists()) { gameDir.mkdirs(); } if (natDir.exists()) { natDir.delete(); } natDir.mkdirs(); packmcversion = pack.getMcVersion(Settings.getSettings().getPackVer(pack.getDir())); packbasejson = ""; if (new File(gameDir, "pack.json").exists()) { Version packjson = JsonFactory.loadVersion(new File(gameDir, "pack.json")); if (packjson.jar != null && !packjson.jar.isEmpty()) { packmcversion = packjson.jar; // is this needed or not? } if (packjson.inheritsFrom != null && !packjson.inheritsFrom.isEmpty()) { packbasejson = packjson.inheritsFrom; } } if (packbasejson == null || packbasejson.isEmpty()) { packbasejson = packmcversion; } Logger.logDebug("packbaseJSON " + packbasejson); Version base = JsonFactory.loadVersion(new File(installDir, "versions/{MC_VER}/{MC_VER}.json".replace("{MC_VER}", packbasejson))); byte[] buf = new byte[1024]; for(Library lib : base.getLibraries()) { if (lib.natives != null) { File local = new File(libDir, lib.getPathNatives()); ZipInputStream input = null; try { input = new ZipInputStream(new FileInputStream(local)); ZipEntry entry = input.getNextEntry(); while (entry != null) { String name = entry.getName(); int n; if (lib.extract == null || !lib.extract.exclude(name)) { File output = new File(natDir, name); output.getParentFile().mkdirs(); FileOutputStream out = new FileOutputStream(output); while ((n = input.read(buf, 0, 1024)) > -1) { out.write(buf, 0, n); } out.close(); } input.closeEntry(); entry = input.getNextEntry(); } } catch (Exception e) { ErrorUtils.tossError("Error extracting native libraries"); Logger.logError("", e); } finally { try { input.close(); } catch (IOException e) {} } } } List<File> classpath = Lists.newArrayList(); Version packjson = new Version(); if (new File(gameDir, "pack.json").exists()) { packjson = JsonFactory.loadVersion(new File(gameDir, "pack.json")); for(Library lib : packjson.getLibraries()) { // Logger.logError(new File(libDir, lib.getPath()).getAbsolutePath()); classpath.add(new File(libDir, lib.getPath())); } // } } else { packjson = base; } if (!isLegacy) // we copy the jar to a new location for legacy { classpath.add(new File(installDir, "versions/{MC_VER}/{MC_VER}.jar".replace("{MC_VER}", packmcversion))); } else { FTBFileUtils.copyFile(new File(installDir, "versions/{MC_VER}/{MC_VER}.jar".replace("{MC_VER}", packmcversion)), new File(gameDir, "bin/" + Locations.OLDMCJARNAME)); FTBFileUtils.killMetaInf(); } for(Library lib : base.getLibraries()) { classpath.add(new File(libDir, lib.getPath())); } Process minecraftProcess = MCLauncher.launchMinecraft(Settings.getSettings().getJavaPath(), gameFolder, assetDir, natDir, classpath, packjson.mainClass != null ? packjson.mainClass : base.mainClass, packjson.minecraftArguments != null ? packjson.minecraftArguments : base.minecraftArguments, packjson.assets != null ? packjson.assets : base.getAssets(), Settings.getSettings().getRamMax(), pack.getMaxPermSize(), pack.getMcVersion(packVer), resp.getAuth(), isLegacy, packjson.type); LaunchFrame.MCRunning = true; if (!CommandLineSettings.getSettings().isDisableMCLogging()) { StreamLogger.prepare(minecraftProcess.getInputStream(), new LogEntry().level(LogLevel.UNKNOWN)); String[] ignore = {"Session ID is token"}; StreamLogger.setIgnore(ignore); StreamLogger.doStart(); } else { // stderr is combined with stdout AppUtils.voidInputStream(minecraftProcess.getInputStream()); Logger.logWarn("Not logging MC messages via launcher!"); } Logger.logDebug("MC PID: " + OSUtils.getPID(minecraftProcess)); String curVersion = (Settings.getSettings().getPackVer().equalsIgnoreCase("recommended version") ? pack.getVersion() : Settings.getSettings().getPackVer()).replace(".", "_"); TrackerUtils.sendPageView(ModPack.getSelectedPack().getName(), "Launched / " + ModPack.getSelectedPack().getName() + " / " + curVersion.replace('_', '.')); try { Thread.sleep(1500); } catch (InterruptedException e) {} try { minecraftProcess.exitValue(); } catch (IllegalThreadStateException e) { LaunchFrame.getInstance().setVisible(false); LaunchFrame.setProcMonitor(ProcessMonitor.create(minecraftProcess, new Runnable() { @Override public void run () { if (!Settings.getSettings().getKeepLauncherOpen()) { System.exit(0); } else { if (LaunchFrame.con != null) { LaunchFrame.con.minecraftStopped(); } LaunchFrame launchFrame = LaunchFrame.getInstance(); launchFrame.setVisible(true); Main.getEventBus().post(new EnableObjectsEvent()); try { Settings.getSettings().load(new FileInputStream(Settings.getSettings().getConfigFile())); LaunchFrame.getInstance().tabbedPane.remove(1); LaunchFrame.getInstance().optionsPane = new OptionsPane(Settings.getSettings()); LaunchFrame.getInstance().tabbedPane.add(LaunchFrame.getInstance().optionsPane, 1); LaunchFrame.getInstance().tabbedPane.setIconAt(1, LauncherStyle.getCurrentStyle().filterHeaderIcon(this.getClass().getResource("/image/tabs/options.png"))); } catch (Exception e1) { Logger.logError("Failed to reload settings after launcher closed", e1); } } LaunchFrame.MCRunning = false; } })); if (LaunchFrame.con != null) { LaunchFrame.con.minecraftStarted(); } } } catch (Exception e) { Logger.logError("Error while running launchMinecraft()", e); } } /** * @param modPackName - The pack to install (should already be downloaded) * @throws IOException */ public static void installMods (String modPackName, boolean softUpdate) throws IOException { String installpath = Settings.getSettings().getInstallPath(); String temppath = OSUtils.getCacheStorageLocation(); ModPack pack = ModPack.getSelectedPack(); List<String> blacklist = Lists.newArrayList(); if (!softUpdate) { blacklist.add("options.txt"); } String packDir = pack.getDir(); Logger.logInfo("dirs mk'd"); File source = new File(temppath, "ModPacks/" + packDir + "/.minecraft"); if (!source.exists()) { source = new File(temppath, "ModPacks/" + packDir + "/minecraft"); } Logger.logDebug("install path: " + installpath); Logger.logDebug("temp path: " + temppath); Logger.logDebug("source: " + source); Logger.logDebug("packDir: " + packDir); FTBFileUtils.copyFolder(source, new File(installpath, packDir + "/minecraft/"), blacklist); FTBFileUtils.copyFolder(new File(temppath, "ModPacks/" + packDir + "/instMods/"), new File(installpath, packDir + "/instMods/")); FTBFileUtils.copyFolder(new File(temppath, "ModPacks/" + packDir + "/libraries/"), new File(installpath, "/libraries/"), false); } private static void extractLegacyJson (File newLoc) { try { if (!new File(newLoc.getParent()).exists()) { new File(newLoc.getParent()).mkdirs(); } if (newLoc.exists()) { newLoc.delete();// we want to have the current version always!!! } URL u = LaunchFrame.class.getResource("/launch/legacypack.json"); FileUtils.copyURLToFile(u, newLoc); } catch (Exception e) { Logger.logError("Error extracting legacy json to maven directory"); } } private static void grabJava8CompatFix (Library.Artifact forgeArtifact, ModPack pack, String packmcversion, String installBase) { String fgVsn = forgeArtifact.getVersion(); String fgRelease; int vsn_ = 0; int count = StringUtils.countMatches(fgVsn, "-"); if (count == 2) { // forge > 1291 has three subsection, third section is name of the branch // e.g. 1.7.10-10.13.2.1352-1.7.10 or fgRelease = fgVsn.substring((StringUtils.indexOf(fgVsn, "-") + 1), (StringUtils.lastIndexOf(fgVsn, "-"))); fgRelease = fgRelease.substring(StringUtils.lastIndexOf(fgRelease, ".") + 1); vsn_ = Integer.parseInt(fgRelease); } else if (count == 1 || count == 0) { // e.g. 1.7.10-10.13.2.1291 or 9.11.1.965 fgRelease = fgVsn.substring(StringUtils.lastIndexOf(fgVsn, ".") + 1); vsn_ = Integer.parseInt(fgRelease); } if (vsn_ >= Settings.getSettings().getMinJava8HackVsn() && vsn_ <= Settings.getSettings().getMaxJava8HackVsn()) { Logger.logDebug("adding legacyjavafixer to modpack as it is needed for this forge version to make java 8 function correctly"); String json = "{\"url\":\"http://ftb.cursecdn.com/FTB2/maven/\",\"name\":\"net.minecraftforge.lex:legacyjavafixer:1.0\",\"checksums\":[\"a11b502bef19f49bfc199722b94da5f3d7b470a8\"]}"; Library l = JsonFactory.loadLibrary(json);// TODO this should be pulled from the same json file try {// TODO we should have a method to grab a single library file to a location DownloadUtils.downloadToFile(installBase + "/minecraft/mods/legacyjavafixer-1.0.jar", l.getUrl() + l.getPath()); } catch (Exception e) { Logger.logError("Error grabbing legacy java wrapper library", e); } } } }