diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java index 3df57f58c..704ab5010 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java @@ -16,6 +16,7 @@ import net.minecraft.util.io.netty.buffer.ByteBuf; import net.minecraft.util.io.netty.buffer.Unpooled; import net.minecraft.util.io.netty.handler.codec.base64.Base64; +import net.minecraft.util.io.netty.util.IllegalReferenceCountException; import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.reflect.EquivalentConverter; @@ -256,9 +257,18 @@ public WrappedServerPing deepClone() { * Represents a compressed favicon. * @author Kristian */ + // Should not have been an inner class ... oh well. public static class CompressedImage { - private final String mime; - private final byte[] data; + protected volatile String mime; + protected volatile byte[] data; + protected volatile String encoded; + + /** + * Represents a compressed image with no content. + */ + protected CompressedImage() { + // Derived class should initialize some of the fields + } /** * Construct a new compressed image. @@ -289,6 +299,20 @@ public static CompressedImage fromPng(byte[] data) { return new CompressedImage("image/png", data); } + /** + * Retrieve a compressed image from a base-64 encoded PNG file. + * @param base64 - the base 64-encoded PNG. + * @return The compressed image. + */ + public static CompressedImage fromBase64Png(String base64) { + try { + return new EncodedCompressedImage("data:image/png;base64," + base64); + } catch (IllegalArgumentException e) { + // Remind the caller + throw new IllegalReferenceCountException("Must be a pure base64 encoded string. Cannot be an encoded text.", e); + } + } + /** * Retrieve a compressed image from an image. * @param image - the image. @@ -306,24 +330,7 @@ public static CompressedImage fromPng(RenderedImage image) throws IOException { * @return The corresponding compressed image. */ public static CompressedImage fromEncodedText(String text) { - String mime = null; - byte[] data = null; - - for (String segment : Splitter.on(";").split(text)) { - if (segment.startsWith("data:")) { - mime = segment.substring(5); - } else if (segment.startsWith("base64,")) { - byte[] encoded = segment.substring(7).getBytes(Charsets.UTF_8); - ByteBuf decoded = Base64.decode(Unpooled.wrappedBuffer(encoded)); - - // Read into a byte array - data = new byte[decoded.readableBytes()]; - decoded.readBytes(data); - } else { - // We will ignore these segments - } - } - return new CompressedImage(mime, data); + return new EncodedCompressedImage(text); } /** @@ -341,7 +348,15 @@ public String getMime() { * @return The underlying compressed image. */ public byte[] getDataCopy() { - return data.clone(); + return getData().clone(); + } + + /** + * Retrieve the underlying data, with no copying. + * @return The underlying data. + */ + protected byte[] getData() { + return data; } /** @@ -350,15 +365,79 @@ public byte[] getDataCopy() { * @throws IOException If the image data could not be decoded. */ public BufferedImage getImage() throws IOException { - return ImageIO.read(new ByteArrayInputStream(data)); + return ImageIO.read(new ByteArrayInputStream(getData())); } - + /** * Convert the compressed image to encoded text. * @return The encoded text. */ public String toEncodedText() { - return "data:" + mime + ";base64," + Base64.encode(Unpooled.wrappedBuffer(data)).toString(Charsets.UTF_8); + if (encoded == null) { + final ByteBuf buffer = Unpooled.wrappedBuffer(getData()); + String computed = "data:" + mime + ";base64," + + Base64.encode(buffer).toString(Charsets.UTF_8); + + encoded = computed; + } + return encoded; + } + } + + /** + * Represents a compressed image that starts out as an encoded base 64 string. + * @author Kristian + */ + private static class EncodedCompressedImage extends CompressedImage { + public EncodedCompressedImage(String encoded) { + this.encoded = encoded; + } + + /** + * Ensure that we have decoded the content of the encoded text. + */ + protected void initialize() { + if (mime == null || data == null) { + decode(); + } + } + + /** + * Decode the encoded text. + */ + protected void decode() { + for (String segment : Splitter.on(";").split(encoded)) { + if (segment.startsWith("data:")) { + this.mime = segment.substring(5); + } else if (segment.startsWith("base64,")) { + byte[] encoded = segment.substring(7).getBytes(Charsets.UTF_8); + ByteBuf decoded = Base64.decode(Unpooled.wrappedBuffer(encoded)); + + // Read into a byte array + byte[] data = new byte[decoded.readableBytes()]; + decoded.readBytes(data); + this.data = data; + } else { + // We will ignore these segments + } + } + } + + @Override + protected byte[] getData() { + initialize(); + return super.getData(); + } + + @Override + public String getMime() { + initialize(); + return super.getMime(); + } + + @Override + public String toEncodedText() { + return encoded; } } } diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedServerPingTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedServerPingTest.java index 38801baa7..d3c9c6410 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedServerPingTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedServerPingTest.java @@ -6,6 +6,7 @@ import org.junit.BeforeClass; import org.junit.Test; +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; import com.comphenix.protocol.BukkitInitialization; import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage; @@ -35,7 +36,10 @@ public void test() throws IOException { assertEquals("Minecraft 123", serverPing.getVersionName()); assertEquals(4, serverPing.getVersionProtocol()); - assertArrayEquals(original, serverPing.getFavicon().getDataCopy()); + assertArrayEquals(original, serverPing.getFavicon().getData()); + + CompressedImage copy = CompressedImage.fromBase64Png(Base64Coder.encodeLines(tux.getData())); + assertArrayEquals(copy.getData(), serverPing.getFavicon().getData()); } }