diff --git a/build.gradle b/build.gradle index 6e8e919e5..56b5f0366 100644 --- a/build.gradle +++ b/build.gradle @@ -250,4 +250,4 @@ retrolambda { javaVersion JavaVersion.VERSION_1_7 incremental true jvmArgs '-noverify' -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java b/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java index 81c12e4a2..938c7813d 100644 --- a/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java +++ b/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java @@ -65,7 +65,7 @@ public class EnvMapUtils { public final static int NUM_SH_COEFFICIENT = 9; // See Peter-Pike Sloan paper for these coefficients //http://www.ppsloan.org/publications/StupidSH36.pdf - public static float[] shBandFactor = {1.0f, + private static float[] shBandFactor = {1.0f, 2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f}; @@ -96,6 +96,10 @@ public static enum GenerationType { private EnvMapUtils() { } + public static float[] getShBandFactor() { + return shBandFactor.clone(); + } + /** * Creates a cube map from 6 images * diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java index 337623423..21bc4da3a 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java @@ -236,7 +236,7 @@ protected void generateDeclarationAndMainBody(List shaderNodes, Stri protected void appendNodeDeclarationAndMain(String loadedSource, StringBuilder sourceDeclaration, StringBuilder source, ShaderNode shaderNode, ShaderGenerationInfo info, String shaderPath) { if (loadedSource.length() > 1) { loadedSource = loadedSource.substring(0, loadedSource.lastIndexOf("}")); - String[] sourceParts = loadedSource.split("\\s*void\\s*main\\s*\\(\\s*\\)\\s*\\{"); + String[] sourceParts = loadedSource.split("\\s*void\\s+main\\s*\\(\\s*\\)\\s*\\{"); if(sourceParts.length<2){ throw new IllegalArgumentException("Syntax error in "+ shaderPath +". Cannot find 'void main(){' in \n"+ loadedSource); } diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java b/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java index d1db58262..3da19a3d4 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java @@ -123,7 +123,7 @@ public static int getCardinality(String type, String swizzling) { card = 0; } } else { - card = Integer.parseInt(type.replaceAll(".*vec", "")); + card = type.startsWith("vec") ? Integer.parseInt(type.substring(3, 4)) : 0; if (swizzling.length() > 0) { card = swizzling.length(); diff --git a/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java b/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java index e6eefa54b..d2cf46d6b 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java +++ b/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java @@ -55,7 +55,6 @@ /** * Released under BSD License - * * @author monceaux, normenhansen, entrusC */ public class MjpegFileWriter implements AutoCloseable { @@ -70,18 +69,12 @@ public class MjpegFileWriter implements AutoCloseable { long aviMovieOffset = 0; long position = 0; AVIIndexList indexlist = null; - private byte[] fcc; - private int size; - private byte[] fcc2; - private byte[] data; public MjpegFileWriter(File aviFile, int width, int height, double framerate) throws IOException { this(aviFile, width, height, framerate, 0); } - public MjpegFileWriter(File aviFile, int width, int height, double framerate, int numFrames) - throws IOException { - + public MjpegFileWriter(File aviFile, int width, int height, double framerate, int numFrames) throws IOException { this.aviFile = aviFile; this.width = width; this.height = height; @@ -89,8 +82,15 @@ public MjpegFileWriter(File aviFile, int width, int height, double framerate, in this.numFrames = numFrames; FileOutputStream fos = new FileOutputStream(aviFile); aviOutput = new BufferedOutputStream(fos); + + RIFFHeader rh = new RIFFHeader(); ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); - writeHeadersToStream(baos, 0, false); + baos.write(rh.toBytes()); + baos.write(new AVIMainHeader().toBytes()); + baos.write(new AVIStreamList().toBytes()); + baos.write(new AVIStreamHeader().toBytes()); + baos.write(new AVIStreamFormat().toBytes()); + baos.write(new AVIJunk().toBytes()); byte[] headerBytes = baos.toByteArray(); aviOutput.write(headerBytes); aviMovieOffset = headerBytes.length; @@ -101,16 +101,16 @@ public MjpegFileWriter(File aviFile, int width, int height, double framerate, in position = (long) headerBytes.length + listBytes.length; } - public void addImage(Image image) throws Exception { + public void addImage(Image image) throws IOException { addImage(image, 0.8f); } - public void addImage(Image image, float quality) throws Exception { + public void addImage(Image image, float quality) throws IOException { addImage(writeImageToBytes(image, quality)); } - public void addImage(byte[] imageData) throws Exception { - byte[] fcc = new byte[] { '0', '0', 'd', 'b' }; + public void addImage(byte[] imageData) throws IOException { + byte[] fcc = new byte[]{'0', '0', 'd', 'b'}; int useLength = imageData.length; int extra = (useLength + (int) position) % 4; if (extra > 0) { @@ -130,7 +130,7 @@ public void addImage(byte[] imageData) throws Exception { } byte[] data = baos.toByteArray(); aviOutput.write(data); - numFrames++; // add a frame + numFrames++; //add a frame position += data.length; } @@ -141,34 +141,22 @@ public void finishAVI() throws IOException { int fileSize = (int) aviFile.length(); int listSize = (int) (fileSize - 8 - aviMovieOffset - indexlistBytes.length); - // add header and length by writing the headers again - // with the now available information + //add header and length by writing the headers again + //with the now available information try (SeekableByteChannel sbc = Files.newByteChannel(aviFile.toPath(), StandardOpenOption.WRITE); - ByteArrayOutputStream baos = new ByteArrayOutputStream()) { - writeHeadersToStream(baos, fileSize, true); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + baos.write(new RIFFHeader(fileSize).toBytes()); + baos.write(new AVIMainHeader().toBytes()); + baos.write(new AVIStreamList().toBytes()); + baos.write(new AVIStreamHeader().toBytes()); + baos.write(new AVIStreamFormat().toBytes()); + baos.write(new AVIJunk().toBytes()); baos.write(new AVIMovieList(listSize).toBytes()); + sbc.write(ByteBuffer.wrap(baos.toByteArray())); } } - private void writeHeadersToStream(ByteArrayOutputStream baos, int fileSize, boolean useFileSize) - throws IOException { - baos.write((useFileSize ? new RIFFHeader(fileSize) : new RIFFHeader()).toBytes()); - baos.write(new AVIMainHeader().toBytes()); - baos.write(new AVIStreamList().toBytes()); - baos.write(new AVIStreamHeader().toBytes()); - baos.write(new AVIStreamFormat().toBytes()); - baos.write(new AVIJunk().toBytes()); - } - - private byte[] toBytesInternal(byte[] content) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write(fcc); - baos.write(intBytes(swapInt(size))); - baos.write(content); - return baos.toByteArray(); - } - public static int swapInt(int v) { return (v >>> 24) | (v << 24) | ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00); } @@ -202,12 +190,12 @@ public void close() throws Exception { private class RIFFHeader { - public byte[] fcc = new byte[] { 'R', 'I', 'F', 'F' }; + public byte[] fcc = new byte[]{'R', 'I', 'F', 'F'}; public int fileSize = 0; - public byte[] fcc2 = new byte[] { 'A', 'V', 'I', ' ' }; - public byte[] fcc3 = new byte[] { 'L', 'I', 'S', 'T' }; + public byte[] fcc2 = new byte[]{'A', 'V', 'I', ' '}; + public byte[] fcc3 = new byte[]{'L', 'I', 'S', 'T'}; public int listSize = 200; - public byte[] fcc4 = new byte[] { 'h', 'd', 'r', 'l' }; + private byte[] fcc4 = new byte[]{'h', 'd', 'r', 'l'}; public RIFFHeader() { } @@ -230,16 +218,9 @@ public byte[] toBytes() throws IOException { } private class AVIMainHeader { - /* - * - * FOURCC fcc; DWORD cb; DWORD dwMicroSecPerFrame; DWORD dwMaxBytesPerSec; DWORD dwPaddingGranularity; - * DWORD dwFlags; DWORD dwTotalFrames; DWORD dwInitialFrames; DWORD dwStreams; DWORD - * dwSuggestedBufferSize; DWORD dwWidth; DWORD dwHeight; DWORD dwReserved[4]; - */ - - public byte[] fcc = new byte[] { 'a', 'v', 'i', 'h' }; + public byte[] fcc = new byte[]{'a', 'v', 'i', 'h'}; public int cb = 56; - public int dwMicroSecPerFrame = 0; // (1 + public int dwMicroSecPerFrame = 0; // (1 // / // frames // per @@ -249,18 +230,18 @@ private class AVIMainHeader { public int dwMaxBytesPerSec = 10000000; public int dwPaddingGranularity = 0; public int dwFlags = 65552; - public int dwTotalFrames = 0; // replace + public int dwTotalFrames = 0; // replace // with // correct // value public int dwInitialFrames = 0; public int dwStreams = 1; public int dwSuggestedBufferSize = 0; - public int dwWidth = 0; // replace + public int dwWidth = 0; // replace // with // correct // value - public int dwHeight = 0; // replace + public int dwHeight = 0; // replace // with // correct // value @@ -274,7 +255,9 @@ public AVIMainHeader() { } public byte[] toBytes() throws IOException { - ByteArrayOutputStream baos = createByteArrayOutputStreamWriteFCCWriteSwapInt(fcc, cb); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(cb))); baos.write(intBytes(swapInt(dwMicroSecPerFrame))); baos.write(intBytes(swapInt(dwMaxBytesPerSec))); baos.write(intBytes(swapInt(dwPaddingGranularity))); @@ -305,46 +288,40 @@ public static byte[] baosWrite(byte[] byteSet1, byte[] byteSet2, int size) throw private class AVIStreamList { - public byte[] fcc = new byte[] { 'L', 'I', 'S', 'T' }; + public byte[] fcc = new byte[]{'L', 'I', 'S', 'T'}; public int size = 124; - public byte[] fcc2 = new byte[] { 's', 't', 'r', 'l' }; + public byte[] fcc2 = new byte[]{'s', 't', 'r', 'l'}; public AVIStreamList() { throw new UnsupportedOperationException("AVIStreamList constructor is not supported."); } public byte[] toBytes() throws IOException { - return toBytesInternal(fcc2); + return baosWrite(fcc, fcc2, size); } } private class AVIStreamHeader { - /* - * FOURCC fcc; DWORD cb; FOURCC fccType; FOURCC fccHandler; DWORD dwFlags; WORD wPriority; WORD - * wLanguage; DWORD dwInitialFrames; DWORD dwScale; DWORD dwRate; DWORD dwStart; DWORD dwLength; DWORD - * dwSuggestedBufferSize; DWORD dwQuality; DWORD dwSampleSize; struct { short int left; short int top; - * short int right; short int bottom; } rcFrame; - */ - - public byte[] fcc = new byte[] { 's', 't', 'r', 'h' }; + + public byte[] fcc = new byte[]{'s', 't', 'r', 'h'}; public int cb = 64; - public byte[] fccType = new byte[] { 'v', 'i', 'd', 's' }; - public byte[] fccHandler = new byte[] { 'M', 'J', 'P', 'G' }; + public byte[] fccType = new byte[]{'v', 'i', 'd', 's'}; + public byte[] fccHandler = new byte[]{'M', 'J', 'P', 'G'}; public int dwFlags = 0; public short wPriority = 0; public short wLanguage = 0; public int dwInitialFrames = 0; - public int dwScale = 0; // microseconds + public int dwScale = 0; // microseconds // per // frame - public int dwRate = 1000000; // dwRate + public int dwRate = 1000000; // dwRate // / // dwScale // = // frame // rate public int dwStart = 0; - public int dwLength = 0; // num + public int dwLength = 0; // num // frames public int dwSuggestedBufferSize = 0; public int dwQuality = -1; @@ -360,7 +337,9 @@ public AVIStreamHeader() { } public byte[] toBytes() throws IOException { - ByteArrayOutputStream baos = createByteArrayOutputStreamWriteFCCWriteSwapInt(fcc, cb); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(cb))); baos.write(fccType); baos.write(fccHandler); baos.write(intBytes(swapInt(dwFlags))); @@ -383,32 +362,25 @@ public byte[] toBytes() throws IOException { } } - public ByteArrayOutputStream createByteArrayOutputStreamWriteFCCWriteSwapInt(byte[] fcc, int intToSwap) - throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write(fcc); - baos.write(intBytes(swapInt(intToSwap))); - return baos; - } - private class AVIStreamFormat { /* - * FOURCC fcc; DWORD cb; DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; - * DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD - * biClrUsed; DWORD biClrImportant; + * FOURCC fcc; DWORD cb; DWORD biSize; LONG biWidth; LONG biHeight; WORD + * biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; + * LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD + * biClrImportant; */ - public byte[] fcc = new byte[] { 's', 't', 'r', 'f' }; + public byte[] fcc = new byte[]{'s', 't', 'r', 'f'}; public int cb = 40; - public int biSize = 40; // same + public int biSize = 40; // same // as // cb public int biWidth = 0; public int biHeight = 0; public short biPlanes = 1; public short biBitCount = 24; - public byte[] biCompression = new byte[] { 'M', 'J', 'P', 'G' }; - public int biSizeImage = 0; // width + public byte[] biCompression = new byte[]{'M', 'J', 'P', 'G'}; + public int biSizeImage = 0; // width // x // height // in @@ -446,9 +418,9 @@ public byte[] toBytes() throws IOException { private class AVIMovieList { - public byte[] fcc = new byte[] { 'L', 'I', 'S', 'T' }; + public byte[] fcc = new byte[]{'L', 'I', 'S', 'T'}; public int listSize = 0; - public byte[] fcc2 = new byte[] { 'm', 'o', 'v', 'i' }; + public byte[] fcc2 = new byte[]{'m', 'o', 'v', 'i'}; // 00db size jpg image data ... public AVIMovieList() { @@ -470,7 +442,7 @@ public byte[] toBytes() throws IOException { private class AVIIndexList { - public byte[] fcc = new byte[] { 'i', 'd', 'x', '1' }; + public byte[] fcc = new byte[]{'i', 'd', 'x', '1'}; public int cb = 0; public List ind = new ArrayList<>(); @@ -504,7 +476,7 @@ public byte[] toBytes() throws IOException { private class AVIIndex { - public byte[] fcc = new byte[] { '0', '0', 'd', 'b' }; + public byte[] fcc = new byte[]{'0', '0', 'd', 'b'}; public int dwFlags = 16; public int dwOffset = 0; public int dwSize = 0; @@ -515,7 +487,13 @@ public AVIIndex(int dwOffset, int dwSize) { } public byte[] toBytes() throws IOException { - return toBytesInternal(data); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(dwFlags))); + baos.write(intBytes(swapInt(dwOffset))); + baos.write(intBytes(swapInt(dwSize))); + + return baos.toByteArray(); } } @@ -524,7 +502,6 @@ private static class AVIJunk { public static final byte[] fcc = new byte[] { 'J', 'U', 'N', 'K' }; public static final int size = 1808; public static final byte[] data = new byte[size]; - public AVIJunk() { Arrays.fill(data, (byte) 0); } @@ -536,8 +513,7 @@ public byte[] toBytes() throws IOException { public byte[] writeImageToBytes(Image image, float quality) throws IOException { BufferedImage bi; - if (image instanceof BufferedImage - && ((BufferedImage) image).getType() == BufferedImage.TYPE_INT_RGB) { + if (image instanceof BufferedImage && ((BufferedImage) image).getType() == BufferedImage.TYPE_INT_RGB) { bi = (BufferedImage) image; } else { bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); @@ -558,4 +534,4 @@ public byte[] writeImageToBytes(Image image, float quality) throws IOException { return baos.toByteArray(); } -} \ No newline at end of file +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java index 76cb9d963..37e912cf9 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java @@ -74,22 +74,23 @@ public JmeDesktopSystem() { public URL getPlatformAssetConfigURL() { return Resources.getResource("com/jme3/asset/Desktop.cfg"); } - + private static BufferedImage verticalFlip(BufferedImage original) { AffineTransform tx = AffineTransform.getScaleInstance(1, -1); tx.translate(0, -original.getHeight()); AffineTransformOp transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); - BufferedImage awtImage = new BufferedImage(original.getWidth(), original.getHeight(), - original.getType()); + BufferedImage awtImage = new BufferedImage(original.getWidth(), original.getHeight(), original.getType()); Graphics2D g2d = awtImage.createGraphics(); - g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_SPEED); g2d.drawImage(original, transformOp, 0, 0); g2d.dispose(); return awtImage; } private static BufferedImage ensureOpaque(BufferedImage original) { - if (original.getTransparency() == BufferedImage.OPAQUE) return original; + if (original.getTransparency() == BufferedImage.OPAQUE) + return original; int w = original.getWidth(); int h = original.getHeight(); int[] pixels = new int[w * h]; @@ -100,11 +101,8 @@ private static BufferedImage ensureOpaque(BufferedImage original) { } @Override - public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, - int height) throws IOException { - BufferedImage awtImage = ImageToAwt.convert( - new Image(Image.Format.RGBA8, width, height, imageData.duplicate(), ColorSpace.Linear), false, - true, 0); + public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException { + BufferedImage awtImage = ImageToAwt.convert(new Image(Image.Format.RGBA8, width, height, imageData.duplicate(), ColorSpace.Linear), false, true, 0); awtImage = verticalFlip(awtImage); ImageWriter writer = ImageIO.getImageWritersByFormatName(format).next(); @@ -117,7 +115,7 @@ public void writeImageFile(OutputStream outStream, String format, ByteBuffer ima jpegParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); jpegParam.setCompressionQuality(0.95f); } - + ImageOutputStream imgOut = new MemoryCacheImageOutputStream(outStream); writer.setOutput(imgOut); IIOImage outputImage = new IIOImage(awtImage, null, null); @@ -148,8 +146,9 @@ private JmeContext newContextLwjgl(AppSettings settings, JmeContext.Type type) { } return (JmeContext) ctxClazz.getDeclaredConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | NoSuchMethodException | SecurityException ex) { + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException ex) { logger.log(Level.SEVERE, "Failed to create context", ex); } catch (ClassNotFoundException ex) { logger.log(Level.SEVERE, "CRITICAL ERROR: Context class is missing!\n" @@ -160,12 +159,13 @@ private JmeContext newContextLwjgl(AppSettings settings, JmeContext.Type type) { } // Helper method to avoid repetition - private JmeContext createContext(String className) { + private JmeContext createContext(String className) { try { Class ctxClazz = Class.forName(className); return (JmeContext) ctxClazz.getDeclaredConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | NoSuchMethodException | SecurityException ex) { + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException ex) { logger.log(Level.SEVERE, "Failed to create context", ex); } catch (ClassNotFoundException ex) { logger.log(Level.SEVERE, "CRITICAL ERROR: Context class is missing!\n" @@ -204,7 +204,8 @@ private JmeContext newContextCustom(AppSettings settings, JmeContext.Type type) public JmeContext newContext(AppSettings settings, Type contextType) { initialize(settings); JmeContext ctx; - if (settings.getRenderer() == null || settings.getRenderer().equals("NULL") + if (settings.getRenderer() == null + || settings.getRenderer().equals("NULL") || contextType == JmeContext.Type.Headless) { ctx = new NullContext(); ctx.setSettings(settings); @@ -219,7 +220,8 @@ public JmeContext newContext(AppSettings settings, Type contextType) { ctx.setSettings(settings); } else { throw new UnsupportedOperationException( - "Unrecognizable renderer specified: " + settings.getRenderer()); + "Unrecognizable renderer specified: " + + settings.getRenderer()); } return ctx; } @@ -230,10 +232,11 @@ private T newObject(String className) { Class clazz = (Class) Class.forName(className); return clazz.getDeclaredConstructor().newInstance(); } catch (ClassNotFoundException ex) { - logger.log(Level.SEVERE, - "CRITICAL ERROR: Audio implementation class " + className + " is missing!\n", ex); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | NoSuchMethodException | SecurityException ex) { + logger.log(Level.SEVERE, "CRITICAL ERROR: Audio implementation class " + + className + " is missing!\n", ex); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException ex) { logger.log(Level.SEVERE, "Failed to create context", ex); } @@ -257,7 +260,8 @@ public AudioRenderer newAudioRenderer(AppSettings settings) { efx = newObject("com.jme3.audio.joal.JoalEFX"); } else { throw new UnsupportedOperationException( - "Unrecognizable audio renderer specified: " + settings.getAudioRenderer()); + "Unrecognizable audio renderer specified: " + + settings.getAudioRenderer()); } if (al == null || alc == null || efx == null) { diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java index a06fb01a2..f93734900 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java @@ -45,6 +45,10 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import com.jme3.util.res.Resources; @@ -380,27 +384,28 @@ public static File[] getJarsWithNatives() { } public static void extractNativeLibraries(Platform platform, File targetDir) throws IOException { - // Define the base directory where extraction is allowed - File baseDir = new File("allowed/base/directory").getCanonicalFile(); + // Ensure the target directory is canonicalized and sanitized + Path canonicalTargetDir = targetDir.toPath().toRealPath(); // Resolves any symlinks or relative paths - // Validate the target directory path - Path targetPath = targetDir.toPath().normalize(); // Normalize to resolve ".." or "." - if (!targetPath.startsWith(baseDir.toPath())) { - throw new SecurityException("Path traversal attempt detected: " + targetDir.getPath()); - } - // Proceed with extracting native libraries if validation passes - for (Map.Entry lib : nativeLibraryMap.entrySet()) { - if (lib.getValue().getPlatform() == platform) { - if (!targetDir.exists()) { - boolean dirsCreated = targetDir.mkdirs(); - if (!dirsCreated) { - throw new IOException("Failed to create target directory: " + targetDir.getPath()); - } - } - extractNativeLibrary(platform, lib.getValue().getName(), targetDir); + for (Map.Entry lib : nativeLibraryMap.entrySet()) { + if (lib.getValue().getPlatform() == platform) { + // Validate the directory before proceeding + Path entryPath = new File(targetDir, lib.getValue().getName()).toPath().normalize(); + + // Ensure the entryPath is within the canonicalTargetDir + if (!entryPath.startsWith(canonicalTargetDir)) { + throw new IOException("Path traversal detected: " + entryPath); + } + + if (!targetDir.exists()) { + targetDir.mkdirs(); } + + extractNativeLibrary(platform, lib.getValue().getName(), targetDir); } } +} + /** * Removes platform-specific portions of a library file name so @@ -557,9 +562,11 @@ public static void loadNativeLibrary(String name, boolean isRequired) { "The required native library '" + library.getName() + "'" + " was not found in the classpath via '" + pathInJar); } else if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "The optional native library ''{0}''" + - " was not found in the classpath via ''{1}''.", - new Object[]{library.getName(), pathInJar}); + logger.log(Level.FINE, + "The optional native library ''{0}''" + + " was not found in the classpath via ''{1}''.", + new Object[] { library.getName().replaceAll("[\\r\\n]", ""), + pathInJar.replaceAll("[\\r\\n]", "") }); } return; }