/*
* 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.util;
import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.CodeSource;
import java.util.Enumeration;
import java.util.Map;
import java.util.UUID;
import javax.swing.text.html.StyleSheet;
import org.apache.commons.io.FileUtils;
import com.sun.jna.Library;
import com.sun.jna.Native;
import lombok.Getter;
import net.ftb.data.CommandLineSettings;
import net.ftb.gui.LaunchFrame;
import net.ftb.log.Logger;
import net.ftb.util.winreg.JavaFinder;
import net.ftb.util.winreg.RuntimeStreamer;
public class OSUtils
{
private static byte[] cachedMacAddress;
private static String cachedUserHome;
/**
* gets the number of cores for use in DL threading
*
* @return number of cores on the system
*/
@Getter
private static int numCores;
private static byte[] hardwareID;
private static UUID clientUUID;
public static Proxy getProxy (String url)
{
// this is set explicitly with command line define or by our proxy setting
String system = System.getProperty("java.net.useSystemProxies");
// System-wide setting from java control panel or command line define
String socks = System.getProperty("socksProxyHost");
if (system != null && system.equals("true"))
{
Logger.logDebug("Detected system proxy");
}
if (socks != null && !socks.isEmpty())
{
Logger.logDebug("Detected socks proxy");
}
java.util.List<Proxy> l = null;
try
{
l = ProxySelector.getDefault().select(new URI(url));
if (l != null)
{
for(Proxy p : l)
{
InetSocketAddress address = (InetSocketAddress)p.address();
if (address == null)
{
Logger.logDebug("ProxySelector: type: " + p.type() + ", no proxy for " + url);
}
else
{
Logger.logDebug("ProxySelector: type: " + p.type() + ", for " + url);
}
}
// correct? Can' decide without feedback
return l.get(0);
}
}
catch (Exception e)
{
Logger.logDebug("failed", e);
}
Logger.logWarn("Proxy was turned on but ProxySelector did not returned proxies for " + url);
return Proxy.NO_PROXY;
}
public static enum OS
{
WINDOWS,
UNIX,
MACOSX,
OTHER,
}
static
{
cachedUserHome = System.getProperty("user.home");
numCores = Runtime.getRuntime().availableProcessors();
}
/**
* Gets the default installation path for the current OS.
* @return a string containing the default install path for the current OS.
*/
public static String getDefInstallPath ()
{
switch (getCurrentOS())
{
case WINDOWS:
String defaultLocation = "c:\\ftnt";
File testFile = new File(defaultLocation);
// existing directory and we can write
if (testFile.canWrite())
{
return defaultLocation;
}
// We can create default directory
if (testFile.getParentFile().canWrite())
{
return defaultLocation;
}
Logger.logWarn("Can't use default installation location. Using current location of the launcher executable.");
case MACOSX:
return System.getProperty("user.home") + "/ftnt";
case UNIX:
return System.getProperty("user.home") + "/ftnt";
default:
try
{
CodeSource codeSource = LaunchFrame.class.getProtectionDomain().getCodeSource();
File jarFile;
jarFile = new File(codeSource.getLocation().toURI().getPath());
return jarFile.getParentFile().getPath();
}
catch (URISyntaxException e)
{
Logger.logError("Unexcepted error", e);
}
return System.getProperty("user.home") + System.getProperty("path.separator") + "FTNT";
}
}
/**
* Used to get the dynamic storage location based off OS
* @return string containing dynamic storage location
*/
public static String getDynamicStorageLocation ()
{
if (CommandLineSettings.getSettings().getDynamicDir() != null && !CommandLineSettings.getSettings().getDynamicDir().isEmpty())
{
return CommandLineSettings.getSettings().getDynamicDir();
}
switch (getCurrentOS())
{
case WINDOWS:
return System.getenv("APPDATA") + "/ftntlauncher/";
case MACOSX:
return cachedUserHome + "/Library/Application Support/ftntlauncher/";
case UNIX:
return cachedUserHome + "/.ftntlauncher/";
default:
return getDefInstallPath() + "/temp/";
}
}
/**
* Used to get a location to store cached content such as maps,
* texture packs and pack archives.
*
* @return string containing cache storage location
*/
public static String getCacheStorageLocation ()
{
if (CommandLineSettings.getSettings().getCacheDir() != null && !CommandLineSettings.getSettings().getCacheDir().isEmpty())
{
return CommandLineSettings.getSettings().getCacheDir();
}
switch (getCurrentOS())
{
case WINDOWS:
if (System.getenv("LOCALAPPDATA") != null && System.getenv("LOCALAPPDATA").length() > 5)
{
return System.getenv("LOCALAPPDATA") + "/ftntlauncher/";
}
else
{
return System.getenv("APPDATA") + "/ftntlauncher/";
}
case MACOSX:
return cachedUserHome + "/Library/Application Support/ftntlauncher/";
case UNIX:
return cachedUserHome + "/.ftntlauncher/";
default:
return getDefInstallPath() + "/temp/";
}
}
public static void createStorageLocations ()
{
File cacheDir = new File(OSUtils.getCacheStorageLocation());
File dynamicDir = new File(OSUtils.getDynamicStorageLocation());
if (!cacheDir.exists())
{
cacheDir.mkdirs();
if (dynamicDir.exists() && !cacheDir.equals(dynamicDir))
{
// Migrate cached archives from the user's roaming profile to their local cache
Logger.logInfo("Migrating cached Maps from Roaming to Local storage");
FTBFileUtils.move(new File(dynamicDir, "Maps"), new File(cacheDir, "Maps"));
Logger.logInfo("Migrating cached Modpacks from Roaming to Local storage");
FTBFileUtils.move(new File(dynamicDir, "ModPacks"), new File(cacheDir, "ModPacks"));
Logger.logInfo("Migrating cached Texturepacks from Roaming to Local storage");
FTBFileUtils.move(new File(dynamicDir, "TexturePacks"), new File(cacheDir, "TexturePacks"));
Logger.logInfo("Migration complete.");
}
}
if (!dynamicDir.exists())
{
dynamicDir.mkdirs();
}
if (getCurrentOS() == OS.WINDOWS)
{
File oldLoginData = new File(dynamicDir, "logindata");
File newLoginData = new File(cacheDir, "logindata");
try
{
if (oldLoginData.exists() && !oldLoginData.getCanonicalPath().equals(newLoginData.getCanonicalPath()))
{
newLoginData.delete();
}
}
catch (Exception e)
{
Logger.logError("Error deleting login data", e);
}
}
}
public static long getOSTotalMemory ()
{
return getOSMemory("getTotalPhysicalMemorySize", "Could not get RAM Value");
}
public static long getOSFreeMemory ()
{
return getOSMemory("getFreePhysicalMemorySize", "Could not get free RAM Value");
}
private static long getOSMemory (String methodName, String warning)
{
long ram = 0;
OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
Method m;
try
{
m = operatingSystemMXBean.getClass().getDeclaredMethod(methodName);
m.setAccessible(true);
Object value = m.invoke(operatingSystemMXBean);
if (value != null)
{
ram = Long.valueOf(value.toString()) / 1024 / 1024;
}
else
{
Logger.logWarn(warning);
ram = 1024;
}
}
catch (Exception e)
{
Logger.logError("Error while getting OS memory info", e);
}
return ram;
}
/**
* Used to get the java delimiter for current OS
* @return string containing java delimiter for current OS
*/
public static String getJavaDelimiter ()
{
switch (getCurrentOS())
{
case WINDOWS:
return ";";
case UNIX:
return ":";
case MACOSX:
return ":";
default:
return ";";
}
}
/**
* Used to get the current operating system
* @return OS enum representing current operating system
*/
public static OS getCurrentOS ()
{
String osString = System.getProperty("os.name").toLowerCase();
if (osString.contains("win"))
{
return OS.WINDOWS;
}
else if (osString.contains("nix") || osString.contains("nux"))
{
return OS.UNIX;
}
else if (osString.contains("mac"))
{
return OS.MACOSX;
}
else
{
return OS.OTHER;
}
}
/**
* Used to check if Windows is 64-bit
* @return true if 64-bit Windows
*/
public static boolean is64BitWindows ()
{
String arch = System.getenv("PROCESSOR_ARCHITECTURE");
String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432");
return (arch.endsWith("64") || (wow64Arch != null && wow64Arch.endsWith("64")));
}
/**
* Used to check if a posix OS is 64-bit
* @return true if 64-bit Posix OS
*/
public static boolean is64BitPosix ()
{
String line, result = "";
try
{
Process command = Runtime.getRuntime().exec("uname -m");
BufferedReader in = new BufferedReader(new InputStreamReader(command.getInputStream()));
while ((line = in.readLine()) != null)
{
result += (line + "\n");
}
}
catch (Exception e)
{
Logger.logError("Posix bitness check failed", e);
}
// 32-bit Intel Linuces, it returns i[3-6]86. For 64-bit Intel, it says x86_64
return result.contains("_64");
}
/**
* Used to check if OS X is 64-bit
* @return true if 64-bit OS X
*/
public static boolean is64BitOSX ()
{
String line, result = "";
if (!(System.getProperty("os.version").startsWith("10.6") || System.getProperty("os.version").startsWith("10.5")))
{
return true;// 10.7+ only shipped on hardware capable of using 64 bit java
}
try
{
Process command = Runtime.getRuntime().exec("/usr/sbin/sysctl -n hw.cpu64bit_capable");
BufferedReader in = new BufferedReader(new InputStreamReader(command.getInputStream()));
while ((line = in.readLine()) != null)
{
result += (line + "\n");
}
}
catch (Exception e)
{
Logger.logError("OS X bitness check failed", e);
}
return result.equals("1");
}
/**
* Used to check if operating system is 64-bit
* @return true if 64-bit operating system
*/
public static boolean is64BitOS ()
{
switch (getCurrentOS())
{
case WINDOWS:
return is64BitWindows();
case UNIX:
return is64BitPosix();
case MACOSX:
return is64BitOSX();
case OTHER:
return true;
default:
return true;
}
}
/**
* Used to get check if JVM is 64-bit
* @return true if 64-bit JVM
*/
public static Boolean is64BitVM ()
{
Boolean bits64;
if ((getCurrentOS() == OS.WINDOWS || getCurrentOS() == OS.MACOSX) && JavaFinder.parseJavaVersion() != null)
{
bits64 = JavaFinder.parseJavaVersion().is64bits;
}
else
{
bits64 = System.getProperty("sun.arch.data.model").equals("64");
}
return bits64;
}
/**
* Used to get the OS name for use in google analytics
* @return Linux/OSX/Windows/other/
*/
public static String getOSString ()
{
String osString = System.getProperty("os.name").toLowerCase();
if (osString.contains("win"))
{
return "Windows";
}
else if (osString.contains("linux"))
{
return "linux";
}
else if (osString.contains("mac"))
{
return "OSX";
}
else
{
return osString;
}
}
/**
* sees if the hash of the UUID matches the one stored in the config
* @return true if UUID matches hash or false if it does not
*/
public static boolean verifyUUID ()
{
return true;
}
/**
* Grabs the mac address of computer and makes it 10 times longer
* @return a byte array containing mac address
*/
public static byte[] getMacAddress ()
{
if (cachedMacAddress != null && cachedMacAddress.length >= 10)
{
return cachedMacAddress;
}
try
{
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements())
{
NetworkInterface network = networkInterfaces.nextElement();
byte[] mac = network.getHardwareAddress();
if (mac != null && mac.length > 0 && !network.isLoopback() && !network.isVirtual() && !network.isPointToPoint() && network.getName().substring(0, 3) != "ham" && network.getName().substring(0, 3) != "vir" && !network.getName().startsWith("docker"))
{
Logger.logDebug("Interface: " + network.getDisplayName() + " : " + network.getName());
cachedMacAddress = new byte[mac.length * 10];
for(int i = 0; i < cachedMacAddress.length; i++)
{
cachedMacAddress[i] = mac[i - (Math.round(i / mac.length) * mac.length)];
}
return cachedMacAddress;
}
}
}
catch (SocketException e)
{
Logger.logWarn("Exception getting MAC address", e);
}
Logger.logWarn("Failed to get MAC address, using default logindata key");
return new byte[]
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
}
/**
*
* @return Unique Id based on hardware
*/
public static byte[] getHardwareID ()
{
if (hardwareID == null)
{
hardwareID = genHardwareID();
}
return hardwareID;
}
private static byte[] genHardwareID ()
{
switch (getCurrentOS())
{
case WINDOWS:
return genHardwareIDWINDOWS();
case UNIX:
return genHardwareIDUNIX();
case MACOSX:
return genHardwareIDMACOSX();
default:
return null;
}
}
private static byte[] genHardwareIDUNIX ()
{
String line;
if (CommandLineSettings.getSettings().isUseMac())
{
BufferedReader reader = null;
try
{
reader = new BufferedReader(new FileReader("/etc/machine-id"));
line = reader.readLine();
}
catch (Exception e)
{
Logger.logDebug("failed", e);
return new byte[] {};
}
finally
{
if (reader != null) try
{
reader.close();
}
catch (IOException e)
{
Logger.logWarn("Error while generating Hardware ID UNIX", e);
}
}
return line.getBytes();
}
else
{
return new byte[] {};
}
}
private static byte[] genHardwareIDMACOSX ()
{
String line;
try
{
Process command = Runtime.getRuntime().exec(new String[]
{"system_profiler", "SPHardwareDataType"});
BufferedReader in = new BufferedReader(new InputStreamReader(command.getInputStream()));
while ((line = in.readLine()) != null)
{
if (line.contains("Serial Number"))
// TODO: does that more checks?
{
return line.split(":")[1].trim().getBytes();
}
}
return new byte[] {};
}
catch (Exception e)
{
Logger.logDebug("failed", e);
return new byte[] {};
}
}
private static byte[] genHardwareIDWINDOWS ()
{
String processOutput;
try
{
processOutput = RuntimeStreamer.execute(new String[]
{"wmic", "bios", "get", "serialnumber"});
/*
* wmic's output has special formatting:
* SerialNumber<SP><SP><SP><CR><CR><LF>
* 00000000000000000<SP><CR><CR><LF><CR><CR><LF>
*
* readLin()e uses <LF>, <CR> or <CR><LF> as line ending => we need to get third line from RuntimeStreamers output
*/
String line = processOutput.split("\n")[2].trim();
// at least VM will report serial to be 0. Does real hardware do it?
if (line.equals("0"))
{
return new byte[] {};
}
else
{
return line.trim().getBytes();
}
}
catch (Exception e)
{
Logger.logDebug("failed", e);
return new byte[] {};
}
}
/**
* Opens the given URL in the default browser
* @param url The URL
*/
public static void browse (String url)
{
try
{
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE))
{
Desktop.getDesktop().browse(new URI(url.replace(" ", "+")));
}
else if (getCurrentOS() == OS.UNIX)
{
// Work-around to support non-GNOME Linux desktop environments with xdg-open installed
new ProcessBuilder("xdg-open", url).start();
}
else
{
Logger.logWarn("Could not open Java Download url, not supported");
}
}
catch (Exception e)
{
Logger.logError("Could not open link: " + url, e);
}
}
/**
* Opens the given path with the default application
* @param path The path
*/
public static void open (File path)
{
if (!path.exists())
{
return;
}
try
{
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.OPEN))
{
Desktop.getDesktop().open(path);
}
else if (getCurrentOS() == OS.UNIX)
{
// Work-around to support non-GNOME Linux desktop environments with xdg-open installed
new ProcessBuilder("xdg-open", path.toString()).start();
}
}
catch (Exception e)
{
Logger.logError("Could not open file", e);
}
}
/**
* @return if java 7+ can be ran on that version of osx
*/
public static boolean canRun7OnMac ()
{
return getCurrentOS() == OS.MACOSX && !(System.getProperty("os.version").startsWith("10.6") || System.getProperty("os.version").startsWith("10.5"));
}
/**
* Removes environment variables which may cause faulty JVM memory allocations
*/
public static void cleanEnvVars (Map<String, String> environment)
{
environment.remove("_JAVA_OPTIONS");
environment.remove("JAVA_TOOL_OPTIONS");
environment.remove("JAVA_OPTIONS");
if (OSUtils.getCurrentOS() == OS.WINDOWS)
{
environment.put("__COMPAT_LAYER", "WIN8RTM");
}
}
public static StyleSheet makeStyleSheet (String name)
{
try
{
StyleSheet sheet = new StyleSheet();
Reader reader = new InputStreamReader(System.class.getResourceAsStream("/css/" + name + ".css"));
sheet.loadRules(reader, null);
reader.close();
return sheet;
}
catch (Exception ex)
{
ex.printStackTrace();
return null;
}
}
public static UUID getClientToken ()
{
if (clientUUID != null)
{
return clientUUID;
}
else
{
String s = null;
File tokenFile = new File(getCacheStorageLocation() + File.separator + "clientToken");
if (tokenFile.exists() && tokenFile.isFile())
{
try
{
s = FileUtils.readFileToString(tokenFile);
}
catch (IOException e)
{
s = null;
Logger.logError("Client token read failed:", e);
}
}
if (s != null)
{
try
{
clientUUID = UUID.fromString(s);
}
catch (IllegalArgumentException e)
{
Logger.logError("Client token read failed", e);
clientUUID = createUUID();
}
}
else
{
clientUUID = createUUID();
}
return clientUUID;
}
}
public static void setClientToken (UUID u)
{
File tokenFile = new File(getCacheStorageLocation() + File.separator + "clientToken");
try
{
FileUtils.writeStringToFile(tokenFile, u.toString());
}
catch (IOException e)
{
Logger.logError("Client token write failed", e);
}
}
private static UUID createUUID ()
{
UUID u = UUID.randomUUID();
setClientToken(u);
return u;
}
/**
*
* @return pid of the running process. -1 if fail
*/
public static long getPID ()
{
String name = ManagementFactory.getRuntimeMXBean().getName();
String pid = name.split("@")[0];
long numericpid = -1;
try
{
numericpid = Long.parseLong(pid);
}
catch (Exception e)
{
numericpid = -1;
Logger.logDebug("failed", e);
}
return numericpid;
}
public static long getPID (Process process)
{
// windows
if (getCurrentOS() == OS.WINDOWS && (process.getClass().getName().equals("java.lang.Win32Process") || process.getClass().getName().equals("java.lang.ProcessImpl")))
{
long pid = -1;
try
{
Field f = process.getClass().getDeclaredField("handle");
f.setAccessible(true);
pid = Kernel32.INSTANCE.GetProcessId((Long)f.get(process));
}
catch (Exception e)
{
pid = -1;
Logger.logDebug("failed", e);
}
return pid;
}
// java 9 removes java.lang.UNIXProcess and uses revised java.lang.ProcessImple which includes field pid
// http://openjdk.java.net/jeps/102
if (process.getClass().getName().equals("java.lang.UNIXProcess") || process.getClass().getName().equals("java.lang.ProcessImpl"))
{
/* get the PID on unix/linux systems */
long pid = -1;
try
{
Field f = process.getClass().getDeclaredField("pid");
f.setAccessible(true);
pid = f.getInt(process);
}
catch (Throwable e)
{
pid = -1;
Logger.logDebug("failed", e);
}
return pid;
}
Logger.logWarn("Unable to find getpid implementation");
return -1;
}
public static boolean genThreadDump (long pid)
{
if (OSUtils.getCurrentOS() == OS.WINDOWS)
{
File directory = null;
File sendsignal = null;
try
{
directory = new File(OSUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParentFile();
}
catch (Exception e)
{
Logger.logDebug("failed", e);
}
if (directory != null && directory.exists() && directory.isDirectory())
{
sendsignal = new File(directory, "sendsignal.exe");
if (!sendsignal.exists())
{
// try to download file automatically
try
{
Logger.logInfo("Downloading sendsignal.exe");
String address;
if (is64BitOS())
{
address = DownloadUtils.getCreeperhostLink("launcher/tools/sendsignal.exe ");
}
else
{
address = DownloadUtils.getCreeperhostLink("launcher/tools/sendsignal32.exe ");
}
DownloadUtils.downloadToFile(sendsignal.getCanonicalPath(), address);
}
catch (Exception e)
{
Logger.logDebug("failed", e);
}
}
}
// Now file is downloaded by the launcher or user. Try to run sendsignal.exe from %path% or %cd%
try
{
Runtime runtime = Runtime.getRuntime();
runtime.exec(new String[]
{"sendsignal.exe", Long.toString(pid)});
}
catch (Exception e)
{
Logger.logError("Failed. You need to install sendsignal.exe in your path to eanble this functionality");
Logger.logError("Failed", e);
return false;
}
return true;
}
else if (OSUtils.getCurrentOS() == OS.UNIX || OSUtils.getCurrentOS() == OS.MACOSX)
{
Runtime runtime = Runtime.getRuntime();
try
{
runtime.exec(new String[]
{"kill", "-3", Long.toString(pid)});
}
catch (Exception e)
{
Logger.logError("Failed", e);
return false;
}
return true;
}
else
{
Logger.logError("Unable to find genThreadDump implementation");
return false;
}
}
public static void printGPUinformation ()
{
if (getCurrentOS() == OS.WINDOWS)
{
String result = RuntimeStreamer.execute(new String[]
{"wmic", "path", "win32_VideoController", "get", "description,adapterRAM,driverDate,DriverVersion,InstalledDisplayDrivers"});
if (result != null)
{
Logger.logDebug("GPU information:\n" + result.replace("\n\n", "\n").trim().replaceAll("[ ]*\n", "\n"));
}
else
{
Logger.logError("Getting GPU information failed!! Launcher has detected windows path environment variable issues, " + "please see http://support.feed-the-beast.com/t/windows-path-issues/19630 for more information.");
}
}
else
{
// not implemented yet
}
}
static interface Kernel32 extends Library
{
public static Kernel32 INSTANCE = (Kernel32)Native.loadLibrary("kernel32", Kernel32.class);
public int GetProcessId (Long hProcess);
}
}