1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.jme3.system; 33 34 import java.io.*; 35 import java.net.MalformedURLException; 36 import java.net.URL; 37 import java.net.URLConnection; 38 import java.util.logging.Level; 39 import java.util.logging.Logger; 40 41 /** 42 * Helper class for extracting the natives (dll, so) from the jars. 43 * This class should only be used internally. 44 */ 45 public final class Natives { 46 47 private static final Logger logger = Logger.getLogger(Natives.class.getName()); 48 private static final byte[] buf = new byte[1024]; 49 private static File extractionDirOverride = null; 50 private static File extractionDir = null; 51 52 public static void setExtractionDir(String name) { 53 extractionDirOverride = new File(name).getAbsoluteFile(); 54 } 55 56 public static File getExtractionDir() { 57 if (extractionDirOverride != null) { 58 return extractionDirOverride; 59 } 60 if (extractionDir == null) { 61 File workingFolder = new File("").getAbsoluteFile(); 62 if (!workingFolder.canWrite()) { 63 setStorageExtractionDir(); 64 } else { 65 try { 66 File file = new File(workingFolder.getAbsolutePath() + File.separator + ".jmetestwrite"); 67 file.createNewFile(); 68 file.delete(); 69 extractionDir = workingFolder; 70 } catch (Exception e) { 71 setStorageExtractionDir(); 72 } 73 } 74 } 75 return extractionDir; 76 } 77 78 private static void setStorageExtractionDir() { 79 logger.log(Level.WARNING, "Working directory is not writable. Using home directory instead."); 80 extractionDir = new File(JmeSystem.getStorageFolder(), 81 "natives_" + Integer.toHexString(computeNativesHash())); 82 if (!extractionDir.exists()) { 83 extractionDir.mkdir(); 84 } 85 } 86 87 private static int computeNativesHash() { 88 try { 89 String classpath = System.getProperty("java.class.path"); 90 URL url = Thread.currentThread().getContextClassLoader().getResource("com/jme3/system/Natives.class"); 91 92 StringBuilder sb = new StringBuilder(url.toString()); 93 if (sb.indexOf("jar:") == 0) { 94 sb.delete(0, 4); 95 sb.delete(sb.indexOf("!"), sb.length()); 96 sb.delete(sb.lastIndexOf("/") + 1, sb.length()); 97 } 98 try { 99 url = new URL(sb.toString()); 100 } catch (MalformedURLException ex) { 101 throw new UnsupportedOperationException(ex); 102 } 103 104 URLConnection conn = url.openConnection(); 105 int hash = classpath.hashCode() ^ (int) conn.getLastModified(); 106 return hash; 107 } catch (IOException ex) { 108 throw new UnsupportedOperationException(ex); 109 } 110 } 111 112 public static void extractNativeLib(String sysName, String name) throws IOException { 113 extractNativeLib(sysName, name, false, true); 114 } 115 116 public static void extractNativeLib(String sysName, String name, boolean load) throws IOException { 117 extractNativeLib(sysName, name, load, true); 118 } 119 120 public static void extractNativeLib(String sysName, String name, boolean load, boolean warning) throws IOException { 121 String fullname = System.mapLibraryName(name); 122 123 String path = "native/" + sysName + "/" + fullname; 124 URL url = Thread.currentThread().getContextClassLoader().getResource(path); 125 126 if (url == null) { 127 if (!warning) { 128 logger.log(Level.WARNING, "Cannot locate native library: {0}/{1}", 129 new String[]{sysName, fullname}); 130 } 131 return; 132 } 133 134 URLConnection conn = url.openConnection(); 135 InputStream in = conn.getInputStream(); 136 File targetFile = new File(getExtractionDir(), fullname); 137 OutputStream out = null; 138 try { 139 if (targetFile.exists()) { 140 // OK, compare last modified date of this file to 141 // file in jar 142 long targetLastModified = targetFile.lastModified(); 143 long sourceLastModified = conn.getLastModified(); 144 145 // Allow ~1 second range for OSes that only support low precision 146 if (targetLastModified + 1000 > sourceLastModified) { 147 logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.", fullname); 148 return; 149 } 150 } 151 152 out = new FileOutputStream(targetFile); 153 int len; 154 while ((len = in.read(buf)) > 0) { 155 out.write(buf, 0, len); 156 } 157 in.close(); 158 in = null; 159 out.close(); 160 out = null; 161 162 // NOTE: On OSes that support "Date Created" property, 163 // this will cause the last modified date to be lower than 164 // date created which makes no sense 165 targetFile.setLastModified(conn.getLastModified()); 166 } catch (FileNotFoundException ex) { 167 if (ex.getMessage().contains("used by another process")) { 168 return; 169 } 170 171 throw ex; 172 } finally { 173 if (load) { 174 System.load(targetFile.getAbsolutePath()); 175 } 176 if(in != null){ 177 in.close(); 178 } 179 if(out != null){ 180 out.close(); 181 } 182 } 183 logger.log(Level.FINE, "Copied {0} to {1}", new Object[]{fullname, targetFile}); 184 } 185 186 protected static boolean isUsingNativeBullet() { 187 try { 188 Class clazz = Class.forName("com.jme3.bullet.util.NativeMeshUtil"); 189 return clazz != null; 190 } catch (ClassNotFoundException ex) { 191 return false; 192 } 193 } 194 195 public static void extractNativeLibs(Platform platform, AppSettings settings) throws IOException { 196 String renderer = settings.getRenderer(); 197 String audioRenderer = settings.getAudioRenderer(); 198 boolean needLWJGL = false; 199 boolean needOAL = false; 200 boolean needJInput = false; 201 boolean needNativeBullet = isUsingNativeBullet(); 202 203 if (renderer != null) { 204 if (renderer.startsWith("LWJGL")) { 205 needLWJGL = true; 206 } 207 } 208 if (audioRenderer != null) { 209 if (audioRenderer.equals("LWJGL")) { 210 needLWJGL = true; 211 needOAL = true; 212 } 213 } 214 needJInput = settings.useJoysticks(); 215 216 String libraryPath = getExtractionDir().toString(); 217 if (needLWJGL) { 218 logger.log(Level.INFO, "Extraction Directory: {0}", getExtractionDir().toString()); 219 220 // LWJGL supports this feature where 221 // it can load libraries from this path. 222 System.setProperty("org.lwjgl.librarypath", libraryPath); 223 } 224 if (needJInput) { 225 // AND Luckily enough JInput supports the same feature. 226 System.setProperty("net.java.games.input.librarypath", libraryPath); 227 } 228 229 switch (platform) { 230 case Windows64: 231 if (needLWJGL) { 232 extractNativeLib("windows", "lwjgl64"); 233 } 234 if (needOAL) { 235 extractNativeLib("windows", "OpenAL64"); 236 } 237 if (needJInput) { 238 extractNativeLib("windows", "jinput-dx8_64"); 239 extractNativeLib("windows", "jinput-raw_64"); 240 } 241 if (needNativeBullet) { 242 extractNativeLib("windows", "bulletjme64", true, false); 243 } 244 break; 245 case Windows32: 246 if (needLWJGL) { 247 extractNativeLib("windows", "lwjgl"); 248 } 249 if (needOAL) { 250 extractNativeLib("windows", "OpenAL32"); 251 } 252 if (needJInput) { 253 extractNativeLib("windows", "jinput-dx8"); 254 extractNativeLib("windows", "jinput-raw"); 255 } 256 if (needNativeBullet) { 257 extractNativeLib("windows", "bulletjme", true, false); 258 } 259 break; 260 case Linux64: 261 if (needLWJGL) { 262 extractNativeLib("linux", "lwjgl64"); 263 } 264 if (needJInput) { 265 extractNativeLib("linux", "jinput-linux64"); 266 } 267 if (needOAL) { 268 extractNativeLib("linux", "openal64"); 269 } 270 if (needNativeBullet) { 271 extractNativeLib("linux", "bulletjme64", true, false); 272 } 273 break; 274 case Linux32: 275 if (needLWJGL) { 276 extractNativeLib("linux", "lwjgl"); 277 } 278 if (needJInput) { 279 extractNativeLib("linux", "jinput-linux"); 280 } 281 if (needOAL) { 282 extractNativeLib("linux", "openal"); 283 } 284 if (needNativeBullet) { 285 extractNativeLib("linux", "bulletjme", true, false); 286 } 287 break; 288 case MacOSX_PPC32: 289 case MacOSX32: 290 case MacOSX_PPC64: 291 case MacOSX64: 292 if (needLWJGL) { 293 extractNativeLib("macosx", "lwjgl"); 294 } 295 // if (needOAL) 296 // extractNativeLib("macosx", "openal"); 297 if (needJInput) { 298 extractNativeLib("macosx", "jinput-osx"); 299 } 300 if (needNativeBullet) { 301 extractNativeLib("macosx", "bulletjme", true, false); 302 } 303 break; 304 } 305 } 306 } 307