/*
* 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.awt.Dimension;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import com.google.common.collect.Lists;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.mojang.authlib.UserAuthentication;
import com.mojang.authlib.UserType;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
import com.mojang.util.UUIDTypeAdapter;
import net.feed_the_beast.launcher.json.JsonFactory;
import net.feed_the_beast.launcher.json.OldPropertyMapSerializer;
import net.feed_the_beast.launcher.json.assets.AssetIndex;
import net.feed_the_beast.launcher.json.assets.AssetIndex.Asset;
import net.ftb.data.ModPack;
import net.ftb.data.Settings;
import net.ftb.download.Locations;
import net.ftb.log.Logger;
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;
import net.ftb.util.winreg.JavaVersion;
public class MCLauncher
{
public static boolean isLegacy = false;
private static String separator = File.separator;
private static String gameDirectory;
private static StringBuilder cpb;
public static Process launchMinecraft (String javaPath, String gameFolder, File assetDir, File nativesDir, List<File> classpath, String mainClass, String args, String assetIndex, String rmax, String maxPermSize, String version, UserAuthentication authentication, boolean legacy, String versionType) throws IOException
{
cpb = new StringBuilder("");
isLegacy = legacy;
gameDirectory = gameFolder;
File gameDir = new File(gameFolder);
assetDir = syncAssets(assetDir, assetIndex);
for(File f : classpath)
{
cpb.append(OSUtils.getJavaDelimiter());
cpb.append(f.getAbsolutePath());
}
cpb.deleteCharAt(0);
if (isLegacy)
{
setupLegacyStuff(gameDirectory, Locations.FORGENAME, version);
}
// Logger.logInfo("ClassPath: " + cpb.toString());
List<String> arguments = Lists.newArrayList();
Logger.logInfo("Java Path: " + javaPath);
Logger.logInfo("Pack: " + ModPack.getSelectedPack().getName() + " " + version);
arguments.add(javaPath);
setMemory(arguments, rmax);
if (Settings.getSettings().getCurrentJava().isOlder(JavaVersion.createJavaVersion("1.8.0")))
{
if (OSUtils.getCurrentOS().equals(OSUtils.OS.WINDOWS))
{
if (!OSUtils.is64BitWindows())
{
if (maxPermSize == null || maxPermSize.isEmpty())
{
if (OSUtils.getOSTotalMemory() > 2046)
{
maxPermSize = "192m";
Logger.logInfo("Defaulting PermSize to 192m");
}
else
{
maxPermSize = "128m";
Logger.logInfo("Defaulting PermSize to 128m");
}
}
}
}
if (maxPermSize == null || maxPermSize.isEmpty())
{
// 64-bit or Non-Windows
maxPermSize = "256m";
Logger.logInfo("Defaulting PermSize to 256m");
}
arguments.add("-XX:PermSize=" + maxPermSize);
}
arguments.add("-Djava.library.path=" + nativesDir.getAbsolutePath());
arguments.add("-Dorg.lwjgl.librarypath=" + nativesDir.getAbsolutePath());
arguments.add("-Dnet.java.games.input.librarypath=" + nativesDir.getAbsolutePath());
arguments.add("-Duser.home=" + gameDir.getParentFile().getAbsolutePath());
arguments.add("-Duser.language=en");
if (OSUtils.getCurrentOS() == OSUtils.OS.WINDOWS)
{
arguments.add("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump");
}
// Use IPv4 when possible, only use IPv6 when connecting to IPv6 only addresses
arguments.add("-Djava.net.preferIPv4Stack=true");
if (Settings.getSettings().getUseSystemProxy())
{
arguments.add("-Djava.net.useSystemProxies=true");
}
arguments.add("-cp");
arguments.add(cpb.toString());
String additionalOptions = Settings.getSettings().getAdditionalJavaOptions();
if (!additionalOptions.isEmpty())
{
Logger.logInfo("Additional java parameters: " + additionalOptions);
for(String s : additionalOptions.split("\\s+"))
{
if (s.equalsIgnoreCase("-Dfml.ignoreInvalidMinecraftCertificates=true") && !isLegacy)
{
String curVersion = (Settings.getSettings().getPackVer().equalsIgnoreCase("recommended version") ? ModPack.getSelectedPack().getVersion() : Settings.getSettings().getPackVer()).replace(".", "_");
TrackerUtils.sendPageView("JarmodAttempt", "JarmodAttempt / " + ModPack.getSelectedPack().getName() + " / " + curVersion.replace('_', '.'));
ErrorUtils.tossError("JARMODDING DETECTED in 1.6.4+ " + s, "FTB Does not support jarmodding in MC 1.6+ ");
}
else
{
arguments.add(s);
}
}
}
if (Settings.getSettings().getOptJavaArgs())
{
String optArgs = "-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CICompilerCountPerCPU -XX:+TieredCompilation";
Logger.logInfo("Adding Optimization Arguments: " + optArgs);
Collections.addAll(arguments, optArgs.split("\\s+"));
}
// Undocumented environment variable to control JVM
String additionalEnvVar = System.getenv("_JAVA_OPTIONS");
if (additionalEnvVar != null && !additionalEnvVar.isEmpty())
{
Logger.logDebug("_JAVA_OPTIONS has following arguments: " + additionalEnvVar + " Arguments will be ignored at MC startup");
}
// Documented environment variable to control JVM
additionalEnvVar = System.getenv("JAVA_TOOL_OPTIONS");
if (additionalEnvVar != null && !additionalEnvVar.isEmpty())
{
Logger.logDebug("JAVA_TOOL_OPTIONS has following arguments: " + additionalEnvVar + " Arguments will be ignored at MC startup");
}
// Documented environment variable to control JVM
additionalEnvVar = System.getenv("JAVA_OPTIONS");
if (additionalEnvVar != null && !additionalEnvVar.isEmpty())
{
Logger.logDebug("JAVA_OPTIONS has following arguments: " + additionalEnvVar + " Arguments will be ignored at MC startup");
}
arguments.add(mainClass);
for(String s : args.split(" "))
{
boolean done = false;
if (authentication.getSelectedProfile() != null)
{
if (s.equals("${auth_player_name}"))
{
arguments.add(authentication.getSelectedProfile().getName());
done = true;
}
else if (s.equals("${auth_uuid}"))
{
arguments.add(UUIDTypeAdapter.fromUUID(authentication.getSelectedProfile().getId()));
done = true;
}
else if (s.equals("${user_type}"))
{
arguments.add(authentication.getUserType().getName());
done = true;
}
}
else
{
if (s.equals("${auth_player_name}"))
{
arguments.add("Player");
done = true;
}
else if (s.equals("${auth_uuid}"))
{
arguments.add(new UUID(0L, 0L).toString());
done = true;
}
else if (s.equals("${user_type}"))
{
arguments.add(UserType.LEGACY.getName());
done = true;
}
}
if (!done)
{
if (s.equals("${auth_session}"))
{
if (authentication.isLoggedIn() && authentication.canPlayOnline())
{
if (authentication instanceof YggdrasilUserAuthentication && !isLegacy)
{
arguments.add(String.format("token:%s:%s", authentication.getAuthenticatedToken(), UUIDTypeAdapter.fromUUID(authentication.getSelectedProfile().getId())));
}
else
{
arguments.add(authentication.getAuthenticatedToken());
}
}
else
{
arguments.add("-");
}
}
else if (s.equals("${auth_access_token}"))
{
arguments.add(authentication.getAuthenticatedToken());
}
else if (s.equals("${version_name}"))
{
arguments.add(version);
}
else if (s.equals("${game_directory}"))
{
arguments.add(gameDir.getAbsolutePath());
}
else if (s.equals("${game_assets}") || s.equals("${assets_root}"))
{
arguments.add(assetDir.getAbsolutePath());
}
else if (s.equals("${assets_index_name}"))
{
arguments.add(assetIndex == null ? "legacy" : assetIndex);
}
else if (s.equals("${user_properties}"))
{
arguments.add(new GsonBuilder().registerTypeAdapter(PropertyMap.class, new OldPropertyMapSerializer()).create().toJson(authentication.getUserProperties()));
}
else if (s.equals("${user_properties_map}"))
{
arguments.add(new GsonBuilder().registerTypeAdapter(PropertyMap.class, new PropertyMap.Serializer()).create().toJson(authentication.getUserProperties()));
}
else if (isLegacy)
{
arguments.add(parseLegacyArgs(s));
}
else if (s.equals("${version_type}"))
{
arguments.add(versionType);
}
else
{
arguments.add(s);
}
}
}
if (!isLegacy)
{// legacy is handled separately
boolean fullscreen = false;
if (Settings.getSettings().getLastExtendedState() == JFrame.MAXIMIZED_BOTH)
{
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
arguments.add("--width");
arguments.add(String.valueOf((int)bounds.getWidth()));
arguments.add("--height");
arguments.add(String.valueOf((int)bounds.getHeight()));
fullscreen = true;
}
Dimension def = new Dimension(854, 480);
if (Settings.getSettings().getLastDimension().getWidth() != def.getWidth() && !fullscreen)
{
arguments.add("--width");
arguments.add(String.valueOf(Math.abs((int)Settings.getSettings().getLastDimension().getWidth())));
}
if (Settings.getSettings().getLastDimension().getHeight() != def.getHeight() && !fullscreen)
{
arguments.add("--height");
arguments.add(String.valueOf(Math.abs((int)Settings.getSettings().getLastDimension().getHeight())));
}
}
ProcessBuilder builder = new ProcessBuilder(arguments);
/*
* StringBuilder tmp = new StringBuilder();
* for (String a : builder.command())
* tmp.append(a).append(" \n");
* Logger.logInfo("Launching: \n" + tmp.toString());
* //
*/
builder.directory(gameDir);
builder.redirectErrorStream(true);
OSUtils.cleanEnvVars(builder.environment());
return builder.start();
}
private static void setMemory (List<String> arguments, String rmax)
{
boolean memorySet = false;
try
{
int min = 256;
if (rmax != null && Integer.parseInt(rmax) > 0)
{
arguments.add("-Xms" + min + "M");
Logger.logInfo("Setting MinMemory to " + min);
arguments.add("-Xmx" + rmax + "M");
Logger.logInfo("Setting MaxMemory to " + rmax);
memorySet = true;
}
}
catch (Exception e)
{
Logger.logError("Error parsing memory settings", e);
}
if (!memorySet)
{
arguments.add("-Xms" + 256 + "M");
Logger.logInfo("Defaulting MinMemory to " + 256);
arguments.add("-Xmx" + 1024 + "M");
Logger.logInfo("Defaulting MaxMemory to " + 1024);
}
}
private static File syncAssets (File assetDir, String indexName) throws JsonSyntaxException, JsonIOException, IOException
{
Logger.logInfo("Syncing Assets:");
final File objects = new File(assetDir, "objects");
AssetIndex index = JsonFactory.loadAssetIndex(new File(assetDir, "indexes/{INDEX}.json".replace("{INDEX}", indexName)));
if (!index.virtual)
{
return assetDir;
}
final File targetDir = new File(assetDir, "virtual/" + indexName);
final ConcurrentSkipListSet<File> old = new ConcurrentSkipListSet();
old.addAll(FTBFileUtils.listFiles(targetDir));
Benchmark.reset("threading");
Parallel.TaskHandler th = new Parallel.ForEach(index.objects.entrySet()).withFixedThreads(2 * OSUtils.getNumCores())
// .configurePoolSize(2*2*OSUtils.getNumCores(), 10)
.apply(new Parallel.F<Entry<String, Asset>, Void>()
{
public Void apply (Entry<String, Asset> e)
{
Asset asset = e.getValue();
File local = new File(targetDir, e.getKey());
File object = new File(objects, asset.hash.substring(0, 2) + "/" + asset.hash);
old.remove(local);
try
{
if (local.exists() && !DownloadUtils.fileSHA(local).equals(asset.hash))
{
Logger.logInfo(" Changed: " + e.getKey());
FTBFileUtils.copyFile(object, local, true);
}
else if (!local.exists())
{
Logger.logInfo(" Added: " + e.getKey());
FTBFileUtils.copyFile(object, local);
}
}
catch (Exception ex)
{
Logger.logError("Asset checking failed: ", ex);
}
return null;
}
});
try
{
th.shutdown();
th.wait(60, TimeUnit.SECONDS);
}
catch (Exception ex)
{
Logger.logError("Asset checking failed: ", ex);
}
Benchmark.logBenchAs("threading", "parallel asset(virtual) check");
for(File f : old)
{
String name = f.getAbsolutePath().replace(targetDir.getAbsolutePath(), "");
Logger.logInfo(" Removed: " + name.substring(1));
f.delete();
}
return targetDir;
}
public static String parseLegacyArgs (String s)
{
if (s.equals("${animation_name}"))
{
return (((!ModPack.getSelectedPack().getAnimation().equalsIgnoreCase("empty")) ? OSUtils.getCacheStorageLocation() + "ModPacks" + separator + ModPack.getSelectedPack().getDir() + separator + ModPack.getSelectedPack().getAnimation() : "empty"));
}
else if (s.equals("${forge_name}"))
{
return Locations.FORGENAME;
}
else if (s.equals("${pack_name}"))
{
return (ModPack.getSelectedPack().getName() + " v" + (Settings.getSettings().getPackVer().equalsIgnoreCase("recommended version") ? ModPack.getSelectedPack().getVersion() : Settings.getSettings().getPackVer()));
}
else if (s.equals("${pack_image}"))
{
return (OSUtils.getCacheStorageLocation() + "ModPacks" + separator + ModPack.getSelectedPack().getDir() + separator + ModPack.getSelectedPack().getLogoName());
}
else if (s.equals("${extended_state}"))
{
return (String.valueOf(Settings.getSettings().getLastExtendedState()));
}
else if (s.equals("${width}"))
{
return (String.valueOf(Settings.getSettings().getLastDimension().getWidth()));
}
else if (s.equals("${height}"))
{
return (String.valueOf(Settings.getSettings().getLastDimension().getHeight()));
}
else if (s.equals("${minecraft_jar}"))
{
return (gameDirectory + File.separator + "bin" + File.separator + Locations.OLDMCJARNAME);
}
else
{
return s;
}
}
public static void setupLegacyStuff (String workingDir, String forgename, String mcVersion)
{
File instModsDir = new File(new File(workingDir).getParentFile(), "instMods/");
// jarmods are added inside the wrapper
// false: basically old code
// true: correct code, fixes beta a but breaks retro smp/ssp
if (mcVersion.equals("1.4.2"))
{
cpb.append(OSUtils.getJavaDelimiter());
cpb.append(new File(instModsDir, forgename).getAbsolutePath());
cpb.append(OSUtils.getJavaDelimiter());
cpb.append(new File(new File(workingDir, "bin"), Locations.OLDMCJARNAME).getAbsolutePath());
}
File libsDir = new File(workingDir, "lib/");
if (libsDir.isDirectory())
{
String[] files = libsDir.list();
Arrays.sort(files);
for(String name : files)
{
if ((name.toLowerCase().endsWith(".zip") || name.toLowerCase().endsWith(".jar")))
{
cpb.append(OSUtils.getJavaDelimiter());
cpb.append(new File(libsDir, name).getAbsolutePath());
}
}
}
else
{
Logger.logInfo("Not loading any FML libs, as the directory does not exist.");
}
}
}