diff --git a/.gitignore b/.gitignore index 12af15c1..164f1920 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,13 @@ project.properties .settings/ .classpath dependency-reduced-pom.xml -*.exe \ No newline at end of file +*.exe +linux-jdk/ +linux-aarch64-jdk/ +native-linux-x86_64/ +native-linux-aarch64/ +linux-jdk/ +*.AppImage +*.AppImage-patched +OpenJDK11U-jre_*_linux_hotspot_*.tar.gz +packr_runelite-*.jar diff --git a/src/main/java/net/runelite/launcher/Launcher.java b/src/main/java/net/runelite/launcher/Launcher.java index 0edaa612..0ec695ac 100644 --- a/src/main/java/net/runelite/launcher/Launcher.java +++ b/src/main/java/net/runelite/launcher/Launcher.java @@ -94,14 +94,56 @@ @Slf4j public class Launcher { - private static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite"); - public static final File LOGS_DIR = new File(RUNELITE_DIR, "logs"); - private static final File REPO_DIR = new File(RUNELITE_DIR, "repository2"); - public static final File CRASH_FILES = new File(LOGS_DIR, "jvm_crash_pid_%p.log"); + private static final File LEGACY_RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite"); + + private static final File RUNELITE_DIR; + private static final File REPO_DIR; + public static final File LOGS_DIR; + public static final File CRASH_FILES; + private static final String USER_AGENT = "RuneLite/" + LauncherProperties.getVersion(); + private static final String APP_NAME = "runelite"; + + static + { + // if ~/.runelite is found, use it instead of xdg defaults + // to bypass this check, either rename/move/delete the directory, or set RUNELITE_USE_XDG=1 in your environment + if (LEGACY_RUNELITE_DIR.exists() && System.getProperty("RUNELITE_USE_XDG", null) == null) + { + RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite"); + REPO_DIR = new File(RUNELITE_DIR, "repository2"); + + LOGS_DIR = new File(RUNELITE_DIR, "logs"); + CRASH_FILES = new File(LOGS_DIR, "jvm_crash_pid_%p.log"); + } + else + { + switch (OS.getOS()) + { + case Linux: + case MacOS: + RUNELITE_DIR = new File(OS.getXDG("data", APP_NAME)); + REPO_DIR = new File(OS.getXDG("data", APP_NAME), "repository2"); + + LOGS_DIR = new File(OS.getXDG("state", APP_NAME)); + CRASH_FILES = new File(LOGS_DIR, "jvm_crash_pid_%p.log"); + break; + case Windows: + case Other: + default: + RUNELITE_DIR = new File(System.getProperty("user.home"), "." + APP_NAME); // ~/.runelite + REPO_DIR = new File(RUNELITE_DIR, "repository2"); + + LOGS_DIR = new File(RUNELITE_DIR, "logs"); + CRASH_FILES = new File(LOGS_DIR, "jvm_crash_pid_%p.log"); + break; + } + } + } public static void main(String[] args) { + OptionParser parser = new OptionParser(false); parser.allowsUnrecognizedOptions(); parser.accepts("postinstall", "Perform post-install tasks"); @@ -114,7 +156,7 @@ public static void main(String[] args) parser.accepts("scale", "Custom scale factor for Java 2D").withRequiredArg(); parser.accepts("help", "Show this text (use --clientargs --help for client help)").forHelp(); - if (OS.getOs() == OS.OSType.MacOS) + if (OS.equals("mac")) { // Parse macos PSN, eg: -psn_0_352342 parser.accepts("p").withRequiredArg(); @@ -124,7 +166,7 @@ public static void main(String[] args) final ArgumentAcceptingOptionSpec mode = parser.accepts("mode") .withRequiredArg() .ofType(HardwareAccelerationMode.class) - .defaultsTo(HardwareAccelerationMode.defaultMode(OS.getOs())); + .defaultsTo(HardwareAccelerationMode.defaultMode(OS.getOS())); final OptionSet options; final HardwareAccelerationMode hardwareAccelerationMode; @@ -190,12 +232,12 @@ public static void main(String[] args) } log.info("Setting hardware acceleration to {}", hardwareAccelerationMode); - jvmProps.addAll(hardwareAccelerationMode.toParams(OS.getOs())); + jvmProps.addAll(hardwareAccelerationMode.toParams(OS.getOS())); // As of JDK-8243269 (11.0.8) and JDK-8235363 (14), AWT makes macOS dark mode support opt-in so interfaces // with hardcoded foreground/background colours don't get broken by system settings. Considering the native // Aqua we draw consists a window border and an about box, it's safe to say we can opt in. - if (OS.getOs() == OS.OSType.MacOS) + if (OS.equals("mac")) { jvmProps.add("-Dapple.awt.application.appearance=system"); } @@ -208,7 +250,7 @@ public static void main(String[] args) jvmProps.add("-Drunelite.insecure-skip-tls-verification=true"); } - if (OS.getOs() == OS.OSType.Windows && !options.has("use-jre-truststore")) + if (OS.equals("windows") && !options.has("use-jre-truststore")) { // Use the Windows Trusted Root Certificate Authorities instead of the bundled cacerts. // Corporations, schools, antivirus, and malware commonly install root certificates onto @@ -323,18 +365,9 @@ public static void main(String[] args) return true; } - final String os = System.getProperty("os.name"); - final String arch = System.getProperty("os.arch"); for (Platform platform : a.getPlatform()) { - if (platform.getName() == null) - { - continue; - } - - OS.OSType platformOs = OS.parseOs(platform.getName()); - if ((platformOs == OS.OSType.Other ? platform.getName().equals(os) : platformOs == OS.getOs()) - && (platform.getArch() == null || platform.getArch().equals(arch))) + if (OS.isCompatible(platform.getName(), platform.getArch())) { return true; } @@ -842,7 +875,7 @@ private static void postInstall(List jvmParams) private static void initDllBlacklist() { - if (OS.getOs() != OS.OSType.Windows) + if (!OS.equals("windows")) { return; } diff --git a/src/main/java/net/runelite/launcher/OS.java b/src/main/java/net/runelite/launcher/OS.java index b7edab4d..00b1e1f6 100644 --- a/src/main/java/net/runelite/launcher/OS.java +++ b/src/main/java/net/runelite/launcher/OS.java @@ -24,32 +24,144 @@ */ package net.runelite.launcher; +import java.nio.file.Path; +import java.nio.file.Paths; + import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; +/* + */ + @Slf4j public class OS { + /* + * XDG prefixed variables are OS-defaults + * non-prefixed variables are the running environment variables + * the minuscule variables are the runelite-specific variables + * */ + private static final Path CONFIG_HOME; + private static final Path DATA_HOME; + private static final Path CACHE_HOME; + private static final Path STATE_HOME; + private static final Path RUNTIME_DIR; + private static final Path PICTURES_DIR; + public enum OSType { Windows, MacOS, Linux, Other } private static final OSType DETECTED_OS; + private static final String DETECTED_ARCH; + private static final String placeholder = "%{project_name}"; // used on Windows static { - final String os = System - .getProperty("os.name", "generic") - .toLowerCase(); - DETECTED_OS = parseOs(os); + String os = System.getProperty("os.name", "generic").toLowerCase(); + DETECTED_ARCH = System.getProperty("os.arch", "unknown"); + + Path XDG_CONFIG_HOME; + Path XDG_DATA_HOME; + Path XDG_CACHE_HOME; + Path XDG_STATE_HOME; + Path XDG_RUNTIME_DIR; + Path XDG_PICTURES_DIR; + + if (os.contains("mac") || os.contains("darwin")) + { + XDG_CONFIG_HOME = Paths.get(System.getProperty("user.home"), "Library", "Preferences"); + XDG_DATA_HOME = Paths.get(System.getProperty("user.home"), "Library", "Application Support"); + XDG_CACHE_HOME = Paths.get(System.getProperty("user.home"), "Library", "Caches"); + // STATE is a newcomer to the standard. it was split apart from the cache directory... but not on MacOS (yet, at least) + XDG_STATE_HOME = Paths.get(System.getProperty("user.home"), "Library", "Caches"); + XDG_RUNTIME_DIR = Paths.get(System.getProperty("user.home"), "Library", "Caches"); + + XDG_PICTURES_DIR = Paths.get(System.getProperty("user.home"), "Pictures"); + + DETECTED_OS = OSType.MacOS; + } + else if (os.contains("win")) + { + XDG_CONFIG_HOME = Paths.get(System.getProperty("user.home"), "AppData", "Roaming", placeholder, "Config"); + XDG_DATA_HOME = Paths.get(System.getProperty("user.home"), "AppData", "Local", placeholder, "Data" ); + XDG_CACHE_HOME = Paths.get(System.getProperty("user.home"), "AppData", "Local", placeholder, "Cache"); + XDG_STATE_HOME = Paths.get(System.getProperty("user.home"), "AppData", "Local", placeholder, "Cache"); + XDG_RUNTIME_DIR = Paths.get(System.getProperty("user.home"), "AppData", "Roaming", placeholder); + + XDG_PICTURES_DIR = Paths.get(System.getProperty("user.home"), "Pictures", placeholder); + + DETECTED_OS = OSType.Windows; + } + else if (os.contains("linux")) + { + XDG_CONFIG_HOME = Paths.get(System.getProperty("user.home"), ".config"); + XDG_DATA_HOME = Paths.get(System.getProperty("user.home"), ".local", "share"); + XDG_CACHE_HOME = Paths.get(System.getProperty("user.home"), ".cache"); + XDG_STATE_HOME = Paths.get(System.getProperty("user.home"), ".local", "state"); + // the default runtime directory on systemd is /run/user/1000, but this depends on the init. other + // init systems should make sure they've exported the runtime directory to the environment + XDG_RUNTIME_DIR = Paths.get("run", "user", System.getProperty("UID")); + + XDG_PICTURES_DIR = Paths.get(System.getProperty("user.home"), "Pictures"); + + DETECTED_OS = OSType.Linux; + } + else + { + XDG_CONFIG_HOME = Paths.get(System.getProperty("user.home"), ".runelite", "config"); + XDG_DATA_HOME = Paths.get(System.getProperty("user.home"), ".runelite", "data"); + XDG_CACHE_HOME = Paths.get(System.getProperty("user.home"), ".runelite", "cache"); + XDG_STATE_HOME = Paths.get(System.getProperty("user.home"), ".runelite", "state"); + XDG_RUNTIME_DIR = Paths.get(System.getProperty("user.home"), ".runelite", "runtime"); + + XDG_PICTURES_DIR = Paths.get(System.getProperty("user.home"), ".runelite", "Pictures"); + + DETECTED_OS = OSType.Other; + } + + // note: system variables don't have a placeholder for the appname. + switch (DETECTED_OS) + { + case Linux: + case MacOS: + PICTURES_DIR = Paths.get(System.getProperty("XDG_PICTURES_DIR", XDG_PICTURES_DIR.toString())); + CONFIG_HOME = Paths.get(System.getProperty("XDG_CONFIG_HOME", XDG_CONFIG_HOME.toString())); + DATA_HOME = Paths.get(System.getProperty("XDG_DATA_HOME", XDG_DATA_HOME.toString())); + CACHE_HOME = Paths.get(System.getProperty("XDG_CACHE_HOME", XDG_CACHE_HOME.toString())); + STATE_HOME = Paths.get(System.getProperty("XDG_STATE_HOME", XDG_STATE_HOME.toString())); + RUNTIME_DIR = Paths.get(System.getProperty("XDG_RUNTIME_DIR", XDG_RUNTIME_DIR.toString())); + break; + case Windows: + case Other: + default: + CONFIG_HOME = XDG_CONFIG_HOME; + DATA_HOME = XDG_DATA_HOME; + CACHE_HOME = XDG_CACHE_HOME; + STATE_HOME = XDG_STATE_HOME; + RUNTIME_DIR = XDG_RUNTIME_DIR; + PICTURES_DIR = XDG_PICTURES_DIR; + break; + } + log.debug("Detect OS: {}", DETECTED_OS); } - static OSType parseOs(@Nonnull String os) + + public static OSType getOs(@Nonnull String os) + { + return getOS(os); + } + + public static OSType getOs() { - os = os.toLowerCase(); - if ((os.contains("mac")) || (os.contains("darwin"))) + return getOS(); + } + + public static OSType getOS(@Nonnull String os) + { + if (os.contains("mac") || os.contains("darwin")) { return OSType.MacOS; } @@ -67,8 +179,89 @@ else if (os.contains("linux")) } } - public static OSType getOs() + public static OSType getOS() { return DETECTED_OS; } + public static String getArch() + { + return DETECTED_ARCH; + } + + public static boolean equals(String os) + { + return DETECTED_OS.equals(getOS(os.toLowerCase())); + } + + public static String getXDG(@Nonnull String home, String appName) throws IllegalArgumentException + { + if (appName == null) + { + appName = ""; + } + + String config_home; + String data_home; + String cache_home; + String state_home; + String pictures_dir; + String runtime_dir; + + if (OS.equals("windows")) + { + config_home = CONFIG_HOME.toString().replace(placeholder, appName); + data_home = DATA_HOME.toString().replace(placeholder, appName); + cache_home = CACHE_HOME.toString().replace(placeholder, appName); + state_home = STATE_HOME.toString().replace(placeholder, appName); + runtime_dir = RUNTIME_DIR.toString().replace(placeholder, appName); + } + else + { + config_home = Paths.get(CONFIG_HOME.toString(), appName).toString(); + data_home = Paths.get(DATA_HOME.toString(), appName).toString(); + cache_home = Paths.get(CACHE_HOME.toString(), appName).toString(); + state_home = Paths.get(STATE_HOME.toString(), appName).toString(); + runtime_dir = Paths.get(RUNTIME_DIR.toString(), appName).toString(); + } + pictures_dir = Paths.get(PICTURES_DIR.toString(), appName).toString(); + + switch (home.toLowerCase()) + { + case "config": + return config_home; + case "data": + return data_home; + case "cache": + return cache_home; + case "state": + return state_home; + case "runtime": + return state_home; + case "pictures": + case "screenshots": + return pictures_dir; + default: + throw new IllegalArgumentException("XDG paths must be config, data, cache or state"); + } + } + + public static boolean isCompatible(String platformName, String platformArch) + { + if (platformName == null) + { + return false; + } + + OSType platformOS = OS.getOS(platformName); + + // we should document why we are using the platform name when we don't find a match for the platform os + if (platformOS == OSType.Other ? platformName.equals(DETECTED_OS) : platformOS == DETECTED_OS) + { + if (platformArch == null || platformArch.equals(DETECTED_ARCH)) + { + return true; + } + } + return false; + } }