Implement EPK compiler/decompiler

This commit is contained in:
lax1dude 2024-06-16 22:42:28 -07:00
commit 9a9e50e223
9 changed files with 856 additions and 0 deletions

7
.gitattributes vendored Normal file
View File

@ -0,0 +1,7 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
*.bat text eol=crlf
*.sh text eol=lf
gradlew text eol=lf

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.classpath
.project
bin

View File

@ -0,0 +1,194 @@
package net.lax1dude.eaglercraft.bintools;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import net.lax1dude.eaglercraft.bintools.utils.GZIPOutputStream2;
/**
* Copyright (c) 2022-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 EPKCompiler {
public static void _main(String[] args) throws IOException {
if (args.length < 2 || args.length > 4) {
System.out.println("Usage: epkcompiler <input directory> <output file> [gzip|zlib|none] [file-type]");
return;
}
File root = new File(args[0]);
File output = new File(args[1]);
char compressionType;
if (args.length > 2) {
if (args[2].equalsIgnoreCase("gzip")) {
compressionType = 'G';
} else if (args[2].equalsIgnoreCase("zlib")) {
compressionType = 'Z';
} else if (args[2].equalsIgnoreCase("none")) {
compressionType = '0';
} else {
throw new IllegalArgumentException("Unknown compression method: " + args[2]);
}
} else {
compressionType = 'G';
}
System.out.println("Scanning input directory...");
ArrayList<File> files = new ArrayList();
listDirectory(root, files);
ByteArrayOutputStream osb = new ByteArrayOutputStream();
String start = root.getAbsolutePath();
System.out.println("Compiling: " + output.getAbsolutePath());
osb.write("EAGPKG$$".getBytes(StandardCharsets.US_ASCII));
String chars = "ver2.0";
osb.write(chars.length());
osb.write(chars.getBytes(StandardCharsets.US_ASCII));
Date d = new Date();
String comment = "\n\n # Eagler EPK v2.0 - Generated by EaglerBinaryTools\n" + " # update: on "
+ (new SimpleDateFormat("MM/dd/yyyy")).format(d) + " at "
+ (new SimpleDateFormat("hh:mm:ss aa")).format(d) + "\n\n";
String nm = output.getName();
osb.write(nm.length());
osb.write(nm.getBytes(StandardCharsets.US_ASCII));
writeShort(comment.length(), osb);
osb.write(comment.getBytes(StandardCharsets.US_ASCII));
writeLong(d.getTime(), osb);
writeInt(files.size() + 1, osb);
osb.write(compressionType);
OutputStream os;
if (compressionType == 'G') {
System.out.println("Using GZIP compression");
os = new GZIPOutputStream2(osb, 9, 16384, true);
} else if (compressionType == 'Z') {
System.out.println("Using ZLIB (DEFLATE) compression");
os = new DeflaterOutputStream(osb, new Deflater(9), 16384, true);
} else {
System.out.println("Using no compression");
os = osb;
}
os.write("HEAD".getBytes(StandardCharsets.US_ASCII));
String key = "file-type";
os.write(key.length());
os.write(key.getBytes(StandardCharsets.US_ASCII));
String value;
if (args.length > 3) {
value = args[3];
} else {
value = "epk/resources";
}
writeInt(value.length(), os);
os.write(value.getBytes(StandardCharsets.US_ASCII));
os.write('>');
CRC32 checkSum = new CRC32();
for (File f : files) {
InputStream stream = new FileInputStream(f);
byte[] targetArray = new byte[(int) f.length()];
stream.read(targetArray);
stream.close();
checkSum.reset();
checkSum.update(targetArray, 0, targetArray.length);
int ch = (int) checkSum.getValue();
os.write("FILE".getBytes(StandardCharsets.US_ASCII));
String p = f.getAbsolutePath().replace(start, "").replace('\\', '/');
if (p.startsWith("/")) {
p = p.substring(1);
}
os.write(p.length());
os.write(p.getBytes(StandardCharsets.US_ASCII));
writeInt(targetArray.length + 5, os);
writeInt(ch, os);
os.write(targetArray);
os.write(':');
os.write('>');
}
os.write("END$".getBytes(StandardCharsets.US_ASCII));
os.close();
osb.write(":::YEE:>".getBytes(StandardCharsets.US_ASCII));
System.out.println("Compiled " + files.size() + " files into the EPK");
System.out.println("Writing to disk...");
FileOutputStream out = new FileOutputStream(output);
out.write(osb.toByteArray());
out.close();
}
public static void writeShort(int i, OutputStream os) throws IOException {
os.write((i >> 8) & 0xFF);
os.write(i & 0xFF);
}
public static void writeInt(int i, OutputStream os) throws IOException {
os.write((i >> 24) & 0xFF);
os.write((i >> 16) & 0xFF);
os.write((i >> 8) & 0xFF);
os.write(i & 0xFF);
}
public static void writeLong(long i, OutputStream os) throws IOException {
os.write((int) ((i >> 56) & 0xFF));
os.write((int) ((i >> 48) & 0xFF));
os.write((int) ((i >> 40) & 0xFF));
os.write((int) ((i >> 32) & 0xFF));
os.write((int) ((i >> 24) & 0xFF));
os.write((int) ((i >> 16) & 0xFF));
os.write((int) ((i >> 8) & 0xFF));
os.write((int) (i & 0xFF));
}
public static void listDirectory(File dir, ArrayList<File> files) {
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
listDirectory(f, files);
} else {
files.add(f);
}
}
}
}

View File

@ -0,0 +1,93 @@
package net.lax1dude.eaglercraft.bintools;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
/**
* Copyright (c) 2022-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 EPKCompilerLegacy {
public static void _main(String[] args) throws IOException, NoSuchAlgorithmException {
if (args.length != 2) {
System.out.print("Usage: legacy-epkcompiler <input directory> <output file>");
return;
}
File root = new File(args[0]);
ArrayList<File> files = new ArrayList();
System.out.println("Scanning input directory...");
listDirectory(root, files);
ByteArrayOutputStream osb = new ByteArrayOutputStream();
DataOutputStream os = new DataOutputStream(osb);
String start = root.getAbsolutePath();
File output = new File(args[1]);
System.out.println("Compiling: " + output.getAbsolutePath());
os.write("EAGPKG!!".getBytes(StandardCharsets.UTF_8));
os.writeUTF(
"\n\n # eaglercraft package file - assets copyright mojang ab\n # eagler eagler eagler eagler eagler eagler eagler\n\n");
Deflater d = new Deflater(9);
os = new DataOutputStream(new DeflaterOutputStream(osb, d));
MessageDigest md = MessageDigest.getInstance("SHA-1");
for (File f : files) {
os.writeUTF("<file>");
String p = f.getAbsolutePath().replace(start, "").replace('\\', '/');
if (p.startsWith("/"))
p = p.substring(1);
os.writeUTF(p);
InputStream stream = new FileInputStream(f);
byte[] targetArray = new byte[stream.available()];
stream.read(targetArray);
stream.close();
os.write(md.digest(targetArray));
os.writeInt(targetArray.length);
os.write(targetArray);
os.writeUTF("</file>");
}
os.writeUTF(" end");
os.flush();
os.close();
System.out.println("Compiled " + files.size() + " files into the EPK");
System.out.println("Writing to disk...");
FileOutputStream out = new FileOutputStream(output);
out.write(osb.toByteArray());
out.close();
}
public static void listDirectory(File dir, ArrayList<File> files) {
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
listDirectory(f, files);
} else {
files.add(f);
}
}
}
}

View File

@ -0,0 +1,81 @@
package net.lax1dude.eaglercraft.bintools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.bintools.utils.EPKDecompilerSP;
/**
* Copyright (c) 2022-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 EPKDecompiler {
public static void _main(String[] args) throws IOException {
if (args.length != 2) {
System.out.print("Usage: epkdecompiler <input epk> <output folder>");
return;
}
File input = new File(args[0]);
if(!input.isFile()) {
System.err.println("Input file does not exist!");
return;
}
System.out.println("Decompiling: " + input.getAbsolutePath());
File output = new File(args[1]);
byte[] inputBytes = new byte[(int)input.length()];
try(FileInputStream fis = new FileInputStream(input)) {
fis.read(inputBytes);
}
EPKDecompilerSP epkDecompiler = new EPKDecompilerSP(inputBytes);
if(epkDecompiler.isOld()) {
System.out.println("Detected legacy EPK format!");
}
int filesWritten = 0;
try {
EPKDecompilerSP.FileEntry f = null;
while((f = epkDecompiler.readFile()) != null) {
if(f.type.equals("HEAD")) {
System.out.println("Skipping HEAD: \"" + f.name + "\": \"" + (new String(f.data, StandardCharsets.US_ASCII)) + "\"");
}else if(f.type.equals("FILE")) {
String safeName = f.name.replace('\\', '/');
if(safeName.startsWith("../") || safeName.contains("/../") || safeName.endsWith("/..") || safeName.equals("..")) {
System.out.println("Skipping unsafe relative path: \"" + f.name + "\"");
}else {
File destFile = new File(output, safeName);
File parent = destFile.getParentFile();
if(!parent.isDirectory()) {
if(!parent.mkdirs()) {
throw new IOException("Could not create directory: " + parent.getAbsolutePath());
}
}
try(FileOutputStream fos = new FileOutputStream(destFile)) {
fos.write(f.data);
++filesWritten;
}
}
}else {
System.err.println("Skipping unknown entry type \"" + f.type + "\" name \"" + f.name + "\", data is " + f.data.length + " bytes");
}
}
}finally {
epkDecompiler.close();
}
System.out.println("Extracted " + filesWritten + " from the EPK");
}
}

View File

@ -0,0 +1,79 @@
package net.lax1dude.eaglercraft.bintools;
/**
* Copyright (c) 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 EaglerBinaryTools {
public static void main(String[] args) throws Throwable {
if(args.length < 1) {
usage();
return;
}
String[] argz = new String[args.length - 1];
System.arraycopy(args, 1, argz, 0, argz.length);
switch(args[0].toLowerCase()) {
case "epkcompiler":
EPKCompiler._main(argz);
return;
case "legacy-epkcompiler":
case "legacyepkcompiler":
EPKCompilerLegacy._main(argz);
return;
case "epkdecompiler":
EPKDecompiler._main(argz);
return;
case "obj2mdl-1.5":
case "obj2mdl1.5":
return;
case "obj2mdl-1.8":
case "obj2mdl1.8":
return;
case "ebp-encode":
case "ebpencode":
return;
case "ebp-decode":
case "ebpdecode":
return;
case "skybox-gen":
case "skyboxgen":
return;
case "eagler-moon-gen":
case "eaglermoongen":
return;
case "lens-flare-gen":
case "lensflaregen":
return;
default:
usage();
return;
}
}
private static void usage() {
System.out.println("Usage: java -jar EaglerBinaryTools.jar <epkcompiler|legacy-epkcompiler|epkdecompiler|obj2mdl-1.5|obj2mdl-1.8|ebp-encode|ebp-decode|skybox-gen|eagler-moon-gen|lens-flare-gen> [args...]");
System.out.println(" - 'epkcompiler': Compile an EPK file from a folder");
System.out.println(" - 'legacy-epkcompiler': Compile an EPK file in legacy format");
System.out.println(" - 'epkdecompiler': Decompile an EPK file into a folder");
System.out.println(" - 'obj2mdl-1.5': Compile FNAW skin MDL file for 1.5");
System.out.println(" - 'obj2mdl-1.8': Compile FNAW skin MDL file for 1.8");
System.out.println(" - 'ebp-encode': Encode EBP file from PNG");
System.out.println(" - 'ebp-decode': Decode EBP file to PNG");
System.out.println(" - 'skybox-gen': Generate skybox.dat from OBJ for shader packs");
System.out.println(" - 'eagler-moon-gen': Generate eagler_moon.bmp from PNG for shader packs");
System.out.println(" - 'lens-flare-gen': Generate lens_streaks.bmp, lens_ghosts.bmp from PNG for shader packs");
}
}

View File

@ -0,0 +1,248 @@
package net.lax1dude.eaglercraft.bintools.utils;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.zip.CRC32;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
/**
* Copyright (c) 2022-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 EPKDecompilerSP {
public static class FileEntry {
public final String type;
public final String name;
public final byte[] data;
protected FileEntry(String type, String name, byte[] data) {
this.type = type;
this.name = name;
this.data = data;
}
}
private ByteArrayInputStream in2;
private DataInputStream in;
private InputStream zis;
private MessageDigest dg;
private CRC32 crc32;
private int numFiles;
private boolean isFinished = false;
private boolean isOldFormat = false;
public EPKDecompilerSP(byte[] data) throws IOException {
in2 = new ByteArrayInputStream(data);
byte[] header = new byte[8];
in2.read(header);
if(Arrays.equals(header, new byte[]{(byte)69,(byte)65,(byte)71,(byte)80,(byte)75,(byte)71,(byte)36,(byte)36})) {
byte[] endCode = new byte[] { (byte)':', (byte)':', (byte)':', (byte)'Y',
(byte)'E', (byte)'E', (byte)':', (byte)'>' };
for(int i = 0; i < 8; ++i) {
if(data[data.length - 8 + i] != endCode[i]) {
throw new IOException("EPK file is missing EOF code (:::YEE:>)");
}
}
in2 = new ByteArrayInputStream(data, 8, data.length - 16);
initNew();
}else if(Arrays.equals(header, new byte[]{(byte)69,(byte)65,(byte)71,(byte)80,(byte)75,(byte)71,(byte)33,(byte)33})) {
initOld();
}
}
public boolean isOld() {
return isOldFormat;
}
public FileEntry readFile() throws IOException {
if(!isOldFormat) {
return readFileNew();
}else {
return readFileOld();
}
}
private void initNew() throws IOException {
InputStream is = in2;
String vers = readASCII(is);
if(!vers.startsWith("ver2.")) {
throw new IOException("Unknown or invalid EPK version: " + vers);
}
is.skip(is.read()); // skip filename
is.skip(loadShort(is)); // skip comment
is.skip(8); // skip millis date
numFiles = loadInt(is);
char compressionType = (char)is.read();
switch(compressionType) {
case 'G':
zis = new GZIPInputStream(is);
break;
case 'Z':
zis = new InflaterInputStream(is);
break;
case '0':
zis = is;
break;
default:
throw new IOException("Invalid or unsupported EPK compression: " + compressionType);
}
crc32 = new CRC32();
}
private FileEntry readFileNew() throws IOException {
if(isFinished) {
return null;
}
byte[] typeBytes = new byte[4];
IOUtils.readFully(zis, typeBytes);
String type = readASCII(typeBytes);
if(numFiles == 0) {
if(!"END$".equals(type)) {
throw new IOException("EPK file is missing END code (END$)");
}
zis.close();
isFinished = true;
return null;
}else {
if("END$".equals(type)) {
throw new IOException("Unexpected END when there are still " + numFiles + " files remaining");
}else {
String name = readASCII(zis);
int len = loadInt(zis);
byte[] data;
if("FILE".equals(type)) {
if(len < 5) {
throw new IOException("File '" + name + "' is incomplete (no crc)");
}
int loadedCrc = loadInt(zis);
data = new byte[len - 5];
IOUtils.readFully(zis, data);
crc32.reset();
crc32.update(data, 0, data.length);
if((int)crc32.getValue() != loadedCrc) {
throw new IOException("File '" + name + "' has an invalid checksum");
}
if(zis.read() != ':') {
throw new IOException("File '" + name + "' is incomplete");
}
}else {
data = new byte[len];
IOUtils.readFully(zis, data);
}
if(zis.read() != '>') {
throw new IOException("Object '" + name + "' is incomplete");
}
--numFiles;
return new FileEntry(type, name, data);
}
}
}
private static final int loadShort(InputStream is) throws IOException {
return (is.read() << 8) | is.read();
}
private static final int loadInt(InputStream is) throws IOException {
return (is.read() << 24) | (is.read() << 16) | (is.read() << 8) | is.read();
}
public static final String readASCII(byte[] bytesIn) throws IOException {
char[] charIn = new char[bytesIn.length];
for(int i = 0; i < bytesIn.length; ++i) {
charIn[i] = (char)((int)bytesIn[i] & 0xFF);
}
return new String(charIn);
}
private static final String readASCII(InputStream bytesIn) throws IOException {
int len = bytesIn.read();
char[] charIn = new char[len];
for(int i = 0; i < len; ++i) {
charIn[i] = (char)(bytesIn.read() & 0xFF);
}
return new String(charIn);
}
private void initOld() throws IOException {
isOldFormat = true;
try {
dg = MessageDigest.getInstance("SHA-1");
}catch(NoSuchAlgorithmException ex) {
throw new RuntimeException("SHA-1 is not supported in this JRE!", ex);
}
in = new DataInputStream(in2);
in.readUTF();
in = new DataInputStream(new InflaterInputStream(in2));
}
private FileEntry readFileOld() throws IOException {
if(isFinished) {
return null;
}
String s = in.readUTF();
if(s.equals(" end")) {
isFinished = true;
in.close();
return null;
}else if(!s.equals("<file>")) {
throw new IOException("invalid epk file");
}
String path = in.readUTF();
byte[] digest = new byte[20];
IOUtils.readFully(in, digest);
int len = in.readInt();
byte[] file = new byte[len];
IOUtils.readFully(in, file);
byte[] digest2 = dg.digest(file);
if(!Arrays.equals(digest, digest2)) throw new IOException("invalid file hash for "+path);
if(!"</file>".equals(in.readUTF())) throw new IOException("invalid epk file");
return new FileEntry("FILE", path, file);
}
// Avoid Inflater memleak
public void close() throws IOException {
if(zis != null) {
zis.close();
}
if(in != null) {
in.close();
}
isFinished = true;
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package net.lax1dude.eaglercraft.bintools.utils;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
/**
* GZIP output stream class that allows you to set the compression level
*/
public class GZIPOutputStream2 extends DeflaterOutputStream {
protected CRC32 crc = new CRC32();
private final static int GZIP_MAGIC = 0x8b1f;
private final static int TRAILER_SIZE = 8;
public GZIPOutputStream2(OutputStream out, int compressionLevel, int size, boolean syncFlush) throws IOException {
super(out, new Deflater(compressionLevel, true), size, syncFlush);
writeHeader();
crc.reset();
}
public synchronized void write(byte[] buf, int off, int len) throws IOException {
super.write(buf, off, len);
crc.update(buf, off, len);
}
public void finish() throws IOException {
if (!def.finished()) {
try {
def.finish();
while (!def.finished()) {
int len = def.deflate(buf, 0, buf.length);
if (def.finished() && len <= buf.length - TRAILER_SIZE) {
// last deflater buffer. Fit trailer at the end
writeTrailer(buf, len);
len = len + TRAILER_SIZE;
out.write(buf, 0, len);
return;
}
if (len > 0)
out.write(buf, 0, len);
}
// if we can't fit the trailer at the end of the last
// deflater buffer, we write it separately
byte[] trailer = new byte[TRAILER_SIZE];
writeTrailer(trailer, 0);
out.write(trailer);
} catch (IOException e) {
def.end();
throw e;
}
}
}
private void writeHeader() throws IOException {
out.write(new byte[] { (byte) GZIP_MAGIC, // Magic number (short)
(byte) (GZIP_MAGIC >> 8), // Magic number (short)
Deflater.DEFLATED, // Compression method (CM)
0, // Flags (FLG)
0, // Modification time MTIME (int)
0, // Modification time MTIME (int)
0, // Modification time MTIME (int)
0, // Modification time MTIME (int)
0, // Extra flags (XFLG)
0 // Operating system (OS)
});
}
private void writeTrailer(byte[] buf, int offset) throws IOException {
writeInt((int) crc.getValue(), buf, offset); // CRC-32 of uncompr. data
writeInt(def.getTotalIn(), buf, offset + 4); // Number of uncompr. bytes
}
private void writeInt(int i, byte[] buf, int offset) throws IOException {
writeShort(i & 0xffff, buf, offset);
writeShort((i >> 16) & 0xffff, buf, offset + 2);
}
private void writeShort(int s, byte[] buf, int offset) throws IOException {
buf[offset] = (byte) (s & 0xff);
buf[offset + 1] = (byte) ((s >> 8) & 0xff);
}
}

View File

@ -0,0 +1,39 @@
package net.lax1dude.eaglercraft.bintools.utils;
import java.io.IOException;
import java.io.InputStream;
/**
* Copyright (c) 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 IOUtils {
public static int readFully(InputStream is, byte[] out) throws IOException {
int i = 0, j;
while(i < out.length && (j = is.read(out, i, out.length - i)) != -1) {
i += j;
}
return i;
}
public static long skipFully(InputStream is, long skip) throws IOException {
long i = 0, j;
while(i < skip && (j = is.skip(skip - i)) != 0) {
i += j;
}
return i;
}
}