diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java index 9b1e6ad1b0..b1f376235f 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,6 +39,35 @@ public class FbxFile { public List rootElements = new ArrayList<>(); public long version; + /** + * Between file versions 7400 and 7500, the "endOffset", "propCount", and + * "propsLength" fields in an FBX element were extended, from 4 bytes to 8 + * bytes each. + * + * @return true for 8-byte offsets, otherwise false + */ + public boolean hasExtendedOffsets() { + if (version >= 7500L) { + return true; + } else { + return false; + } + } + + /** + * Between file versions 7400 and 7500, the FBX block sentinel was reduced, + * from 13 bytes to 9 bytes. + * + * @return the number of bytes in the block sentinel (≥0) + */ + public int numSentinelBytes() { + if (version >= 7500L) { + return 9; + } else { + return 13; + } + } + @Override public String toString() { return "FBXFile[version=" + version + ",numElements=" + rootElements.size() + "]"; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java index 28b051f376..6a916464db 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,8 +42,6 @@ public class FbxReader { - public static final int BLOCK_SENTINEL_LENGTH = 13; - public static final byte[] BLOCK_SENTINEL_DATA = new byte[BLOCK_SENTINEL_LENGTH]; /** * magic string at start: * "Kaydara FBX Binary\x20\x20\x00\x1a\x00" @@ -74,7 +72,7 @@ public static FbxFile readFBX(InputStream stream) throws IOException { fbxFile.version = getUInt(byteBuffer); // Read root elements while(true) { - FbxElement e = readFBXElement(byteBuffer); + FbxElement e = readFBXElement(byteBuffer, fbxFile); if(e == null) break; fbxFile.rootElements.add(e); @@ -82,13 +80,37 @@ public static FbxFile readFBX(InputStream stream) throws IOException { return fbxFile; } - private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOException { + private static FbxElement readFBXElement(ByteBuffer byteBuffer, FbxFile file) + throws IOException { long endOffset = getUInt(byteBuffer); + if (file.hasExtendedOffsets()) { + long upper = getUInt(byteBuffer); + if (upper != 0L) { + throw new IOException( + "Non-zero upper bytes: 0x" + Long.toHexString(upper)); + } + } if(endOffset == 0) return null; + long propCount = getUInt(byteBuffer); + if (file.hasExtendedOffsets()) { + long upper = getUInt(byteBuffer); + if (upper != 0L) { + throw new IOException( + "Non-zero upper bytes: 0x" + Long.toHexString(upper)); + } + } + getUInt(byteBuffer); // Properties length unused - + if (file.hasExtendedOffsets()) { + long upper = getUInt(byteBuffer); + if (upper != 0L) { + throw new IOException( + "Non-zero upper bytes: 0x" + Long.toHexString(upper)); + } + } + FbxElement element = new FbxElement((int) propCount); element.id = new String(getBytes(byteBuffer, getUByte(byteBuffer))); @@ -98,17 +120,39 @@ private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOExcepti element.propertiesTypes[i] = dataType; } if(byteBuffer.position() < endOffset) { - while(byteBuffer.position() < (endOffset - BLOCK_SENTINEL_LENGTH)) - element.children.add(readFBXElement(byteBuffer)); - - if(!Arrays.equals(BLOCK_SENTINEL_DATA, getBytes(byteBuffer, BLOCK_SENTINEL_LENGTH))) - throw new IOException("Failed to read block sentinel, expected 13 zero bytes"); + int blockSentinelLength = file.numSentinelBytes(); + while (byteBuffer.position() < (endOffset - blockSentinelLength)) { + FbxElement child = readFBXElement(byteBuffer, file); + if (child != null) { + element.children.add(child); + } + } + + if (!allZero(getBytes(byteBuffer, blockSentinelLength))) { + throw new IOException("Block sentinel is corrupt: expected all zeros."); + } } if(byteBuffer.position() != endOffset) throw new IOException("Data length not equal to expected"); return element; } + /** + * Tests whether all bytes in the specified array are zero. + * + * @param array the array to test (not null, unaffected) + * @return true if all zeroes, otherwise false + */ + private static boolean allZero(byte[] array) { + for (byte b : array) { + if (b != 0) { + return false; + } + } + + return true; + } + private static Object readData(ByteBuffer byteBuffer, char dataType) throws IOException { switch(dataType) { case 'Y':