diff --git a/src/main/java/net/lax1dude/eaglercraft/bintools/EBPFileDecoder.java b/src/main/java/net/lax1dude/eaglercraft/bintools/EBPFileDecoder.java new file mode 100644 index 0000000..6deffbf --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/bintools/EBPFileDecoder.java @@ -0,0 +1,116 @@ +package net.lax1dude.eaglercraft.bintools; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +/** + * Copyright (c) 2023-2024 lax1dude. All Rights Reserved. + * + * 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 HOLDER 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. + * + */ +public class EBPFileDecoder { + + private static int getFromBits(int idxx, int bits, byte[] bytes) { + int startByte = idxx >> 3; + int endByte = (idxx + bits - 1) >> 3; + if(startByte == endByte) { + return (((int)bytes[startByte] & 0xff) >> (8 - (idxx & 7) - bits)) & ((1 << bits) - 1); + }else { + return (((((int)bytes[startByte] & 0xff) << 8) | ((int)bytes[endByte] & 0xff)) >> (16 - (idxx & 7) - bits)) & ((1 << bits) - 1); + } + } + + public static void _main(String[] args) throws IOException { + if(args.length == 1) { + File input = new File(args[0]); + if(!input.isDirectory()) { + System.err.println("Error: Not a directory: " + input.getAbsolutePath()); + System.exit(-1); + return; + } + File[] f = input.listFiles(); + for(int i = 0; i < f.length; ++i) { + String name = f[i].getAbsolutePath(); + if(!name.toLowerCase().endsWith(".ebp")) { + continue; + } + File ff = new File(name.substring(0, name.length() - 3) + "png"); + System.out.println(f[i].getName()); + BufferedImage img; + try(InputStream is = new FileInputStream(f[i])) { + img = read(is); + } + ImageIO.write(img, "PNG", ff); + } + }else if(args.length == 2) { + System.out.println("Reading input file..."); + BufferedImage img; + try(InputStream is = new FileInputStream(new File(args[0]))) { + img = read(is); + } + File output = new File(args[1]); + System.out.println("Writing PNG: " + output.getAbsolutePath()); + ImageIO.write(img, "PNG", output); + }else { + System.out.println("Usage: ebp-decode "); + System.out.println(" ebp-decode "); + } + } + + public static BufferedImage read(InputStream is) throws IOException { + if(is.read() != '%' || is.read() != 'E' || is.read() != 'B' || is.read() != 'P') { + throw new IOException("Not an EBP file!"); + } + int v = is.read(); + if(v != 1) { + throw new IOException("Unknown EBP version: " + v); + } + v = is.read(); + if(v != 3) { + throw new IOException("Invalid component count: " + v); + } + int w = is.read() | (is.read() << 8); + int h = is.read() | (is.read() << 8); + BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + v = is.read(); + if(v == 0) { + for(int i = 0, l = w * h; i < l; ++i) { + img.setRGB(i % w, i / w, (is.read() << 16) | (is.read() << 8) | is.read() | 0xFF000000); + } + }else if(v == 1) { + int paletteSize = is.read(); + int[] palette = new int[paletteSize + 1]; + palette[0] = 0xFF000000; + for(int i = 0; i < paletteSize; ++i) { + palette[i + 1] = (is.read() << 16) | (is.read() << 8) | is.read() | 0xFF000000; + } + int bpp = is.read(); + byte[] readSet = new byte[is.read() | (is.read() << 8) | (is.read() << 16)]; + is.read(readSet); + for(int i = 0, l = w * h; i < l; ++i) { + img.setRGB(i % w, i / w, palette[getFromBits(i * bpp, bpp, readSet)]); + } + }else { + throw new IOException("Unknown EBP storage type: " + v); + } + if(is.read() != ':' || is.read() != '>') { + throw new IOException("Invalid footer! (:>)"); + } + return img; + } +} diff --git a/src/main/java/net/lax1dude/eaglercraft/bintools/EBPFileEncoder.java b/src/main/java/net/lax1dude/eaglercraft/bintools/EBPFileEncoder.java new file mode 100644 index 0000000..cca5772 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/bintools/EBPFileEncoder.java @@ -0,0 +1,144 @@ +package net.lax1dude.eaglercraft.bintools; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javax.imageio.ImageIO; + +/** + * Copyright (c) 2023-2024 lax1dude. All Rights Reserved. + * + * 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 HOLDER 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. + * + */ +public class EBPFileEncoder { + + private static final int[] paletteHelper = new int[0xFFFFFF + 1]; + + private static void setBit(int idx, boolean v, byte[] bytes) { + int idx2 = idx >> 3; + if(v) { + bytes[idx2] |= (1 << (7 - (idx & 7))); + }else { + bytes[idx2] &= (-1 ^ (1 << (7 - (idx & 7)))); + } + } + + public static void _main(String[] args) throws IOException { + if(args.length == 1) { + File input = new File(args[0]); + if(!input.isDirectory()) { + System.err.println("Error: Not a directory: " + input.getAbsolutePath()); + System.exit(-1); + return; + } + File[] f = input.listFiles(); + for(int i = 0; i < f.length; ++i) { + String name = f[i].getAbsolutePath(); + if(!name.toLowerCase().endsWith(".png")) { + continue; + } + File ff = new File(name.substring(0, name.length() - 3) + "ebp"); + System.out.println(f[i].getName()); + try(OutputStream os = new FileOutputStream(ff)) { + write(ImageIO.read(f[i]), os); + } + } + }else if(args.length == 2) { + System.out.println("Reading input file..."); + BufferedImage img = ImageIO.read(new File(args[0])); + File output = new File(args[1]); + System.out.println("Encoding EBP: " + output.getAbsolutePath()); + try(OutputStream os = new FileOutputStream(output)) { + write(img, os); + } + }else { + System.out.println("Usage: ebp-encode "); + System.out.println(" ebp-encode "); + } + } + + public static void write(BufferedImage img, OutputStream fos) throws IOException { + fos.write(new byte[] { '%', 'E', 'B', 'P'}); + fos.write(1); // v1 + fos.write(3); // 3 component + int w = img.getWidth(); + int h = img.getHeight(); + fos.write(w & 0xFF); + fos.write((w >> 8) & 0xFF); + fos.write(h & 0xFF); + fos.write((h >> 8) & 0xFF); + + int[] pixels = img.getRGB(0, 0, w, h, null, 0, w); + + Set colorPalette = new HashSet(); + for(int i = 0; i < pixels.length; ++i) { + if((pixels[i] & 0xFF000000) == 0) { + pixels[i] = 0; + }else { + pixels[i] &= 0xFFFFFF; + } + if(pixels[i] == 0) { + continue; + } + colorPalette.add(pixels[i]); + } + + if(colorPalette.size() > 255) { + fos.write(0); // type is no palette + for(int i = 0; i < pixels.length; ++i) { + fos.write((pixels[i] >> 16) & 0xFF); + fos.write((pixels[i] >> 8) & 0xFF); + fos.write(pixels[i] & 0xFF); + } + }else { + fos.write(1); // type is palette + fos.write(colorPalette.size()); // write palette size + Iterator paletteItr = colorPalette.iterator(); + int paletteIdx = 0; + paletteHelper[0] = 0; + while(paletteItr.hasNext()) { + int j = paletteItr.next().intValue(); + paletteHelper[j] = ++paletteIdx; + fos.write((j >> 16) & 0xFF); + fos.write((j >> 8) & 0xFF); + fos.write(j & 0xFF); + } + int bpp = 1; + while((paletteIdx >> bpp) != 0) { + ++bpp; + } + fos.write(bpp); // write bpp + int totalBits = pixels.length * bpp; + byte[] completedBitSet = new byte[(totalBits & 7) == 0 ? (totalBits >> 3) : ((totalBits >> 3) + 1)]; + int bsi = 0; + for(int i = 0; i < pixels.length; ++i) { + int wr = paletteHelper[pixels[i]]; + for(int j = bpp - 1; j >= 0; --j) { + setBit(bsi++, ((wr >> j) & 1) != 0, completedBitSet); + } + } + fos.write(completedBitSet.length & 0xFF); // write expect length (as 24 bits not 32) + fos.write((completedBitSet.length >> 8) & 0xFF); + fos.write((completedBitSet.length >> 16) & 0xFF); + fos.write(completedBitSet); // write the bits + } + fos.write(new byte[] { ':', '>' }); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/bintools/EaglerBinaryTools.java b/src/main/java/net/lax1dude/eaglercraft/bintools/EaglerBinaryTools.java index 54cc10f..b74c540 100644 --- a/src/main/java/net/lax1dude/eaglercraft/bintools/EaglerBinaryTools.java +++ b/src/main/java/net/lax1dude/eaglercraft/bintools/EaglerBinaryTools.java @@ -26,10 +26,13 @@ public class EaglerBinaryTools { System.arraycopy(args, 1, argz, 0, argz.length); switch(args[0].toLowerCase()) { case "epkcompiler": + case "epkcompile": EPKCompiler._main(argz); return; case "legacy-epkcompiler": case "legacyepkcompiler": + case "legacy-epkcompile": + case "legacyepkcompile": EPKCompilerLegacy._main(argz); return; case "epkdecompiler": @@ -46,13 +49,22 @@ public class EaglerBinaryTools { return; case "ebp-encode": case "ebpencode": + case "ebp-encoder": + case "ebpencoder": + EBPFileEncoder._main(argz); return; case "ebp-decode": case "ebpdecode": + case "ebp-decoder": + case "ebpdecoder": + EBPFileDecoder._main(argz); return; case "skybox-gen": case "skyboxgen": return; + case "light-mesh-gen": + case "lightmeshgen": + return; case "eagler-moon-gen": case "eaglermoongen": return; diff --git a/src/main/java/net/lax1dude/eaglercraft/bintools/OBJConverter.java b/src/main/java/net/lax1dude/eaglercraft/bintools/OBJConverter.java index 76b80c9..75dadb3 100644 --- a/src/main/java/net/lax1dude/eaglercraft/bintools/OBJConverter.java +++ b/src/main/java/net/lax1dude/eaglercraft/bintools/OBJConverter.java @@ -46,7 +46,7 @@ public class OBJConverter { } } File output = new File(args[1]); - System.out.println("Exporting MDL: " + output.getAbsolutePath()); + System.out.println("Exporting " + (v1_8 ? "1.8" : "1.5") + " MDL: " + output.getAbsolutePath()); boolean tex = args[2].equalsIgnoreCase("true") || args[2].equals("1"); try(FileOutputStream fs = new FileOutputStream(output)) { convertModel(lns, fs, tex, v1_8);