diff --git a/build.xml b/build.xml index 34e731e9..a37459c8 100644 --- a/build.xml +++ b/build.xml @@ -120,6 +120,9 @@ then run (code is compiled automatically when you run) + + + diff --git a/pom.xml b/pom.xml index 90b879ba..bdc9cc69 100644 --- a/pom.xml +++ b/pom.xml @@ -145,6 +145,9 @@ ${mainClassName} ${XDG} + OpenAL32.dll,OpenAL64.dll,lwjgl.dll,lwjgl64.dll + liblwjgl.so,liblwjgl64.so,libopenal.so,libopenal64.so + liblwjgl.dylib,openal.dylib diff --git a/src/itdelatrisu/opsu/NativeLoader.java b/src/itdelatrisu/opsu/NativeLoader.java index 454f02ea..840fc9b5 100644 --- a/src/itdelatrisu/opsu/NativeLoader.java +++ b/src/itdelatrisu/opsu/NativeLoader.java @@ -19,33 +19,18 @@ package itdelatrisu.opsu; import org.newdawn.slick.util.Log; +import yugecin.opsudance.utils.ManifestWrapper; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.lang.reflect.Field; -import java.util.Enumeration; -import java.util.jar.JarEntry; +import java.util.jar.JarFile; import static yugecin.opsudance.core.InstanceContainer.*; -/** - * Native loader, based on the JarSplice launcher. - * - * @author http://ninjacave.com - */ public class NativeLoader { - public static void loadNatives() { - try { - unpackNatives(); - } catch (IOException e) { - String msg = String.format("Could not unpack native(s): %s", e.getMessage()); - throw new RuntimeException(msg, e); - } - + public static void setNativePath() { String nativepath = config.NATIVE_DIR.getAbsolutePath(); System.setProperty("org.lwjgl.librarypath", nativepath); System.setProperty("java.library.path", nativepath); @@ -65,62 +50,43 @@ public class NativeLoader { * Unpacks natives for the current operating system to the natives directory. * @throws IOException if an I/O exception occurs */ - public static void unpackNatives() throws IOException { - if (env.jarfile == null) { - return; - } - + public static void loadNatives(JarFile jarfile, ManifestWrapper manifest) throws IOException { if (!config.NATIVE_DIR.exists() && !config.NATIVE_DIR.mkdir()) { String msg = String.format("Could not create folder '%s'", config.NATIVE_DIR.getAbsolutePath()); throw new RuntimeException(msg); } - - Enumeration entries = env.jarfile.entries(); - while (entries.hasMoreElements()) { - JarEntry e = entries.nextElement(); - if (e == null) - break; - - File f = new File(config.NATIVE_DIR, e.getName()); - if (isNativeFile(e.getName()) && !e.isDirectory() && e.getName().indexOf('/') == -1 && !f.exists()) { - InputStream in = env.jarfile.getInputStream(env.jarfile.getEntry(e.getName())); - OutputStream out = new FileOutputStream(f); - - byte[] buffer = new byte[65536]; - int bufferSize; - while ((bufferSize = in.read(buffer, 0, buffer.length)) != -1) - out.write(buffer, 0, bufferSize); - - in.close(); - out.close(); - } - } - - env.jarfile.close(); - } - - /** - * Returns whether the given file name is a native file for the current operating system. - * @param entryName the file name - * @return true if the file is a native that should be loaded, false otherwise - */ - private static boolean isNativeFile(String entryName) { String osName = System.getProperty("os.name"); - String name = entryName.toLowerCase(); - + String nativekey = null; if (osName.startsWith("Win")) { - if (name.endsWith(".dll")) - return true; + nativekey = "WinNatives"; } else if (osName.startsWith("Linux")) { - if (name.endsWith(".so")) - return true; + nativekey = "NixNatives"; } else if (osName.startsWith("Mac") || osName.startsWith("Darwin")) { - if (name.endsWith(".dylib") || name.endsWith(".jnilib")) - return true; + nativekey = "MacNatives"; + } + + if (nativekey == null) { + Log.warn("Cannot determine natives for os " + osName); + return; + } + + String natives = manifest.valueOrDefault(null, nativekey, null); + if (natives == null) { + String msg = String.format("No entry for '%s' in manifest, jar is badly packed or damaged", + nativekey); + throw new RuntimeException(msg); + } + + String[] nativefiles = natives.split(","); + for (String nativefile : nativefiles) { + File unpackedFile = new File(config.NATIVE_DIR, nativefile); + if (unpackedFile.exists()) { + continue; + } + Utils.unpackFromJar(jarfile, unpackedFile, nativefile); } - return false; } } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index f3a6b0ec..bc2c51d6 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -21,14 +21,7 @@ package itdelatrisu.opsu; import com.sun.istack.internal.Nullable; import itdelatrisu.opsu.downloads.Download; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URL; @@ -37,6 +30,7 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Scanner; +import java.util.jar.JarFile; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; @@ -53,6 +47,7 @@ import org.newdawn.slick.util.Log; import com.sun.jna.platform.FileUtils; import yugecin.opsudance.core.DisplayContainer; +import yugecin.opsudance.core.NotNull; import yugecin.opsudance.core.errorhandling.ErrorHandler; import static yugecin.opsudance.core.InstanceContainer.*; @@ -537,4 +532,19 @@ public class Utils { key != Keyboard.KEY_F7 && key != Keyboard.KEY_F10 && key != Keyboard.KEY_F12); } + public static void unpackFromJar(@NotNull JarFile jarfile, @NotNull File unpackedFile, + @NotNull String filename) throws IOException { + InputStream in = jarfile.getInputStream(jarfile.getEntry(filename)); + OutputStream out = new FileOutputStream(unpackedFile); + + byte[] buffer = new byte[65536]; + int bufferSize; + while ((bufferSize = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, bufferSize); + } + + in.close(); + out.close(); + } + } diff --git a/src/yugecin/opsudance/core/Environment.java b/src/yugecin/opsudance/core/Environment.java index 749eeb63..3a615116 100644 --- a/src/yugecin/opsudance/core/Environment.java +++ b/src/yugecin/opsudance/core/Environment.java @@ -18,9 +18,7 @@ package yugecin.opsudance.core; import java.io.File; -import java.io.IOException; import java.nio.file.Paths; -import java.util.jar.JarFile; import static yugecin.opsudance.core.Constants.PROJECT_NAME; @@ -28,7 +26,7 @@ public class Environment { public final boolean isJarRunning; public final File workingdir; - public final JarFile jarfile; + public final File jarfile; public Environment() { Class thiz = Environment.class; @@ -38,7 +36,7 @@ public class Environment { this.workingdir = Paths.get(".").toAbsolutePath().normalize().toFile(); this.jarfile = null; } else { - String wdir = thisClassLocation.substring(6); // remove the jar:// + String wdir = thisClassLocation.substring(9); // remove jar:file: String separator = "!/"; int separatorIdx = wdir.indexOf(separator); int lastSeparatorIdx = wdir.lastIndexOf(separator); @@ -47,15 +45,8 @@ public class Environment { PROJECT_NAME, thisClassLocation.substring(0, lastSeparatorIdx)); throw new RuntimeException(msg); } - File jar = new File(wdir.substring(0, separatorIdx)); - this.workingdir = jar.getParentFile(); - try { - this.jarfile = new JarFile(jar); - } catch (IOException e) { - String msg = String.format("Cannot read from jarfile (%s): %s", jar.getAbsolutePath(), - e.getMessage()); - throw new RuntimeException(msg, e); - } + this.jarfile = new File(wdir.substring(0, separatorIdx)); + this.workingdir = jarfile.getParentFile(); } } diff --git a/src/yugecin/opsudance/core/InstanceContainer.java b/src/yugecin/opsudance/core/InstanceContainer.java index da3cdec5..a51fa91c 100644 --- a/src/yugecin/opsudance/core/InstanceContainer.java +++ b/src/yugecin/opsudance/core/InstanceContainer.java @@ -29,8 +29,14 @@ import yugecin.opsudance.options.Configuration; import yugecin.opsudance.options.OptionsService; import yugecin.opsudance.render.GameObjectRenderer; import yugecin.opsudance.skinning.SkinService; +import yugecin.opsudance.utils.ManifestWrapper; import java.io.File; +import java.io.IOException; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import static yugecin.opsudance.utils.SyntacticSugar.closeAndSwallow; public class InstanceContainer { @@ -60,9 +66,22 @@ public class InstanceContainer { public static void kickstart() { updater = new Updater(); env = new Environment(); - config = new Configuration(); - NativeLoader.loadNatives(); + JarFile jarfile = getJarfile(); + ManifestWrapper manifest = new ManifestWrapper(getJarManifest(jarfile)); + config = new Configuration(manifest); + if (jarfile != null) { + try { + NativeLoader.loadNatives(jarfile, manifest); + } catch (IOException e) { + String msg = String.format("Could not unpack native(s): %s", e.getMessage()); + throw new RuntimeException(msg, e); + } finally { + closeAndSwallow(jarfile); + } + } + NativeLoader.setNativePath(); + ResourceLoader.addResourceLocation(new FileSystemLocation(new File("./res/"))); optionservice = new OptionsService(); @@ -86,4 +105,31 @@ public class InstanceContainer { pauseState = new GamePauseMenu(); } + @Nullable + private static JarFile getJarfile() { + if (env.jarfile == null) { + return null; + } + try { + return new JarFile(env.jarfile); + } catch (IOException e) { + String msg = String.format("Cannot read from jarfile (%s): %s", env.jarfile.getAbsolutePath(), + e.getMessage()); + throw new RuntimeException(msg, e); + } + } + + @Nullable + private static Manifest getJarManifest(@Nullable JarFile jarfile) { + if (jarfile == null) { + return null; + } + try { + return jarfile.getManifest(); + } catch (IOException e) { + String msg = String.format("Cannot read manifest from jarfile: %s", e.getMessage()); + throw new RuntimeException(msg, e); + } + } + } diff --git a/src/yugecin/opsudance/options/Configuration.java b/src/yugecin/opsudance/options/Configuration.java index 5f0a56fa..e621ba83 100644 --- a/src/yugecin/opsudance/options/Configuration.java +++ b/src/yugecin/opsudance/options/Configuration.java @@ -17,6 +17,7 @@ */ package yugecin.opsudance.options; +import com.sun.istack.internal.Nullable; import com.sun.jna.platform.win32.Advapi32Util; import com.sun.jna.platform.win32.Win32Exception; import com.sun.jna.platform.win32.WinReg; @@ -30,17 +31,15 @@ import org.lwjgl.opengl.GL11; import yugecin.opsudance.core.errorhandling.ErrorHandler; import yugecin.opsudance.core.events.EventBus; import yugecin.opsudance.events.BubbleNotificationEvent; +import yugecin.opsudance.utils.ManifestWrapper; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; -import java.util.jar.Attributes; -import java.util.jar.Manifest; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -74,8 +73,8 @@ public class Configuration { public File replayImportDir; public File skinRootDir; - public Configuration() { - USE_XDG = areXDGDirectoriesEnabled(); + public Configuration(ManifestWrapper jarmanifest) { + USE_XDG = jarmanifest.valueOrDefault(null, "Use-XDG", "").equalsIgnoreCase("true"); CONFIG_DIR = getXDGBaseDir("XDG_CONFIG_HOME", ".config"); DATA_DIR = getXDGBaseDir("XDG_DATA_HOME", ".local/share"); @@ -176,23 +175,6 @@ public class Configuration { return loadDirectory(dir, defaultDir, kind); } - private boolean areXDGDirectoriesEnabled() { - if (env.jarfile == null) { - return false; - } - try { - Manifest manifest = env.jarfile.getManifest(); - if (manifest == null) { - return false; - } - Attributes attributes = manifest.getMainAttributes(); - String value = attributes.getValue("Use-XDG"); - return (value != null && value.equalsIgnoreCase("true")); - } catch (IOException e) { - return false; - } - } - /** * Returns the directory based on the XDG base directory specification for * Unix-like operating systems, only if the "XDG" flag is enabled. diff --git a/src/yugecin/opsudance/utils/ManifestWrapper.java b/src/yugecin/opsudance/utils/ManifestWrapper.java new file mode 100644 index 00000000..b02804c8 --- /dev/null +++ b/src/yugecin/opsudance/utils/ManifestWrapper.java @@ -0,0 +1,54 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.utils; + +import yugecin.opsudance.core.NotNull; +import yugecin.opsudance.core.Nullable; + +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +public class ManifestWrapper { + + @Nullable + public final Manifest manifest; + + public ManifestWrapper(@Nullable Manifest manifest) { + this.manifest = manifest; + } + + /** + * @param attribute attribute in jarfile or null for default attributes + */ + public String valueOrDefault(@Nullable String attribute, @NotNull String key, @Nullable String dfault) { + if (manifest == null) { + return dfault; + } + Attributes attributes = + attribute == null ? manifest.getMainAttributes() : manifest.getAttributes(attribute); + if (attributes == null) { + return dfault; + } + String val = attributes.getValue(key); + if (val == null) { + return dfault; + } + return val; + } + +} diff --git a/src/yugecin/opsudance/utils/SyntacticSugar.java b/src/yugecin/opsudance/utils/SyntacticSugar.java new file mode 100644 index 00000000..6368086a --- /dev/null +++ b/src/yugecin/opsudance/utils/SyntacticSugar.java @@ -0,0 +1,34 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2017 yugecin + * + * opsu!dance is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu!dance is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!dance. If not, see . + */ +package yugecin.opsudance.utils; + +import java.io.Closeable; +import java.io.IOException; + +public class SyntacticSugar { + + /** + * close the closeable and swallow exceptions, if any + */ + public static void closeAndSwallow(Closeable closable) { + try { + closable.close(); + } catch (IOException ignored) {} + } + +}