/* * Copyright (c) 2009-2010 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.system; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.logging.Level; import java.util.logging.Logger; /** * Helper class for extracting the natives (dll, so) from the jars. * This class should only be used internally. */ public final class Natives { private static final Logger logger = Logger.getLogger(Natives.class.getName()); private static final byte[] buf = new byte[1024]; private static File extractionDirOverride = null; private static File extractionDir = null; public static void setExtractionDir(String name) { extractionDirOverride = new File(name).getAbsoluteFile(); } public static File getExtractionDir() { if (extractionDirOverride != null) { return extractionDirOverride; } if (extractionDir == null) { File workingFolder = new File("").getAbsoluteFile(); if (!workingFolder.canWrite()) { setStorageExtractionDir(); } else { try { File file = new File(workingFolder.getAbsolutePath() + File.separator + ".jmetestwrite"); file.createNewFile(); file.delete(); extractionDir = workingFolder; } catch (Exception e) { setStorageExtractionDir(); } } } return extractionDir; } private static void setStorageExtractionDir() { logger.log(Level.WARNING, "Working directory is not writable. Using home directory instead."); extractionDir = new File(JmeSystem.getStorageFolder(), "natives_" + Integer.toHexString(computeNativesHash())); if (!extractionDir.exists()) { extractionDir.mkdir(); } } private static int computeNativesHash() { try { String classpath = System.getProperty("java.class.path"); URL url = Thread.currentThread().getContextClassLoader().getResource("com/jme3/system/Natives.class"); StringBuilder sb = new StringBuilder(url.toString()); if (sb.indexOf("jar:") == 0) { sb.delete(0, 4); sb.delete(sb.indexOf("!"), sb.length()); sb.delete(sb.lastIndexOf("/") + 1, sb.length()); } try { url = new URL(sb.toString()); } catch (MalformedURLException ex) { throw new UnsupportedOperationException(ex); } URLConnection conn = url.openConnection(); int hash = classpath.hashCode() ^ (int) conn.getLastModified(); return hash; } catch (IOException ex) { throw new UnsupportedOperationException(ex); } } public static void extractNativeLib(String sysName, String name) throws IOException { extractNativeLib(sysName, name, false, true); } public static void extractNativeLib(String sysName, String name, boolean load) throws IOException { extractNativeLib(sysName, name, load, true); } public static void extractNativeLib(String sysName, String name, boolean load, boolean warning) throws IOException { String fullname = System.mapLibraryName(name); String path = "native/" + sysName + "/" + fullname; URL url = Thread.currentThread().getContextClassLoader().getResource(path); if (url == null) { if (!warning) { logger.log(Level.WARNING, "Cannot locate native library: {0}/{1}", new String[]{sysName, fullname}); } return; } URLConnection conn = url.openConnection(); InputStream in = conn.getInputStream(); File targetFile = new File(getExtractionDir(), fullname); OutputStream out = null; try { if (targetFile.exists()) { // OK, compare last modified date of this file to // file in jar long targetLastModified = targetFile.lastModified(); long sourceLastModified = conn.getLastModified(); // Allow ~1 second range for OSes that only support low precision if (targetLastModified + 1000 > sourceLastModified) { logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.", fullname); return; } } out = new FileOutputStream(targetFile); int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); in = null; out.close(); out = null; // NOTE: On OSes that support "Date Created" property, // this will cause the last modified date to be lower than // date created which makes no sense targetFile.setLastModified(conn.getLastModified()); } catch (FileNotFoundException ex) { if (ex.getMessage().contains("used by another process")) { return; } throw ex; } finally { if (load) { System.load(targetFile.getAbsolutePath()); } if(in != null){ in.close(); } if(out != null){ out.close(); } } logger.log(Level.FINE, "Copied {0} to {1}", new Object[]{fullname, targetFile}); } protected static boolean isUsingNativeBullet() { try { Class clazz = Class.forName("com.jme3.bullet.util.NativeMeshUtil"); return clazz != null; } catch (ClassNotFoundException ex) { return false; } } public static void extractNativeLibs(Platform platform, AppSettings settings) throws IOException { String renderer = settings.getRenderer(); String audioRenderer = settings.getAudioRenderer(); boolean needLWJGL = false; boolean needOAL = false; boolean needJInput = false; boolean needNativeBullet = isUsingNativeBullet(); if (renderer != null) { if (renderer.startsWith("LWJGL")) { needLWJGL = true; } } if (audioRenderer != null) { if (audioRenderer.equals("LWJGL")) { needLWJGL = true; needOAL = true; } } needJInput = settings.useJoysticks(); String libraryPath = getExtractionDir().toString(); if (needLWJGL) { logger.log(Level.INFO, "Extraction Directory: {0}", getExtractionDir().toString()); // LWJGL supports this feature where // it can load libraries from this path. System.setProperty("org.lwjgl.librarypath", libraryPath); } if (needJInput) { // AND Luckily enough JInput supports the same feature. System.setProperty("net.java.games.input.librarypath", libraryPath); } switch (platform) { case Windows64: if (needLWJGL) { extractNativeLib("windows", "lwjgl64"); } if (needOAL) { extractNativeLib("windows", "OpenAL64"); } if (needJInput) { extractNativeLib("windows", "jinput-dx8_64"); extractNativeLib("windows", "jinput-raw_64"); } if (needNativeBullet) { extractNativeLib("windows", "bulletjme64", true, false); } break; case Windows32: if (needLWJGL) { extractNativeLib("windows", "lwjgl"); } if (needOAL) { extractNativeLib("windows", "OpenAL32"); } if (needJInput) { extractNativeLib("windows", "jinput-dx8"); extractNativeLib("windows", "jinput-raw"); } if (needNativeBullet) { extractNativeLib("windows", "bulletjme", true, false); } break; case Linux64: if (needLWJGL) { extractNativeLib("linux", "lwjgl64"); } if (needJInput) { extractNativeLib("linux", "jinput-linux64"); } if (needOAL) { extractNativeLib("linux", "openal64"); } if (needNativeBullet) { extractNativeLib("linux", "bulletjme64", true, false); } break; case Linux32: if (needLWJGL) { extractNativeLib("linux", "lwjgl"); } if (needJInput) { extractNativeLib("linux", "jinput-linux"); } if (needOAL) { extractNativeLib("linux", "openal"); } if (needNativeBullet) { extractNativeLib("linux", "bulletjme", true, false); } break; case MacOSX_PPC32: case MacOSX32: case MacOSX_PPC64: case MacOSX64: if (needLWJGL) { extractNativeLib("macosx", "lwjgl"); } // if (needOAL) // extractNativeLib("macosx", "openal"); if (needJInput) { extractNativeLib("macosx", "jinput-osx"); } if (needNativeBullet) { extractNativeLib("macosx", "bulletjme", true, false); } break; } } }