Update #24 - 1000s of optimizations, shared worlds on desktop

This commit is contained in:
lax1dude
2024-03-02 20:51:44 -08:00
parent a50c93cb75
commit 8ab65942c5
624 changed files with 8091 additions and 3620 deletions

View File

@ -88,7 +88,7 @@ public class PlatformApplication {
});
}
@JSBody(params = { "cb" }, script = "if(!window.navigator.clipboard) cb(\"\"); else window.navigator.clipboard.readText().then(function(s) { cb(s); }, function(s) { cb(\"\"); });")
@JSBody(params = { "cb" }, script = "if(!window.navigator.clipboard || !window.navigator.clipboard.readText) cb(\"\"); else window.navigator.clipboard.readText().then(function(s) { cb(s); }, function(s) { cb(\"\"); });")
private static native void getClipboard1(StupidFunctionResolveString cb);
@JSBody(params = { "str" }, script = "if(window.navigator.clipboard) window.navigator.clipboard.writeText(str);")
@ -248,7 +248,7 @@ public class PlatformApplication {
documentWrite(newWin.getDocument(), "<!DOCTYPE html><html><head><meta charset=\"UTF-8\" />"
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /><title>EaglercraftX 1.8 Credits</title>"
+ "<link type=\"image/png\" rel=\"shortcut icon\" href=\"" + PlatformApplication.faviconURLTeaVM() + "\" />"
+ "</head><body><pre>" + text + "</pre></body></html>");
+ "</head><body><pre style=\"font:15px Consolas,monospace;\">" + text + "</pre></body></html>");
}
public static void clearFileChooserResult() {

View File

@ -22,9 +22,8 @@ import org.teavm.jso.typedarrays.DataView;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.EaglerArrayBufferAllocator;
import net.lax1dude.eaglercraft.v1_8.internal.vfs.BooleanResult;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.BooleanResult;
import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFSIterator2;
import net.lax1dude.eaglercraft.v1_8.sp.server.internal.ServerPlatformSingleplayer;
/**
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
@ -46,17 +45,16 @@ public class PlatformFilesystem {
private static String filesystemDB = null;
private static IDBDatabase database = null;
public static void initialize() {
filesystemDB = "_net_lax1dude_eaglercraft_v1_8_internal_PlatformFilesystem_1_8_8_"
+ ServerPlatformSingleplayer.getClientConfigAdapter().getWorldsDB();
public static void initialize(String dbName) {
filesystemDB = "_net_lax1dude_eaglercraft_v1_8_internal_PlatformFilesystem_1_8_8_" + dbName;
DatabaseOpen dbOpen = AsyncHandlers.openDB(filesystemDB);
if(dbOpen.failedLocked) {
throw new WorldsDatabaseLockedException(dbOpen.failedError);
throw new FilesystemDatabaseLockedException(dbOpen.failedError);
}
if(dbOpen.failedInit) {
throw new WorldsDatabaseInitializationException(dbOpen.failedError);
throw new FilesystemDatabaseInitializationException(dbOpen.failedError);
}
if(dbOpen.database == null) {
@ -66,14 +64,14 @@ public class PlatformFilesystem {
database = dbOpen.database;
}
public static class WorldsDatabaseLockedException extends RuntimeException {
public WorldsDatabaseLockedException(String message) {
public static class FilesystemDatabaseLockedException extends RuntimeException {
public FilesystemDatabaseLockedException(String message) {
super(message);
}
}
public static class WorldsDatabaseInitializationException extends RuntimeException {
public WorldsDatabaseInitializationException(String message) {
public static class FilesystemDatabaseInitializationException extends RuntimeException {
public FilesystemDatabaseInitializationException(String message) {
super(message);
}
}

View File

@ -28,6 +28,8 @@ import org.teavm.jso.dom.html.HTMLCanvasElement;
import org.teavm.jso.dom.html.HTMLDocument;
import org.teavm.jso.dom.html.HTMLElement;
import org.teavm.jso.typedarrays.ArrayBuffer;
import org.teavm.jso.typedarrays.DataView;
import org.teavm.jso.typedarrays.Uint8Array;
import org.teavm.jso.webaudio.MediaStream;
import org.teavm.jso.webgl.WebGLFramebuffer;
@ -36,6 +38,7 @@ import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.GZIPOutputStream;
import com.jcraft.jzlib.InflaterInputStream;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem.FilesystemDatabaseLockedException;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.EaglerArrayBufferAllocator;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.FloatBuffer;
@ -51,6 +54,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.WebGL2RenderingContext;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.minecraft.EaglerFolderResourcePack;
import net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums;
/**
@ -168,6 +172,22 @@ public class PlatformRuntime {
byte[] finalLoadScreen = PlatformAssets.getResourceBytes("/assets/eagler/eagtek.png");
logger.info("Initializing filesystem...");
try {
PlatformFilesystem.initialize(getClientConfigAdapter().getResourcePacksDB());
EaglerFolderResourcePack.setSupported(true);
}catch(FilesystemDatabaseLockedException t) {
logger.error("Could not initialize filesystem, database is locked!");
}catch(Throwable t) {
logger.error("Could not initialize filesystem, encountered an exception!");
logger.error(t);
}
if(!EaglerFolderResourcePack.isSupported()) {
logger.error("Resource packs will be disabled for this session");
}
logger.info("Initializing sound engine...");
PlatformInput.pressAnyKeyScreen();
@ -178,6 +198,8 @@ public class PlatformRuntime {
EarlyLoadScreen.paintFinal(finalLoadScreen);
}
EarlyLoadScreen.destroy();
logger.info("Platform initialization complete");
FixWebMDurationJS.checkOldScriptStillLoaded();
@ -251,7 +273,31 @@ public class PlatformRuntime {
public static FloatBuffer allocateFloatBuffer(int length) {
return EaglerArrayBufferAllocator.allocateFloatBuffer(length);
}
public static ByteBuffer castPrimitiveByteArray(byte[] array) {
return EaglerArrayBufferAllocator.wrapByteBufferTeaVM(DataView.create(TeaVMUtils.unwrapArrayBuffer(array)));
}
public static IntBuffer castPrimitiveIntArray(int[] array) {
return EaglerArrayBufferAllocator.wrapIntBufferTeaVM(DataView.create(TeaVMUtils.unwrapArrayBuffer(array)));
}
public static FloatBuffer castPrimitiveFloatArray(float[] array) {
return EaglerArrayBufferAllocator.wrapFloatBufferTeaVM(DataView.create(TeaVMUtils.unwrapArrayBuffer(array)));
}
public static byte[] castNativeByteBuffer(ByteBuffer buffer) {
return TeaVMUtils.wrapUnsignedByteArray(EaglerArrayBufferAllocator.getDataViewStupid(buffer));
}
public static int[] castNativeIntBuffer(IntBuffer buffer) {
return TeaVMUtils.wrapIntArray(EaglerArrayBufferAllocator.getDataViewStupid32(buffer));
}
public static float[] castNativeFloatBuffer(FloatBuffer buffer) {
return TeaVMUtils.wrapFloatArray(EaglerArrayBufferAllocator.getFloatArrayStupid(buffer));
}
public static void freeByteBuffer(ByteBuffer byteBuffer) {
}
@ -264,6 +310,10 @@ public class PlatformRuntime {
}
public static void downloadRemoteURIByteArray(String assetPackageURI, final Consumer<byte[]> cb) {
downloadRemoteURI(assetPackageURI, arr -> cb.accept(TeaVMUtils.wrapUnsignedByteArray(Uint8Array.create(arr))));
}
public static void downloadRemoteURI(String assetPackageURI, final Consumer<ArrayBuffer> cb) {
downloadRemoteURI(assetPackageURI, false, cb);
}
@ -331,7 +381,7 @@ public class PlatformRuntime {
public static native ArrayBuffer downloadRemoteURI(String assetPackageURI, boolean forceCache);
private static void downloadRemoteURI(String assetPackageURI, boolean useCache, final AsyncCallback<ArrayBuffer> cb) {
doFetchDownload(assetPackageURI, useCache ? "force-cache" : "no-store", (bb) -> cb.complete(bb));
doFetchDownload(assetPackageURI, useCache ? "force-cache" : "no-store", cb::complete);
}
public static boolean isDebugRuntime() {
@ -577,9 +627,7 @@ public class PlatformRuntime {
a.setHref(url);
a.click();
TeaVMUtils.freeDataURL(url);
}, (msg) -> {
logger.info(msg);
});
}, logger::info);
}
});
onRecFrame();

View File

@ -1,6 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.internal;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglerInputStream;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
@ -29,7 +30,6 @@ import org.teavm.jso.websocket.WebSocket;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.*;
@ -619,7 +619,7 @@ public class PlatformWebRTC {
}else {
if(open) {
try {
IPacket pkt = IPacket.readPacket(new DataInputStream(new ByteArrayInputStream(arr)));
IPacket pkt = IPacket.readPacket(new DataInputStream(new EaglerInputStream(arr)));
if(pkt instanceof IPacket69Pong) {
IPacket69Pong ipkt = (IPacket69Pong)pkt;
versError = VersionMismatch.COMPATIBLE;
@ -875,7 +875,7 @@ public class PlatformWebRTC {
}else {
if(open) {
try {
IPacket pkt = IPacket.readPacket(new DataInputStream(new ByteArrayInputStream(arr)));
IPacket pkt = IPacket.readPacket(new DataInputStream(new EaglerInputStream(arr)));
if(pkt instanceof IPacket07LocalWorlds) {
worlds = ((IPacket07LocalWorlds)pkt).worldsList;
sock.close();
@ -1065,7 +1065,7 @@ public class PlatformWebRTC {
if(evt.getData() != null && !isString(evt.getData())) {
hasRecievedAnyData = true;
try {
IPacket pkt = IPacket.readPacket(new DataInputStream(new ByteArrayInputStream(TeaVMUtils.wrapUnsignedByteArray(Uint8Array.create(evt.getDataAsArray())))));
IPacket pkt = IPacket.readPacket(new DataInputStream(new EaglerInputStream(TeaVMUtils.wrapUnsignedByteArray(Uint8Array.create(evt.getDataAsArray())))));
if(pkt instanceof IPacket70SpecialUpdate) {
IPacket70SpecialUpdate ipkt = (IPacket70SpecialUpdate)pkt;
if(ipkt.operation == IPacket70SpecialUpdate.OPERATION_UPDATE_CERTIFICATE) {

View File

@ -3,6 +3,7 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer;
import org.teavm.jso.typedarrays.ArrayBuffer;
import org.teavm.jso.typedarrays.DataView;
import org.teavm.jso.typedarrays.Float32Array;
import org.teavm.jso.typedarrays.Int32Array;
import org.teavm.jso.typedarrays.Uint16Array;
import org.teavm.jso.typedarrays.Uint8Array;
@ -126,6 +127,19 @@ public class EaglerArrayBufferAllocator {
}
}
public static Int32Array getDataViewStupid32(IntBuffer buffer) {
if(buffer instanceof EaglerArrayIntBuffer) {
EaglerArrayIntBuffer b = (EaglerArrayIntBuffer)buffer;
DataView d = b.dataView;
int p = b.position;
int l = b.limit;
int i = d.getByteOffset();
return Int32Array.create(d.getBuffer(), i + (p << 2), (l - p) << 2);
}else {
throw notEagler(buffer);
}
}
public static DataView getDataView(FloatBuffer buffer) {
if(buffer instanceof EaglerArrayFloatBuffer) {
EaglerArrayFloatBuffer b = (EaglerArrayFloatBuffer)buffer;

View File

@ -1,4 +1,4 @@
package net.lax1dude.eaglercraft.v1_8.internal.vfs;
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.

View File

@ -321,8 +321,9 @@ public class ClientMain {
}else {
System.err.println();
System.err.println("An additional crash report was supressed:");
for(String l : t.split("[\\r\\n]+")) {
System.err.println(" " + l);
String[] s = t.split("[\\r\\n]+");
for(int i = 0; i < s.length; ++i) {
System.err.println(" " + s[i]);
}
}
}

View File

@ -245,8 +245,10 @@ public class EarlyLoadScreen {
_wglBindTexture(GL_TEXTURE_2D, null);
_wglDeleteTextures(tex);
_wglDeleteVertexArrays(vao);
}
public static void destroy() {
_wglDeleteBuffers(vbo);
_wglDeleteProgram(program);
}
}

View File

@ -1,16 +1,13 @@
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglerInputStream;
import net.lax1dude.eaglercraft.v1_8.EaglercraftRandom;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.ThreadLocalRandom;
import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayManager;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
import org.json.JSONArray;
import org.json.JSONObject;
@ -41,8 +38,9 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
private List<RelayEntry> relays = new ArrayList();
private String serverToJoin = null;
private String worldsDB = "worlds";
private String resourcePacksDB = "resourcePacks";
private JSONObject originalEaglercraftOpts;
private boolean isCheckShaderGLErrors = false;
private boolean checkShaderGLErrors = false;
private boolean demoMode = EaglercraftVersion.forceDemoMode;
private boolean isAllowUpdateSvc = EaglercraftVersion.enableUpdateService;
private boolean isAllowUpdateDL = EaglercraftVersion.enableUpdateService;
@ -52,14 +50,14 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
private boolean logInvalidCerts = false;
private boolean checkRelaysForUpdates = false;
private boolean enableSignatureBadge = false;
private static final EaglercraftRandom random = new EaglercraftRandom();
public void loadJSON(JSONObject eaglercraftOpts) {
originalEaglercraftOpts = eaglercraftOpts;
defaultLocale = eaglercraftOpts.optString("lang", "en_US");
serverToJoin = eaglercraftOpts.optString("joinServer", null);
worldsDB = eaglercraftOpts.optString("worldsDB", "worlds");
isCheckShaderGLErrors = eaglercraftOpts.optBoolean("checkShaderGLErrors", false);
resourcePacksDB = eaglercraftOpts.optString("resourcePacksDB", "resourcePacks");
checkShaderGLErrors = eaglercraftOpts.optBoolean("checkShaderGLErrors", false);
if(EaglercraftVersion.forceDemoMode) {
eaglercraftOpts.put("demoMode", true);
}
@ -88,7 +86,7 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
boolean gotAPrimary = false;
for (int i = 0, l = relaysArray.length(); i < l; ++i) {
JSONObject relay = relaysArray.getJSONObject(i);
boolean p = relay.getBoolean("primary");
boolean p = relay.optBoolean("primary");
if(p) {
if(gotAPrimary) {
p = false;
@ -100,42 +98,28 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
}
}
boolean officialUpdates = !demoMode && EaglercraftVersion.updateBundlePackageName.equals("net.lax1dude.eaglercraft.v1_8.client");
if (relays.size() <= 0) {
int choice = random.nextInt(3);
int choice = ThreadLocalRandom.current().nextInt(3);
relays.add(new RelayEntry("wss://relay.deev.is/", "lax1dude relay #1", choice == 0));
relays.add(new RelayEntry("wss://relay.lax1dude.net/", "lax1dude relay #2", choice == 1));
relays.add(new RelayEntry("wss://relay.shhnowisnottheti.me/", "ayunami relay #1", choice == 2));
checkRelaysForUpdates = !demoMode && eaglercraftOpts.optBoolean("checkRelaysForUpdates", true);
checkRelaysForUpdates = !demoMode && eaglercraftOpts.optBoolean("checkRelaysForUpdates", officialUpdates);
}else {
boolean isOfficial = true;
for(int i = 0, l = relays.size(); i < l; ++i) {
String addr = relays.get(i).address;
if(!addr.contains("deev.is") && !addr.contains("lax1dude.net") && !addr.contains("shhnowisnottheti.me")) {
isOfficial = false;
break;
if(officialUpdates) {
for(int i = 0, l = relays.size(); i < l; ++i) {
String addr = relays.get(i).address;
if(!addr.contains("deev.is") && !addr.contains("lax1dude.net") && !addr.contains("shhnowisnottheti.me")) {
officialUpdates = false;
break;
}
}
}
checkRelaysForUpdates = !demoMode && eaglercraftOpts.optBoolean("checkRelaysForUpdates", isOfficial);
checkRelaysForUpdates = !demoMode && eaglercraftOpts.optBoolean("checkRelaysForUpdates", officialUpdates);
}
try {
byte[] localStorage = EagRuntime.getStorage("r");
if (localStorage != null) {
NBTTagCompound nbttagcompound = CompressedStreamTools
.readCompressed(new EaglerInputStream(localStorage));
if (nbttagcompound == null) {
RelayManager.relayManager.load(null);
} else {
RelayManager.relayManager.load(nbttagcompound.getTagList("relays", 10));
}
} else {
RelayManager.relayManager.load(null);
}
} catch (IOException e) {
EagRuntime.debugPrintStackTrace(e);
}
RelayManager.relayManager.load(EagRuntime.getStorage("r"));
if (RelayManager.relayManager.count() <= 0) {
RelayManager.relayManager.loadDefaults();
RelayManager.relayManager.save();
@ -162,6 +146,11 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
return worldsDB;
}
@Override
public String getResourcePacksDB() {
return resourcePacksDB;
}
@Override
public JSONObject dumpConfig() {
return originalEaglercraftOpts;
@ -173,8 +162,8 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
}
@Override
public boolean checkShaderGLErrors() {
return isCheckShaderGLErrors;
public boolean isCheckShaderGLErrors() {
return checkShaderGLErrors;
}
@Override

View File

@ -153,7 +153,7 @@ public class TeaVMUpdateThread implements Runnable {
return;
}
updateProg.statusString2 = "Signature Invalid!";
logger.error("File signature is invalid: {}");
logger.error("File signature is invalid: {}", url);
EagUtils.sleep(1000l);
}
@ -222,11 +222,11 @@ public class TeaVMUpdateThread implements Runnable {
if(data.getByteLength() == updateCert.bundleDataLength) {
cb.complete(TeaVMUtils.wrapUnsignedByteArray(Uint8Array.create(data)));
}else {
logger.error("Unexpected response length {} (expect: {}) from URL: {}", xhr.getStatus(), xhr.getStatusText());
logger.error("Unexpected response length {} (expect: {}) from URL: {}", xhr.getStatus(), xhr.getStatusText(), url);
cb.complete(null);
}
}else {
logger.error("Got response code {} \"{}\" for url: {}", xhr.getStatus(), xhr.getStatusText());
logger.error("Got response code {} \"{}\" for url: {}", xhr.getStatus(), xhr.getStatusText(), url);
cb.complete(null);
}
}
@ -251,7 +251,7 @@ public class TeaVMUpdateThread implements Runnable {
}
public static void downloadSignedOffline(UpdateCertificate cert, byte[] data) {
PlatformApplication.downloadFileWithName(cert.bundleDisplayName.replaceAll("[^a-zA-Z0-9\\-_]", "_") + "_" + cert.bundleDisplayVersion.replaceAll("[^a-zA-Z0-9\\-_]", "_") + "_Offline_Signed.html", generateSignedOffline(cert, data));
PlatformApplication.downloadFileWithName(cert.bundleDisplayName.replaceAll("[^a-zA-Z0-9\\-_\\.]", "_") + "_" + cert.bundleDisplayVersion.replaceAll("[^a-zA-Z0-9\\-_]", "_") + "_Offline_Signed.html", generateSignedOffline(cert, data));
}
public static byte[] generateSignedOffline(UpdateCertificate cert, byte[] data) {

View File

@ -53,6 +53,10 @@ public class TeaVMUtils {
return Int8Array.create(((TeaVMArrayObject)(Object)buf).getData().getBuffer());
}
public static ArrayBuffer unwrapArrayBuffer(byte[] buf) {
return ((TeaVMArrayObject)(Object)buf).getData().getBuffer();
}
@JSBody(params = { "buf" }, script = "return $rt_createByteArray(buf.buffer)")
private static native JSObject wrapByteArray0(JSObject buf);
@ -72,6 +76,10 @@ public class TeaVMUtils {
return Int32Array.create(((TeaVMArrayObject)(Object)buf).getData().getBuffer());
}
public static ArrayBuffer unwrapArrayBuffer(int[] buf) {
return ((TeaVMArrayObject)(Object)buf).getData().getBuffer();
}
@JSBody(params = { "buf" }, script = "return $rt_createIntArray(buf.buffer)")
private static native JSObject wrapIntArray0(JSObject buf);
@ -83,6 +91,10 @@ public class TeaVMUtils {
return Float32Array.create(((TeaVMArrayObject)(Object)buf).getData().getBuffer());
}
public static ArrayBuffer unwrapArrayBuffer(float[] buf) {
return ((TeaVMArrayObject)(Object)buf).getData().getBuffer();
}
@JSBody(params = { "buf" }, script = "return $rt_createFloatArray(buf.buffer)")
private static native JSObject wrapFloatArray0(JSObject buf);

View File

@ -1,59 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.internal.vfs;
import com.google.common.collect.Sets;
import net.minecraft.client.resources.AbstractResourcePack;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
/**
* Copyright (c) 2022-2023 lax1dude, ayunami2000. 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 FolderResourcePack extends AbstractResourcePack {
private final String prefix;
public FolderResourcePack(String resourcePackFileIn, String prefix) {
super(resourcePackFileIn);
this.prefix = prefix;
}
protected InputStream getInputStreamByName(String name) {
return SYS.VFS.getFile(prefix + this.resourcePackFile + "/" + name).getInputStream();
}
protected boolean hasResourceName(String name) {
return SYS.VFS.fileExists(prefix + this.resourcePackFile + "/" + name);
}
public Set<String> getResourceDomains() {
Set<String> set = Sets.<String>newHashSet();
String pfx = prefix + this.resourcePackFile + "/assets/";
List<String> files = SYS.VFS.listFiles(pfx);
for (String file : files) {
String s = file.substring(pfx.length());
int ind = s.indexOf('/');
if (ind != -1) s = s.substring(0, ind);
if (!s.equals(s.toLowerCase())) {
this.logNameNotLowercase(s);
} else {
set.add(s);
}
}
return set;
}
}

View File

@ -1,194 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.internal.vfs;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.crypto.SHA1Digest;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.ArrayBufferInputStream;
import net.lax1dude.eaglercraft.v1_8.internal.vfs.VirtualFilesystem.VFSHandle;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Copyright (c) 2022-2023 lax1dude, ayunami2000. 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 SYS {
public static final VirtualFilesystem VFS;
static {
VFSHandle vh = VirtualFilesystem.openVFS("_net_lax1dude_eaglercraft_v1_8_VirtualFilesystem_");
if(vh.vfs == null) {
System.err.println("Could not init filesystem!");
}
VFS = vh.vfs;
List<String> srp = getResourcePackNames(true);
for (String name : srp) {
if (System.currentTimeMillis() - Long.parseLong(name.substring(name.lastIndexOf('_') + 1)) >= 604800000L) {
deleteResourcePack(name, true);
}
}
}
public static final void loadRemoteResourcePack(String url, String hash, Consumer<String> cb, Consumer<Runnable> ast, Runnable loading) {
if (!hash.matches("^[a-f0-9]{40}$")) {
cb.accept(null);
return;
}
List<String> srpPre = getResourcePackNames(true);
String alreadyHere = srpPre.stream().filter(s -> s.startsWith(hash + "_")).findFirst().orElse(null);
if (alreadyHere != null) {
cb.accept(alreadyHere);
return;
}
PlatformRuntime.downloadRemoteURI(url, ab -> {
ast.accept(() -> {
if (ab == null) {
cb.accept(null);
return;
}
List<String> srp = getResourcePackNames(true);
// delete old server resource packs - todo: test
if (srp.size() > 5) {
srp.sort(Comparator.comparingLong(val -> Long.parseLong(val.substring(val.lastIndexOf('_') + 1))));
for (int i = 0; i < srp.size() - 5; i++) {
deleteResourcePack(srp.get(i), true);
}
}
String packName = hash + "_" + System.currentTimeMillis();
loading.run();
boolean success = loadResourcePack(packName + ".zip", new ArrayBufferInputStream(ab), hash);
if (success) {
cb.accept(packName);
return;
}
cb.accept(null);
});
});
}
public static final boolean loadResourcePack(String name, InputStream is, String hash) {
BufferedInputStream bis = new BufferedInputStream(is);
bis.mark(Integer.MAX_VALUE);
if (hash != null) {
try {
SHA1Digest digest = new SHA1Digest();
byte[] buffer = new byte[16000];
int read = 0;
while ((read = bis.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
byte[] sha1sum = new byte[20];
digest.doFinal(sha1sum, 0);
bis.reset();
if (!hash.equals((new BigInteger(1, sha1sum)).toString(16))) {
return false;
}
} catch (IOException e) {
EagRuntime.debugPrintStackTrace(e);
return false;
}
}
String packName = name.substring(0, name.lastIndexOf('.')).replace('/', '_');
try {
int prefixLen = Integer.MAX_VALUE;
ZipInputStream ziss = new ZipInputStream(bis);
ZipEntry zipEntryy;
while ((zipEntryy = ziss.getNextEntry()) != null) {
String zn;
if (!zipEntryy.isDirectory() && ((zn = zipEntryy.getName()).equals("pack.mcmeta") || zn.endsWith("/pack.mcmeta"))) {
int currPrefixLen = zn.length() - 11;
if (prefixLen > currPrefixLen) {
prefixLen = currPrefixLen;
}
}
}
if (prefixLen == Integer.MAX_VALUE) {
prefixLen = 0;
}
bis.reset();
ZipInputStream zis = new ZipInputStream(bis);
byte[] bb = new byte[16000];
ZipEntry zipEntry;
while ((zipEntry = zis.getNextEntry()) != null) {
if (zipEntry.isDirectory()) continue;
if (zipEntry.getName().length() <= prefixLen) continue;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len;
while ((len = zis.read(bb)) != -1) {
baos.write(bb, 0, len);
}
baos.close();
SYS.VFS.getFile((hash == null ? "resourcepacks/" : "srp/") + packName + "/" + zipEntry.getName().substring(prefixLen)).setAllBytes(baos.toByteArray());
}
zis.closeEntry();
zis.close();
return true;
} catch (IOException e) {
EagRuntime.debugPrintStackTrace(e);
return false;
}
}
public static final List<String> getResourcePackNames() {
return getResourcePackNames(false);
}
private static final List<String> getResourcePackNames(boolean srp) {
List<String> res = new ArrayList<>();
List<String> resourcePackFiles = SYS.VFS.listFiles(srp ? "srp/" : "resourcepacks/");
for (String path : resourcePackFiles) {
String trimmed = path.substring(srp ? 4 : 14);
trimmed = trimmed.substring(0, trimmed.indexOf('/'));
boolean hasIt = false;
for (String alreadyHas : res) {
if (trimmed.equals(alreadyHas)) {
hasIt = true;
break;
}
}
if (hasIt) continue;
res.add(trimmed);
}
return res;
}
public static final void deleteResourcePack(String packName) {
deleteResourcePack(packName, false);
}
private static final void deleteResourcePack(String packName, boolean srp) {
SYS.VFS.deleteFiles((srp ? "srp/" : "resourcepacks/") + packName);
}
}

View File

@ -1,32 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.internal.vfs;
/**
* Copyright (c) 2022 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 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);
}

View File

@ -1,242 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.internal.vfs;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Copyright (c) 2022 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 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<String> 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<String> list() {
if(isRelative()) {
return Arrays.asList(path);
}
return SYS.VFS.listFiles(path);
}
public int deleteAll() {
return isRelative() ? 0 : SYS.VFS.deleteFiles(path);
}
}

View File

@ -1,299 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.internal.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;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils;
/**
* Copyright (c) 2022 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.
*
*/
/**
* 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 = Uint8Array.create(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;
}
return TeaVMUtils.wrapUnsignedByteArray(Uint8Array.create(arr));
}
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<String> 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 = ArrayBuffer.create(bytes.length);
Uint8Array ar = Uint8Array.create(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<BooleanResult> cb) {
r.addEventListener("success", new EventListener<Event>() {
@Override
public void handleEvent(Event evt) {
cb.complete(BooleanResult._new(true));
}
});
r.addEventListener("error", new EventListener<Event>() {
@Override
public void handleEvent(Event evt) {
cb.complete(BooleanResult._new(false));
}
});
}
}
}

View File

@ -1,702 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.internal.vfs;
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.v1_8.EaglerInputStream;
import net.lax1dude.eaglercraft.v1_8.internal.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.Uint8Array;
/**
* Copyright (c) 2022 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 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 = System.currentTimeMillis();
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 = System.currentTimeMillis();
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 EaglerInputStream(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 = System.currentTimeMillis();
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");
}
Uint8Array a = Uint8Array.create(aa);
this.fileSize = a.getByteLength();
if(cacheEnabled) {
cache = new byte[fileSize];
for(int i = 0; i < fileSize; ++i) {
cache[i] = (byte)a.get(i);
}
}
if(a.getLength() < fileOffset + length) {
throw new ArrayIndexOutOfBoundsException("file '" + filePath + "' 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() {
if(!cacheEnabled && !hasBeenDeleted && !(hasBeenAccessed && !exists)) {
cacheHit = System.currentTimeMillis();
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 = System.currentTimeMillis();
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;
}
Uint8Array a = Uint8Array.create(b);
this.fileSize = a.getByteLength();
byte[] array = TeaVMUtils.wrapUnsignedByteArray(a);
if(cacheEnabled) {
if(copy) {
cache = new byte[fileSize];
System.arraycopy(b, 0, cache, 0, cache.length);
}else {
cache = array;
}
}
return array;
}
}
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 = System.currentTimeMillis();
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.unwrapUnsignedByteArray(bytes).getBuffer()).bool;
hasBeenAccessed = true;
exists = exists || s;
return s;
}
}
public boolean sync() {
if(cacheEnabled && cache != null && !hasBeenDeleted) {
cacheHit = System.currentTimeMillis();
boolean tryWrite = AsyncHandlers.writeWholeFile(virtualFilesystem.indexeddb, filePath, TeaVMUtils.unwrapUnsignedByteArray(cache).getBuffer()).bool;
hasBeenAccessed = true;
exists = exists || tryWrite;
return tryWrite;
}
return false;
}
public boolean delete() {
if(!hasBeenDeleted && !(hasBeenAccessed && !exists)) {
cacheHit = System.currentTimeMillis();
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 = System.currentTimeMillis();
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 = System.currentTimeMillis();
if(hasBeenAccessed) {
return exists;
}
exists = AsyncHandlers.fileExists(virtualFilesystem.indexeddb, filePath).bool;
hasBeenAccessed = true;
return exists;
}
}
private final HashMap<String, VFSFile> 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<String> listFiles(String prefix) {
final ArrayList<String> 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<String> 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 = System.currentTimeMillis();
Iterator<VFSFile> 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<DatabaseOpen> 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<IDBVersionChangeEvent>() {
@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<BooleanResult> 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<ArrayBuffer> 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<Integer> 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<Integer> 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<BooleanResult> 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<BooleanResult> 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");
}
}