From 1e4f4079767cf5bb2f646ccba94d05e39b80c2aa Mon Sep 17 00:00:00 2001 From: ayunami2000 Date: Mon, 9 Dec 2024 04:26:55 -0500 Subject: [PATCH] work on texture pack redo --- .../lax1dude/eaglercraft/AssetRepository.java | 111 ++- .../lax1dude/eaglercraft/EPKDecompiler.java | 217 ++++++ .../lax1dude/eaglercraft/GuiScreenRelay.java | 2 +- .../eaglercraft/TextureTerrainMap.java | 2 +- .../java/net/minecraft/client/Minecraft.java | 12 - .../java/net/minecraft/src/FontRenderer.java | 5 +- .../java/net/minecraft/src/GuiMainMenu.java | 3 +- .../net/minecraft/src/GuiTexturePacks.java | 36 +- .../java/net/minecraft/src/ITexturePack.java | 22 +- .../net/minecraft/src/TexturePackCustom.java | 37 - .../net/minecraft/src/TexturePackDefault.java | 4 +- .../net/minecraft/src/TexturePackFolder.java | 40 + .../src/TexturePackImplementation.java | 198 +++-- .../net/minecraft/src/TexturePackList.java | 257 +++---- .../adapter/teavm/vfs/BooleanResult.java | 18 + .../eaglercraft/adapter/teavm/vfs/SYS.java | 19 + .../adapter/teavm/vfs/VFSIterator.java | 17 + .../eaglercraft/adapter/teavm/vfs/VFile.java | 227 ++++++ .../adapter/teavm/vfs/VIteratorFile.java | 289 ++++++++ .../adapter/teavm/vfs/VirtualFilesystem.java | 681 ++++++++++++++++++ 20 files changed, 1862 insertions(+), 335 deletions(-) create mode 100644 src/main/java/net/lax1dude/eaglercraft/EPKDecompiler.java delete mode 100644 src/main/java/net/minecraft/src/TexturePackCustom.java create mode 100644 src/main/java/net/minecraft/src/TexturePackFolder.java create mode 100644 src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/BooleanResult.java create mode 100644 src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/SYS.java create mode 100644 src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VFSIterator.java create mode 100644 src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VFile.java create mode 100644 src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VIteratorFile.java create mode 100644 src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VirtualFilesystem.java diff --git a/src/main/java/net/lax1dude/eaglercraft/AssetRepository.java b/src/main/java/net/lax1dude/eaglercraft/AssetRepository.java index cabf774..4c15dec 100644 --- a/src/main/java/net/lax1dude/eaglercraft/AssetRepository.java +++ b/src/main/java/net/lax1dude/eaglercraft/AssetRepository.java @@ -13,10 +13,9 @@ import com.jcraft.jzlib.InflaterInputStream; import org.json.JSONObject; public class AssetRepository { - - private static final HashMap filePool = new HashMap<>(); - private static final HashMap filePoolTemp = new HashMap<>(); - public static final HashMap fileNameOverrides = new HashMap<>(); + + private static final HashMap filePool = new HashMap(); + public static final HashMap fileNameOverrides = new HashMap(); public static final void loadOverrides(JSONObject json) { JSONObject overrides = json.optJSONObject("assetOverrides", null); @@ -34,39 +33,13 @@ public class AssetRepository { } } - private static byte[] def = null; - - public static final void reset() throws IOException { - if (def != null) { - filePool.clear(); - install(def); - } - } - - public static final void installTemp(byte[] pkg) throws IOException { - filePoolTemp.clear(); - filePoolTemp.putAll(filePool); - reset(); - install(pkg); - } - - public static final void resetTemp() throws IOException { - filePool.clear(); - filePool.putAll(filePoolTemp); - filePoolTemp.clear(); - } - public static final void install(byte[] pkg) throws IOException { - if (def == null) { - def = pkg; - } - ByteArrayInputStream in = new ByteArrayInputStream(pkg); - + byte[] header = new byte[8]; in.read(header); String type = readASCII(header); - + if("EAGPKG$$".equals(type)) { int l = pkg.length - 16; if(l < 1) { @@ -86,15 +59,15 @@ public class AssetRepository { throw new IOException("invalid epk file type '" + type + "'"); } } - + 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(); } - + private static final String readASCII(byte[] bytesIn) throws IOException { char[] charIn = new char[bytesIn.length]; for(int i = 0; i < bytesIn.length; ++i) { @@ -102,7 +75,7 @@ public class AssetRepository { } return new String(charIn); } - + private static final String readASCII(InputStream bytesIn) throws IOException { int len = bytesIn.read(); char[] charIn = new char[len]; @@ -111,54 +84,54 @@ public class AssetRepository { } return new String(charIn); } - + public static final void loadNew(InputStream is) throws IOException { - + 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 - + int numFiles = loadInt(is); - + char compressionType = (char)is.read(); - + InputStream zis; 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); + 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); } int blockFile = ('F' << 24) | ('I' << 16) | ('L' << 8) | 'E'; int blockEnd = ('E' << 24) | ('N' << 16) | ('D' << 8) | '$'; int blockHead = ('H' << 24) | ('E' << 16) | ('A' << 8) | 'D'; - + CRC32 crc32 = new CRC32(); int blockType; for(int i = 0; i < numFiles; ++i) { - + blockType = loadInt(zis); - + if(blockType == blockEnd) { throw new IOException("Unexpected END when there are still " + (numFiles - i) + " files remaining"); } - + String name = readASCII(zis); int len = loadInt(zis); - + if(i == 0) { if(blockType == blockHead) { byte[] readType = new byte[len]; @@ -174,14 +147,14 @@ public class AssetRepository { throw new IOException("File '" + name + "' did not have a file-type block as the first entry in the file"); } } - + if(blockType == blockFile) { if(len < 5) { throw new IOException("File '" + name + "' is incomplete"); } - + int expectedCRC = loadInt(zis); - + byte[] load = new byte[len - 5]; zis.read(load); @@ -192,13 +165,13 @@ public class AssetRepository { throw new IOException("File '" + name + "' has an invalid checksum"); } } - + if(zis.read() != ':') { throw new IOException("File '" + name + "' is incomplete"); } - + filePool.put(name, load); - + if(name.endsWith("title/eagtek.png")) { try { int off = 27375; @@ -218,14 +191,14 @@ public class AssetRepository { throw new IOException("Object '" + name + "' is incomplete"); } } - + if(loadInt(zis) != blockEnd) { throw new IOException("EPK missing END$ object"); } - + zis.close(); } - + public static final void loadOld(InputStream is) throws IOException { DataInputStream in = new DataInputStream(is); in.readUTF(); @@ -248,7 +221,7 @@ public class AssetRepository { } if(in.available() > 0 || !" end".equals(s)) throw new IOException("invalid epk file"); } - + public static final byte[] getResource(String path) { if(path.startsWith("/")) path = path.substring(1); return filePool.get(path); @@ -258,4 +231,4 @@ public class AssetRepository { filePool.put(path, EaglerAdapter.downloadURL(url)); } -} +} \ No newline at end of file diff --git a/src/main/java/net/lax1dude/eaglercraft/EPKDecompiler.java b/src/main/java/net/lax1dude/eaglercraft/EPKDecompiler.java new file mode 100644 index 0000000..5810de3 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/EPKDecompiler.java @@ -0,0 +1,217 @@ +package net.lax1dude.eaglercraft; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import com.jcraft.jzlib.CRC32; +import com.jcraft.jzlib.GZIPInputStream; +import com.jcraft.jzlib.InflaterInputStream; + +public class EPKDecompiler { + + 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 SHA1Digest dg; + private CRC32 crc32; + private int numFiles; + private boolean isFinished = false; + private boolean isOldFormat = false; + + public EPKDecompiler(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]; + zis.read(typeBytes); + String type = readASCII(typeBytes); + + if(numFiles == 0) { + if(!"END$".equals(type)) { + throw new IOException("EPK file is missing END code (END$)"); + } + 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]; + zis.read(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]; + zis.read(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; + dg = new SHA1Digest(); + 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; + return null; + }else if(!s.equals("")) { + throw new IOException("invalid epk file"); + } + String path = in.readUTF(); + byte[] digest = new byte[20]; + byte[] digest2 = new byte[20]; + in.read(digest); + int len = in.readInt(); + byte[] file = new byte[len]; + in.read(file); + dg.update(file, 0, len); dg.doFinal(digest2, 0); + if(!Arrays.equals(digest, digest2)) throw new IOException("invalid file hash for "+path); + if(!"".equals(in.readUTF())) throw new IOException("invalid epk file"); + return new FileEntry("FILE", path, file); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/GuiScreenRelay.java b/src/main/java/net/lax1dude/eaglercraft/GuiScreenRelay.java index a78f492..1fd78be 100644 --- a/src/main/java/net/lax1dude/eaglercraft/GuiScreenRelay.java +++ b/src/main/java/net/lax1dude/eaglercraft/GuiScreenRelay.java @@ -93,7 +93,7 @@ public class GuiScreenRelay extends GuiScreen { } lastRefresh += 60l; } else if(btn.id == 6) { - EaglerAdapter.downloadBytes("EaglerSPRelay.zip", AssetRepository.getResource("relay_download.zip")); + EaglerAdapter.downloadBytes("EaglerSPRelay.zip", EaglerAdapter.loadResourceBytes("relay_download.zip")); } } diff --git a/src/main/java/net/lax1dude/eaglercraft/TextureTerrainMap.java b/src/main/java/net/lax1dude/eaglercraft/TextureTerrainMap.java index 829ee78..c837a97 100644 --- a/src/main/java/net/lax1dude/eaglercraft/TextureTerrainMap.java +++ b/src/main/java/net/lax1dude/eaglercraft/TextureTerrainMap.java @@ -150,7 +150,7 @@ public class TextureTerrainMap implements IconRegister { } private void loadData() { - byte[] data = EaglerAdapter.loadResourceBytes("/" + map.basePath + name + ".png"); + byte[] data = Minecraft.getMinecraft().texturePackList.getSelectedTexturePack().getResourceAsBytes("/" + map.basePath + name + ".png"); if(data == null) { map.replaceTexture(this, map.missingData); }else { diff --git a/src/main/java/net/minecraft/client/Minecraft.java b/src/main/java/net/minecraft/client/Minecraft.java index 275ea74..eccb06d 100644 --- a/src/main/java/net/minecraft/client/Minecraft.java +++ b/src/main/java/net/minecraft/client/Minecraft.java @@ -1,12 +1,10 @@ package net.minecraft.client; -import java.io.IOException; import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.util.HashSet; import java.util.List; -import net.lax1dude.eaglercraft.AssetRepository; import net.lax1dude.eaglercraft.DefaultSkinRenderer; import net.lax1dude.eaglercraft.EaglerAdapter; import net.lax1dude.eaglercraft.EaglerProfile; @@ -19,7 +17,6 @@ import net.lax1dude.eaglercraft.IntegratedServer; import net.lax1dude.eaglercraft.IntegratedServerLAN; import net.lax1dude.eaglercraft.Voice; import net.lax1dude.eaglercraft.WorkerNetworkManager; -import net.lax1dude.eaglercraft.adapter.SimpleStorage; import net.lax1dude.eaglercraft.adapter.Tessellator; import net.lax1dude.eaglercraft.glemu.FixedFunctionShader; import net.minecraft.src.AchievementList; @@ -83,7 +80,6 @@ import net.minecraft.src.StatCollector; import net.minecraft.src.StatStringFormatKeyInv; import net.minecraft.src.StringTranslate; import net.minecraft.src.TextureManager; -import net.minecraft.src.TexturePackCustom; import net.minecraft.src.TexturePackList; import net.minecraft.src.Timer; import net.minecraft.src.WorldClient; @@ -339,14 +335,6 @@ public class Minecraft implements Runnable { String s = EaglerAdapter.getServerToJoinOnLaunch(); GuiScreen scr; - - if (!TexturePackList.defaultTexturePack.getTexturePackFileName().equals(this.gameSettings.skin)) { - try { - AssetRepository.reset(); - AssetRepository.install(SimpleStorage.get(this.gameSettings.skin)); - this.texturePackList.selectedTexturePack = new TexturePackCustom(this.gameSettings.skin, TexturePackList.defaultTexturePack); - } catch (IOException ignored) {} - } if(s != null) { scr = new GuiScreenEditProfile(new GuiConnecting(new GuiMainMenu(), this, new ServerData("Eaglercraft Server", s, false))); diff --git a/src/main/java/net/minecraft/src/FontRenderer.java b/src/main/java/net/minecraft/src/FontRenderer.java index 2f7bb29..a54da33 100644 --- a/src/main/java/net/minecraft/src/FontRenderer.java +++ b/src/main/java/net/minecraft/src/FontRenderer.java @@ -9,6 +9,7 @@ import net.lax1dude.eaglercraft.EaglerImage; import net.lax1dude.eaglercraft.EaglercraftRandom; import net.lax1dude.eaglercraft.TextureLocation; import net.lax1dude.eaglercraft.adapter.Tessellator; +import net.minecraft.client.Minecraft; public class FontRenderer { /** Array of width of all the characters in default.png */ @@ -131,7 +132,7 @@ public class FontRenderer { private void readFontTexture(String par1Str) { //EaglerImage e = EaglerImage.loadImage(EaglerAdapter.loadResourceBytes(par1Str)); - EaglerImage e = EaglerAdapter.loadPNG(EaglerAdapter.loadResourceBytes(par1Str)); + EaglerImage e = EaglerAdapter.loadPNG(Minecraft.getMinecraft().texturePackList.getSelectedTexturePack().getResourceAsBytes(par1Str)); int[] var5 = e.data; int var3 = e.w; int var4 = e.h; @@ -174,7 +175,7 @@ public class FontRenderer { } private void readGlyphSizes() { - this.glyphWidth = EaglerAdapter.loadResourceBytes("/font/glyph_sizes.bin"); + this.glyphWidth = Minecraft.getMinecraft().texturePackList.getSelectedTexturePack().getResourceAsBytes("/font/glyph_sizes.bin"); } /** diff --git a/src/main/java/net/minecraft/src/GuiMainMenu.java b/src/main/java/net/minecraft/src/GuiMainMenu.java index 767ccf6..2d69e39 100644 --- a/src/main/java/net/minecraft/src/GuiMainMenu.java +++ b/src/main/java/net/minecraft/src/GuiMainMenu.java @@ -5,7 +5,6 @@ import java.util.Calendar; import java.util.Date; import java.util.List; -import net.lax1dude.eaglercraft.AssetRepository; import net.lax1dude.eaglercraft.ConfigConstants; import net.lax1dude.eaglercraft.EaglerAdapter; import net.lax1dude.eaglercraft.EaglercraftRandom; @@ -172,7 +171,7 @@ public class GuiMainMenu extends GuiScreen { this.field_92019_w = this.field_92021_u + 12; } - ConfigConstants.panoramaBlur = AssetRepository.getResource("/title/no-pano-blur.flag") == null; + ConfigConstants.panoramaBlur = mc.texturePackList.getSelectedTexturePack().getResourceAsBytes("/title/no-pano-blur.flag") == null; if(this.ackLines.isEmpty()) { int width = 315; diff --git a/src/main/java/net/minecraft/src/GuiTexturePacks.java b/src/main/java/net/minecraft/src/GuiTexturePacks.java index e4c748f..96b556a 100644 --- a/src/main/java/net/minecraft/src/GuiTexturePacks.java +++ b/src/main/java/net/minecraft/src/GuiTexturePacks.java @@ -1,8 +1,15 @@ package net.minecraft.src; +import net.lax1dude.eaglercraft.EPKDecompiler; import net.lax1dude.eaglercraft.EaglerAdapter; -import net.lax1dude.eaglercraft.adapter.SimpleStorage; +import net.lax1dude.eaglercraft.EaglerInflater; +import net.lax1dude.eaglercraft.adapter.teavm.vfs.VFile; + +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; public class GuiTexturePacks extends GuiScreen { protected GuiScreen guiScreen; @@ -33,7 +40,7 @@ public class GuiTexturePacks extends GuiScreen { this.buttonList.add(new GuiSmallButton(5, this.width / 2 - 154, this.height - 48, var1.translateKey("texturePack.openFolder"))); this.buttonList.add(new GuiSmallButton(6, this.width / 2 + 4, this.height - 48, var1.translateKey("gui.done"))); this.mc.texturePackList.updateAvaliableTexturePacks(); - //this.fileLocation = (new File("texturepacks")).getAbsolutePath(); + this.fileLocation = "texturepacks"; this.guiTexturePackSlot = new GuiTexturePackSlot(this); this.guiTexturePackSlot.registerScrollButtons(this.buttonList, 7, 8); } @@ -98,7 +105,26 @@ public class GuiTexturePacks extends GuiScreen { if (isSelectingPack && EaglerAdapter.getFileChooserResultAvailable()) { isSelectingPack = false; String name = EaglerAdapter.getFileChooserResultName(); - SimpleStorage.set(name.replaceAll("[^A-Za-z0-9_]", "_"), name.toLowerCase().endsWith(".zip") ? TexturePackList.zipToEpk(EaglerAdapter.getFileChooserResult()) : EaglerAdapter.getFileChooserResult()); + String safeName = name.replaceAll("[^A-Za-z0-9_]", "_"); + try { + if (name.toLowerCase().endsWith(".zip")) { + ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(EaglerAdapter.getFileChooserResult())); + ZipEntry entry; + while ((entry = zipInputStream.getNextEntry()) != null) { + if (entry.isDirectory()) continue; + new VFile(fileLocation, safeName, entry.getName()).setAllBytes(EaglerInflater.getBytesFromInputStream(zipInputStream)); + } + zipInputStream.close(); + } else { + EPKDecompiler epkDecompiler = new EPKDecompiler(EaglerAdapter.getFileChooserResult()); + EPKDecompiler.FileEntry file; + while ((file = epkDecompiler.readFile()) != null) { + new VFile(fileLocation, safeName, file.name).setAllBytes(file.data); + } + } + } catch (IOException e) { + e.printStackTrace(); + } EaglerAdapter.clearFileChooserResult(); this.mc.displayGuiScreen(this); } @@ -111,7 +137,7 @@ public class GuiTexturePacks extends GuiScreen { List var3 = this.mc.texturePackList.availableTexturePacks(); if (par1) { - SimpleStorage.set(((ITexturePack) var3.get(par2)).getTexturePackFileName(), null); + new VFile(fileLocation, ((ITexturePack) var3.get(par2)).getTexturePackFileName()).deleteAll(); } else { try { this.mc.texturePackList.setTexturePack((ITexturePack) var3.get(par2)); @@ -122,7 +148,7 @@ public class GuiTexturePacks extends GuiScreen { this.mc.texturePackList.setTexturePack((ITexturePack) var3.get(0)); this.mc.renderEngine.refreshTextures(); this.mc.renderGlobal.loadRenderers(); - SimpleStorage.set(((ITexturePack) var3.get(par2)).getTexturePackFileName(), null); + new VFile(fileLocation, ((ITexturePack) var3.get(par2)).getTexturePackFileName()).deleteAll(); } } } diff --git a/src/main/java/net/minecraft/src/ITexturePack.java b/src/main/java/net/minecraft/src/ITexturePack.java index 0696877..2d5920d 100644 --- a/src/main/java/net/minecraft/src/ITexturePack.java +++ b/src/main/java/net/minecraft/src/ITexturePack.java @@ -1,11 +1,12 @@ package net.minecraft.src; +import java.io.IOException; import java.io.InputStream; -public interface ITexturePack { +public interface ITexturePack +{ /** - * Delete the OpenGL texture id of the pack's thumbnail image, and close the zip - * file in case of TexturePackCustom. + * Delete the OpenGL texture id of the pack's thumbnail image, and close the zip file in case of TexturePackCustom. */ void deleteTexturePack(RenderEngine var1); @@ -14,12 +15,12 @@ public interface ITexturePack { */ void bindThumbnailTexture(RenderEngine var1); - InputStream func_98137_a(String var1, boolean var2); + InputStream func_98137_a(String var1, boolean var2) throws IOException; /** * Gives a texture resource as InputStream. */ - InputStream getResourceAsStream(String var1); + byte[] getResourceAsBytes(String var1); /** * Get the texture pack ID @@ -27,26 +28,21 @@ public interface ITexturePack { String getTexturePackID(); /** - * Get the file name of the texture pack, or Default if not from a custom - * texture pack + * Get the file name of the texture pack, or Default if not from a custom texture pack */ String getTexturePackFileName(); /** - * Get the first line of the texture pack description (read from the pack.txt - * file) + * Get the first line of the texture pack description (read from the pack.txt file) */ String getFirstDescriptionLine(); /** - * Get the second line of the texture pack description (read from the pack.txt - * file) + * Get the second line of the texture pack description (read from the pack.txt file) */ String getSecondDescriptionLine(); boolean func_98138_b(String var1, boolean var2); boolean isCompatible(); - - byte[] getResourceAsBytes(String par1Str); } diff --git a/src/main/java/net/minecraft/src/TexturePackCustom.java b/src/main/java/net/minecraft/src/TexturePackCustom.java deleted file mode 100644 index 671781a..0000000 --- a/src/main/java/net/minecraft/src/TexturePackCustom.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.minecraft.src; - -import java.io.IOException; -import java.io.InputStream; - -import net.lax1dude.eaglercraft.AssetRepository; -import net.lax1dude.eaglercraft.EaglerAdapter; -import net.lax1dude.eaglercraft.adapter.SimpleStorage; - -public class TexturePackCustom extends TexturePackImplementation { - public TexturePackCustom(String name, ITexturePack base) { - super(name, name, base); - try { - AssetRepository.installTemp(SimpleStorage.get(name)); - this.loadThumbnailImage(); - this.loadDescription(); - AssetRepository.resetTemp(); - } catch (IOException ignored) {} - } - - public boolean func_98140_c(String par1Str) { - return EaglerAdapter.loadResource(par1Str) != null; - } - - public boolean isCompatible() { - return true; - } - - protected InputStream func_98139_b(String par1Str) throws IOException { - return EaglerAdapter.loadResource(par1Str); - } - - @Override - public byte[] getResourceAsBytes(String par1Str) { - return EaglerAdapter.loadResourceBytes(par1Str); - } -} diff --git a/src/main/java/net/minecraft/src/TexturePackDefault.java b/src/main/java/net/minecraft/src/TexturePackDefault.java index 7c04786..a867a2b 100644 --- a/src/main/java/net/minecraft/src/TexturePackDefault.java +++ b/src/main/java/net/minecraft/src/TexturePackDefault.java @@ -7,7 +7,7 @@ import net.lax1dude.eaglercraft.EaglerAdapter; public class TexturePackDefault extends TexturePackImplementation { public TexturePackDefault() { - super("default", "Default", (ITexturePack) null); + super("default", null, "Default", (ITexturePack) null); } /** @@ -25,7 +25,7 @@ public class TexturePackDefault extends TexturePackImplementation { return true; } - protected InputStream func_98139_b(String par1Str) throws IOException { + protected InputStream func_98139_b(String par1Str) { return EaglerAdapter.loadResource(par1Str); } diff --git a/src/main/java/net/minecraft/src/TexturePackFolder.java b/src/main/java/net/minecraft/src/TexturePackFolder.java new file mode 100644 index 0000000..89309d8 --- /dev/null +++ b/src/main/java/net/minecraft/src/TexturePackFolder.java @@ -0,0 +1,40 @@ +package net.minecraft.src; + +import net.lax1dude.eaglercraft.adapter.teavm.vfs.VFile; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class TexturePackFolder extends TexturePackImplementation +{ + public TexturePackFolder(String par1, VFile par2, ITexturePack par3ITexturePack) + { + super(par1, par2, par2.getName(), par3ITexturePack); + } + + protected InputStream func_98139_b(String par1Str) throws IOException + { + VFile var2 = new VFile(this.texturePackFile, par1Str.substring(1)); + + if (!var2.exists()) + { + throw new IOException(par1Str); + } + else + { + return new BufferedInputStream(var2.getInputStream()); + } + } + + public boolean func_98140_c(String par1Str) + { + VFile var2 = new VFile(this.texturePackFile, par1Str); + return var2.exists(); + } + + public boolean isCompatible() + { + return true; + } +} diff --git a/src/main/java/net/minecraft/src/TexturePackImplementation.java b/src/main/java/net/minecraft/src/TexturePackImplementation.java index 2b1611f..9ca4b1c 100644 --- a/src/main/java/net/minecraft/src/TexturePackImplementation.java +++ b/src/main/java/net/minecraft/src/TexturePackImplementation.java @@ -7,21 +7,26 @@ import java.io.InputStreamReader; import net.lax1dude.eaglercraft.EaglerAdapter; import net.lax1dude.eaglercraft.EaglerImage; -import net.lax1dude.eaglercraft.TextureLocation; +import net.lax1dude.eaglercraft.EaglerInflater; +import net.lax1dude.eaglercraft.adapter.teavm.vfs.VFile; -public abstract class TexturePackImplementation implements ITexturePack { +public abstract class TexturePackImplementation implements ITexturePack +{ /** - * Texture pack ID as returnd by generateTexturePackID(). Used only internally - * and not visible to the user. + * Texture pack ID as returnd by generateTexturePackID(). Used only internally and not visible to the user. */ private final String texturePackID; /** - * The name of the texture pack's zip file/directory or "Default" for the - * builtin texture pack. Shown in the GUI. + * The name of the texture pack's zip file/directory or "Default" for the builtin texture pack. Shown in the GUI. */ private final String texturePackFileName; + /** + * File object for the texture pack's zip file in TexturePackCustom or the directory in TexturePackFolder. + */ + protected final VFile texturePackFile; + /** * First line of texture pack description (from /pack.txt) displayed in the GUI */ @@ -39,9 +44,11 @@ public abstract class TexturePackImplementation implements ITexturePack { /** The texture id for this pcak's thumbnail image. */ private int thumbnailTextureName = -1; - protected TexturePackImplementation(String par1, String par3Str, ITexturePack par4ITexturePack) { + protected TexturePackImplementation(String par1, VFile par2File, String par3Str, ITexturePack par4ITexturePack) + { this.texturePackID = par1; this.texturePackFileName = par3Str; + this.texturePackFile = par2File; this.field_98141_g = par4ITexturePack; this.loadThumbnailImage(); this.loadDescription(); @@ -50,8 +57,10 @@ public abstract class TexturePackImplementation implements ITexturePack { /** * Truncate strings to at most 34 characters. Truncates description lines */ - private static String trimStringToGUIWidth(String par0Str) { - if (par0Str != null && par0Str.length() > 34) { + private static String trimStringToGUIWidth(String par0Str) + { + if (par0Str != null && par0Str.length() > 34) + { par0Str = par0Str.substring(0, 34); } @@ -61,48 +70,90 @@ public abstract class TexturePackImplementation implements ITexturePack { /** * Load and initialize thumbnailImage from the the /pack.png file. */ - protected void loadThumbnailImage() { - //this.thumbnailImage = EaglerImage.loadImage(EaglerAdapter.loadResourceBytes("/pack.png")); - this.thumbnailImage = EaglerAdapter.loadPNG(EaglerAdapter.loadResourceBytes("/pack.png")); - } - - /** - * Load texture pack description from /pack.txt file in the texture pack - */ - protected void loadDescription() { + private void loadThumbnailImage() + { InputStream var1 = null; - BufferedReader var2 = null; - try { - var1 = this.func_98139_b("/pack.txt"); - var2 = new BufferedReader(new InputStreamReader(var1)); - this.firstDescriptionLine = trimStringToGUIWidth(var2.readLine()); - this.secondDescriptionLine = trimStringToGUIWidth(var2.readLine()); - } catch (IOException var12) { + try + { + var1 = this.func_98137_a("/pack.png", false); + this.thumbnailImage = EaglerImage.loadImage(EaglerInflater.getBytesFromInputStream(var1)); + } + catch (IOException var11) + { ; - } finally { - try { - if (var2 != null) { - var2.close(); - } - - if (var1 != null) { + } + finally + { + try + { + if (var1 != null) + { var1.close(); } - } catch (IOException var11) { + } + catch (IOException var10) + { ; } } } - public InputStream func_98137_a(String par1Str, boolean par2) { - try { + /** + * Load texture pack description from /pack.txt file in the texture pack + */ + protected void loadDescription() + { + InputStream var1 = null; + BufferedReader var2 = null; + + try + { + var1 = this.func_98139_b("/pack.txt"); + var2 = new BufferedReader(new InputStreamReader(var1)); + this.firstDescriptionLine = trimStringToGUIWidth(var2.readLine()); + this.secondDescriptionLine = trimStringToGUIWidth(var2.readLine()); + } + catch (IOException var12) + { + ; + } + finally + { + try + { + if (var2 != null) + { + var2.close(); + } + + if (var1 != null) + { + var1.close(); + } + } + catch (IOException var11) + { + ; + } + } + } + + public InputStream func_98137_a(String par1Str, boolean par2) throws IOException + { + try + { return this.func_98139_b(par1Str); - } catch (IOException var4) { - if (this.field_98141_g != null && par2) { + } + catch (IOException var4) + { + if (this.field_98141_g != null && par2) + { return this.field_98141_g.func_98137_a(par1Str, true); - }else { - return null; + } + else + { + throw var4; } } } @@ -110,47 +161,53 @@ public abstract class TexturePackImplementation implements ITexturePack { /** * Gives a texture resource as InputStream. */ - public InputStream getResourceAsStream(String par1Str) { - return this.func_98137_a(par1Str, true); + public byte[] getResourceAsBytes(String par1Str) + { + try { + return EaglerInflater.getBytesFromInputStream(this.func_98137_a(par1Str, true)); + } catch (IOException e) { + return null; + } } protected abstract InputStream func_98139_b(String var1) throws IOException; /** - * Delete the OpenGL texture id of the pack's thumbnail image, and close the zip - * file in case of TexturePackCustom. + * Delete the OpenGL texture id of the pack's thumbnail image, and close the zip file in case of TexturePackCustom. */ - public void deleteTexturePack(RenderEngine par1RenderEngine) { - if (this.thumbnailImage != null && this.thumbnailTextureName != -1) { + public void deleteTexturePack(RenderEngine par1RenderEngine) + { + if (this.thumbnailImage != null && this.thumbnailTextureName != -1) + { par1RenderEngine.deleteTexture(this.thumbnailTextureName); } } - - private static final TextureLocation tex_unknown_pack = new TextureLocation("/gui/unknown_pack.png"); - + /** * Bind the texture id of the pack's thumbnail image, loading it if necessary. */ - public void bindThumbnailTexture(RenderEngine par1RenderEngine) { - if (this.thumbnailImage != null) { - if (this.thumbnailTextureName == -1) { + public void bindThumbnailTexture(RenderEngine par1RenderEngine) + { + if (this.thumbnailImage != null) + { + if (this.thumbnailTextureName == -1) + { this.thumbnailTextureName = par1RenderEngine.allocateAndSetupTexture(this.thumbnailImage); } EaglerAdapter.glBindTexture(EaglerAdapter.GL_TEXTURE_2D, this.thumbnailTextureName); par1RenderEngine.resetBoundTexture(); - } else { - tex_unknown_pack.bindTexture(); + } + else + { + par1RenderEngine.bindTexture("/gui/unknown_pack.png"); } } - public boolean func_98138_b(String par1Str, boolean par2) { - try { - boolean var3 = this.func_98140_c(par1Str); - return !var3 && par2 && this.field_98141_g != null ? this.field_98141_g.func_98138_b(par1Str, par2) : var3; - } catch (Exception e) { - return false; - } + public boolean func_98138_b(String par1Str, boolean par2) + { + boolean var3 = this.func_98140_c(par1Str); + return !var3 && par2 && this.field_98141_g != null ? this.field_98141_g.func_98138_b(par1Str, par2) : var3; } public abstract boolean func_98140_c(String var1); @@ -158,31 +215,32 @@ public abstract class TexturePackImplementation implements ITexturePack { /** * Get the texture pack ID */ - public String getTexturePackID() { + public String getTexturePackID() + { return this.texturePackID; } /** - * Get the file name of the texture pack, or Default if not from a custom - * texture pack + * Get the file name of the texture pack, or Default if not from a custom texture pack */ - public String getTexturePackFileName() { + public String getTexturePackFileName() + { return this.texturePackFileName; } /** - * Get the first line of the texture pack description (read from the pack.txt - * file) + * Get the first line of the texture pack description (read from the pack.txt file) */ - public String getFirstDescriptionLine() { + public String getFirstDescriptionLine() + { return this.firstDescriptionLine; } /** - * Get the second line of the texture pack description (read from the pack.txt - * file) + * Get the second line of the texture pack description (read from the pack.txt file) */ - public String getSecondDescriptionLine() { + public String getSecondDescriptionLine() + { return this.secondDescriptionLine; } } diff --git a/src/main/java/net/minecraft/src/TexturePackList.java b/src/main/java/net/minecraft/src/TexturePackList.java index 4d9efcc..fc61a3b 100644 --- a/src/main/java/net/minecraft/src/TexturePackList.java +++ b/src/main/java/net/minecraft/src/TexturePackList.java @@ -1,71 +1,68 @@ package net.minecraft.src; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.*; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; -import net.lax1dude.eaglercraft.AssetRepository; -import net.lax1dude.eaglercraft.EPK2Compiler; -import net.lax1dude.eaglercraft.EaglerAdapter; -import net.lax1dude.eaglercraft.adapter.SimpleStorage; +import net.lax1dude.eaglercraft.EaglerProfile; +import net.lax1dude.eaglercraft.adapter.teavm.vfs.VFile; import net.minecraft.client.Minecraft; -public class TexturePackList { +public class TexturePackList +{ /** - * An instance of TexturePackDefault for the always available builtin texture - * pack. + * An instance of TexturePackDefault for the always available builtin texture pack. */ - public static final ITexturePack defaultTexturePack = new TexturePackDefault(); + private static final ITexturePack defaultTexturePack = new TexturePackDefault(); /** The Minecraft instance. */ private final Minecraft mc; /** The directory the texture packs will be loaded from. */ - //private final File texturePackDir; + private final VFile texturePackDir; /** Folder for the multi-player texturepacks. Returns File. */ - //private final File mpTexturePackFolder; + private final VFile mpTexturePackFolder; /** The list of the available texture packs. */ private List availableTexturePacks = new ArrayList(); /** - * A mapping of texture IDs to TexturePackBase objects used by - * updateAvaliableTexturePacks() to avoid reloading texture packs that haven't - * changed on disk. + * A mapping of texture IDs to TexturePackBase objects used by updateAvaliableTexturePacks() to avoid reloading + * texture packs that haven't changed on disk. */ private Map texturePackCache = new HashMap(); /** The TexturePack that will be used. */ - public ITexturePack selectedTexturePack; + private ITexturePack selectedTexturePack; /** True if a texture pack is downloading in the background. */ private boolean isDownloading; - public TexturePackList(Minecraft par2Minecraft) { + public TexturePackList(Minecraft par2Minecraft) + { this.mc = par2Minecraft; - //this.texturePackDir = new File(par1File, "texturepacks"); - //this.mpTexturePackFolder = new File(par1File, "texturepacks-mp-cache"); + this.texturePackDir = new VFile("texturepacks"); + this.mpTexturePackFolder = new VFile("texturepacks-mp-cache"); this.updateAvaliableTexturePacks(); } /** - * Sets the new TexturePack to be used, returning true if it has actually - * changed, false if nothing changed. + * Sets the new TexturePack to be used, returning true if it has actually changed, false if nothing changed. */ - public boolean setTexturePack(ITexturePack par1ITexturePack) { - if (par1ITexturePack == this.selectedTexturePack) { + public boolean setTexturePack(ITexturePack par1ITexturePack) + { + if (par1ITexturePack == this.selectedTexturePack) + { return false; - } else { + } + else + { this.isDownloading = false; this.selectedTexturePack = par1ITexturePack; - try { - AssetRepository.reset(); - AssetRepository.install(SimpleStorage.get(this.selectedTexturePack.getTexturePackFileName())); - } catch (IOException ignored) {} this.mc.gameSettings.skin = par1ITexturePack.getTexturePackFileName(); this.mc.gameSettings.saveOptions(); return true; @@ -73,38 +70,53 @@ public class TexturePackList { } /** - * filename must end in .zip or .epk + * filename must end in .zip */ - public void requestDownloadOfTexture(String par1Str) { + public void requestDownloadOfTexture(String par1Str) + { String var2 = par1Str.substring(par1Str.lastIndexOf("/") + 1); - if (var2.contains("?")) { + if (var2.contains("?")) + { var2 = var2.substring(0, var2.indexOf("?")); } - if (var2.toLowerCase().endsWith(".zip") || var2.toLowerCase().endsWith(".epk")) { - this.downloadTexture(par1Str, var2); + if (var2.endsWith(".zip")) + { + VFile var3 = new VFile(this.mpTexturePackFolder, var2); + this.downloadTexture(par1Str, var3); } } - private void downloadTexture(String par1Str, String par2File) { + private void downloadTexture(String par1Str, VFile par2File) + { + HashMap var3 = new HashMap(); + GuiProgress var4 = new GuiProgress(); + var3.put("X-Minecraft-Username", EaglerProfile.username); + var3.put("X-Minecraft-Version", "1.5.2"); + var3.put("X-Minecraft-Supported-Resolutions", "16"); this.isDownloading = true; - SimpleStorage.set(par2File.replaceAll("[^A-Za-z0-9_]", "_"), par2File.toLowerCase().endsWith(".zip") ? zipToEpk(EaglerAdapter.downloadURL(par1Str)) : EaglerAdapter.downloadURL(par1Str)); - this.onDownloadFinished(); + this.mc.displayGuiScreen(var4); + // todo: extract epk/zip to VFS, activate, and signal success + onDownloadFinished(); // temp + // SimpleStorage.set(par2File.replaceAll("[^A-Za-z0-9_]", "_"), par2File.toLowerCase().endsWith(".zip") ? zipToEpk(EaglerAdapter.downloadURL(par1Str)) : EaglerAdapter.downloadURL(par1Str)); + // HttpUtil.downloadTexturePack(par2File, par1Str, new TexturePackDownloadSuccess(this), var3, 10000000, var4); } /** * Return true if a texture pack is downloading in the background. */ - public boolean getIsDownloading() { + public boolean getIsDownloading() + { return this.isDownloading; } /** - * Called from Minecraft.loadWorld() if getIsDownloading() returned true to - * prepare the downloaded texture for usage. + * Called from Minecraft.loadWorld() if getIsDownloading() returned true to prepare the downloaded texture for + * usage. */ - public void onDownloadFinished() { + public void onDownloadFinished() + { this.isDownloading = false; this.updateAvaliableTexturePacks(); this.mc.scheduleTexturePackRefresh(); @@ -113,41 +125,43 @@ public class TexturePackList { /** * check the texture packs the client has installed */ - public void updateAvaliableTexturePacks() { + public void updateAvaliableTexturePacks() + { ArrayList var1 = new ArrayList(); + this.selectedTexturePack = defaultTexturePack; var1.add(defaultTexturePack); Iterator var2 = this.getTexturePackDirContents().iterator(); - while (var2.hasNext()) { - String var3 = (String) var2.next(); + while (var2.hasNext()) + { + VFile var3 = (VFile)var2.next(); + String var4 = this.generateTexturePackID(var3); - Object var5 = (ITexturePack) this.texturePackCache.get(var3); + if (var4 != null) + { + Object var5 = (ITexturePack)this.texturePackCache.get(var4); - if (var5 == null) { - try { - var5 = new TexturePackCustom(var3, defaultTexturePack); - this.texturePackCache.put(var3, var5); - } catch (RuntimeException e) { - e.printStackTrace(); // bad texture pack + if (var5 == null) + { + var5 = new TexturePackFolder(var4, var3, defaultTexturePack); + this.texturePackCache.put(var4, var5); } - } - if (((ITexturePack) var5).getTexturePackFileName().equals(this.mc.gameSettings.skin)) { - this.selectedTexturePack = (ITexturePack) var5; - try { - AssetRepository.reset(); - AssetRepository.install(SimpleStorage.get(this.selectedTexturePack.getTexturePackFileName())); - } catch (IOException ignored) {} - } + if (((ITexturePack)var5).getTexturePackFileName().equals(this.mc.gameSettings.skin)) + { + this.selectedTexturePack = (ITexturePack)var5; + } - var1.add(var5); + var1.add(var5); + } } this.availableTexturePacks.removeAll(var1); var2 = this.availableTexturePacks.iterator(); - while (var2.hasNext()) { - ITexturePack var6 = (ITexturePack) var2.next(); + while (var2.hasNext()) + { + ITexturePack var6 = (ITexturePack)var2.next(); var6.deleteTexturePack(this.mc.renderEngine); this.texturePackCache.remove(var6.getTexturePackID()); } @@ -156,103 +170,104 @@ public class TexturePackList { } /** - * Generate an internal texture pack ID from the file/directory name, last - * modification time, and file size. Returns null if the file/directory is not a - * texture pack. + * Generate an internal texture pack ID from the file/directory name, last modification time, and file size. Returns + * null if the file/directory is not a texture pack. */ -// private String generateTexturePackID(File par1File) { -// return par1File.isFile() && par1File.getName().toLowerCase().endsWith(".zip") ? par1File.getName() + ":" + par1File.length() + ":" + par1File.lastModified() -// : (par1File.isDirectory() && (new File(par1File, "pack.txt")).exists() ? par1File.getName() + ":folder:" + par1File.lastModified() : null); -// } + private String generateTexturePackID(VFile par1File) + { + return (new VFile(par1File, "pack.txt")).exists() ? par1File.getName() + ":folder" : null; + } /** - * Return a List of file/directories in the texture pack directory. + * Return a List of file/directories in the texture pack directory. */ - private List getTexturePackDirContents() { - return SimpleStorage.isAvailable() ? Arrays.asList(SimpleStorage.list()) : Collections.emptyList(); + private List getTexturePackDirContents() + { + // TODO: MAKE THIS MORE EFFICIENT!! THIS IS A TEMPORARY FIX BC IM TIRED + List strings = this.texturePackDir.list(); + List strings2 = new ArrayList<>(); + List files = new ArrayList<>(); + for (String name : strings) { + name = name.substring(this.texturePackDir.getPath().length() + 1); + name = name.substring(0, name.indexOf('/')); + name = this.texturePackDir.getPath() + "/" + name; + if (strings2.contains(name)) continue; + strings2.add(name); + files.add(new VFile(name)); + } + strings2.clear(); + strings.clear(); + return files; } /** * Returns a list of the available texture packs. */ - public List availableTexturePacks() { + public List availableTexturePacks() + { return Collections.unmodifiableList(this.availableTexturePacks); } - public ITexturePack getSelectedTexturePack() { - if (this.selectedTexturePack == null) { - this.selectedTexturePack = defaultTexturePack; - } + public ITexturePack getSelectedTexturePack() + { return this.selectedTexturePack; } - public boolean func_77300_f() { - if (!this.mc.gameSettings.serverTextures) { + public boolean func_77300_f() + { + if (!this.mc.gameSettings.serverTextures) + { return false; - } else { + } + else + { ServerData var1 = this.mc.getServerData(); return var1 == null ? true : var1.func_78840_c(); } } - public boolean getAcceptsTextures() { - if (!this.mc.gameSettings.serverTextures) { + public boolean getAcceptsTextures() + { + if (!this.mc.gameSettings.serverTextures) + { return false; - } else { + } + else + { ServerData var1 = this.mc.getServerData(); return var1 == null ? false : var1.getAcceptsTextures(); } } - static boolean isDownloading(TexturePackList par0TexturePackList) { + static boolean isDownloading(TexturePackList par0TexturePackList) + { return par0TexturePackList.isDownloading; } /** * Set the selectedTexturePack field (Inner class static accessor method). */ - static ITexturePack setSelectedTexturePack(TexturePackList par0TexturePackList, ITexturePack par1ITexturePack) { + static ITexturePack setSelectedTexturePack(TexturePackList par0TexturePackList, ITexturePack par1ITexturePack) + { return par0TexturePackList.selectedTexturePack = par1ITexturePack; } -/* + /** - * Generate an internal texture pack ID from the file/directory name, last - * modification time, and file size. Returns null if the file/directory is not a - * texture pack. (Inner class static accessor method). - - static String generateTexturePackID(TexturePackList par0TexturePackList, File par1File) { + * Generate an internal texture pack ID from the file/directory name, last modification time, and file size. Returns + * null if the file/directory is not a texture pack. (Inner class static accessor method). + */ + static String generateTexturePackID(TexturePackList par0TexturePackList, VFile par1File) + { return par0TexturePackList.generateTexturePackID(par1File); } -*/ - static ITexturePack func_98143_h() { + + static ITexturePack func_98143_h() + { return defaultTexturePack; } - static Minecraft getMinecraft(TexturePackList par0TexturePackList) { + static Minecraft getMinecraft(TexturePackList par0TexturePackList) + { return par0TexturePackList.mc; } - - public static final byte[] zipToEpk(byte[] in) { - try { - EPK2Compiler epk2Compiler = new EPK2Compiler(); - try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(in))) { - ZipEntry zipEntry; - byte[] bb = new byte[16000]; - while ((zipEntry = zis.getNextEntry()) != null) { - if (zipEntry.isDirectory()) continue; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int len; - while ((len = zis.read(bb)) != -1) { - baos.write(bb, 0, len); - } - baos.close(); - epk2Compiler.append(zipEntry.getName(), baos.toByteArray()); - } - } - return epk2Compiler.complete(); - } catch (IOException e) { - e.printStackTrace(); - return in; - } - } } diff --git a/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/BooleanResult.java b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/BooleanResult.java new file mode 100644 index 0000000..c967763 --- /dev/null +++ b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/BooleanResult.java @@ -0,0 +1,18 @@ +package net.lax1dude.eaglercraft.adapter.teavm.vfs; + +public class BooleanResult { + + public static final BooleanResult TRUE = new BooleanResult(true); + public static final BooleanResult FALSE = new BooleanResult(false); + + public final boolean bool; + + private BooleanResult(boolean b) { + bool = b; + } + + public static BooleanResult _new(boolean b) { + return b ? TRUE : FALSE; + } + +} \ No newline at end of file diff --git a/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/SYS.java b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/SYS.java new file mode 100644 index 0000000..b46a910 --- /dev/null +++ b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/SYS.java @@ -0,0 +1,19 @@ +package net.lax1dude.eaglercraft.adapter.teavm.vfs; + +public class SYS { + + public static final VirtualFilesystem VFS; + + static { + + VirtualFilesystem.VFSHandle vh = VirtualFilesystem.openVFS("_net_lax1dude_eaglercraft_adapter_teavm_vfs_VirtualFilesystem_1_5_2_eagStorage"); + + if(vh.vfs == null) { + System.err.println("Could not init filesystem!"); + throw new RuntimeException("Could not init filesystem: VFSHandle.vfs was null"); + } + + VFS = vh.vfs; + + } +} diff --git a/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VFSIterator.java b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VFSIterator.java new file mode 100644 index 0000000..128582a --- /dev/null +++ b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VFSIterator.java @@ -0,0 +1,17 @@ +package net.lax1dude.eaglercraft.adapter.teavm.vfs; + +public interface VFSIterator { + + public static class BreakLoop extends RuntimeException { + public BreakLoop() { + super("iterator loop break request"); + } + } + + public default void end() { + throw new BreakLoop(); + } + + public void next(VIteratorFile entry); + +} diff --git a/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VFile.java b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VFile.java new file mode 100644 index 0000000..849681d --- /dev/null +++ b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VFile.java @@ -0,0 +1,227 @@ +package net.lax1dude.eaglercraft.adapter.teavm.vfs; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class VFile { + + public static final String pathSeperator = "/"; + public static final String[] altPathSeperator = new String[] { "\\" }; + + public static String normalizePath(String p) { + for(int i = 0; i < altPathSeperator.length; ++i) { + p = p.replace(altPathSeperator[i], pathSeperator); + } + if(p.startsWith(pathSeperator)) { + p = p.substring(1); + } + if(p.endsWith(pathSeperator)) { + p = p.substring(0, p.length() - pathSeperator.length()); + } + return p; + } + + public static String[] splitPath(String p) { + String[] pth = normalizePath(p).split(pathSeperator); + for(int i = 0; i < pth.length; ++i) { + pth[i] = pth[i].trim(); + } + return pth; + } + + protected String path; + + public static String createPath(Object... p) { + ArrayList r = new ArrayList<>(); + for(int i = 0; i < p.length; ++i) { + if(p[i] == null) { + continue; + } + String gg = p[i].toString(); + if(gg == null) { + continue; + } + String[] parts = splitPath(gg); + for(int j = 0; j < parts.length; ++j) { + if(parts[j] == null || parts[j].equals(".")) { + continue; + }else if(parts[j].equals("..") && r.size() > 0) { + int k = r.size() - 1; + if(!r.get(k).equals("..")) { + r.remove(k); + }else { + r.add(".."); + } + }else { + r.add(parts[j]); + } + } + } + if(r.size() > 0) { + StringBuilder s = new StringBuilder(); + for(int i = 0; i < r.size(); ++i) { + if(i > 0) { + s.append(pathSeperator); + } + s.append(r.get(i)); + } + return s.toString(); + }else { + return null; + } + } + + public VFile(Object... p) { + this.path = createPath(p); + } + + public InputStream getInputStream() { + return isRelative() ? null : SYS.VFS.getFile(path).getInputStream(); + } + + public OutputStream getOutputStream() { + return isRelative() ? null : SYS.VFS.getFile(path).getOutputStream(); + } + + public String toString() { + return path; + } + + public boolean isRelative() { + return path == null || path.contains(".."); + } + + public boolean canRead() { + return !isRelative() && SYS.VFS.fileExists(path); + } + + public String getPath() { + return path.equals("unnamed") ? null : path; + } + + public String getName() { + if(path == null) { + return null; + } + int i = path.indexOf(pathSeperator); + return i == -1 ? path : path.substring(i + 1); + } + + public boolean canWrite() { + return !isRelative(); + } + + public String getParent() { + if(path == null) { + return null; + } + int i = path.indexOf(pathSeperator); + return i == -1 ? ".." : path.substring(0, i); + } + + public int hashCode() { + return path == null ? 0 : path.hashCode(); + } + + public boolean equals(Object o) { + return path != null && o != null && (o instanceof VFile) && path.equals(((VFile)o).path); + } + + public boolean exists() { + return !isRelative() && SYS.VFS.fileExists(path); + } + + public boolean delete() { + return !isRelative() && SYS.VFS.deleteFile(path); + } + + public boolean renameTo(String p, boolean copy) { + if(!isRelative() && SYS.VFS.renameFile(path, p, copy)) { + path = p; + return true; + } + return false; + } + + public int length() { + return isRelative() ? -1 : SYS.VFS.getFile(path).getSize(); + } + + public void getBytes(int fileOffset, byte[] array, int offset, int length) { + if(isRelative()) { + throw new ArrayIndexOutOfBoundsException("File is relative"); + } + SYS.VFS.getFile(path).getBytes(fileOffset, array, offset, length); + } + + public void setCacheEnabled() { + if(isRelative()) { + throw new RuntimeException("File is relative"); + } + SYS.VFS.getFile(path).setCacheEnabled(); + } + + public byte[] getAllBytes() { + if(isRelative()) { + return null; + } + return SYS.VFS.getFile(path).getAllBytes(); + } + + public String getAllChars() { + if(isRelative()) { + return null; + } + return SYS.VFS.getFile(path).getAllChars(); + } + + public String[] getAllLines() { + if(isRelative()) { + return null; + } + return SYS.VFS.getFile(path).getAllLines(); + } + + public byte[] getAllBytes(boolean copy) { + if(isRelative()) { + return null; + } + return SYS.VFS.getFile(path).getAllBytes(copy); + } + + public boolean setAllChars(String bytes) { + if(isRelative()) { + return false; + } + return SYS.VFS.getFile(path).setAllChars(bytes); + } + + public boolean setAllBytes(byte[] bytes) { + if(isRelative()) { + return false; + } + return SYS.VFS.getFile(path).setAllBytes(bytes); + } + + public boolean setAllBytes(byte[] bytes, boolean copy) { + if(isRelative()) { + return false; + } + return SYS.VFS.getFile(path).setAllBytes(bytes, copy); + } + + public List list() { + if(isRelative()) { + return Arrays.asList(path); + } + return SYS.VFS.listFiles(path); + } + + public int deleteAll() { + return isRelative() ? 0 : SYS.VFS.deleteFiles(path); + } + +} diff --git a/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VIteratorFile.java b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VIteratorFile.java new file mode 100644 index 0000000..cc96f9d --- /dev/null +++ b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VIteratorFile.java @@ -0,0 +1,289 @@ +package net.lax1dude.eaglercraft.adapter.teavm.vfs; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.dom.events.Event; +import org.teavm.jso.dom.events.EventListener; +import org.teavm.jso.indexeddb.IDBCursor; +import org.teavm.jso.indexeddb.IDBRequest; +import org.teavm.jso.typedarrays.ArrayBuffer; +import org.teavm.jso.typedarrays.Uint8Array; + +/** + * Do not use an instance of this class outside of the VFSIterator.next() method + */ +public class VIteratorFile extends VFile { + + static final VIteratorFile instance = new VIteratorFile(); + + private VIteratorFile() { + super(""); + this.idx = -1; + this.cur = null; + this.vfs = null; + } + + private static class VirtualIteratorOutputStream extends ByteArrayOutputStream { + + private final VIteratorFile itr; + + protected VirtualIteratorOutputStream(VIteratorFile itr) { + this.itr = itr; + } + + public void close() throws IOException { + if(!itr.setAllBytes(super.toByteArray(), false)) { + throw new IOException("Could not close stream and write to \"" + itr.path + "\" on VFS \"" + itr.vfs.database + "\" (the file was probably deleted)"); + } + } + + } + + private int idx; + private IDBCursor cur; + private VirtualFilesystem vfs; + private boolean wasDeleted; + + @JSBody(params = { "k" }, script = "return ((typeof k) === \"string\") ? k : (((typeof k) === \"undefined\") ? null : (((typeof k[0]) === \"string\") ? k[0] : null));") + private static native String readKey(JSObject k); + + static VIteratorFile create(int idx, VirtualFilesystem vfs, IDBCursor cur) { + String k = readKey(cur.getKey()); + if(k == null) { + return null; + } + instance.update(idx, k, vfs, cur); + return instance; + } + + public VFile makeVFile() { + return new VFile(path); + } + + private void update(int idx, String path, VirtualFilesystem vfs, IDBCursor cur) { + this.idx = idx; + this.path = path; + this.vfs = vfs; + this.cur = cur; + this.wasDeleted = false; + } + + public InputStream getInputStream() { + return !wasDeleted ? new ByteArrayInputStream(getAllBytes()) : null; + } + + public OutputStream getOutputStream() { + return !wasDeleted ? new VirtualIteratorOutputStream(this) : null; + } + + public String toString() { + return path; + } + + public boolean isRelative() { + return false; + } + + public boolean canRead() { + return !wasDeleted; + } + + public String getPath() { + return path; + } + + public String getName() { + if(path == null) { + return null; + } + int i = path.indexOf(pathSeperator); + return i == -1 ? path : path.substring(i + 1); + } + + public boolean canWrite() { + return !wasDeleted; + } + + public String getParent() { + if(path == null) { + return null; + } + int i = path.indexOf(pathSeperator); + return i == -1 ? ".." : path.substring(0, i); + } + + public int hashCode() { + return path == null ? 0 : path.hashCode(); + } + + public boolean equals(Object o) { + return path != null && o != null && (o instanceof VFile) && path.equals(((VFile)o).path); + } + + public boolean exists() { + return !wasDeleted; + } + + public boolean delete() { + return wasDeleted = AsyncHandlers.awaitRequest(cur.delete()).bool; + } + + public boolean renameTo(String p) { + byte[] data = getAllBytes(); + String op = path; + path = p; + if(!setAllBytes(data)) { + path = op; + return false; + } + path = op; + if(!delete()) { + return false; + } + path = p; + return true; + } + + public int length() { + JSObject obj = cur.getValue(); + + if(obj == null) { + throw new RuntimeException("Value of entry is missing"); + } + + ArrayBuffer arr = readRow(obj); + + if(arr == null) { + throw new RuntimeException("Value of the fucking value of the entry is missing"); + } + + return arr.getByteLength(); + } + + public void getBytes(int fileOffset, byte[] array, int offset, int length) { + JSObject obj = cur.getValue(); + + if(obj == null) { + throw new ArrayIndexOutOfBoundsException("Value of entry is missing"); + } + + ArrayBuffer arr = readRow(obj); + + if(arr == null) { + throw new ArrayIndexOutOfBoundsException("Value of the fucking value of the entry is missing"); + } + + Uint8Array a = new Uint8Array(arr); + + if(a.getLength() < fileOffset + length) { + throw new ArrayIndexOutOfBoundsException("file '" + path + "' size was "+a.getLength()+" but user tried to read index "+(fileOffset + length - 1)); + } + + for(int i = 0; i < length; ++i) { + array[i + offset] = (byte)a.get(i + fileOffset); + } + } + + public void setCacheEnabled() { + // no + } + + @JSBody(params = { "obj" }, script = "return (typeof obj === 'undefined') ? null : ((typeof obj.data === 'undefined') ? null : obj.data);") + private static native ArrayBuffer readRow(JSObject obj); + + public byte[] getAllBytes() { + JSObject obj = cur.getValue(); + + if(obj == null) { + return null; + } + + ArrayBuffer arr = readRow(obj); + + if(arr == null) { + return null; + } + + Uint8Array a = new Uint8Array(arr); + int ii = a.getByteLength(); + + byte[] array = new byte[ii]; + for(int i = 0; i < ii; ++i) { + array[i] = (byte)a.get(i); + } + + return array; + } + + public String getAllChars() { + return VirtualFilesystem.utf8(getAllBytes()); + } + + public String[] getAllLines() { + return VirtualFilesystem.lines(VirtualFilesystem.utf8(getAllBytes())); + } + + public byte[] getAllBytes(boolean copy) { + return getAllBytes(); + } + + public boolean setAllChars(String bytes) { + return setAllBytes(VirtualFilesystem.utf8(bytes)); + } + + public List list() { + throw new RuntimeException("Cannot perform list all in VFS callback"); + } + + public int deleteAll() { + throw new RuntimeException("Cannot perform delete all in VFS callback"); + } + + @JSBody(params = { "pat", "dat" }, script = "return { path: pat, data: dat };") + private static native JSObject writeRow(String name, ArrayBuffer data); + + public boolean setAllBytes(byte[] bytes) { + ArrayBuffer a = new ArrayBuffer(bytes.length); + Uint8Array ar = new Uint8Array(a); + ar.set(bytes); + JSObject obj = writeRow(path, a); + BooleanResult r = AsyncHandlers.awaitRequest(cur.update(obj)); + return r.bool; + } + + public boolean setAllBytes(byte[] bytes, boolean copy) { + return setAllBytes(bytes); + } + + public static class AsyncHandlers { + + @Async + public static native BooleanResult awaitRequest(IDBRequest r); + + private static void awaitRequest(IDBRequest r, final AsyncCallback cb) { + r.addEventListener("success", new EventListener() { + @Override + public void handleEvent(Event evt) { + cb.complete(BooleanResult._new(true)); + } + }); + r.addEventListener("error", new EventListener() { + @Override + public void handleEvent(Event evt) { + cb.complete(BooleanResult._new(false)); + } + }); + } + + } + +} diff --git a/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VirtualFilesystem.java b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VirtualFilesystem.java new file mode 100644 index 0000000..91ebedb --- /dev/null +++ b/src/teavm/java/net/lax1dude/eaglercraft/adapter/teavm/vfs/VirtualFilesystem.java @@ -0,0 +1,681 @@ +package net.lax1dude.eaglercraft.adapter.teavm.vfs; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import net.lax1dude.eaglercraft.EaglerAdapter; +import net.lax1dude.eaglercraft.adapter.teavm.TeaVMUtils; +import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.dom.events.EventListener; +import org.teavm.jso.indexeddb.EventHandler; +import org.teavm.jso.indexeddb.IDBCountRequest; +import org.teavm.jso.indexeddb.IDBCursor; +import org.teavm.jso.indexeddb.IDBCursorRequest; +import org.teavm.jso.indexeddb.IDBDatabase; +import org.teavm.jso.indexeddb.IDBFactory; +import org.teavm.jso.indexeddb.IDBGetRequest; +import org.teavm.jso.indexeddb.IDBObjectStoreParameters; +import org.teavm.jso.indexeddb.IDBOpenDBRequest; +import org.teavm.jso.indexeddb.IDBRequest; +import org.teavm.jso.indexeddb.IDBTransaction; +import org.teavm.jso.indexeddb.IDBVersionChangeEvent; +import org.teavm.jso.typedarrays.ArrayBuffer; +import org.teavm.jso.typedarrays.Int8Array; + +public class VirtualFilesystem { + + protected static class VirtualOutputStream extends ByteArrayOutputStream { + private final VFSFile file; + + protected VirtualOutputStream(VFSFile file) { + this.file = file; + } + + public void close() throws IOException { + if(!file.setAllBytes(super.toByteArray(), false)) { + throw new IOException("Could not close stream and write to \"" + file.filePath + "\" on VFS \"" + file.virtualFilesystem.database + "\" (the file was probably deleted)"); + } + } + } + + public static class VFSFile { + + public final VirtualFilesystem virtualFilesystem; + protected boolean cacheEnabled; + protected String filePath; + protected int fileSize = -1; + protected boolean hasBeenDeleted = false; + protected boolean hasBeenAccessed = false; + protected boolean exists = false; + + protected byte[] cache = null; + protected long cacheHit; + + protected VFSFile(VirtualFilesystem vfs, String filePath, boolean cacheEnabled) { + this.virtualFilesystem = vfs; + this.filePath = filePath; + this.cacheHit = EaglerAdapter.steadyTimeMillis(); + if(cacheEnabled) { + setCacheEnabled(); + } + } + + public boolean equals(Object o) { + return (o instanceof VFSFile) && ((VFSFile)o).filePath.equals(filePath); + } + + public int hashCode() { + return filePath.hashCode(); + } + + public String getPath() { + return filePath; + } + + public int getSize() { + cacheHit = EaglerAdapter.steadyTimeMillis(); + if(fileSize < 0) { + if(cacheEnabled) { + byte[] b = getAllBytes(false); + if(b != null) { + fileSize = b.length; + } + }else { + ArrayBuffer dat = AsyncHandlers.readWholeFile(virtualFilesystem.indexeddb, filePath); + if(dat != null) { + fileSize = dat.getByteLength(); + } + } + } + return fileSize; + } + + public InputStream getInputStream() { + byte[] dat = getAllBytes(false); + if(dat == null) { + return null; + } + return new ByteArrayInputStream(dat); + } + + public OutputStream getOutputStream() { + return new VirtualOutputStream(this); + } + + public void getBytes(int fileOffset, byte[] array, int offset, int length) { + if(hasBeenDeleted) { + throw new ArrayIndexOutOfBoundsException("file '" + filePath + "' has been deleted"); + }else if(hasBeenAccessed && !exists) { + throw new ArrayIndexOutOfBoundsException("file '" + filePath + "' does not exist"); + } + cacheHit = EaglerAdapter.steadyTimeMillis(); + if(cacheEnabled && cache != null) { + System.arraycopy(cache, fileOffset, array, offset, length); + }else { + ArrayBuffer aa = AsyncHandlers.readWholeFile(virtualFilesystem.indexeddb, filePath); + hasBeenAccessed = true; + if(aa != null) { + exists = true; + }else { + exists = false; + throw new ArrayIndexOutOfBoundsException("file '" + filePath + "' does not exist"); + } + this.fileSize = aa.getByteLength(); + if(cacheEnabled) { + cache = TeaVMUtils.wrapByteArrayBuffer(aa); + } + if(fileSize < fileOffset + length) { + throw new ArrayIndexOutOfBoundsException("file '" + filePath + "' size was "+fileSize+" but user tried to read index "+(fileOffset + length - 1)); + } + TeaVMUtils.unwrapByteArray(array).set(new Int8Array(aa, fileOffset, length), offset); + } + } + + public void setCacheEnabled() { + if(!cacheEnabled && !hasBeenDeleted && !(hasBeenAccessed && !exists)) { + cacheHit = EaglerAdapter.steadyTimeMillis(); + cache = getAllBytes(false); + cacheEnabled = true; + } + } + + public byte[] getAllBytes() { + return getAllBytes(false); + } + + public String getAllChars() { + return utf8(getAllBytes(false)); + } + + public String[] getAllLines() { + return lines(getAllChars()); + } + + public byte[] getAllBytes(boolean copy) { + if(hasBeenDeleted || (hasBeenAccessed && !exists)) { + return null; + } + cacheHit = EaglerAdapter.steadyTimeMillis(); + if(cacheEnabled && cache != null) { + byte[] b = cache; + if(copy) { + b = new byte[cache.length]; + System.arraycopy(cache, 0, b, 0, cache.length); + } + return b; + }else { + hasBeenAccessed = true; + ArrayBuffer b = AsyncHandlers.readWholeFile(virtualFilesystem.indexeddb, filePath); + if(b != null) { + exists = true; + }else { + exists = false; + return null; + } + this.fileSize = b.getByteLength(); + if(cacheEnabled) { + if(copy) { + cache = new byte[fileSize]; + TeaVMUtils.unwrapByteArray(cache).set(new Int8Array(b)); + }else { + cache = TeaVMUtils.wrapByteArrayBuffer(b); + } + } + return TeaVMUtils.wrapByteArrayBuffer(b); + } + } + + public boolean setAllChars(String bytes) { + return setAllBytes(utf8(bytes), true); + } + + public boolean setAllBytes(byte[] bytes) { + return setAllBytes(bytes, true); + } + + public boolean setAllBytes(byte[] bytes, boolean copy) { + if(hasBeenDeleted || bytes == null) { + return false; + } + cacheHit = EaglerAdapter.steadyTimeMillis(); + this.fileSize = bytes.length; + if(cacheEnabled) { + byte[] copz = bytes; + if(copy) { + copz = new byte[bytes.length]; + System.arraycopy(bytes, 0, copz, 0, bytes.length); + } + cache = copz; + return sync(); + }else { + boolean s = AsyncHandlers.writeWholeFile(virtualFilesystem.indexeddb, filePath, + TeaVMUtils.unwrapArrayBuffer(bytes)).bool; + hasBeenAccessed = true; + exists = exists || s; + return s; + } + } + + public boolean sync() { + if(cacheEnabled && cache != null && !hasBeenDeleted) { + cacheHit = EaglerAdapter.steadyTimeMillis(); + boolean tryWrite = AsyncHandlers.writeWholeFile(virtualFilesystem.indexeddb, filePath, + TeaVMUtils.unwrapArrayBuffer(cache)).bool; + hasBeenAccessed = true; + exists = exists || tryWrite; + return tryWrite; + } + return false; + } + + public boolean delete() { + if(!hasBeenDeleted && !(hasBeenAccessed && !exists)) { + cacheHit = EaglerAdapter.steadyTimeMillis(); + if(!AsyncHandlers.deleteFile(virtualFilesystem.indexeddb, filePath).bool) { + hasBeenAccessed = true; + return false; + } + virtualFilesystem.fileMap.remove(filePath); + hasBeenDeleted = true; + hasBeenAccessed = true; + exists = false; + return true; + } + return false; + } + + public boolean rename(String newName, boolean copy) { + if(!hasBeenDeleted && !(hasBeenAccessed && !exists)) { + cacheHit = EaglerAdapter.steadyTimeMillis(); + ArrayBuffer arr = AsyncHandlers.readWholeFile(virtualFilesystem.indexeddb, filePath); + hasBeenAccessed = true; + if(arr != null) { + exists = true; + if(!AsyncHandlers.writeWholeFile(virtualFilesystem.indexeddb, newName, arr).bool) { + return false; + } + if(!copy && !AsyncHandlers.deleteFile(virtualFilesystem.indexeddb, filePath).bool) { + return false; + } + }else { + exists = false; + } + if(!copy) { + virtualFilesystem.fileMap.remove(filePath); + filePath = newName; + virtualFilesystem.fileMap.put(newName, this); + } + return true; + } + return false; + } + + public boolean exists() { + if(hasBeenDeleted) { + return false; + } + cacheHit = EaglerAdapter.steadyTimeMillis(); + if(hasBeenAccessed) { + return exists; + } + exists = AsyncHandlers.fileExists(virtualFilesystem.indexeddb, filePath).bool; + hasBeenAccessed = true; + return exists; + } + + } + + private final HashMap fileMap = new HashMap<>(); + + public final String database; + private final IDBDatabase indexeddb; + + public static class VFSHandle { + + public final boolean failedInit; + public final boolean failedLocked; + public final String failedError; + public final VirtualFilesystem vfs; + + public VFSHandle(boolean init, boolean locked, String error, VirtualFilesystem db) { + failedInit = init; + failedLocked = locked; + failedError = error; + vfs = db; + } + + public String toString() { + if(failedInit) { + return "IDBFactory threw an exception, IndexedDB is most likely not supported in this browser." + (failedError == null ? "" : "\n\n" + failedError); + } + if(failedLocked) { + return "The filesystem requested is already in use on a different tab."; + } + if(failedError != null) { + return "The IDBFactory.open() request failed, reason: " + failedError; + } + return "Virtual Filesystem Object: " + vfs.database; + } + + } + + public static VFSHandle openVFS(String db) { + DatabaseOpen evt = AsyncHandlers.openDB(db); + if(evt.failedInit) { + return new VFSHandle(true, false, evt.failedError, null); + } + if(evt.failedLocked) { + return new VFSHandle(false, true, null, null); + } + if(evt.failedError != null) { + return new VFSHandle(false, false, evt.failedError, null); + } + return new VFSHandle(false, false, null, new VirtualFilesystem(db, evt.database)); + } + + private VirtualFilesystem(String db, IDBDatabase idb) { + database = db; + indexeddb = idb; + } + + public void close() { + indexeddb.close(); + } + + public VFSFile getFile(String path) { + return getFile(path, false); + } + + public VFSFile getFile(String path, boolean cache) { + VFSFile f = fileMap.get(path); + if(f == null) { + fileMap.put(path, f = new VFSFile(this, path, cache)); + }else { + if(cache) { + f.setCacheEnabled(); + } + } + return f; + } + + public boolean renameFile(String oldName, String newName, boolean copy) { + return getFile(oldName).rename(newName, copy); + } + + public boolean deleteFile(String path) { + return getFile(path).delete(); + } + + public boolean fileExists(String path) { + return getFile(path).exists(); + } + + public List listFiles(String prefix) { + final ArrayList list = new ArrayList<>(); + AsyncHandlers.iterateFiles(indexeddb, this, prefix, false, (v) -> { + list.add(v.getPath()); + }); + return list; + } + + public int deleteFiles(String prefix) { + return AsyncHandlers.deleteFiles(indexeddb, prefix); + } + + public int iterateFiles(String prefix, boolean rw, VFSIterator itr) { + return AsyncHandlers.iterateFiles(indexeddb, this, prefix, rw, itr); + } + + public int renameFiles(String oldPrefix, String newPrefix, boolean copy) { + List filesToCopy = listFiles(oldPrefix); + int i = 0; + for(String str : filesToCopy) { + String f = VFile.createPath(newPrefix, str.substring(oldPrefix.length())); + if(!renameFile(str, f, copy)) { + System.err.println("Could not " + (copy ? "copy" : "rename") + " file \"" + str + "\" to \"" + f + "\" for some reason"); + }else { + ++i; + } + } + return i; + } + + public void flushCache(long age) { + long curr = EaglerAdapter.steadyTimeMillis(); + Iterator files = fileMap.values().iterator(); + while(files.hasNext()) { + if(curr - files.next().cacheHit > age) { + files.remove(); + } + } + } + + protected static class DatabaseOpen { + + protected final boolean failedInit; + protected final boolean failedLocked; + protected final String failedError; + + protected final IDBDatabase database; + + protected DatabaseOpen(boolean init, boolean locked, String error, IDBDatabase db) { + failedInit = init; + failedLocked = locked; + failedError = error; + database = db; + } + + } + + @JSBody(script = "return ((typeof indexedDB) !== 'undefined') ? indexedDB : null;") + protected static native IDBFactory createIDBFactory(); + + protected static class AsyncHandlers { + + @Async + protected static native DatabaseOpen openDB(String name); + + private static void openDB(String name, final AsyncCallback cb) { + IDBFactory i = createIDBFactory(); + if(i == null) { + cb.complete(new DatabaseOpen(false, false, "window.indexedDB was null or undefined", null)); + return; + } + final IDBOpenDBRequest f = i.open(name, 1); + f.setOnBlocked(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(new DatabaseOpen(false, true, null, null)); + } + }); + f.setOnSuccess(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(new DatabaseOpen(false, false, null, f.getResult())); + } + }); + f.setOnError(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(new DatabaseOpen(false, false, "open error", null)); + } + }); + f.setOnUpgradeNeeded(new EventListener() { + @Override + public void handleEvent(IDBVersionChangeEvent evt) { + f.getResult().createObjectStore("filesystem", IDBObjectStoreParameters.create().keyPath("path")); + } + }); + } + + @Async + protected static native BooleanResult deleteFile(IDBDatabase db, String name); + + private static void deleteFile(IDBDatabase db, String name, final AsyncCallback cb) { + IDBTransaction tx = db.transaction("filesystem", "readwrite"); + final IDBRequest r = tx.objectStore("filesystem").delete(makeTheFuckingKeyWork(name)); + + r.setOnSuccess(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(BooleanResult._new(true)); + } + }); + r.setOnError(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(BooleanResult._new(false)); + } + }); + } + + @JSBody(params = { "obj" }, script = "return (typeof obj === 'undefined') ? null : ((typeof obj.data === 'undefined') ? null : obj.data);") + protected static native ArrayBuffer readRow(JSObject obj); + + @JSBody(params = { "obj" }, script = "return [obj];") + private static native JSObject makeTheFuckingKeyWork(String k); + + @Async + protected static native ArrayBuffer readWholeFile(IDBDatabase db, String name); + + private static void readWholeFile(IDBDatabase db, String name, final AsyncCallback cb) { + IDBTransaction tx = db.transaction("filesystem", "readonly"); + final IDBGetRequest r = tx.objectStore("filesystem").get(makeTheFuckingKeyWork(name)); + r.setOnSuccess(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(readRow(r.getResult())); + } + }); + r.setOnError(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(null); + } + }); + + } + + @JSBody(params = { "k" }, script = "return ((typeof k) === \"string\") ? k : (((typeof k) === \"undefined\") ? null : (((typeof k[0]) === \"string\") ? k[0] : null));") + private static native String readKey(JSObject k); + + @JSBody(params = { "k" }, script = "return ((typeof k) === \"undefined\") ? null : (((typeof k.path) === \"undefined\") ? null : (((typeof k.path) === \"string\") ? k[0] : null));") + private static native String readRowKey(JSObject r); + + @Async + protected static native Integer iterateFiles(IDBDatabase db, final VirtualFilesystem vfs, final String prefix, boolean rw, final VFSIterator itr); + + private static void iterateFiles(IDBDatabase db, final VirtualFilesystem vfs, final String prefix, boolean rw, final VFSIterator itr, final AsyncCallback cb) { + IDBTransaction tx = db.transaction("filesystem", rw ? "readwrite" : "readonly"); + final IDBCursorRequest r = tx.objectStore("filesystem").openCursor(); + final int[] res = new int[1]; + r.setOnSuccess(new EventHandler() { + @Override + public void handleEvent() { + IDBCursor c = r.getResult(); + if(c == null || c.getKey() == null || c.getValue() == null) { + cb.complete(res[0]); + return; + } + String k = readKey(c.getKey()); + if(k != null) { + if(k.startsWith(prefix)) { + int ci = res[0]++; + try { + itr.next(VIteratorFile.create(ci, vfs, c)); + }catch(VFSIterator.BreakLoop ex) { + cb.complete(res[0]); + return; + } + } + } + c.doContinue(); + } + }); + r.setOnError(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(res[0] > 0 ? res[0] : -1); + } + }); + } + + @Async + protected static native Integer deleteFiles(IDBDatabase db, final String prefix); + + private static void deleteFiles(IDBDatabase db, final String prefix, final AsyncCallback cb) { + IDBTransaction tx = db.transaction("filesystem", "readwrite"); + final IDBCursorRequest r = tx.objectStore("filesystem").openCursor(); + final int[] res = new int[1]; + r.setOnSuccess(new EventHandler() { + @Override + public void handleEvent() { + IDBCursor c = r.getResult(); + if(c == null || c.getKey() == null || c.getValue() == null) { + cb.complete(res[0]); + return; + } + String k = readKey(c.getKey()); + if(k != null) { + if(k.startsWith(prefix)) { + c.delete(); + ++res[0]; + } + } + c.doContinue(); + } + }); + r.setOnError(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(res[0] > 0 ? res[0] : -1); + } + }); + } + + @Async + protected static native BooleanResult fileExists(IDBDatabase db, String name); + + private static void fileExists(IDBDatabase db, String name, final AsyncCallback cb) { + IDBTransaction tx = db.transaction("filesystem", "readonly"); + final IDBCountRequest r = tx.objectStore("filesystem").count(makeTheFuckingKeyWork(name)); + r.setOnSuccess(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(BooleanResult._new(r.getResult() > 0)); + } + }); + r.setOnError(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(BooleanResult._new(false)); + } + }); + } + + @JSBody(params = { "pat", "dat" }, script = "return { path: pat, data: dat };") + protected static native JSObject writeRow(String name, ArrayBuffer data); + + @Async + protected static native BooleanResult writeWholeFile(IDBDatabase db, String name, ArrayBuffer data); + + private static void writeWholeFile(IDBDatabase db, String name, ArrayBuffer data, final AsyncCallback cb) { + IDBTransaction tx = db.transaction("filesystem", "readwrite"); + final IDBRequest r = tx.objectStore("filesystem").put(writeRow(name, data)); + + r.setOnSuccess(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(BooleanResult._new(true)); + } + }); + r.setOnError(new EventHandler() { + @Override + public void handleEvent() { + cb.complete(BooleanResult._new(false)); + } + }); + } + + } + + public static byte[] utf8(String str) { + if(str == null) return null; + return str.getBytes(Charset.forName("UTF-8")); + } + + public static String utf8(byte[] str) { + if(str == null) return null; + return new String(str, Charset.forName("UTF-8")); + } + + public static String CRLFtoLF(String str) { + if(str == null) return null; + str = str.indexOf('\r') != -1 ? str.replace("\r", "") : str; + str = str.trim(); + if(str.endsWith("\n")) { + str = str.substring(0, str.length() - 1); + } + if(str.startsWith("\n")) { + str = str.substring(1); + } + return str; + } + + public static String[] lines(String str) { + if(str == null) return null; + return CRLFtoLF(str).split("\n"); + } + +} \ No newline at end of file