teavm source added

This commit is contained in:
catfoolyou 2025-02-05 14:30:54 -05:00
parent 7634a8dae3
commit 5e4df34706
24 changed files with 7358 additions and 1 deletions

View File

@ -1,5 +1,8 @@
import org.teavm.gradle.api.OptimizationLevel
plugins {
id "java"
id "org.teavm" version "0.10.2"
}
sourceSets {
@ -22,10 +25,33 @@ tasks.withType(JavaCompile) {
dependencies {
implementation fileTree(dir: './lwjgl-rundir/', include: '*.jar')
teavm(teavm.libs.jso)
teavm(teavm.libs.jsoApis)
compileOnly "org.teavm:teavm-core:0.10.2" // workaround for a few hacks
}
def folder = "javascript"
def name = "classes.js"
teavm.js {
obfuscated = true
sourceMap = true
targetFileName = "../" + name
optimization = OptimizationLevel.AGGRESSIVE
outOfProcess = false
fastGlobalAnalysis = false
processMemory = 512
entryPointName = "main"
mainClass = "net.lax1dude.eaglercraft.Client"
outputDir = file(folder)
properties = [ "java.util.TimeZone.autodetect": "true" ]
debugInformation = false
}
/*
tasks.register('copyDebugJar', Copy) {
project.delete("lwjgl-rundir/eaglercraft.jar")
from layout.buildDirectory.file("eaglercraft.jar")
into ("lwjgl-rundir/")
}
}
*/

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,189 @@
package net.lax1dude.eaglercraft.adapter;
import static net.lax1dude.eaglercraft.adapter.teavm.WebGL2RenderingContext.*;
import org.teavm.jso.browser.Window;
import org.teavm.jso.dom.html.HTMLCanvasElement;
import org.teavm.jso.typedarrays.Uint8Array;
import net.lax1dude.eaglercraft.Client;
import net.lax1dude.eaglercraft.adapter.teavm.TeaVMUtils;
import net.lax1dude.eaglercraft.adapter.teavm.WebGL2RenderingContext;
import net.lax1dude.eaglercraft.adapter.teavm.WebGLVertexArray;
import org.teavm.jso.webgl.*;
public class DetectAnisotropicGlitch {
private static boolean known = false;
private static boolean detected = false;
public static boolean hasGlitch() {
if(!known) {
detected = detect();
known = true;
}
return detected;
}
public static boolean detect() {
HTMLCanvasElement cvs = (HTMLCanvasElement) Window.current().getDocument().createElement("canvas");
cvs.setWidth(400);
cvs.setHeight(300);
WebGL2RenderingContext ctx = (WebGL2RenderingContext) cvs.getContext("webgl2");
if(ctx == null) {
Client.showIncompatibleScreen("WebGL 2.0 is not supported on this device!");
throw new UnsupportedOperationException("WebGL 2 is not supported on this device!");
}
if(ctx.getExtension("EXT_texture_filter_anisotropic") != null) {
String vshSrc = "#version 300 es\n"
+ "precision lowp float;"
+ "in vec2 a_pos;"
+ "out vec2 v_pos;"
+ "void main() {"
+ " gl_Position = vec4((v_pos = a_pos) * 2.0 - 1.0, 0.0, 1.0);"
+ "}";
String fshSrc = "#version 300 es\n"
+ "precision lowp float;"
+ "precision lowp sampler2D;"
+ "uniform sampler2D tex;"
+ "in vec2 v_pos;"
+ "out vec4 fragColor;"
+ "void main() {"
+ " fragColor = vec4(texture(tex, v_pos).rgb, 1.0);"
+ "}";
WebGLShader vsh = ctx.createShader(VERTEX_SHADER);
ctx.shaderSource(vsh, vshSrc);
ctx.compileShader(vsh);
if(!ctx.getShaderParameterb(vsh, COMPILE_STATUS)) {
System.err.println("ERROR: Could not check for ANGLE Issue #4994, VERTEX_SHADER did not compile:");
System.err.println(ctx.getShaderInfoLog(vsh));
ctx.deleteShader(vsh);
return false;
}
WebGLShader fsh = ctx.createShader(FRAGMENT_SHADER);
ctx.shaderSource(fsh, fshSrc);
ctx.compileShader(fsh);
if(!ctx.getShaderParameterb(fsh, COMPILE_STATUS)) {
System.err.println("ERROR: Could not check for ANGLE Issue #4994, FRAGMENT_SHADER did not compile:");
System.err.println(ctx.getShaderInfoLog(fsh));
ctx.deleteShader(vsh);
ctx.deleteShader(fsh);
return false;
}
WebGLProgram pr = ctx.createProgram();
ctx.attachShader(pr, vsh);
ctx.attachShader(pr, fsh);
ctx.bindAttribLocation(pr, 0, "a_pos");
ctx.bindAttribLocation(pr, 0, "fragColor");
ctx.linkProgram(pr);
ctx.detachShader(pr, vsh);
ctx.detachShader(pr, fsh);
ctx.deleteShader(vsh);
ctx.deleteShader(fsh);
if(!ctx.getProgramParameterb(pr, LINK_STATUS)) {
System.err.println("ERROR: Could not check for ANGLE Issue #4994, program did not link:");
System.err.println(ctx.getProgramInfoLog(pr));
ctx.deleteProgram(pr);
return false;
}
ctx.useProgram(pr);
ctx.uniform1i(ctx.getUniformLocation(pr, "tex"), 0);
byte x0 = (byte)0x00;
byte x1 = (byte)0xFF;
byte[] pixelsData = new byte[] {
x0, x0, x0, x1,
x0, x0, x0, x1,
x1, x1, x1, x1,
x0, x0, x0, x1,
x0, x0, x0, x1,
x0, x0, x0, x1,
x1, x1, x1, x1,
x0, x0, x0, x1,
x0, x0, x0, x1,
x0, x0, x0, x1,
x1, x1, x1, x1,
x0, x0, x0, x1
};
WebGLTexture tex = ctx.createTexture();
ctx.bindTexture(TEXTURE_2D, tex);
ctx.texParameteri(TEXTURE_2D, TEXTURE_WRAP_S, REPEAT);
ctx.texParameteri(TEXTURE_2D, TEXTURE_WRAP_T, REPEAT);
ctx.texParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST_MIPMAP_LINEAR);
ctx.texParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST);
ctx.texParameterf(TEXTURE_2D, TEXTURE_MAX_ANISOTROPY_EXT, 16.0f);
ctx.texImage2D(TEXTURE_2D, 0, RGBA, 4, 3, 0, RGBA, UNSIGNED_BYTE, TeaVMUtils.unwrapUnsignedByteArray(pixelsData));
ctx.generateMipmap(TEXTURE_2D);
float[] vertsData = new float[] {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f
};
WebGLBuffer buf = ctx.createBuffer();
ctx.bindBuffer(ARRAY_BUFFER, buf);
ctx.bufferData(ARRAY_BUFFER, TeaVMUtils.unwrapFloatArray(vertsData), STATIC_DRAW);
WebGLVertexArray arr = ctx.createVertexArray();
ctx.bindVertexArray(arr);
ctx.enableVertexAttribArray(0);
ctx.vertexAttribPointer(0, 2, FLOAT, false, 8, 0);
ctx.viewport(0, 0, 400, 300);
ctx.drawArrays(TRIANGLES, 0, 6);
ctx.deleteVertexArray(arr);
ctx.deleteBuffer(buf);
ctx.deleteTexture(tex);
ctx.deleteProgram(pr);
Uint8Array readPx = new Uint8Array(4);
ctx.readPixels(175, 150, 1, 1, RGBA, UNSIGNED_BYTE, readPx);
boolean b = (readPx.get(0) + readPx.get(1) + readPx.get(2)) != 0;
if(b) {
System.out.println("ANGLE issue #4994 is unpatched on this browser, enabling anisotropic fix");
}
return b;
}else {
System.err.println("WARNING: EXT_texture_filter_anisotropic is not supported!");
return false;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,190 @@
package net.lax1dude.eaglercraft.adapter;
import org.teavm.interop.Async;
import org.teavm.interop.AsyncCallback;
import org.teavm.jso.core.JSString;
import org.teavm.jso.indexeddb.IDBDatabase;
import org.teavm.jso.indexeddb.IDBFactory;
import org.teavm.jso.indexeddb.IDBGetRequest;
import org.teavm.jso.indexeddb.IDBObjectStore;
import org.teavm.jso.indexeddb.IDBOpenDBRequest;
import org.teavm.jso.indexeddb.IDBRequest;
import org.teavm.jso.typedarrays.ArrayBuffer;
import net.lax1dude.eaglercraft.adapter.teavm.TeaVMUtils;
public class SimpleStorage {
private static IDBDatabase database;
private static boolean available;
public static boolean isAvailable() {
return available;
}
static {
IDBOpenDBRequest request = IDBFactory.getInstance().open("eagstorage", 1);
request.setOnUpgradeNeeded(evt -> {
database = request.getResult();
database.createObjectStore("store");
});
request.setOnSuccess(() -> {
database = request.getResult();
available = true;
});
request.setOnError(() -> {
database = request.getResult();
available = false;
});
}
private static IDBObjectStore getStore() {
return database.transaction(new String[] { "store" }, "readwrite").objectStore("store");
}
@Async
public static native byte[] get(String key);
private static void get(String key, final AsyncCallback<byte[]> cb) {
if (key.equals("__LIST__") || key.contains("\n")) {
cb.complete(null);
return;
}
IDBGetRequest request = getStore().get(JSString.valueOf(key));
request.setOnSuccess(() -> {
cb.complete(TeaVMUtils.wrapByteArrayBuffer((ArrayBuffer) request.getResult()));
});
request.setOnError(() -> {
cb.complete(null);
});
}
@Async
public static native Boolean set(String key, byte[] value);
private static void set(String key, byte[] value, final AsyncCallback<Boolean> cb) {
if (key.equals("__LIST__") || key.contains("\n")) {
cb.complete(false);
return;
}
if (value == null) {
IDBGetRequest request3 = getStore().get(JSString.valueOf("__LIST__"));
request3.setOnSuccess(() -> {
String listVal;
if (JSString.isInstance(request3.getResult()) && !(listVal = ((JSString) request3.getResult().cast()).stringValue()).isEmpty()) {
String[] list = listVal.replaceAll("[^a-zA-Z0-9_\n]", "").split("\n");
String[] newList = new String[list.length - 1];
int a = 0;
for (int i = 0; i < list.length; ++i) {
if (list[i].equals(key)) {
--a;
} else {
newList[i + a] = list[i];
}
}
IDBRequest request2 = getStore().put(JSString.valueOf(String.join("\n", newList)), JSString.valueOf("__LIST__"));
request2.setOnSuccess(() -> {
IDBRequest request = getStore().delete(JSString.valueOf(key));
request.setOnSuccess(() -> {
cb.complete(Boolean.TRUE);
});
request.setOnError(() -> {
cb.complete(Boolean.FALSE);
});
});
request2.setOnError(() -> {
cb.complete(Boolean.FALSE);
});
} else {
IDBRequest request = getStore().delete(JSString.valueOf(key));
request.setOnSuccess(() -> {
cb.complete(Boolean.TRUE);
});
request.setOnError(() -> {
cb.complete(Boolean.FALSE);
});
}
});
request3.setOnError(() -> {
IDBRequest request = getStore().delete(JSString.valueOf(key));
request.setOnSuccess(() -> {
cb.complete(Boolean.TRUE);
});
request.setOnError(() -> {
cb.complete(Boolean.FALSE);
});
});
} else {
ArrayBuffer arr = TeaVMUtils.unwrapArrayBuffer(value);
IDBRequest request2 = getStore().put(arr, JSString.valueOf(key));
request2.setOnSuccess(() -> {
IDBGetRequest request3 = getStore().get(JSString.valueOf("__LIST__"));
request3.setOnSuccess(() -> {
String listVal;
if (JSString.isInstance(request3.getResult()) && !(listVal = ((JSString) request3.getResult().cast()).stringValue()).isEmpty()) {
String[] list = listVal.replaceAll("[^a-zA-Z0-9_\n]", "").split("\n");
boolean alrHas = false;
for (String s : list) {
if (s.equals(key)) {
alrHas = true;
break;
}
}
String[] newList;
if (alrHas) {
newList = list;
} else {
newList = new String[list.length + 1];
System.arraycopy(list, 0, newList, 0, list.length);
newList[list.length] = key;
}
IDBRequest request = getStore().put(JSString.valueOf(String.join("\n", newList).replaceAll("[^a-zA-Z0-9_\n]", "")), JSString.valueOf("__LIST__"));
request.setOnSuccess(() -> {
cb.complete(Boolean.TRUE);
});
request.setOnError(() -> {
cb.complete(Boolean.FALSE);
});
} else {
IDBRequest request = getStore().put(JSString.valueOf(key), JSString.valueOf("__LIST__"));
request.setOnSuccess(() -> {
cb.complete(Boolean.TRUE);
});
request.setOnError(() -> {
cb.complete(Boolean.FALSE);
});
}
});
request3.setOnError(() -> {
IDBRequest request = getStore().put(JSString.valueOf(key), JSString.valueOf("__LIST__"));
request.setOnSuccess(() -> {
cb.complete(Boolean.TRUE);
});
request.setOnError(() -> {
cb.complete(Boolean.FALSE);
});
});
});
request2.setOnError(() -> {
cb.complete(Boolean.FALSE);
});
}
}
@Async
public static native String[] list();
private static void list(final AsyncCallback<String[]> cb) {
IDBGetRequest request = getStore().get(JSString.valueOf("__LIST__"));
request.setOnSuccess(() -> {
String listVal;
if (JSString.isInstance(request.getResult()) && !(listVal = ((JSString) request.getResult().cast()).stringValue()).isEmpty()) {
cb.complete(listVal.replaceAll("[^a-zA-Z0-9_\n]", "").split("\n"));
} else {
cb.complete(new String[0]);
}
});
request.setOnError(() -> {
cb.complete(new String[0]);
});
}
}

View File

@ -0,0 +1,386 @@
package net.lax1dude.eaglercraft.adapter;
import org.teavm.jso.typedarrays.ArrayBuffer;
import org.teavm.jso.typedarrays.Float32Array;
import org.teavm.jso.typedarrays.Int32Array;
import net.lax1dude.eaglercraft.EaglerAdapter;
public class Tessellator {
/** The byte buffer used for GL allocation. */
private Int32Array intBuffer;
private Float32Array floatBuffer;
/**
* The number of vertices to be drawn in the next draw call. Reset to 0 between
* draw calls.
*/
private int vertexCount = 0;
/** The first coordinate to be used for the texture. */
private float textureU;
/** The second coordinate to be used for the texture. */
private float textureV;
private int brightness;
/** The color (RGBA) value to be used for the following draw call. */
private int color;
/**
* Whether the current draw object for this tessellator has color values.
*/
private boolean hasColor = false;
/**
* Whether the current draw object for this tessellator has texture coordinates.
*/
private boolean hasTexture = false;
private boolean hasBrightness = false;
/**
* Whether the current draw object for this tessellator has normal values.
*/
private boolean hasNormals = false;
/** The index into the raw buffer to be used for the next data. */
private int rawBufferIndex = 0;
/**
* The number of vertices manually added to the given draw call. This differs
* from vertexCount because it adds extra vertices when converting quads to
* triangles.
*/
private int addedVertices = 0;
/** Disables all color information for the following draw call. */
private boolean isColorDisabled = false;
/** The draw mode currently being used by the tessellator. */
private int drawMode;
/**
* An offset to be applied along the x-axis for all vertices in this draw call.
*/
private double xOffset;
/**
* An offset to be applied along the y-axis for all vertices in this draw call.
*/
private double yOffset;
/**
* An offset to be applied along the z-axis for all vertices in this draw call.
*/
private double zOffset;
/** The normal to be applied to the face being drawn. */
private int normal;
/** The static instance of the Tessellator. */
public static final Tessellator instance = new Tessellator(525000);
/** Whether this tessellator is currently in draw mode. */
private boolean isDrawing = false;
/** Whether we are currently using VBO or not. */
private boolean useVBO = false;
/** The size of the buffers used (in integers). */
private int bufferSize;
private Tessellator(int par1) {
this.bufferSize = par1;
ArrayBuffer a = new ArrayBuffer(par1 * 4);
this.intBuffer = new Int32Array(a);
this.floatBuffer = new Float32Array(a);
}
/**
* Draws the data set up in this tessellator and resets the state to prepare for
* new drawing.
*/
public int draw() {
if (!this.isDrawing) {
return 0;
} else {
this.isDrawing = false;
if (this.vertexCount > 0) {
if (this.hasTexture) {
EaglerAdapter.glEnableVertexAttrib(EaglerAdapter.GL_TEXTURE_COORD_ARRAY);
}
if (this.hasColor) {
EaglerAdapter.glEnableVertexAttrib(EaglerAdapter.GL_COLOR_ARRAY);
}
if (this.hasNormals) {
EaglerAdapter.glEnableVertexAttrib(EaglerAdapter.GL_NORMAL_ARRAY);
}
if (this.hasBrightness) {
EaglerAdapter.glClientActiveTexture(EaglerAdapter.GL_TEXTURE1);
EaglerAdapter.glEnableVertexAttrib(EaglerAdapter.GL_TEXTURE_COORD_ARRAY);
EaglerAdapter.glClientActiveTexture(EaglerAdapter.GL_TEXTURE0);
}
EaglerAdapter.glDrawArrays(this.drawMode, 0, this.vertexCount, new Int32Array(intBuffer.getBuffer(), 0, this.vertexCount * 8));
if (this.hasTexture) {
EaglerAdapter.glDisableVertexAttrib(EaglerAdapter.GL_TEXTURE_COORD_ARRAY);
}
if (this.hasColor) {
EaglerAdapter.glDisableVertexAttrib(EaglerAdapter.GL_COLOR_ARRAY);
}
if (this.hasNormals) {
EaglerAdapter.glDisableVertexAttrib(EaglerAdapter.GL_NORMAL_ARRAY);
}
if (this.hasBrightness) {
EaglerAdapter.glClientActiveTexture(EaglerAdapter.GL_TEXTURE1);
EaglerAdapter.glDisableVertexAttrib(EaglerAdapter.GL_TEXTURE_COORD_ARRAY);
EaglerAdapter.glClientActiveTexture(EaglerAdapter.GL_TEXTURE0);
}
}
int var1 = this.rawBufferIndex * 4;
this.reset();
return var1;
}
}
/**
* Clears the tessellator state in preparation for new drawing.
*/
private void reset() {
this.vertexCount = 0;
//this.byteBuffer.clear();
this.rawBufferIndex = 0;
this.addedVertices = 0;
}
/**
* Sets draw mode in the tessellator to draw quads.
*/
public void startDrawingQuads() {
this.startDrawing(EaglerAdapter.GL_QUADS);
}
/**
* Resets tessellator state and prepares for drawing (with the specified draw
* mode).
*/
public void startDrawing(int par1) {
if (this.isDrawing) {
this.draw();
}
this.isDrawing = true;
this.reset();
this.drawMode = par1;
this.hasNormals = false;
this.hasColor = false;
this.hasTexture = false;
this.hasBrightness = false;
this.isColorDisabled = false;
}
/**
* Sets the texture coordinates.
*/
public void setTextureUV(double par1, double par3) {
this.hasTexture = true;
this.textureU = (float) par1;
this.textureV = (float) par3;
}
public void setBrightness(int par1) {
this.hasBrightness = true;
this.brightness = par1;
}
/**
* Sets the RGB values as specified, converting from floats between 0 and 1 to
* integers from 0-255.
*/
public void setColorOpaque_F(float par1, float par2, float par3) {
this.setColorOpaque((int) (par1 * 255.0F), (int) (par2 * 255.0F), (int) (par3 * 255.0F));
}
/**
* Sets the RGBA values for the color, converting from floats between 0 and 1 to
* integers from 0-255.
*/
public void setColorRGBA_F(float par1, float par2, float par3, float par4) {
this.setColorRGBA((int) (par1 * 255.0F), (int) (par2 * 255.0F), (int) (par3 * 255.0F), (int) (par4 * 255.0F));
}
/**
* Sets the RGB values as specified, and sets alpha to opaque.
*/
public void setColorOpaque(int par1, int par2, int par3) {
this.setColorRGBA(par1, par2, par3, 255);
}
/**
* Sets the RGBA values for the color. Also clamps them to 0-255.
*/
public void setColorRGBA(int par1, int par2, int par3, int par4) {
if (!this.isColorDisabled) {
if (par1 > 255) {
par1 = 255;
}
if (par2 > 255) {
par2 = 255;
}
if (par3 > 255) {
par3 = 255;
}
if (par4 > 255) {
par4 = 255;
}
if (par1 < 0) {
par1 = 0;
}
if (par2 < 0) {
par2 = 0;
}
if (par3 < 0) {
par3 = 0;
}
if (par4 < 0) {
par4 = 0;
}
this.hasColor = true;
this.color = par4 << 24 | par3 << 16 | par2 << 8 | par1;
}
}
/**
* Adds a vertex specifying both x,y,z and the texture u,v for it.
*/
public void addVertexWithUV(double par1, double par3, double par5, double par7, double par9) {
this.setTextureUV(par7, par9);
this.addVertex(par1, par3, par5);
}
/**
* Adds a vertex with the specified x,y,z to the current draw call. It will
* trigger a draw() if the buffer gets full.
*/
public void addVertex(double par1, double par3, double par5) {
if(this.addedVertices > 65534) return;
++this.addedVertices;
++this.vertexCount;
int bufferIndex = this.rawBufferIndex;
Int32Array intBuffer0 = intBuffer;
Float32Array floatBuffer0 = floatBuffer;
floatBuffer0.set(bufferIndex + 0, (float) (par1 + this.xOffset));
floatBuffer0.set(bufferIndex + 1, (float) (par3 + this.yOffset));
floatBuffer0.set(bufferIndex + 2, (float) (par5 + this.zOffset));
if (this.hasTexture) {
floatBuffer0.set(bufferIndex + 3, this.textureU);
floatBuffer0.set(bufferIndex + 4, this.textureV);
}
if (this.hasColor) {
intBuffer0.set(bufferIndex + 5, this.color);
}
if (this.hasNormals) {
intBuffer0.set(bufferIndex + 6, this.normal);
}
if (this.hasBrightness) {
intBuffer0.set(bufferIndex + 7, this.brightness);
}
this.rawBufferIndex += 8;
}
/**
* Sets the color to the given opaque value (stored as byte values packed in an
* integer).
*/
public void setColorOpaque_I(int par1) {
int var2 = par1 >>> 16 & 255;
int var3 = par1 >>> 8 & 255;
int var4 = par1 & 255;
this.setColorOpaque(var2, var3, var4);
}
/**
* Sets the color to the given color (packed as bytes in integer) and alpha
* values.
*/
public void setColorRGBA_I(int par1, int par2) {
int var3 = par1 >>> 16 & 255;
int var4 = par1 >>> 8 & 255;
int var5 = par1 & 255;
this.setColorRGBA(var3, var4, var5, par2);
}
/**
* Disables colors for the current draw call.
*/
public void disableColor() {
this.isColorDisabled = true;
}
/**
* Sets the normal for the current draw call.
*/
public void setNormal(float par1, float par2, float par3) {
this.hasNormals = true;
int var4 = (int)(par1 * 127.0F) + 127;
int var5 = (int)(par2 * 127.0F) + 127;
int var6 = (int)(par3 * 127.0F) + 127;
this.normal = var4 & 255 | (var5 & 255) << 8 | (var6 & 255) << 16;
}
/**
* Sets the normal for the current draw call.
*/
public void setNormalN(float par1, float par2, float par3) {
this.hasNormals = true;
float len = (float) Math.sqrt(par1 * par1 + par2 * par2 + par3 * par3);
int var4 = (int)((par1 / len) * 127.0F) + 127;
int var5 = (int)((par2 / len) * 127.0F) + 127;
int var6 = (int)((par3 / len) * 127.0F) + 127;
this.normal = var4 & 255 | (var5 & 255) << 8 | (var6 & 255) << 16;
}
/**
* Sets the translation for all vertices in the current draw call.
*/
public void setTranslation(double par1, double par3, double par5) {
this.xOffset = par1;
this.yOffset = par3;
this.zOffset = par5;
}
/**
* Offsets the translation for all vertices in the current draw call.
*/
public void addTranslation(float par1, float par2, float par3) {
this.xOffset += (float) par1;
this.yOffset += (float) par2;
this.zOffset += (float) par3;
}
}

View File

@ -0,0 +1,59 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.teavm.jso.typedarrays.Int8Array;
import org.teavm.jso.typedarrays.Uint8Array;
public class BufferConverter {
public static final Int8Array convertByteBuffer(ByteBuffer b) {
if(b.hasArray()) {
int p = b.position();
int l = b.remaining();
return new Int8Array(TeaVMUtils.unwrapArrayBuffer(b.array()), p, l);
}else {
byte[] ret = new byte[b.remaining()];
b.get(ret);
return TeaVMUtils.unwrapByteArray(ret);
}
}
public static final Uint8Array convertByteBufferUnsigned(ByteBuffer b) {
if(b.hasArray()) {
int p = b.position();
int l = b.remaining();
return new Uint8Array(TeaVMUtils.unwrapArrayBuffer(b.array()), p, l);
}else {
byte[] ret = new byte[b.remaining()];
b.get(ret);
return TeaVMUtils.unwrapUnsignedByteArray(ret);
}
}
public static final Int8Array convertIntBuffer(IntBuffer b) {
if(b.hasArray()) {
int p = b.position() << 2;
int l = b.remaining() << 2;
return new Int8Array(TeaVMUtils.unwrapArrayBuffer(b.array()), p, l);
}else {
int[] ret = new int[b.remaining()];
b.get(ret);
return new Int8Array(TeaVMUtils.unwrapArrayBuffer(ret));
}
}
public static final Uint8Array convertIntBufferUnsigned(IntBuffer b) {
if(b.hasArray()) {
int p = b.position() << 2;
int l = b.remaining() << 2;
return new Uint8Array(TeaVMUtils.unwrapArrayBuffer(b.array()), p, l);
}else {
int[] ret = new int[b.remaining()];
b.get(ret);
return new Uint8Array(TeaVMUtils.unwrapArrayBuffer(ret));
}
}
}

View File

@ -0,0 +1,63 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.typedarrays.ArrayBuffer;
public interface EaglercraftLANClient extends JSObject {
final int READYSTATE_INIT_FAILED = -2;
final int READYSTATE_FAILED = -1;
final int READYSTATE_DISCONNECTED = 0;
final int READYSTATE_CONNECTING = 1;
final int READYSTATE_CONNECTED = 2;
boolean LANClientSupported();
void initializeClient();
void setICEServers(String[] urls);
void setICECandidateHandler(ICECandidateHandler callback);
void setDescriptionHandler(DescriptionHandler callback);
void setRemoteDataChannelHandler(ClientSignalHandler cb);
void setRemoteDisconnectHandler(ClientSignalHandler cb);
void setRemotePacketHandler(RemotePacketHandler cb);
int getReadyState();
void sendPacketToServer(ArrayBuffer buffer);
void signalRemoteConnect();
void signalRemoteDescription(String descJSON);
void signalRemoteICECandidate(String candidate);
void signalRemoteDisconnect(boolean quiet);
@JSFunctor
public static interface ICECandidateHandler extends JSObject {
void call(String candidate);
}
@JSFunctor
public static interface DescriptionHandler extends JSObject {
void call(String description);
}
@JSFunctor
public static interface ClientSignalHandler extends JSObject {
void call();
}
@JSFunctor
public static interface RemotePacketHandler extends JSObject {
void call(ArrayBuffer buffer);
}
}

View File

@ -0,0 +1,57 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.typedarrays.ArrayBuffer;
public interface EaglercraftLANServer extends JSObject {
boolean LANServerSupported();
void initializeServer();
void setICEServers(String[] urls);
void setICECandidateHandler(ICECandidateHandler cb);
void setDescriptionHandler(DescriptionHandler cb);
void setRemoteClientDataChannelHandler(ClientSignalHandler cb);
void setRemoteClientDisconnectHandler(ClientSignalHandler cb);
void setRemoteClientPacketHandler(PeerPacketHandler cb);
void sendPacketToRemoteClient(String peerId, ArrayBuffer buffer);
void signalRemoteConnect(String peerId);
void signalRemoteDescription(String peerId, String descJSON);
void signalRemoteICECandidate(String peerId, String candidate);
void signalRemoteDisconnect(String peerId);
int countPeers();
@JSFunctor
public static interface ICECandidateHandler extends JSObject {
void call(String peerId, String candidate);
}
@JSFunctor
public static interface DescriptionHandler extends JSObject {
void call(String peerId, String candidate);
}
@JSFunctor
public static interface ClientSignalHandler extends JSObject {
void call(String peerId);
}
@JSFunctor
public static interface PeerPacketHandler extends JSObject {
void call(String peerId, ArrayBuffer buffer);
}
}

View File

@ -0,0 +1,63 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.webaudio.MediaStream;
public interface EaglercraftVoiceClient extends JSObject {
int READYSTATE_NONE = 0;
int READYSTATE_ABORTED = -1;
int READYSTATE_DEVICE_INITIALIZED = 1;
boolean voiceClientSupported();
void initializeDevices();
void setICEServers(String[] urls);
void setICECandidateHandler(ICECandidateHandler callback);
void setDescriptionHandler(DescriptionHandler callback);
void setPeerTrackHandler(PeerTrackHandler callback);
void setPeerDisconnectHandler(PeerDisconnectHandler callback);
void activateVoice(boolean active);
void setMicVolume(float volume);
void mutePeer(String peerId, boolean muted);
int getReadyState();
int signalConnect(String peerId, boolean offer);
int signalDescription(String peerId, String description);
int signalDisconnect(String peerId, boolean quiet);
int signalICECandidate(String peerId, String candidate);
@JSFunctor
public static interface ICECandidateHandler extends JSObject {
void call(String peerId, String candidate);
}
@JSFunctor
public static interface DescriptionHandler extends JSObject {
void call(String peerId, String candidate);
}
@JSFunctor
public static interface PeerTrackHandler extends JSObject {
void call(String peerId, MediaStream audioNode);
}
@JSFunctor
public static interface PeerDisconnectHandler extends JSObject {
void call(String peerId, boolean quiet);
}
}

View File

@ -0,0 +1,36 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSClass;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
import org.teavm.jso.workers.MessagePort;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
@JSClass
public class MessageChannel implements JSObject {
@JSBody(params = { }, script = "return (typeof MessageChannel !== \"undefined\");")
public static native boolean supported();
@JSProperty
public native MessagePort getPort1();
@JSProperty
public native MessagePort getPort2();
}

View File

@ -0,0 +1,103 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import java.util.ArrayList;
import java.util.List;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.browser.TimerHandler;
import org.teavm.jso.browser.Window;
import org.teavm.jso.core.JSArrayReader;
import org.teavm.jso.dom.html.HTMLCanvasElement;
import org.teavm.jso.dom.html.HTMLDocument;
import org.teavm.jso.dom.html.HTMLIFrameElement;
import org.teavm.jso.dom.xml.Element;
import org.teavm.jso.dom.xml.NodeList;
public class SelfDefence {
private static HTMLCanvasElement canvas = null;
private static boolean ignoreNextWindow = false;
@JSFunctor
private static interface NewWindowCallback extends JSObject {
void call(Window newWindow);
}
@JSBody(params = { "cb" }, script = "const ccb = cb; const _open = window.open; window.open = (url,name,params) => { var rw = _open(url,name,params); ccb(rw); return rw; }")
private static native void injectWindowCapture(NewWindowCallback callback);
private static final List<Window> capturedChildWindows = new ArrayList<>();
public static void init(HTMLCanvasElement legitCanvas) {
canvas = legitCanvas;
for(int i = 0; i < 15; ++i) {
Window.setTimeout(new TimerHandler() {
@Override
public void onTimer() {
Window.setTimeout(this, (long)(Math.random() * 25000l));
run(Window.current());
for(int i = 0, l = capturedChildWindows.size(); i < l; ++i) {
run(capturedChildWindows.get(i));
}
}
}, (long)(Math.random() * 25000l));
}
injectWindowCapture(new NewWindowCallback() {
@Override
public void call(Window newWindow) {
if(!ignoreNextWindow) {
capturedChildWindows.add(newWindow);
}
ignoreNextWindow = false;
}
});
}
public static void openWindowIgnore(String url, String name) {
ignoreNextWindow = true;
Window.current().open(url, name);
}
private static void run(Window win) {
try {
run0(win);
}catch(Throwable t) {
}
}
private static void run0(Window win) {
run(win.getDocument());
JSArrayReader<HTMLIFrameElement> frms = win.getFrames();
for(int i = 0, l = frms.getLength(); i < l; ++i) {
HTMLIFrameElement frm = frms.get(i);
if(checkFrame(frm)) {
run(frm.getContentWindow());
}
}
}
@JSBody(params = { "frm" }, script = "try { var g = frm.contentWindow; g[\"fuck_off\"] = \"dick\"; return g[\"fuck_off\"] === \"dick\"; } catch (e) { return false; }")
private static native boolean checkFrame(HTMLIFrameElement frame);
private static void run(HTMLDocument doc) {
try {
run0(doc);
}catch(Throwable t) {
}
}
private static void run0(HTMLDocument doc) {
NodeList<Element> els = doc.getElementsByTagName("canvas");
for(int i = 0, l = els.getLength(); i < l; ++i) {
HTMLCanvasElement canv = (HTMLCanvasElement) els.get(i);
if(canvas != canv) {
canv.delete();
}
}
}
}

View File

@ -0,0 +1,95 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import org.teavm.backend.javascript.spi.GeneratedBy;
import org.teavm.backend.javascript.spi.InjectedBy;
import org.teavm.jso.typedarrays.ArrayBuffer;
import org.teavm.jso.typedarrays.ArrayBufferView;
import org.teavm.jso.typedarrays.Float32Array;
import org.teavm.jso.typedarrays.Int16Array;
import org.teavm.jso.typedarrays.Int32Array;
import org.teavm.jso.typedarrays.Int8Array;
import org.teavm.jso.typedarrays.Uint8Array;
import net.lax1dude.eaglercraft.adapter.teavm.generators.TeaVMUtilsUnwrapGenerator;
public class TeaVMUtils {
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
public static native Int8Array unwrapByteArray(byte[] buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
public static native ArrayBuffer unwrapArrayBuffer(byte[] buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
public static native ArrayBufferView unwrapArrayBufferView(byte[] buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
public static native byte[] wrapByteArray(Int8Array buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
public static native byte[] wrapByteArrayBuffer(ArrayBuffer buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
public static native byte[] wrapByteArrayBufferView(ArrayBufferView buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapUnsignedTypedArray.class)
public static native Uint8Array unwrapUnsignedByteArray(byte[] buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
public static native byte[] wrapUnsignedByteArray(Uint8Array buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
public static native Int32Array unwrapIntArray(int[] buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
public static native ArrayBuffer unwrapArrayBuffer(int[] buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
public static native ArrayBufferView unwrapArrayBufferView(int[] buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
public static native int[] wrapIntArray(Int32Array buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
public static native int[] wrapIntArrayBuffer(ArrayBuffer buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
public static native int[] wrapIntArrayBufferView(ArrayBufferView buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
public static native Float32Array unwrapFloatArray(float[] buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
public static native ArrayBuffer unwrapArrayBuffer(float[] buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
public static native ArrayBufferView unwrapArrayBufferView(float[] buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
public static native float[] wrapFloatArray(Float32Array buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
public static native float[] wrapFloatArrayBuffer(ArrayBuffer buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
public static native float[] wrapFloatArrayBufferView(ArrayBufferView buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
public static native Int16Array unwrapShortArray(short[] buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
public static native ArrayBuffer unwrapArrayBuffer(short[] buf);
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
public static native ArrayBufferView unwrapArrayBufferView(short[] buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
public static native short[] wrapShortArray(Int16Array buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
public static native short[] wrapShortArrayBuffer(ArrayBuffer buf);
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
public static native short[] wrapShortArrayBuffer(ArrayBufferView buf);
}

View File

@ -0,0 +1,46 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import org.teavm.jso.webgl.WebGLRenderingContext;
public interface WebGL2RenderingContext extends WebGLRenderingContext {
int TEXTURE_MAX_LEVEL = 0x0000813D;
int TEXTURE_MAX_ANISOTROPY_EXT = 0x000084FE;
int UNSIGNED_INT_24_8 = 0x000084FA;
int ANY_SAMPLES_PASSED = 0x00008D6A;
int QUERY_RESULT = 0x00008866;
int QUERY_RESULT_AVAILABLE = 0x00008867;
int DEPTH24_STENCIL8 = 0x000088F0;
int DEPTH_COMPONENT32F = 0x00008CAC;
int READ_FRAMEBUFFER = 0x00008CA8;
int DRAW_FRAMEBUFFER = 0x00008CA9;
int RGB8 = 0x00008051;
int RGBA8 = 0x00008058;
int R8 = 0x00008229;
int RED = 0x00001903;
WebGLQuery createQuery();
void beginQuery(int p1, WebGLQuery obj);
void endQuery(int p1);
void deleteQuery(WebGLQuery obj);
int getQueryParameter(WebGLQuery obj, int p2);
WebGLVertexArray createVertexArray();
void deleteVertexArray(WebGLVertexArray obj);
void bindVertexArray(WebGLVertexArray obj);
void renderbufferStorageMultisample(int p1, int p2, int p3, int p4, int p5);
void blitFramebuffer(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, int p9, int p10);
void drawBuffers(int[] p1);
void readBuffer(int p1);
}

View File

@ -0,0 +1,6 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import org.teavm.jso.JSObject;
public interface WebGLQuery extends JSObject {
}

View File

@ -0,0 +1,6 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import org.teavm.jso.JSObject;
public interface WebGLVertexArray extends JSObject {
}

View File

@ -0,0 +1,158 @@
package net.lax1dude.eaglercraft.adapter.teavm.generators;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.backend.javascript.spi.Injector;
import org.teavm.backend.javascript.spi.InjectorContext;
import org.teavm.model.MethodReference;
public class TeaVMUtilsUnwrapGenerator {
// WARNING: This code uses internal TeaVM APIs that may not have
// been intended for end users of the compiler to program with
public static class UnwrapArrayBuffer implements Injector {
@Override
public void generate(InjectorContext context, MethodReference methodRef) {
context.writeExpr(context.getArgument(0));
context.getWriter().append(".data.buffer");
}
}
public static class UnwrapTypedArray implements Injector {
@Override
public void generate(InjectorContext context, MethodReference methodRef) {
context.writeExpr(context.getArgument(0));
context.getWriter().append(".data");
}
}
public static class WrapArrayBuffer implements Generator {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) {
String parName = context.getParameterName(1);
switch (methodRef.getName()) {
case "wrapByteArrayBuffer":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_bytecls").append(',').ws();
writer.append("new Int8Array(").append(parName).append("))").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
case "wrapIntArrayBuffer":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_intcls").append(',').ws();
writer.append("new Int32Array(").append(parName).append("))").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
case "wrapFloatArrayBuffer":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_floatcls").append(',').ws();
writer.append("new Float32Array(").append(parName).append("))").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
case "wrapShortArrayBuffer":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_shortcls").append(',').ws();
writer.append("new Int16Array(").append(parName).append("))").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
default:
break;
}
}
}
public static class WrapArrayBufferView implements Generator {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) {
String parName = context.getParameterName(1);
switch (methodRef.getName()) {
case "wrapByteArrayBufferView":
case "wrapUnsignedByteArray":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_bytecls").append(',').ws();
writer.append("new Int8Array(").append(parName).append(".buffer))").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
case "wrapIntArrayBufferView":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_intcls").append(',').ws();
writer.append("new Int32Array(").append(parName).append(".buffer))").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
case "wrapFloatArrayBufferView":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_floatcls").append(',').ws();
writer.append("new Float32Array(").append(parName).append(".buffer))").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
case "wrapShortArrayBufferView":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_shortcls").append(',').ws();
writer.append("new Int16Array(").append(parName).append(".buffer))").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
default:
break;
}
}
}
public static class WrapTypedArray implements Generator {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) {
String parName = context.getParameterName(1);
switch (methodRef.getName()) {
case "wrapByteArray":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_shortcls").append(',').ws();
writer.append(parName).append(")").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
case "wrapIntArray":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_intcls").append(',').ws();
writer.append(parName).append(")").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
case "wrapFloatArray":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_floatcls").append(',').ws();
writer.append(parName).append(")").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
case "wrapShortArray":
writer.append("return ").append(parName).ws().append('?').ws();
writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_shortcls").append(',').ws();
writer.append(parName).append(")").ws();
writer.append(':').ws().append("null;").softNewLine();
break;
default:
break;
}
}
}
public static class UnwrapUnsignedTypedArray implements Injector {
@Override
public void generate(InjectorContext context, MethodReference methodRef) {
context.getWriter().append("new Uint8Array(");
context.writeExpr(context.getArgument(0));
context.getWriter().append(".data.buffer)");
}
}
}

View File

@ -0,0 +1,18 @@
package net.lax1dude.eaglercraft.adapter.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;
}
}

View File

@ -0,0 +1,19 @@
package net.lax1dude.eaglercraft.adapter.vfs;
public class SYS {
public static final VirtualFilesystem VFS;
static {
VirtualFilesystem.VFSHandle vh = VirtualFilesystem.openVFS("eagStorage2");
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;
}
}

View File

@ -0,0 +1,17 @@
package net.lax1dude.eaglercraft.adapter.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);
}

View File

@ -0,0 +1,227 @@
package net.lax1dude.eaglercraft.adapter.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<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

@ -0,0 +1,290 @@
package net.lax1dude.eaglercraft.adapter.vfs;
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.EaglerInputStream;
/**
* 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 EaglerInputStream(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<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 = 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<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

@ -0,0 +1,690 @@
package net.lax1dude.eaglercraft.adapter.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.EaglerAdapter;
import net.lax1dude.eaglercraft.EaglerInputStream;
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(true);
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 = 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<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 List<VFile> listVFiles(String prefix) {
final ArrayList<VFile> list = new ArrayList<>();
AsyncHandlers.iterateFiles(indexeddb, this, prefix, false, (v) -> {
list.add(new VFile(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 = EaglerAdapter.steadyTimeMillis();
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");
}
}