mirror of
https://github.com/Eaglercraft-Archive/Eaglercraftx-1.8.8-src.git
synced 2025-06-28 02:48:14 -05:00
Update #37 - Touch support without userscript, many other feats
This commit is contained in:
@ -29,85 +29,127 @@ import net.lax1dude.eaglercraft.v1_8.internal.teavm.WebGLVertexArray;
|
||||
class OpenGLObjects {
|
||||
|
||||
static class BufferGL implements IBufferGL {
|
||||
|
||||
|
||||
private static int hashGen = 0;
|
||||
final WebGLBuffer ptr;
|
||||
|
||||
final int hash;
|
||||
|
||||
BufferGL(WebGLBuffer ptr) {
|
||||
this.ptr = ptr;
|
||||
this.hash = ++hashGen;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
PlatformOpenGL._wglDeleteBuffers(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static class BufferArrayGL implements IBufferArrayGL {
|
||||
|
||||
|
||||
private static int hashGen = 0;
|
||||
final WebGLVertexArray ptr;
|
||||
|
||||
final int hash;
|
||||
|
||||
BufferArrayGL(WebGLVertexArray ptr) {
|
||||
this.ptr = ptr;
|
||||
this.hash = ++hashGen;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
PlatformOpenGL._wglDeleteVertexArrays(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static class TextureGL implements ITextureGL {
|
||||
|
||||
|
||||
private static int hashGen = 0;
|
||||
final WebGLTexture ptr;
|
||||
|
||||
final int hash;
|
||||
|
||||
TextureGL(WebGLTexture ptr) {
|
||||
this.ptr = ptr;
|
||||
this.hash = ++hashGen;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
PlatformOpenGL._wglDeleteTextures(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static class ProgramGL implements IProgramGL {
|
||||
|
||||
|
||||
private static int hashGen = 0;
|
||||
final WebGLProgram ptr;
|
||||
|
||||
final int hash;
|
||||
|
||||
ProgramGL(WebGLProgram ptr) {
|
||||
this.ptr = ptr;
|
||||
this.hash = ++hashGen;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
PlatformOpenGL._wglDeleteProgram(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static class UniformGL implements IUniformGL {
|
||||
|
||||
|
||||
private static int hashGen = 0;
|
||||
final WebGLUniformLocation ptr;
|
||||
|
||||
final int hash;
|
||||
|
||||
UniformGL(WebGLUniformLocation ptr) {
|
||||
this.ptr = ptr;
|
||||
this.hash = ++hashGen;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static class ShaderGL implements IShaderGL {
|
||||
|
||||
|
||||
private static int hashGen = 0;
|
||||
final WebGLShader ptr;
|
||||
final int hash;
|
||||
|
||||
ShaderGL(WebGLShader ptr) {
|
||||
this.ptr = ptr;
|
||||
this.hash = ++hashGen;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,48 +160,69 @@ class OpenGLObjects {
|
||||
}
|
||||
|
||||
static class FramebufferGL implements IFramebufferGL {
|
||||
|
||||
|
||||
private static int hashGen = 0;
|
||||
final WebGLFramebuffer ptr;
|
||||
final int hash;
|
||||
|
||||
FramebufferGL(WebGLFramebuffer ptr) {
|
||||
this.ptr = ptr;
|
||||
this.hash = ++hashGen;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
PlatformOpenGL._wglDeleteFramebuffer(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static class RenderbufferGL implements IRenderbufferGL {
|
||||
|
||||
|
||||
private static int hashGen = 0;
|
||||
final WebGLRenderbuffer ptr;
|
||||
|
||||
final int hash;
|
||||
|
||||
RenderbufferGL(WebGLRenderbuffer ptr) {
|
||||
this.ptr = ptr;
|
||||
this.hash = ++hashGen;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
PlatformOpenGL._wglDeleteRenderbuffer(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static class QueryGL implements IQueryGL {
|
||||
|
||||
|
||||
private static int hashGen = 0;
|
||||
final WebGLQuery ptr;
|
||||
|
||||
final int hash;
|
||||
|
||||
QueryGL(WebGLQuery ptr) {
|
||||
this.ptr = ptr;
|
||||
this.hash = ++hashGen;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
PlatformOpenGL._wglDeleteQueries(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
@ -13,20 +15,25 @@ import org.teavm.jso.browser.Storage;
|
||||
import org.teavm.jso.browser.TimerHandler;
|
||||
import org.teavm.jso.browser.Window;
|
||||
import org.teavm.jso.canvas.CanvasRenderingContext2D;
|
||||
import org.teavm.jso.dom.css.CSSStyleDeclaration;
|
||||
import org.teavm.jso.dom.events.Event;
|
||||
import org.teavm.jso.dom.events.EventListener;
|
||||
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.dom.html.HTMLInputElement;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.Base64;
|
||||
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
|
||||
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.teavm.DebugConsoleWindow;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMBlobURLHandle;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMBlobURLManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
|
||||
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
@ -46,7 +53,18 @@ public class PlatformApplication {
|
||||
if(url.indexOf(':') == -1) {
|
||||
url = "http://" + url;
|
||||
}
|
||||
Window.current().open(url, "_blank", "noopener,noreferrer");
|
||||
URI parsedURL;
|
||||
try {
|
||||
parsedURL = new URI(url);
|
||||
}catch(URISyntaxException ex) {
|
||||
PlatformRuntime.logger.error("Refusing to open invalid URL: {}", url);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Window.current().open(parsedURL.toString(), "_blank", "noopener,noreferrer");
|
||||
}catch(Throwable t) {
|
||||
PlatformRuntime.logger.error("Exception opening link!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void setClipboard(String text) {
|
||||
@ -54,6 +72,10 @@ public class PlatformApplication {
|
||||
setClipboard0(text);
|
||||
}catch(Throwable t) {
|
||||
PlatformRuntime.logger.error("Exception setting clipboard data");
|
||||
try {
|
||||
Window.prompt("Here is the text you're trying to copy:", text);
|
||||
}catch(Throwable t2) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +84,12 @@ public class PlatformApplication {
|
||||
return getClipboard0();
|
||||
}catch(Throwable t) {
|
||||
PlatformRuntime.logger.error("Exception getting clipboard data");
|
||||
return "";
|
||||
try {
|
||||
String ret = Window.prompt("Please enter the text to paste:");
|
||||
return ret != null ? ret : "";
|
||||
}catch(Throwable t2) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,11 +102,11 @@ public class PlatformApplication {
|
||||
private static native String getClipboard0();
|
||||
|
||||
private static void getClipboard0(final AsyncCallback<String> cb) {
|
||||
final long start = System.currentTimeMillis();
|
||||
final long start = PlatformRuntime.steadyTimeMillis();
|
||||
getClipboard1(new StupidFunctionResolveString() {
|
||||
@Override
|
||||
public void resolveStr(String s) {
|
||||
if(System.currentTimeMillis() - start > 500l) {
|
||||
if(PlatformRuntime.steadyTimeMillis() - start > 500l) {
|
||||
PlatformInput.unpressCTRL = true;
|
||||
}
|
||||
cb.complete(s);
|
||||
@ -87,10 +114,10 @@ public class PlatformApplication {
|
||||
});
|
||||
}
|
||||
|
||||
@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(\"\"); });")
|
||||
@JSBody(params = { "cb" }, script = "if(!navigator.clipboard) { cb(prompt(\"Please enter the text to paste:\") || \"\"); } else if (!navigator.clipboard.readText) cb(\"\"); else 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);")
|
||||
@JSBody(params = { "str" }, script = "if(navigator.clipboard) clipboard.writeText(str);")
|
||||
private static native void setClipboard0(String str);
|
||||
|
||||
public static void setLocalStorage(String name, byte[] data) {
|
||||
@ -100,11 +127,11 @@ public class PlatformApplication {
|
||||
public static void setLocalStorage(String name, byte[] data, boolean hooks) {
|
||||
IClientConfigAdapter adapter = PlatformRuntime.getClientConfigAdapter();
|
||||
String eagName = adapter.getLocalStorageNamespace() + "." + name;
|
||||
String b64 = Base64.encodeBase64String(data);
|
||||
String b64 = data != null ? Base64.encodeBase64String(data) : null;
|
||||
try {
|
||||
Storage s = Window.current().getLocalStorage();
|
||||
if(s != null) {
|
||||
if(data != null) {
|
||||
if(b64 != null) {
|
||||
s.setItem(eagName, b64);
|
||||
}else {
|
||||
s.removeItem(eagName);
|
||||
@ -157,24 +184,49 @@ public class PlatformApplication {
|
||||
}
|
||||
}
|
||||
|
||||
private static final DateFormat dateFormatSS = EagRuntime.fixDateFormat(new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss"));
|
||||
private static final DateFormat dateFormatSS = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
|
||||
|
||||
public static String saveScreenshot() {
|
||||
PlatformOpenGL._wglBindFramebuffer(0x8D40, null);
|
||||
int w = PlatformInput.getWindowWidth();
|
||||
int h = PlatformInput.getWindowHeight();
|
||||
ByteBuffer buf = PlatformRuntime.allocateByteBuffer(w * h * 4);
|
||||
PlatformOpenGL._wglReadPixels(0, 0, w, h, 6408, 5121, buf);
|
||||
for(int i = 3, l = buf.remaining(); i < l; i += 4) {
|
||||
buf.put(i, (byte)0xFF);
|
||||
}
|
||||
String name = "screenshot_" + dateFormatSS.format(new Date()).toString() + ".png";
|
||||
int w = PlatformRuntime.canvas.getWidth();
|
||||
int h = PlatformRuntime.canvas.getHeight();
|
||||
HTMLCanvasElement copyCanvas = (HTMLCanvasElement) Window.current().getDocument().createElement("canvas");
|
||||
copyCanvas.setWidth(w);
|
||||
copyCanvas.setHeight(h);
|
||||
CanvasRenderingContext2D ctx = (CanvasRenderingContext2D) copyCanvas.getContext("2d");
|
||||
ctx.drawImage(PlatformRuntime.canvas, 0, 0);
|
||||
saveScreenshot(name, copyCanvas);
|
||||
CanvasRenderingContext2D ctx = (CanvasRenderingContext2D) copyCanvas.getContext("2d", PlatformAssets.youEagler());
|
||||
putImageData(ctx, EaglerArrayBufferAllocator.getDataView8(buf).getBuffer(), w, h);
|
||||
PlatformRuntime.freeByteBuffer(buf);
|
||||
downloadScreenshot(copyCanvas, name, PlatformRuntime.parent);
|
||||
return name;
|
||||
}
|
||||
|
||||
@JSBody(params = { "name", "cvs" }, script = "var a=document.createElement(\"a\");a.href=cvs.toDataURL(\"image/png\");a.download=name;a.click();")
|
||||
private static native void saveScreenshot(String name, HTMLCanvasElement cvs);
|
||||
|
||||
|
||||
@JSBody(params = { "ctx", "buffer", "w", "h" }, script = "var imgData = ctx.createImageData(w, h); var ww = w * 4; for(var i = 0; i < h; ++i) { imgData.data.set(new Uint8ClampedArray(buffer, (h - i - 1) * ww, ww), i * ww); } ctx.putImageData(imgData, 0, 0);")
|
||||
private static native JSObject putImageData(CanvasRenderingContext2D ctx, ArrayBuffer buffer, int w, int h);
|
||||
|
||||
@JSBody(params = { "cvs", "name", "parentElement" }, script =
|
||||
"var vigg = function(el, url){" +
|
||||
"el.style.position = \"absolute\";" +
|
||||
"el.style.left = \"0px\";" +
|
||||
"el.style.top = \"0px\";" +
|
||||
"el.style.zIndex = \"-100\";" +
|
||||
"el.style.color = \"transparent\";" +
|
||||
"el.innerText = \"Download Screenshot\";" +
|
||||
"el.href = url;" +
|
||||
"el.target = \"_blank\";" +
|
||||
"el.download = name;" +
|
||||
"parentElement.appendChild(el);" +
|
||||
"setTimeout(function() { el.click();" +
|
||||
"setTimeout(function() { parentElement.removeChild(el); }, 50);" +
|
||||
"}, 50);" +
|
||||
"}; setTimeout(function() { vigg(document.createElement(\"a\"), cvs.toDataURL(\"image/png\")); }, 50);")
|
||||
private static native void downloadScreenshot(HTMLCanvasElement cvs, String name, HTMLElement parentElement);
|
||||
|
||||
public static void showPopup(final String msg) {
|
||||
Window.setTimeout(new TimerHandler() {
|
||||
@Override
|
||||
@ -205,16 +257,52 @@ public class PlatformApplication {
|
||||
|
||||
}
|
||||
|
||||
private static volatile boolean fileChooserHasResult = false;
|
||||
private static volatile FileChooserResult fileChooserResultObject = null;
|
||||
private static final int FILE_CHOOSER_IMPL_CORE = 0;
|
||||
private static final int FILE_CHOOSER_IMPL_LEGACY = 1;
|
||||
private static int fileChooserImpl = -1;
|
||||
private static boolean fileChooserHasResult = false;
|
||||
private static FileChooserResult fileChooserResultObject = null;
|
||||
private static HTMLInputElement fileChooserElement = null;
|
||||
private static HTMLElement fileChooserMobileElement = null;
|
||||
|
||||
@JSBody(params = { "inputElement", "callback" }, script =
|
||||
"if(inputElement.files.length > 0) {"
|
||||
+ "const value = inputElement.files[0];"
|
||||
+ "var eag = function(value){"
|
||||
+ "value.arrayBuffer().then(function(arr){ callback(value.name, arr); })"
|
||||
+ ".catch(function(){ callback(null, null); });"
|
||||
+ "} else callback(null, null);")
|
||||
private static native void getFileChooserResult(HTMLInputElement inputElement, FileChooserCallback callback);
|
||||
+ "}; eag(inputElement.files[0]); } else callback(null, null);")
|
||||
private static native void getFileChooserResultNew(HTMLInputElement inputElement, FileChooserCallback callback);
|
||||
|
||||
@JSBody(params = { "inputElement", "callback" }, script =
|
||||
"if(inputElement.files.length > 0) {"
|
||||
+ "var eag = function(value, reader){"
|
||||
+ "reader.addEventListener(\"loadend\",function(evt){ callback(value.name, reader.result); });"
|
||||
+ "reader.addEventListener(\"error\",function(evt){ callback(null, null); });"
|
||||
+ "reader.readAsArrayBuffer(value);"
|
||||
+ "}; eag(inputElement.files[0], new FileReader()); } else callback(null, null);")
|
||||
private static native void getFileChooserResultLegacy(HTMLInputElement inputElement, FileChooserCallback callback);
|
||||
|
||||
private static void getFileChooserResult(HTMLInputElement inputElement, FileChooserCallback callback) {
|
||||
if(fileChooserImpl == -1) {
|
||||
fileChooserImpl = getFileChooserImpl();
|
||||
if(fileChooserImpl == FILE_CHOOSER_IMPL_LEGACY) {
|
||||
PlatformRuntime.logger.info("Note: using legacy FileReader implementation because File.prototype.arrayBuffer() is not supported");
|
||||
}
|
||||
}
|
||||
switch(fileChooserImpl) {
|
||||
case FILE_CHOOSER_IMPL_CORE:
|
||||
getFileChooserResultNew(inputElement, callback);
|
||||
break;
|
||||
case FILE_CHOOSER_IMPL_LEGACY:
|
||||
getFileChooserResultLegacy(inputElement, callback);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { }, script = "return (typeof File.prototype.arrayBuffer === \"function\") ? 0 : 1;")
|
||||
private static native int getFileChooserImpl();
|
||||
|
||||
@JSBody(params = { "inputElement", "value" }, script = "inputElement.accept = value;")
|
||||
private static native void setAcceptSelection(HTMLInputElement inputElement, String value);
|
||||
@ -223,21 +311,106 @@ public class PlatformApplication {
|
||||
private static native void setMultipleSelection(HTMLInputElement inputElement, boolean enable);
|
||||
|
||||
public static void displayFileChooser(String mime, String ext) {
|
||||
final HTMLInputElement inputElement = (HTMLInputElement) Window.current().getDocument().createElement("input");
|
||||
inputElement.setType("file");
|
||||
if(mime == null) {
|
||||
setAcceptSelection(inputElement, "." + ext);
|
||||
}else {
|
||||
setAcceptSelection(inputElement, mime);
|
||||
}
|
||||
setMultipleSelection(inputElement, false);
|
||||
inputElement.addEventListener("change", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
getFileChooserResult(inputElement, FileChooserCallbackImpl.instance);
|
||||
clearFileChooserResult();
|
||||
final HTMLDocument doc = PlatformRuntime.doc != null ? PlatformRuntime.doc : Window.current().getDocument();
|
||||
if(PlatformInput.isLikelyMobileBrowser) {
|
||||
final HTMLElement element = fileChooserMobileElement = doc.createElement("div");
|
||||
element.getClassList().add("_eaglercraftX_mobile_file_chooser_popup");
|
||||
CSSStyleDeclaration decl = element.getStyle();
|
||||
decl.setProperty("position", "absolute");
|
||||
decl.setProperty("background-color", "white");
|
||||
decl.setProperty("font-family", "sans-serif");
|
||||
decl.setProperty("top", "10%");
|
||||
decl.setProperty("left", "10%");
|
||||
decl.setProperty("right", "10%");
|
||||
decl.setProperty("border", "5px double black");
|
||||
decl.setProperty("padding", "15px");
|
||||
decl.setProperty("text-align", "left");
|
||||
decl.setProperty("font-size", "20px");
|
||||
decl.setProperty("user-select", "none");
|
||||
decl.setProperty("z-index", "150");
|
||||
final HTMLElement fileChooserTitle = doc.createElement("h3");
|
||||
fileChooserTitle.appendChild(doc.createTextNode("File Chooser"));
|
||||
element.appendChild(fileChooserTitle);
|
||||
final HTMLElement inputElementContainer = doc.createElement("p");
|
||||
final HTMLInputElement inputElement = fileChooserElement = (HTMLInputElement) doc.createElement("input");
|
||||
inputElement.setType("file");
|
||||
if(mime == null) {
|
||||
setAcceptSelection(inputElement, "." + ext);
|
||||
}else {
|
||||
setAcceptSelection(inputElement, mime);
|
||||
}
|
||||
});
|
||||
inputElement.click();
|
||||
setMultipleSelection(inputElement, false);
|
||||
inputElementContainer.appendChild(inputElement);
|
||||
element.appendChild(inputElementContainer);
|
||||
final HTMLElement fileChooserButtons = doc.createElement("p");
|
||||
final HTMLElement fileChooserButtonCancel = doc.createElement("button");
|
||||
fileChooserButtonCancel.getClassList().add("_eaglercraftX_mobile_file_chooser_btn_cancel");
|
||||
fileChooserButtonCancel.getStyle().setProperty("font-size", "1.0em");
|
||||
fileChooserButtonCancel.addEventListener("click", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if(fileChooserMobileElement == element) {
|
||||
PlatformRuntime.parent.removeChild(element);
|
||||
fileChooserMobileElement = null;
|
||||
fileChooserElement = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
fileChooserButtonCancel.appendChild(doc.createTextNode("Cancel"));
|
||||
fileChooserButtons.appendChild(fileChooserButtonCancel);
|
||||
fileChooserButtons.appendChild(doc.createTextNode(" "));
|
||||
final HTMLElement fileChooserButtonDone = doc.createElement("button");
|
||||
fileChooserButtonDone.getClassList().add("_eaglercraftX_mobile_file_chooser_btn_done");
|
||||
fileChooserButtonDone.getStyle().setProperty("font-size", "1.0em");
|
||||
fileChooserButtonDone.getStyle().setProperty("font-weight", "bold");
|
||||
fileChooserButtonDone.addEventListener("click", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if(fileChooserMobileElement == element) {
|
||||
getFileChooserResult(inputElement, FileChooserCallbackImpl.instance);
|
||||
PlatformRuntime.parent.removeChild(element);
|
||||
fileChooserMobileElement = null;
|
||||
fileChooserElement = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
fileChooserButtonDone.appendChild(doc.createTextNode("Done"));
|
||||
fileChooserButtons.appendChild(fileChooserButtonDone);
|
||||
element.appendChild(fileChooserButtons);
|
||||
PlatformRuntime.parent.appendChild(element);
|
||||
}else {
|
||||
final HTMLInputElement inputElement = fileChooserElement = (HTMLInputElement) doc.createElement("input");
|
||||
inputElement.setType("file");
|
||||
CSSStyleDeclaration decl = inputElement.getStyle();
|
||||
decl.setProperty("position", "absolute");
|
||||
decl.setProperty("left", "0px");
|
||||
decl.setProperty("top", "0px");
|
||||
decl.setProperty("z-index", "-100");
|
||||
if(mime == null) {
|
||||
setAcceptSelection(inputElement, "." + ext);
|
||||
}else {
|
||||
setAcceptSelection(inputElement, mime);
|
||||
}
|
||||
setMultipleSelection(inputElement, false);
|
||||
inputElement.addEventListener("change", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if(fileChooserElement == inputElement) {
|
||||
getFileChooserResult(inputElement, FileChooserCallbackImpl.instance);
|
||||
PlatformRuntime.parent.removeChild(inputElement);
|
||||
fileChooserElement = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
PlatformRuntime.parent.appendChild(inputElement);
|
||||
Window.setTimeout(new TimerHandler() {
|
||||
@Override
|
||||
public void onTimer() {
|
||||
inputElement.click();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean fileChooserHasResult() {
|
||||
@ -261,16 +434,16 @@ public class PlatformApplication {
|
||||
private static native void documentWrite(HTMLDocument doc, String str);
|
||||
|
||||
public static void openCreditsPopup(String text) {
|
||||
Window currentWin = Window.current();
|
||||
Window currentWin = PlatformRuntime.win;
|
||||
|
||||
int w = (int)(850 * currentWin.getDevicePixelRatio());
|
||||
int h = (int)(700 * currentWin.getDevicePixelRatio());
|
||||
int w = (int)(850 * PlatformInput.getDPI());
|
||||
int h = (int)(700 * PlatformInput.getDPI());
|
||||
|
||||
int x = (currentWin.getScreen().getWidth() - w) / 2;
|
||||
int y = (currentWin.getScreen().getHeight() - h) / 2;
|
||||
|
||||
Window newWin = Window.current().open("", "_blank", "top=" + y + ",left=" + x + ",width=" + w + ",height=" + h + ",menubar=0,status=0,titlebar=0,toolbar=0");
|
||||
if(newWin == null) {
|
||||
if(newWin == null || TeaVMUtils.isNotTruthy(newWin)) {
|
||||
Window.alert("ERROR: Popup blocked!\n\nPlease make sure you have popups enabled for this site!");
|
||||
return;
|
||||
}
|
||||
@ -285,17 +458,48 @@ public class PlatformApplication {
|
||||
public static void clearFileChooserResult() {
|
||||
fileChooserHasResult = false;
|
||||
fileChooserResultObject = null;
|
||||
if(fileChooserMobileElement != null) {
|
||||
PlatformRuntime.parent.removeChild(fileChooserMobileElement);
|
||||
fileChooserMobileElement = null;
|
||||
fileChooserElement = null;
|
||||
}else if(fileChooserElement != null) {
|
||||
PlatformRuntime.parent.removeChild(fileChooserElement);
|
||||
fileChooserElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { "name", "buf" }, script =
|
||||
"var hr = window.URL.createObjectURL(new Blob([buf], {type: \"octet/stream\"}));" +
|
||||
"var a = document.createElement(\"a\");" +
|
||||
"a.href = hr; a.download = name; a.click();" +
|
||||
"window.URL.revokeObjectURL(hr);")
|
||||
private static final native void downloadBytesImpl(String str, ArrayBuffer buf);
|
||||
@JSFunctor
|
||||
private static interface DownloadBytesBlobURLRevoke extends JSObject {
|
||||
void call();
|
||||
}
|
||||
|
||||
public static final void downloadFileWithName(String str, byte[] dat) {
|
||||
downloadBytesImpl(str, TeaVMUtils.unwrapArrayBuffer(dat));
|
||||
@JSBody(params = { "name", "url", "revokeFunc", "parentElement" }, script =
|
||||
"var vigg = function(el){" +
|
||||
"el.style.position = \"absolute\";" +
|
||||
"el.style.left = \"0px\";" +
|
||||
"el.style.top = \"0px\";" +
|
||||
"el.style.zIndex = \"-100\";" +
|
||||
"el.style.color = \"transparent\";" +
|
||||
"el.innerText = \"Download File\";" +
|
||||
"el.href = url;" +
|
||||
"el.target = \"_blank\";" +
|
||||
"el.download = name;" +
|
||||
"parentElement.appendChild(el);" +
|
||||
"setTimeout(function() { el.click();" +
|
||||
"setTimeout(function() { parentElement.removeChild(el); }, 50);" +
|
||||
"setTimeout(function() { revokeFunc(); }, 60000);" +
|
||||
"}, 50);" +
|
||||
"}; vigg(document.createElement(\"a\"));")
|
||||
private static native void downloadBytesImpl(String str, String url, DownloadBytesBlobURLRevoke revokeFunc, HTMLElement parentElement);
|
||||
|
||||
public static void downloadFileWithName(String str, byte[] dat) {
|
||||
TeaVMBlobURLHandle blobHandle = TeaVMBlobURLManager.registerNewURLByte(dat, "application/octet-stream");
|
||||
downloadBytesImpl(str, blobHandle.toExternalForm(), blobHandle::release, PlatformRuntime.parent);
|
||||
}
|
||||
|
||||
public static void downloadFileWithNameTeaVM(String str, ArrayBuffer dat) {
|
||||
TeaVMBlobURLHandle blobHandle = TeaVMBlobURLManager.registerNewURLArrayBuffer(dat, "application/octet-stream");
|
||||
downloadBytesImpl(str, blobHandle.toExternalForm(), blobHandle::release, PlatformRuntime.parent);
|
||||
}
|
||||
|
||||
public static void showDebugConsole() {
|
||||
@ -312,4 +516,5 @@ public class PlatformApplication {
|
||||
|
||||
@JSBody(params = { "str" }, script = "window.minecraftServer = str;")
|
||||
public static native void setMCServerWindowGlobal(String str);
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import org.teavm.jso.typedarrays.Uint8ClampedArray;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EaglerInputStream;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.ClientMain;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMBlobURLHandle;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMBlobURLManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.ImageData;
|
||||
|
||||
@ -41,10 +43,34 @@ import net.lax1dude.eaglercraft.v1_8.opengl.ImageData;
|
||||
public class PlatformAssets {
|
||||
|
||||
private static final byte[] MISSING_FILE = new byte[0];
|
||||
|
||||
static Map<String,byte[]> assets = new HashMap<>();
|
||||
|
||||
public static boolean getResourceExists(String path) {
|
||||
if(path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
byte[] ret = assets.get(path);
|
||||
if(ret != null && ret != MISSING_FILE) {
|
||||
return true;
|
||||
}else {
|
||||
if(path.startsWith("assets/minecraft/lang/") && !path.endsWith(".mcmeta")) {
|
||||
ArrayBuffer file = PlatformRuntime.downloadRemoteURI(
|
||||
ClientMain.configLocalesFolder + "/" + path.substring(22));
|
||||
if(file != null) {
|
||||
assets.put(path, TeaVMUtils.wrapByteArrayBuffer(file));
|
||||
return true;
|
||||
}else {
|
||||
assets.put(path, MISSING_FILE);
|
||||
return false;
|
||||
}
|
||||
}else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final Map<String,byte[]> assets = new HashMap();
|
||||
|
||||
public static final byte[] getResourceBytes(String path) {
|
||||
public static byte[] getResourceBytes(String path) {
|
||||
if(path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
@ -52,7 +78,7 @@ public class PlatformAssets {
|
||||
if(data == null && path.startsWith("assets/minecraft/lang/") && !path.endsWith(".mcmeta")) {
|
||||
ArrayBuffer file = PlatformRuntime.downloadRemoteURI(
|
||||
ClientMain.configLocalesFolder + "/" + path.substring(22));
|
||||
if(file != null && file.getByteLength() > 0) {
|
||||
if(file != null) {
|
||||
data = TeaVMUtils.wrapByteArrayBuffer(file);
|
||||
assets.put(path, data);
|
||||
return data;
|
||||
@ -65,10 +91,14 @@ public class PlatformAssets {
|
||||
}
|
||||
}
|
||||
|
||||
public static final ImageData loadImageFile(InputStream data) {
|
||||
public static ImageData loadImageFile(InputStream data) {
|
||||
return loadImageFile(data, "image/png");
|
||||
}
|
||||
|
||||
public static ImageData loadImageFile(InputStream data, String mime) {
|
||||
byte[] b = EaglerInputStream.inputStreamToBytesQuiet(data);
|
||||
if(b != null) {
|
||||
return loadImageFile(b);
|
||||
return loadImageFile(b, mime);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
@ -78,21 +108,22 @@ public class PlatformAssets {
|
||||
private static CanvasRenderingContext2D imageLoadContext = null;
|
||||
|
||||
public static ImageData loadImageFile(byte[] data) {
|
||||
return loadImageFile(TeaVMUtils.unwrapArrayBuffer(data));
|
||||
return loadImageFile(data, "image/png");
|
||||
}
|
||||
|
||||
@JSBody(params = { }, script = "return { willReadFrequently: true };")
|
||||
public static native JSObject youEagler();
|
||||
static native JSObject youEagler();
|
||||
|
||||
@JSBody(params = { "ctx" }, script = "ctx.imageSmoothingEnabled = false;")
|
||||
private static native void disableImageSmoothing(CanvasRenderingContext2D ctx);
|
||||
|
||||
@Async
|
||||
private static native ImageData loadImageFile(ArrayBuffer data);
|
||||
public static native ImageData loadImageFile(byte[] data, String mime);
|
||||
|
||||
private static void loadImageFile(ArrayBuffer data, final AsyncCallback<ImageData> ret) {
|
||||
private static void loadImageFile(byte[] data, String mime, final AsyncCallback<ImageData> ret) {
|
||||
final Document doc = Window.current().getDocument();
|
||||
final HTMLImageElement toLoad = (HTMLImageElement) doc.createElement("img");
|
||||
final TeaVMBlobURLHandle[] src = new TeaVMBlobURLHandle[1];
|
||||
toLoad.addEventListener("load", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
@ -114,7 +145,7 @@ public class PlatformAssets {
|
||||
org.teavm.jso.canvas.ImageData pxlsDat = imageLoadContext.getImageData(0, 0, toLoad.getWidth(), toLoad.getHeight());
|
||||
Uint8ClampedArray pxls = pxlsDat.getData();
|
||||
int totalPixels = pxlsDat.getWidth() * pxlsDat.getHeight();
|
||||
TeaVMUtils.freeDataURL(toLoad.getSrc());
|
||||
TeaVMBlobURLManager.releaseURL(src[0]);
|
||||
if(pxls.getByteLength() < totalPixels << 2) {
|
||||
ret.complete(null);
|
||||
return;
|
||||
@ -125,16 +156,19 @@ public class PlatformAssets {
|
||||
toLoad.addEventListener("error", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
TeaVMUtils.freeDataURL(toLoad.getSrc());
|
||||
TeaVMBlobURLManager.releaseURL(src[0]);
|
||||
ret.complete(null);
|
||||
}
|
||||
});
|
||||
String src = TeaVMUtils.getDataURL(data, "image/png");
|
||||
if(src != null) {
|
||||
toLoad.setSrc(src);
|
||||
src[0] = TeaVMBlobURLManager.registerNewURLByte(data, mime);
|
||||
if(src[0] != null) {
|
||||
toLoad.setSrc(src[0].toExternalForm());
|
||||
}else {
|
||||
ret.complete(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void freeAssetRepoTeaVM() {
|
||||
assets = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,17 @@ package net.lax1dude.eaglercraft.v1_8.internal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.audio.SoundCategory;
|
||||
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.typedarrays.ArrayBuffer;
|
||||
import org.teavm.jso.typedarrays.Uint8Array;
|
||||
import org.teavm.jso.typedarrays.Int8Array;
|
||||
import org.teavm.jso.webaudio.AudioBuffer;
|
||||
import org.teavm.jso.webaudio.AudioBufferSourceNode;
|
||||
import org.teavm.jso.webaudio.AudioContext;
|
||||
@ -24,13 +25,16 @@ import org.teavm.jso.webaudio.MediaStream;
|
||||
import org.teavm.jso.webaudio.MediaStreamAudioDestinationNode;
|
||||
import org.teavm.jso.webaudio.PannerNode;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.JOrbisAudioBufferDecoder;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMClientConfigAdapter;
|
||||
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;
|
||||
import net.minecraft.util.MathHelper;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
|
||||
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
@ -49,10 +53,21 @@ public class PlatformAudio {
|
||||
static final Logger logger = LogManager.getLogger("BrowserAudio");
|
||||
|
||||
static AudioContext audioctx = null;
|
||||
static MediaStreamAudioDestinationNode recDest = null;
|
||||
private static final Map<String, BrowserAudioResource> soundCache = new HashMap();
|
||||
|
||||
static MediaStreamAudioDestinationNode recDestNode = null;
|
||||
static MediaStream recDestMediaStream = null;
|
||||
static AudioBuffer silence = null;
|
||||
static AudioBufferSourceNode recDestSilenceNode = null;
|
||||
static GainNode micRecGain = null;
|
||||
static GainNode gameRecGain = null;
|
||||
private static final Map<String, BrowserAudioResource> soundCache = new HashMap<>();
|
||||
private static final List<BrowserAudioHandle> activeSounds = new LinkedList<>();
|
||||
|
||||
private static long cacheFreeTimer = 0l;
|
||||
private static long activeFreeTimer = 0l;
|
||||
private static boolean oggSupport = false;
|
||||
private static boolean loadViaAudioBufferSupport = false;
|
||||
private static boolean loadViaWAV32FSupport = false;
|
||||
private static boolean loadViaWAV16Support = false;
|
||||
|
||||
protected static class BrowserAudioResource implements IAudioResource {
|
||||
|
||||
@ -105,7 +120,7 @@ public class PlatformAudio {
|
||||
if(isEnded) {
|
||||
isEnded = false;
|
||||
AudioBufferSourceNode src = audioctx.createBufferSource();
|
||||
resource.cacheHit = System.currentTimeMillis();
|
||||
resource.cacheHit = PlatformRuntime.steadyTimeMillis();
|
||||
src.setBuffer(resource.buffer);
|
||||
src.getPlaybackRate().setValue(pitch);
|
||||
source.disconnect();
|
||||
@ -166,44 +181,183 @@ public class PlatformAudio {
|
||||
}
|
||||
|
||||
static void initialize() {
|
||||
oggSupport = false;
|
||||
loadViaAudioBufferSupport = false;
|
||||
loadViaWAV32FSupport = false;
|
||||
loadViaWAV16Support = false;
|
||||
|
||||
try {
|
||||
audioctx = AudioContext.create();
|
||||
recDest = audioctx.createMediaStreamDestination();
|
||||
}catch(Throwable t) {
|
||||
throw new PlatformRuntime.RuntimeInitializationFailureException("Could not initialize audio context!", t);
|
||||
audioctx = null;
|
||||
logger.error("Could not initialize audio context!");
|
||||
logger.error(t);
|
||||
return;
|
||||
}
|
||||
|
||||
detectOGGSupport();
|
||||
|
||||
if(!oggSupport) {
|
||||
loadViaAudioBufferSupport = detectLoadViaAudioBufferSupport(audioctx);
|
||||
if(!loadViaAudioBufferSupport) {
|
||||
logger.warn("Missing AudioContext buffer from Float32Array support, attempting to use WAV files as a container to load raw PCM data");
|
||||
detectWAVFallbackSupport();
|
||||
if(!loadViaWAV32FSupport && !loadViaWAV16Support) {
|
||||
try {
|
||||
audioctx.close();
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
audioctx = null;
|
||||
logger.error("Audio context is missing some required features!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlatformInput.clearEvenBuffers();
|
||||
|
||||
}
|
||||
|
||||
private static GainNode micGain;
|
||||
@JSBody(params = { "ctx" }, script = "var tmpBuf = ctx.createBuffer(2, 16, 16000); return (typeof tmpBuf.copyToChannel === \"function\");")
|
||||
private static native boolean detectLoadViaAudioBufferSupport(AudioContext ctx);
|
||||
|
||||
public static void setMicVol(float vol) {
|
||||
if (micGain == null) return;
|
||||
micGain.getGain().setValue(vol);
|
||||
}
|
||||
private static void detectOGGSupport() {
|
||||
byte[] fileData = EagRuntime.getRequiredResourceBytes("/assets/eagler/audioctx_test_ogg.dat");
|
||||
|
||||
protected static void initRecDest() {
|
||||
AudioBufferSourceNode absn = audioctx.createBufferSource();
|
||||
AudioBuffer ab = audioctx.createBuffer(1, 1, 48000);
|
||||
ab.copyToChannel(new float[] { 0 }, 0);
|
||||
absn.setBuffer(ab);
|
||||
absn.setLoop(true);
|
||||
absn.start();
|
||||
absn.connect(recDest);
|
||||
MediaStream mic = PlatformRuntime.getMic();
|
||||
if (mic != null) {
|
||||
micGain = audioctx.createGain();
|
||||
micGain.getGain().setValue(Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.VOICE));
|
||||
audioctx.createMediaStreamSource(mic).connect(micGain);
|
||||
micGain.connect(recDest);
|
||||
if(((TeaVMClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isUseJOrbisAudioDecoderTeaVM()) {
|
||||
logger.info("Note: Using embedded JOrbis OGG decoder");
|
||||
oggSupport = false;
|
||||
}else {
|
||||
try {
|
||||
Int8Array arr = Int8Array.create(fileData.length);
|
||||
arr.set(TeaVMUtils.unwrapByteArray(fileData), 0);
|
||||
AudioBuffer buffer = decodeAudioBrowserAsync(arr.getBuffer(), null);
|
||||
if(buffer == null || buffer.getLength() == 0) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
oggSupport = true;
|
||||
}catch(Throwable t) {
|
||||
oggSupport = false;
|
||||
logger.error("OGG file support detected as false! Using embedded JOrbis OGG decoder");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static MediaStream getRecStream() {
|
||||
return recDest.getStream();
|
||||
private static void detectWAVFallbackSupport() {
|
||||
byte[] fileData = EagRuntime.getRequiredResourceBytes("/assets/eagler/audioctx_test_wav32f.dat");
|
||||
|
||||
try {
|
||||
Int8Array arr = Int8Array.create(fileData.length);
|
||||
arr.set(TeaVMUtils.unwrapByteArray(fileData), 0);
|
||||
AudioBuffer buffer = decodeAudioBrowserAsync(arr.getBuffer(), null);
|
||||
if(buffer == null || buffer.getLength() == 0) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
loadViaWAV32FSupport = true;
|
||||
return;
|
||||
}catch(Throwable t) {
|
||||
loadViaWAV32FSupport = false;
|
||||
logger.error("Could not load a 32-bit floating point WAV file, trying to use 16-bit integers");
|
||||
}
|
||||
|
||||
fileData = EagRuntime.getRequiredResourceBytes("/assets/eagler/audioctx_test_wav16.dat");
|
||||
|
||||
try {
|
||||
Int8Array arr = Int8Array.create(fileData.length);
|
||||
arr.set(TeaVMUtils.unwrapByteArray(fileData), 0);
|
||||
AudioBuffer buffer = decodeAudioBrowserAsync(arr.getBuffer(), null);
|
||||
if(buffer == null || buffer.getLength() == 0) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
loadViaWAV16Support = true;
|
||||
return;
|
||||
}catch(Throwable t) {
|
||||
loadViaWAV16Support = false;
|
||||
logger.error("Could not load a 16-bit integer WAV file, this browser is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
static MediaStream initRecordingStream(float gameVol, float micVol) {
|
||||
if(recDestMediaStream != null) {
|
||||
return recDestMediaStream;
|
||||
}
|
||||
try {
|
||||
if(silence == null) {
|
||||
silence = audioctx.createBuffer(1, 1, 48000);
|
||||
silence.copyToChannel(new float[] { 0 }, 0);
|
||||
}
|
||||
recDestNode = audioctx.createMediaStreamDestination();
|
||||
recDestSilenceNode = audioctx.createBufferSource();
|
||||
recDestSilenceNode.setBuffer(silence);
|
||||
recDestSilenceNode.setLoop(true);
|
||||
recDestSilenceNode.start();
|
||||
recDestSilenceNode.connect(recDestNode);
|
||||
if(micVol > 0.0f) {
|
||||
MediaStream mic = PlatformScreenRecord.getMic();
|
||||
if (mic != null) {
|
||||
micRecGain = audioctx.createGain();
|
||||
micRecGain.getGain().setValue(micVol);
|
||||
audioctx.createMediaStreamSource(mic).connect(micRecGain);
|
||||
micRecGain.connect(recDestNode);
|
||||
}
|
||||
}
|
||||
gameRecGain = audioctx.createGain();
|
||||
gameRecGain.getGain().setValue(gameVol);
|
||||
synchronized(activeSounds) {
|
||||
for(BrowserAudioHandle handle : activeSounds) {
|
||||
if(handle.panner != null) {
|
||||
handle.panner.connect(gameRecGain);
|
||||
}else {
|
||||
handle.gain.connect(gameRecGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
PlatformVoiceClient.addRecordingDest(gameRecGain);
|
||||
gameRecGain.connect(recDestNode);
|
||||
recDestMediaStream = recDestNode.getStream();
|
||||
return recDestMediaStream;
|
||||
}catch(Throwable t) {
|
||||
destroyRecordingStream();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
static void destroyRecordingStream() {
|
||||
if(recDestSilenceNode != null) {
|
||||
try {
|
||||
recDestSilenceNode.disconnect();
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
recDestSilenceNode = null;
|
||||
}
|
||||
if(micRecGain != null) {
|
||||
try {
|
||||
micRecGain.disconnect();
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
micRecGain = null;
|
||||
}
|
||||
if(gameRecGain != null) {
|
||||
try {
|
||||
gameRecGain.disconnect();
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
synchronized(activeSounds) {
|
||||
for(BrowserAudioHandle handle : activeSounds) {
|
||||
try {
|
||||
if(handle.panner != null) {
|
||||
handle.panner.disconnect(gameRecGain);
|
||||
}else {
|
||||
handle.gain.disconnect(gameRecGain);
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
}
|
||||
}
|
||||
PlatformVoiceClient.removeRecordingDest(gameRecGain);
|
||||
gameRecGain = null;
|
||||
}
|
||||
recDestNode = null;
|
||||
recDestMediaStream = null;
|
||||
}
|
||||
|
||||
public static IAudioResource loadAudioData(String filename, boolean holdInCache) {
|
||||
@ -214,7 +368,7 @@ public class PlatformAudio {
|
||||
if(buffer == null) {
|
||||
byte[] file = PlatformAssets.getResourceBytes(filename);
|
||||
if(file == null) return null;
|
||||
buffer = new BrowserAudioResource(decodeAudioAsync(TeaVMUtils.unwrapArrayBuffer(file), filename));
|
||||
buffer = new BrowserAudioResource(decodeAudioData(file, filename));
|
||||
if(holdInCache) {
|
||||
synchronized(soundCache) {
|
||||
soundCache.put(filename, buffer);
|
||||
@ -222,7 +376,7 @@ public class PlatformAudio {
|
||||
}
|
||||
}
|
||||
if(buffer.buffer != null) {
|
||||
buffer.cacheHit = System.currentTimeMillis();
|
||||
buffer.cacheHit = PlatformRuntime.steadyTimeMillis();
|
||||
return buffer;
|
||||
}else {
|
||||
return null;
|
||||
@ -241,9 +395,7 @@ public class PlatformAudio {
|
||||
if(buffer == null) {
|
||||
byte[] file = loader.loadFile(filename);
|
||||
if(file == null) return null;
|
||||
Uint8Array buf = Uint8Array.create(file.length);
|
||||
buf.set(file);
|
||||
buffer = new BrowserAudioResource(decodeAudioAsync(buf.getBuffer(), filename));
|
||||
buffer = new BrowserAudioResource(decodeAudioData(file, filename));
|
||||
if(holdInCache) {
|
||||
synchronized(soundCache) {
|
||||
soundCache.put(filename, buffer);
|
||||
@ -251,17 +403,40 @@ public class PlatformAudio {
|
||||
}
|
||||
}
|
||||
if(buffer.buffer != null) {
|
||||
buffer.cacheHit = System.currentTimeMillis();
|
||||
buffer.cacheHit = PlatformRuntime.steadyTimeMillis();
|
||||
return buffer;
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
public static native AudioBuffer decodeAudioAsync(ArrayBuffer buffer, String errorFileName);
|
||||
private static AudioBuffer decodeAudioData(byte[] data, String errorFileName) {
|
||||
if(data == null) {
|
||||
return null;
|
||||
}
|
||||
if(oggSupport) {
|
||||
// browsers complain if we don't copy the array
|
||||
Int8Array arr = Int8Array.create(data.length);
|
||||
arr.set(TeaVMUtils.unwrapByteArray(data), 0);
|
||||
return decodeAudioBrowserAsync(arr.getBuffer(), errorFileName);
|
||||
}else {
|
||||
if(data.length > 4 && data[0] == (byte)0x4F && data[1] == (byte)0x67 && data[2] == (byte)0x67 && data[3] == (byte)0x53) {
|
||||
return JOrbisAudioBufferDecoder.decodeAudioJOrbis(audioctx, data, errorFileName,
|
||||
loadViaAudioBufferSupport ? JOrbisAudioBufferDecoder.LOAD_VIA_AUDIOBUFFER
|
||||
: (loadViaWAV32FSupport ? JOrbisAudioBufferDecoder.LOAD_VIA_WAV32F
|
||||
: JOrbisAudioBufferDecoder.LOAD_VIA_WAV16));
|
||||
} else {
|
||||
Int8Array arr = Int8Array.create(data.length);
|
||||
arr.set(TeaVMUtils.unwrapByteArray(data), 0);
|
||||
return decodeAudioBrowserAsync(arr.getBuffer(), errorFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void decodeAudioAsync(ArrayBuffer buffer, final String errorFileName, final AsyncCallback<AudioBuffer> cb) {
|
||||
@Async
|
||||
public static native AudioBuffer decodeAudioBrowserAsync(ArrayBuffer buffer, String errorFileName);
|
||||
|
||||
private static void decodeAudioBrowserAsync(ArrayBuffer buffer, final String errorFileName, final AsyncCallback<AudioBuffer> cb) {
|
||||
audioctx.decodeAudioData(buffer, new DecodeSuccessCallback() {
|
||||
@Override
|
||||
public void onSuccess(AudioBuffer decodedData) {
|
||||
@ -270,14 +445,16 @@ public class PlatformAudio {
|
||||
}, new DecodeErrorCallback() {
|
||||
@Override
|
||||
public void onError(JSObject error) {
|
||||
logger.error("Could not load audio: {}", errorFileName);
|
||||
if(errorFileName != null) {
|
||||
logger.error("Could not load audio: {}", errorFileName);
|
||||
}
|
||||
cb.complete(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void clearAudioCache() {
|
||||
long millis = System.currentTimeMillis();
|
||||
long millis = PlatformRuntime.steadyTimeMillis();
|
||||
if(millis - cacheFreeTimer > 30000l) {
|
||||
cacheFreeTimer = millis;
|
||||
synchronized(soundCache) {
|
||||
@ -289,22 +466,36 @@ public class PlatformAudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
if(millis - activeFreeTimer > 700l) {
|
||||
activeFreeTimer = millis;
|
||||
synchronized(activeSounds) {
|
||||
Iterator<BrowserAudioHandle> itr = activeSounds.iterator();
|
||||
while(itr.hasNext()) {
|
||||
if(itr.next().shouldFree()) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void flushAudioCache() {
|
||||
synchronized(soundCache) {
|
||||
soundCache.clear();
|
||||
}
|
||||
synchronized(activeSounds) {
|
||||
activeSounds.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean available() {
|
||||
return true; // this is not used
|
||||
return audioctx != null;
|
||||
}
|
||||
|
||||
public static IAudioHandle beginPlayback(IAudioResource track, float x, float y, float z,
|
||||
float volume, float pitch) {
|
||||
BrowserAudioResource internalTrack = (BrowserAudioResource) track;
|
||||
internalTrack.cacheHit = System.currentTimeMillis();
|
||||
internalTrack.cacheHit = PlatformRuntime.steadyTimeMillis();
|
||||
|
||||
AudioBufferSourceNode src = audioctx.createBufferSource();
|
||||
src.setBuffer(internalTrack.buffer);
|
||||
@ -331,16 +522,22 @@ public class PlatformAudio {
|
||||
src.connect(panner);
|
||||
panner.connect(gain);
|
||||
gain.connect(audioctx.getDestination());
|
||||
gain.connect(recDest);
|
||||
if(gameRecGain != null) {
|
||||
gain.connect(gameRecGain);
|
||||
}
|
||||
|
||||
src.start();
|
||||
|
||||
return new BrowserAudioHandle(internalTrack, src, panner, gain, pitch);
|
||||
BrowserAudioHandle ret = new BrowserAudioHandle(internalTrack, src, panner, gain, pitch);
|
||||
synchronized(activeSounds) {
|
||||
activeSounds.add(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static IAudioHandle beginPlaybackStatic(IAudioResource track, float volume, float pitch) {
|
||||
BrowserAudioResource internalTrack = (BrowserAudioResource) track;
|
||||
internalTrack.cacheHit = System.currentTimeMillis();
|
||||
internalTrack.cacheHit = PlatformRuntime.steadyTimeMillis();
|
||||
|
||||
AudioBufferSourceNode src = audioctx.createBufferSource();
|
||||
src.setBuffer(internalTrack.buffer);
|
||||
@ -353,11 +550,17 @@ public class PlatformAudio {
|
||||
|
||||
src.connect(gain);
|
||||
gain.connect(audioctx.getDestination());
|
||||
gain.connect(recDest);
|
||||
if(gameRecGain != null) {
|
||||
gain.connect(gameRecGain);
|
||||
}
|
||||
|
||||
src.start();
|
||||
|
||||
return new BrowserAudioHandle(internalTrack, src, null, gain, pitch);
|
||||
BrowserAudioHandle ret = new BrowserAudioHandle(internalTrack, src, null, gain, pitch);
|
||||
synchronized(activeSounds) {
|
||||
activeSounds.add(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void setListener(float x, float y, float z, float pitchDegrees, float yawDegrees) {
|
||||
@ -369,5 +572,19 @@ public class PlatformAudio {
|
||||
l.setPosition(x, y, z);
|
||||
l.setOrientation(-var3 * var4, -var5, -var2 * var4, 0.0f, 1.0f, 0.0f);
|
||||
}
|
||||
|
||||
static void destroy() {
|
||||
soundCache.clear();
|
||||
if(audioctx != null) {
|
||||
audioctx.close();
|
||||
audioctx = null;
|
||||
recDestNode = null;
|
||||
recDestMediaStream = null;
|
||||
silence = null;
|
||||
recDestSilenceNode = null;
|
||||
micRecGain = null;
|
||||
gameRecGain = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,29 +1,9 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal;
|
||||
|
||||
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;
|
||||
|
||||
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.teavm.BooleanResult;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFSIterator2;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.IndexedDBFilesystem;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.vfs2.EaglerFileSystemException;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
|
||||
@ -42,311 +22,28 @@ import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFSIterator2;
|
||||
*/
|
||||
public class PlatformFilesystem {
|
||||
|
||||
private static String filesystemDB = null;
|
||||
private static IDBDatabase database = null;
|
||||
private static final Logger logger = LogManager.getLogger("PlatformFilesystem");
|
||||
|
||||
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 FilesystemDatabaseLockedException(dbOpen.failedError);
|
||||
public static IEaglerFilesystem initializePersist(String dbName) {
|
||||
try {
|
||||
return IndexedDBFilesystem.createFilesystem(dbName);
|
||||
}catch(Throwable t) {
|
||||
logger.error("Could not open IndexedDB filesystem: {}", dbName);
|
||||
logger.error(t);
|
||||
return null;
|
||||
}
|
||||
|
||||
if(dbOpen.failedInit) {
|
||||
throw new FilesystemDatabaseInitializationException(dbOpen.failedError);
|
||||
}
|
||||
|
||||
if(dbOpen.database == null) {
|
||||
throw new NullPointerException("IDBDatabase is null!");
|
||||
}
|
||||
|
||||
database = dbOpen.database;
|
||||
}
|
||||
|
||||
public static class FilesystemDatabaseLockedException extends RuntimeException {
|
||||
public static class FilesystemDatabaseLockedException extends EaglerFileSystemException {
|
||||
public FilesystemDatabaseLockedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FilesystemDatabaseInitializationException extends RuntimeException {
|
||||
public static class FilesystemDatabaseInitializationException extends EaglerFileSystemException {
|
||||
public FilesystemDatabaseInitializationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean eaglerDelete(String pathName) {
|
||||
return AsyncHandlers.deleteFile(database, pathName).bool;
|
||||
}
|
||||
|
||||
public static ByteBuffer eaglerRead(String pathName) {
|
||||
ArrayBuffer ar = AsyncHandlers.readWholeFile(database, pathName);
|
||||
if(ar == null) {
|
||||
return null;
|
||||
}
|
||||
return EaglerArrayBufferAllocator.wrapByteBufferTeaVM(Int8Array.create(ar));
|
||||
}
|
||||
|
||||
public static void eaglerWrite(String pathName, ByteBuffer data) {
|
||||
if(!AsyncHandlers.writeWholeFile(database, pathName, EaglerArrayBufferAllocator.getDataView8Unsigned(data).getBuffer()).bool) {
|
||||
throw new RuntimeException("Failed to write " + data.remaining() + " byte file to indexeddb table: " + pathName);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean eaglerExists(String pathName) {
|
||||
return AsyncHandlers.fileExists(database, pathName).bool;
|
||||
}
|
||||
|
||||
public static boolean eaglerMove(String pathNameOld, String pathNameNew) {
|
||||
ArrayBuffer old = AsyncHandlers.readWholeFile(database, pathNameOld);
|
||||
return old != null && AsyncHandlers.writeWholeFile(database, pathNameNew, old).bool && AsyncHandlers.deleteFile(database, pathNameOld).bool;
|
||||
}
|
||||
|
||||
public static int eaglerCopy(String pathNameOld, String pathNameNew) {
|
||||
ArrayBuffer old = AsyncHandlers.readWholeFile(database, pathNameOld);
|
||||
if(old != null && AsyncHandlers.writeWholeFile(database, pathNameNew, old).bool) {
|
||||
return old.getByteLength();
|
||||
}else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static int eaglerSize(String pathName) {
|
||||
ArrayBuffer old = AsyncHandlers.readWholeFile(database, pathName);
|
||||
return old == null ? -1 : old.getByteLength();
|
||||
}
|
||||
|
||||
private static class VFSFilenameIteratorNonRecursive implements VFSFilenameIterator {
|
||||
|
||||
private final VFSFilenameIterator child;
|
||||
private final int pathCount;
|
||||
|
||||
private VFSFilenameIteratorNonRecursive(VFSFilenameIterator child, int pathCount) {
|
||||
this.child = child;
|
||||
this.pathCount = pathCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void next(String entry) {
|
||||
if(countSlashes(entry) == pathCount) {
|
||||
child.next(entry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static int countSlashes(String str) {
|
||||
int j = 0;
|
||||
for(int i = 0, l = str.length(); i < l; ++i) {
|
||||
if(str.charAt(i) == '/') {
|
||||
++j;
|
||||
}
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
public static void eaglerIterate(String pathName, VFSFilenameIterator itr, boolean recursive) {
|
||||
if(recursive) {
|
||||
AsyncHandlers.iterateFiles(database, pathName, false, itr);
|
||||
}else {
|
||||
AsyncHandlers.iterateFiles(database, pathName, false, new VFSFilenameIteratorNonRecursive(itr, countSlashes(pathName) + 1));
|
||||
}
|
||||
}
|
||||
|
||||
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.TRUE);
|
||||
}
|
||||
});
|
||||
r.setOnError(new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(BooleanResult.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 String prefix, boolean rw, final VFSFilenameIterator itr);
|
||||
|
||||
private static void iterateFiles(IDBDatabase db, final String prefix, boolean rw, final VFSFilenameIterator 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(k);
|
||||
}catch(VFSIterator2.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 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.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.TRUE);
|
||||
}
|
||||
});
|
||||
r.setOnError(new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(BooleanResult.FALSE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,26 +1,11 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
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.dom.events.MessageEvent;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
import org.teavm.jso.websocket.WebSocket;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMServerQuery;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMWebSocketClient;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved.
|
||||
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
@ -36,169 +21,20 @@ import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
*/
|
||||
public class PlatformNetworking {
|
||||
|
||||
private static WebSocket sock = null;
|
||||
private static boolean sockIsConnecting = false;
|
||||
private static boolean sockIsConnected = false;
|
||||
private static boolean sockIsAlive = false;
|
||||
private static boolean sockIsFailed = false;
|
||||
private static LinkedList<byte[]> readPackets = new LinkedList();
|
||||
private static String currentSockURI = null;
|
||||
private static EnumServerRateLimit serverRateLimit = null;
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("PlatformNetworking");
|
||||
|
||||
public static EnumEaglerConnectionState playConnectionState() {
|
||||
return !sockIsConnected ? (sockIsFailed ? EnumEaglerConnectionState.FAILED : EnumEaglerConnectionState.CLOSED)
|
||||
: (sockIsConnecting ? EnumEaglerConnectionState.CONNECTING : EnumEaglerConnectionState.CONNECTED);
|
||||
}
|
||||
|
||||
public static void startPlayConnection(String destination) {
|
||||
sockIsFailed = !connectWebSocket(destination).booleanValue();
|
||||
}
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return typeof obj === \"string\";")
|
||||
private static native boolean isString(JSObject obj);
|
||||
|
||||
@Async
|
||||
public static native Boolean connectWebSocket(String sockURI);
|
||||
|
||||
private static void connectWebSocket(String sockURI, final AsyncCallback<Boolean> cb) {
|
||||
sockIsConnecting = true;
|
||||
sockIsConnected = false;
|
||||
sockIsAlive = false;
|
||||
currentSockURI = sockURI;
|
||||
public static IWebSocketClient openWebSocket(String socketURI) {
|
||||
try {
|
||||
sock = WebSocket.create(sockURI);
|
||||
} catch(Throwable t) {
|
||||
sockIsFailed = true;
|
||||
sockIsConnecting = false;
|
||||
sockIsAlive = false;
|
||||
cb.complete(Boolean.FALSE);
|
||||
return;
|
||||
}
|
||||
final WebSocket oldSock = sock;
|
||||
sock.setBinaryType("arraybuffer");
|
||||
TeaVMUtils.addEventListener(sock, "open", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if (oldSock != sock) return;
|
||||
sockIsConnecting = false;
|
||||
sockIsAlive = false;
|
||||
sockIsConnected = true;
|
||||
synchronized(readPackets) {
|
||||
readPackets.clear();
|
||||
}
|
||||
cb.complete(Boolean.TRUE);
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(sock, "close", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if (oldSock != sock) return;
|
||||
sock = null;
|
||||
boolean b = sockIsConnecting;
|
||||
sockIsConnecting = false;
|
||||
sockIsConnected = false;
|
||||
sockIsAlive = false;
|
||||
if(b) cb.complete(Boolean.FALSE);
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(sock, "message", new EventListener<MessageEvent>() {
|
||||
@Override
|
||||
public void handleEvent(MessageEvent evt) {
|
||||
if (oldSock != sock) return;
|
||||
sockIsAlive = true;
|
||||
if(isString(evt.getData())) {
|
||||
String str = evt.getDataAsString();
|
||||
if(str.equalsIgnoreCase("BLOCKED")) {
|
||||
logger.error("Reached full IP ratelimit!");
|
||||
serverRateLimit = EnumServerRateLimit.BLOCKED;
|
||||
}else if(str.equalsIgnoreCase("LOCKED")) {
|
||||
logger.error("Reached full IP ratelimit lockout!");
|
||||
serverRateLimit = EnumServerRateLimit.LOCKED_OUT;
|
||||
}
|
||||
}else {
|
||||
synchronized(readPackets) {
|
||||
readPackets.add(TeaVMUtils.wrapByteArrayBuffer(evt.getDataAsArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(sock, "error", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if (oldSock != sock) return;
|
||||
if(sockIsConnecting) {
|
||||
sockIsFailed = true;
|
||||
sockIsConnecting = false;
|
||||
sockIsAlive = false;
|
||||
cb.complete(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void playDisconnect() {
|
||||
if(sock != null) sock.close();
|
||||
sockIsConnecting = false;
|
||||
}
|
||||
|
||||
public static byte[] readPlayPacket() {
|
||||
synchronized(readPackets) {
|
||||
if(!readPackets.isEmpty()) {
|
||||
return readPackets.remove(0);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<byte[]> readAllPacket() {
|
||||
synchronized(readPackets) {
|
||||
if(!readPackets.isEmpty()) {
|
||||
List<byte[]> ret = new ArrayList<>(readPackets);
|
||||
readPackets.clear();
|
||||
return ret;
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int countAvailableReadData() {
|
||||
int total = 0;
|
||||
synchronized(readPackets) {
|
||||
for(int i = 0, l = readPackets.size(); i < l; ++i) {
|
||||
total += readPackets.get(i).length;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
@JSBody(params = { "sock", "buffer" }, script = "sock.send(buffer);")
|
||||
protected static native void nativeBinarySend(WebSocket sock, ArrayBuffer buffer);
|
||||
|
||||
public static void writePlayPacket(byte[] pkt) {
|
||||
if(sock != null && !sockIsConnecting) {
|
||||
nativeBinarySend(sock, TeaVMUtils.unwrapArrayBuffer(pkt));
|
||||
}
|
||||
}
|
||||
|
||||
public static IServerQuery sendServerQuery(String uri, String accept) {
|
||||
try {
|
||||
return new TeaVMServerQuery(uri, accept);
|
||||
return new TeaVMWebSocketClient(socketURI);
|
||||
}catch(Throwable t) {
|
||||
logger.error("Could not send query to \"{}\"!", uri);
|
||||
logger.error("Could not open WebSocket to \"{}\"!", socketURI);
|
||||
logger.error(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static EnumServerRateLimit getRateLimit() {
|
||||
return serverRateLimit == null ? EnumServerRateLimit.OK : serverRateLimit;
|
||||
}
|
||||
|
||||
public static String getCurrentURI() {
|
||||
return currentSockURI;
|
||||
|
||||
public static IWebSocketClient openWebSocketUnsafe(String socketURI) {
|
||||
return new TeaVMWebSocketClient(socketURI);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,19 +1,27 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.teavm.jso.webgl.WebGLUniformLocation;
|
||||
|
||||
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;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMClientConfigAdapter;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.WebGL2RenderingContext;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.WebGLANGLEInstancedArrays;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.WebGLBackBuffer;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.WebGLOESVertexArrayObject;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.WebGLVertexArray;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Level;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved.
|
||||
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
@ -32,19 +40,129 @@ public class PlatformOpenGL {
|
||||
private static final Logger logger = LogManager.getLogger("PlatformOpenGL");
|
||||
|
||||
static WebGL2RenderingContext ctx = null;
|
||||
static int glesVers = -1;
|
||||
|
||||
static boolean hasDebugRenderInfoExt = false;
|
||||
static boolean hasFramebufferHDR16FSupport = false;
|
||||
static boolean hasFramebufferHDR32FSupport = false;
|
||||
static boolean hasANGLEInstancedArrays = false;
|
||||
static boolean hasEXTColorBufferFloat = false;
|
||||
static boolean hasEXTColorBufferHalfFloat = false;
|
||||
static boolean hasEXTShaderTextureLOD = false;
|
||||
static boolean hasOESFBORenderMipmap = false;
|
||||
static boolean hasOESVertexArrayObject = false;
|
||||
static boolean hasOESTextureFloat = false;
|
||||
static boolean hasOESTextureFloatLinear = false;
|
||||
static boolean hasOESTextureHalfFloat = false;
|
||||
static boolean hasOESTextureHalfFloatLinear = false;
|
||||
static boolean hasEXTTextureFilterAnisotropic = false;
|
||||
static boolean hasWEBGLDebugRendererInfo = false;
|
||||
|
||||
static WebGLANGLEInstancedArrays ANGLEInstancedArrays = null;
|
||||
static WebGLOESVertexArrayObject OESVertexArrayObject = null;
|
||||
|
||||
static boolean hasFBO16FSupport = false;
|
||||
static boolean hasFBO32FSupport = false;
|
||||
static boolean hasLinearHDR16FSupport = false;
|
||||
static boolean hasLinearHDR32FSupport = false;
|
||||
|
||||
static void setCurrentContext(WebGL2RenderingContext context) {
|
||||
|
||||
static final int VAO_IMPL_NONE = -1;
|
||||
static final int VAO_IMPL_CORE = 0;
|
||||
static final int VAO_IMPL_OES = 1;
|
||||
static int vertexArrayImpl = VAO_IMPL_NONE;
|
||||
|
||||
static final int INSTANCE_IMPL_NONE = -1;
|
||||
static final int INSTANCE_IMPL_CORE = 0;
|
||||
static final int INSTANCE_IMPL_ANGLE = 1;
|
||||
static int instancingImpl = INSTANCE_IMPL_NONE;
|
||||
|
||||
static void setCurrentContext(int glesVersIn, WebGL2RenderingContext context) {
|
||||
ctx = context;
|
||||
hasDebugRenderInfoExt = ctx.getExtension("WEBGL_debug_renderer_info") != null;
|
||||
hasFramebufferHDR16FSupport = ctx.getExtension("EXT_color_buffer_half_float") != null;
|
||||
hasFramebufferHDR32FSupport = ctx.getExtension("EXT_color_buffer_float") != null;
|
||||
hasLinearHDR32FSupport = ctx.getExtension("OES_texture_float_linear") != null;
|
||||
_wglClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
if(ctx != null) {
|
||||
glesVers = glesVersIn;
|
||||
boolean allowExts = ((TeaVMClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isUseWebGLExtTeaVM();
|
||||
if(allowExts) {
|
||||
ANGLEInstancedArrays = glesVersIn == 200 ? (WebGLANGLEInstancedArrays) ctx.getExtension("ANGLE_instanced_arrays") : null;
|
||||
hasANGLEInstancedArrays = glesVersIn == 200 && ANGLEInstancedArrays != null;
|
||||
hasEXTColorBufferFloat = (glesVersIn == 310 || glesVersIn == 300) && ctx.getExtension("EXT_color_buffer_float") != null;
|
||||
hasEXTColorBufferHalfFloat = !hasEXTColorBufferFloat
|
||||
&& (glesVersIn == 310 || glesVersIn == 300 || glesVersIn == 200) && ctx.getExtension("EXT_color_buffer_half_float") != null;
|
||||
hasEXTShaderTextureLOD = glesVersIn == 200 && ctx.getExtension("EXT_shader_texture_lod") != null;
|
||||
hasOESFBORenderMipmap = glesVersIn == 200 && ctx.getExtension("OES_fbo_render_mipmap") != null;
|
||||
OESVertexArrayObject = glesVersIn == 200 ? (WebGLOESVertexArrayObject) ctx.getExtension("OES_vertex_array_object") : null;
|
||||
hasOESVertexArrayObject = glesVersIn == 200 && OESVertexArrayObject != null;
|
||||
hasOESTextureFloat = glesVersIn == 200 && ctx.getExtension("OES_texture_float") != null;
|
||||
hasOESTextureFloatLinear = glesVersIn >= 300 && ctx.getExtension("OES_texture_float_linear") != null;
|
||||
hasOESTextureHalfFloat = glesVersIn == 200 && ctx.getExtension("OES_texture_half_float") != null;
|
||||
hasOESTextureHalfFloatLinear = glesVersIn == 200 && ctx.getExtension("OES_texture_half_float_linear") != null;
|
||||
hasEXTTextureFilterAnisotropic = ctx.getExtension("EXT_texture_filter_anisotropic") != null;
|
||||
}else {
|
||||
hasANGLEInstancedArrays = false;
|
||||
hasEXTColorBufferFloat = false;
|
||||
hasEXTColorBufferHalfFloat = false;
|
||||
hasEXTShaderTextureLOD = false;
|
||||
hasOESFBORenderMipmap = false;
|
||||
hasOESVertexArrayObject = false;
|
||||
hasOESTextureFloat = false;
|
||||
hasOESTextureFloatLinear = false;
|
||||
hasOESTextureHalfFloat = false;
|
||||
hasOESTextureHalfFloatLinear = false;
|
||||
hasEXTTextureFilterAnisotropic = false;
|
||||
}
|
||||
hasWEBGLDebugRendererInfo = ctx.getExtension("WEBGL_debug_renderer_info") != null;
|
||||
|
||||
hasFBO16FSupport = glesVersIn >= 320 || ((glesVersIn >= 300 || hasOESTextureFloat) && (hasEXTColorBufferFloat || hasEXTColorBufferHalfFloat));
|
||||
hasFBO32FSupport = glesVersIn >= 320 || ((glesVersIn >= 300 || hasOESTextureHalfFloat) && hasEXTColorBufferFloat);
|
||||
hasLinearHDR16FSupport = glesVersIn >= 300 || hasOESTextureHalfFloatLinear;
|
||||
hasLinearHDR32FSupport = glesVersIn >= 300 && hasOESTextureFloatLinear;
|
||||
|
||||
if(glesVersIn >= 300) {
|
||||
vertexArrayImpl = VAO_IMPL_CORE;
|
||||
instancingImpl = INSTANCE_IMPL_CORE;
|
||||
}else if(glesVersIn == 200) {
|
||||
vertexArrayImpl = hasOESVertexArrayObject ? VAO_IMPL_OES : VAO_IMPL_NONE;
|
||||
instancingImpl = hasANGLEInstancedArrays ? INSTANCE_IMPL_ANGLE : INSTANCE_IMPL_NONE;
|
||||
}else {
|
||||
vertexArrayImpl = VAO_IMPL_NONE;
|
||||
instancingImpl = INSTANCE_IMPL_NONE;
|
||||
}
|
||||
|
||||
_wglClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}else {
|
||||
glesVers = -1;
|
||||
hasANGLEInstancedArrays = false;
|
||||
hasEXTColorBufferFloat = false;
|
||||
hasEXTColorBufferHalfFloat = false;
|
||||
hasEXTShaderTextureLOD = false;
|
||||
hasOESFBORenderMipmap = false;
|
||||
hasOESVertexArrayObject = false;
|
||||
hasOESTextureFloat = false;
|
||||
hasOESTextureFloatLinear = false;
|
||||
hasOESTextureHalfFloat = false;
|
||||
hasOESTextureHalfFloatLinear = false;
|
||||
hasEXTTextureFilterAnisotropic = false;
|
||||
hasWEBGLDebugRendererInfo = false;
|
||||
ANGLEInstancedArrays = null;
|
||||
OESVertexArrayObject = null;
|
||||
hasFBO16FSupport = false;
|
||||
hasFBO32FSupport = false;
|
||||
hasLinearHDR16FSupport = false;
|
||||
hasLinearHDR32FSupport = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static final List<String> dumpActiveExtensions() {
|
||||
List<String> exts = new ArrayList<>();
|
||||
if(hasANGLEInstancedArrays) exts.add("ANGLE_instanced_arrays");
|
||||
if(hasEXTColorBufferFloat) exts.add("EXT_color_buffer_float");
|
||||
if(hasEXTColorBufferHalfFloat) exts.add("EXT_color_buffer_half_float");
|
||||
if(hasEXTShaderTextureLOD) exts.add("EXT_shader_texture_lod");
|
||||
if(hasOESFBORenderMipmap) exts.add("OES_fbo_render_mipmap");
|
||||
if(hasOESVertexArrayObject) exts.add("OES_vertex_array_object");
|
||||
if(hasOESTextureFloat) exts.add("OES_texture_float");
|
||||
if(hasOESTextureFloatLinear) exts.add("OES_texture_float_linear");
|
||||
if(hasOESTextureHalfFloat) exts.add("OES_texture_half_float");
|
||||
if(hasOESTextureHalfFloatLinear) exts.add("OES_texture_half_float_linear");
|
||||
if(hasEXTTextureFilterAnisotropic) exts.add("EXT_texture_filter_anisotropic");
|
||||
if(hasWEBGLDebugRendererInfo) exts.add("WEBGL_debug_renderer_info");
|
||||
return exts;
|
||||
}
|
||||
|
||||
public static final void _wglEnable(int glEnum) {
|
||||
@ -105,17 +223,45 @@ public class PlatformOpenGL {
|
||||
}
|
||||
|
||||
public static final void _wglDrawBuffers(int buffer) {
|
||||
ctx.drawBuffers(new int[] { buffer });
|
||||
if(glesVers == 200) {
|
||||
if(buffer != 0x8CE0) { // GL_COLOR_ATTACHMENT0
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}else {
|
||||
ctx.drawBuffers(new int[] { buffer });
|
||||
}
|
||||
}
|
||||
|
||||
public static final void _wglDrawBuffers(int[] buffers) {
|
||||
ctx.drawBuffers(buffers);
|
||||
if(glesVers == 200) {
|
||||
if(buffers.length != 1 || buffers[0] != 0x8CE0) { // GL_COLOR_ATTACHMENT0
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}else {
|
||||
ctx.drawBuffers(buffers);
|
||||
}
|
||||
}
|
||||
|
||||
public static final void _wglReadBuffer(int buffer) {
|
||||
ctx.readBuffer(buffer);
|
||||
}
|
||||
|
||||
public static final void _wglReadPixels(int x, int y, int width, int height, int format, int type, ByteBuffer data) {
|
||||
ctx.readPixels(x, y, width, height, format, type, EaglerArrayBufferAllocator.getDataView8Unsigned(data));
|
||||
}
|
||||
|
||||
public static final void _wglReadPixels_u16(int x, int y, int width, int height, int format, int type, ByteBuffer data) {
|
||||
ctx.readPixels(x, y, width, height, format, type, EaglerArrayBufferAllocator.getDataView16Unsigned(data));
|
||||
}
|
||||
|
||||
public static final void _wglReadPixels(int x, int y, int width, int height, int format, int type, IntBuffer data) {
|
||||
ctx.readPixels(x, y, width, height, format, type, EaglerArrayBufferAllocator.getDataView32(data));
|
||||
}
|
||||
|
||||
public static final void _wglReadPixels(int x, int y, int width, int height, int format, int type, FloatBuffer data) {
|
||||
ctx.readPixels(x, y, width, height, format, type, EaglerArrayBufferAllocator.getDataView32F(data));
|
||||
}
|
||||
|
||||
public static final void _wglPolygonOffset(float f1, float f2) {
|
||||
ctx.polygonOffset(f1, f2);
|
||||
}
|
||||
@ -133,7 +279,14 @@ public class PlatformOpenGL {
|
||||
}
|
||||
|
||||
public static final IBufferArrayGL _wglGenVertexArrays() {
|
||||
return new OpenGLObjects.BufferArrayGL(ctx.createVertexArray());
|
||||
switch(vertexArrayImpl) {
|
||||
case VAO_IMPL_CORE:
|
||||
return new OpenGLObjects.BufferArrayGL(ctx.createVertexArray());
|
||||
case VAO_IMPL_OES:
|
||||
return new OpenGLObjects.BufferArrayGL(OESVertexArrayObject.createVertexArrayOES());
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static final IProgramGL _wglCreateProgram() {
|
||||
@ -157,51 +310,61 @@ public class PlatformOpenGL {
|
||||
}
|
||||
|
||||
public static final void _wglDeleteBuffers(IBufferGL obj) {
|
||||
ctx.deleteBuffer(obj == null ? null : ((OpenGLObjects.BufferGL)obj).ptr);
|
||||
ctx.deleteBuffer(((OpenGLObjects.BufferGL)obj).ptr);
|
||||
}
|
||||
|
||||
public static final void _wglDeleteTextures(ITextureGL obj) {
|
||||
ctx.deleteTexture(obj == null ? null : ((OpenGLObjects.TextureGL)obj).ptr);
|
||||
ctx.deleteTexture(((OpenGLObjects.TextureGL)obj).ptr);
|
||||
}
|
||||
|
||||
public static final void _wglDeleteVertexArrays(IBufferArrayGL obj) {
|
||||
ctx.deleteVertexArray(obj == null ? null : ((OpenGLObjects.BufferArrayGL)obj).ptr);
|
||||
WebGLVertexArray ptr = ((OpenGLObjects.BufferArrayGL)obj).ptr;
|
||||
switch(vertexArrayImpl) {
|
||||
case VAO_IMPL_CORE:
|
||||
ctx.deleteVertexArray(ptr);
|
||||
break;
|
||||
case VAO_IMPL_OES:
|
||||
OESVertexArrayObject.deleteVertexArrayOES(ptr);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static final void _wglDeleteProgram(IProgramGL obj) {
|
||||
ctx.deleteProgram(obj == null ? null : ((OpenGLObjects.ProgramGL)obj).ptr);
|
||||
ctx.deleteProgram(((OpenGLObjects.ProgramGL)obj).ptr);
|
||||
}
|
||||
|
||||
public static final void _wglDeleteShader(IShaderGL obj) {
|
||||
ctx.deleteShader(obj == null ? null : ((OpenGLObjects.ShaderGL)obj).ptr);
|
||||
ctx.deleteShader(((OpenGLObjects.ShaderGL)obj).ptr);
|
||||
}
|
||||
|
||||
public static final void _wglDeleteFramebuffer(IFramebufferGL obj) {
|
||||
ctx.deleteFramebuffer(obj == null ? null : ((OpenGLObjects.FramebufferGL)obj).ptr);
|
||||
ctx.deleteFramebuffer(((OpenGLObjects.FramebufferGL)obj).ptr);
|
||||
}
|
||||
|
||||
public static final void _wglDeleteRenderbuffer(IRenderbufferGL obj) {
|
||||
ctx.deleteRenderbuffer(obj == null ? null : ((OpenGLObjects.RenderbufferGL)obj).ptr);
|
||||
ctx.deleteRenderbuffer(((OpenGLObjects.RenderbufferGL)obj).ptr);
|
||||
}
|
||||
|
||||
public static final void _wglDeleteQueries(IQueryGL obj) {
|
||||
ctx.deleteQuery(obj == null ? null : ((OpenGLObjects.QueryGL)obj).ptr);
|
||||
ctx.deleteQuery(((OpenGLObjects.QueryGL)obj).ptr);
|
||||
}
|
||||
|
||||
public static final void _wglBindBuffer(int target, IBufferGL obj) {
|
||||
ctx.bindBuffer(target, obj == null ? null : ((OpenGLObjects.BufferGL)obj).ptr);
|
||||
ctx.bindBuffer(target, obj != null ? ((OpenGLObjects.BufferGL)obj).ptr : null);
|
||||
}
|
||||
|
||||
public static final void _wglBufferData(int target, ByteBuffer data, int usage) {
|
||||
ctx.bufferData(target, data == null ? null : EaglerArrayBufferAllocator.getDataView8(data), usage);
|
||||
ctx.bufferData(target, EaglerArrayBufferAllocator.getDataView8(data), usage);
|
||||
}
|
||||
|
||||
public static final void _wglBufferData(int target, IntBuffer data, int usage) {
|
||||
ctx.bufferData(target, data == null ? null : EaglerArrayBufferAllocator.getDataView32(data), usage);
|
||||
ctx.bufferData(target, EaglerArrayBufferAllocator.getDataView32(data), usage);
|
||||
}
|
||||
|
||||
public static final void _wglBufferData(int target, FloatBuffer data, int usage) {
|
||||
ctx.bufferData(target, data == null ? null : EaglerArrayBufferAllocator.getDataView32F(data), usage);
|
||||
ctx.bufferData(target, EaglerArrayBufferAllocator.getDataView32F(data), usage);
|
||||
}
|
||||
|
||||
public static final void _wglBufferData(int target, int size, int usage) {
|
||||
@ -209,19 +372,29 @@ public class PlatformOpenGL {
|
||||
}
|
||||
|
||||
public static final void _wglBufferSubData(int target, int offset, ByteBuffer data) {
|
||||
ctx.bufferSubData(target, offset, data == null ? null : EaglerArrayBufferAllocator.getDataView8(data));
|
||||
ctx.bufferSubData(target, offset, EaglerArrayBufferAllocator.getDataView8(data));
|
||||
}
|
||||
|
||||
public static final void _wglBufferSubData(int target, int offset, IntBuffer data) {
|
||||
ctx.bufferSubData(target, offset, data == null ? null : EaglerArrayBufferAllocator.getDataView32(data));
|
||||
ctx.bufferSubData(target, offset, EaglerArrayBufferAllocator.getDataView32(data));
|
||||
}
|
||||
|
||||
public static final void _wglBufferSubData(int target, int offset, FloatBuffer data) {
|
||||
ctx.bufferSubData(target, offset, data == null ? null : EaglerArrayBufferAllocator.getDataView32F(data));
|
||||
ctx.bufferSubData(target, offset, EaglerArrayBufferAllocator.getDataView32F(data));
|
||||
}
|
||||
|
||||
public static final void _wglBindVertexArray(IBufferArrayGL obj) {
|
||||
ctx.bindVertexArray(obj == null ? null : ((OpenGLObjects.BufferArrayGL)obj).ptr);
|
||||
WebGLVertexArray ptr = obj != null ? ((OpenGLObjects.BufferArrayGL)obj).ptr : null;
|
||||
switch(vertexArrayImpl) {
|
||||
case VAO_IMPL_CORE:
|
||||
ctx.bindVertexArray(ptr);
|
||||
break;
|
||||
case VAO_IMPL_OES:
|
||||
OESVertexArrayObject.bindVertexArrayOES(ptr);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static final void _wglEnableVertexAttribArray(int index) {
|
||||
@ -238,7 +411,16 @@ public class PlatformOpenGL {
|
||||
}
|
||||
|
||||
public static final void _wglVertexAttribDivisor(int index, int divisor) {
|
||||
ctx.vertexAttribDivisor(index, divisor);
|
||||
switch(instancingImpl) {
|
||||
case INSTANCE_IMPL_CORE:
|
||||
ctx.vertexAttribDivisor(index, divisor);
|
||||
break;
|
||||
case INSTANCE_IMPL_ANGLE:
|
||||
ANGLEInstancedArrays.vertexAttribDivisorANGLE(index, divisor);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static final void _wglActiveTexture(int texture) {
|
||||
@ -390,7 +572,16 @@ public class PlatformOpenGL {
|
||||
}
|
||||
|
||||
public static final void _wglDrawArraysInstanced(int mode, int first, int count, int instanced) {
|
||||
ctx.drawArraysInstanced(mode, first, count, instanced);
|
||||
switch(instancingImpl) {
|
||||
case INSTANCE_IMPL_CORE:
|
||||
ctx.drawArraysInstanced(mode, first, count, instanced);
|
||||
break;
|
||||
case INSTANCE_IMPL_ANGLE:
|
||||
ANGLEInstancedArrays.drawArraysInstancedANGLE(mode, first, count, instanced);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
//checkErr("_wglDrawArraysInstanced(" + mode + ", " + first + ", " + count + ", " + instanced + ");");
|
||||
}
|
||||
|
||||
@ -400,7 +591,16 @@ public class PlatformOpenGL {
|
||||
}
|
||||
|
||||
public static final void _wglDrawElementsInstanced(int mode, int count, int type, int offset, int instanced) {
|
||||
ctx.drawElementsInstanced(mode, count, type, offset, instanced);
|
||||
switch(instancingImpl) {
|
||||
case INSTANCE_IMPL_CORE:
|
||||
ctx.drawElementsInstanced(mode, count, type, offset, instanced);
|
||||
break;
|
||||
case INSTANCE_IMPL_ANGLE:
|
||||
ANGLEInstancedArrays.drawElementsInstancedANGLE(mode, count, type, offset, instanced);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
//checkErr("_wglDrawElementsInstanced(" + mode + ", " + count + ", " + type + ", " + offset + ", " + instanced + ");");
|
||||
}
|
||||
|
||||
@ -494,7 +694,9 @@ public class PlatformOpenGL {
|
||||
public static final void _wglBindFramebuffer(int target, IFramebufferGL framebuffer) {
|
||||
if(framebuffer == null) {
|
||||
ctx.bindFramebuffer(target, PlatformRuntime.mainFramebuffer);
|
||||
ctx.drawBuffers(new int[] { WebGL2RenderingContext.COLOR_ATTACHMENT0 });
|
||||
if(glesVers != 200) {
|
||||
ctx.drawBuffers(new int[] { WebGL2RenderingContext.COLOR_ATTACHMENT0 });
|
||||
}
|
||||
}else {
|
||||
ctx.bindFramebuffer(target, ((OpenGLObjects.FramebufferGL) framebuffer).ptr);
|
||||
}
|
||||
@ -537,7 +739,7 @@ public class PlatformOpenGL {
|
||||
}
|
||||
|
||||
public static final String _wglGetString(int param) {
|
||||
if(hasDebugRenderInfoExt) {
|
||||
if(hasWEBGLDebugRendererInfo) {
|
||||
String s;
|
||||
switch(param) {
|
||||
case 0x1f00: // VENDOR
|
||||
@ -568,21 +770,73 @@ public class PlatformOpenGL {
|
||||
return ctx.getError();
|
||||
}
|
||||
|
||||
public static final int checkOpenGLESVersion() {
|
||||
return glesVers;
|
||||
}
|
||||
|
||||
public static final boolean checkEXTGPUShader5Capable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final boolean checkOESGPUShader5Capable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final boolean checkFBORenderMipmapCapable() {
|
||||
return glesVers >= 300 || hasOESFBORenderMipmap;
|
||||
}
|
||||
|
||||
public static final boolean checkVAOCapable() {
|
||||
return vertexArrayImpl != VAO_IMPL_NONE;
|
||||
}
|
||||
|
||||
public static final boolean checkInstancingCapable() {
|
||||
return instancingImpl != INSTANCE_IMPL_NONE;
|
||||
}
|
||||
|
||||
public static final boolean checkTexStorageCapable() {
|
||||
return glesVers >= 300;
|
||||
}
|
||||
|
||||
public static final boolean checkTextureLODCapable() {
|
||||
return glesVers >= 300 || hasEXTShaderTextureLOD;
|
||||
}
|
||||
|
||||
public static final boolean checkHDRFramebufferSupport(int bits) {
|
||||
switch(bits) {
|
||||
case 16:
|
||||
return hasFramebufferHDR16FSupport;
|
||||
return hasFBO16FSupport;
|
||||
case 32:
|
||||
return hasFramebufferHDR32FSupport;
|
||||
return hasFBO32FSupport;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static final boolean checkLinearHDRFilteringSupport(int bits) {
|
||||
switch(bits) {
|
||||
case 16:
|
||||
return hasLinearHDR16FSupport;
|
||||
case 32:
|
||||
return hasLinearHDR32FSupport;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// legacy
|
||||
public static final boolean checkLinearHDR32FSupport() {
|
||||
return hasLinearHDR32FSupport;
|
||||
}
|
||||
|
||||
public static final boolean checkAnisotropicFilteringSupport() {
|
||||
return hasEXTTextureFilterAnisotropic;
|
||||
}
|
||||
|
||||
public static final boolean checkNPOTCapable() {
|
||||
return glesVers >= 300;
|
||||
}
|
||||
|
||||
private static final void checkErr(String name) {
|
||||
int i = ctx.getError();
|
||||
if(i != 0) {
|
||||
@ -599,4 +853,13 @@ public class PlatformOpenGL {
|
||||
logger.error("##############################");
|
||||
}
|
||||
}
|
||||
|
||||
public static final String[] getAllExtensions() {
|
||||
return ctx.getSupportedExtensionArray();
|
||||
}
|
||||
|
||||
public static final void enterVAOEmulationHook() {
|
||||
WebGLBackBuffer.enterVAOEmulationPhase();
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,286 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.teavm.interop.Async;
|
||||
import org.teavm.interop.AsyncCallback;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.teavm.jso.browser.Window;
|
||||
import org.teavm.jso.canvas.CanvasRenderingContext2D;
|
||||
import org.teavm.jso.dom.events.Event;
|
||||
import org.teavm.jso.dom.events.EventListener;
|
||||
import org.teavm.jso.dom.html.HTMLAnchorElement;
|
||||
import org.teavm.jso.dom.html.HTMLCanvasElement;
|
||||
import org.teavm.jso.webaudio.MediaStream;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.FixWebMDurationJS;
|
||||
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;
|
||||
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
|
||||
import net.lax1dude.eaglercraft.v1_8.recording.EnumScreenRecordingCodec;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 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 PlatformScreenRecord {
|
||||
|
||||
static final Logger logger = LogManager.getLogger("PlatformScreenRecord");
|
||||
|
||||
static Window win;
|
||||
static HTMLCanvasElement canvas;
|
||||
static boolean support;
|
||||
static final Set<EnumScreenRecordingCodec> supportedCodecs = new HashSet<>();
|
||||
static float currentGameVolume = 1.0f;
|
||||
static float currentMicVolume = 0.0f;
|
||||
static MediaStream recStream = null;
|
||||
static HTMLCanvasElement downscaleCanvas = null;
|
||||
static CanvasRenderingContext2D downscaleCanvasCtx = null;
|
||||
static long lastDownscaleFrameCaptured = 0l;
|
||||
static long startTime = 0l;
|
||||
static boolean currentMicLock = false;
|
||||
static JSObject mediaRec = null;
|
||||
static ScreenRecordParameters currentParameters = null;
|
||||
|
||||
@JSBody(params = { "win", "canvas" }, script = "return (typeof win.MediaRecorder !== \"undefined\") && (typeof win.MediaRecorder.isTypeSupported === \"function\") && (typeof canvas.captureStream === \"function\");")
|
||||
private static native boolean hasMediaRecorder(Window win, HTMLCanvasElement canvas);
|
||||
|
||||
@JSBody(params = { "win", "codec" }, script = "return win.MediaRecorder.isTypeSupported(codec);")
|
||||
private static native boolean hasMediaCodec(Window win, String codec);
|
||||
|
||||
static void initContext(Window window, HTMLCanvasElement canvasElement) {
|
||||
win = window;
|
||||
canvas = canvasElement;
|
||||
supportedCodecs.clear();
|
||||
try {
|
||||
support = hasMediaRecorder(window, canvasElement);
|
||||
if(support) {
|
||||
logger.info("MediaRecorder is supported, checking codecs...");
|
||||
EnumScreenRecordingCodec[] allCodecs = EnumScreenRecordingCodec.values();
|
||||
for(int i = 0; i < allCodecs.length; ++i) {
|
||||
if(hasMediaCodec(window, allCodecs[i].mimeType)) {
|
||||
supportedCodecs.add(allCodecs[i]);
|
||||
}
|
||||
}
|
||||
if(!supportedCodecs.isEmpty()) {
|
||||
logger.info("Found {} codecs that are probably supported!", supportedCodecs.size());
|
||||
}else {
|
||||
logger.error("No supported codecs found!");
|
||||
support = false;
|
||||
}
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
supportedCodecs.clear();
|
||||
logger.error("Disabling screen recording because of exceptions!");
|
||||
support = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void captureFrameHook() {
|
||||
if(mediaRec != null && currentParameters != null && currentParameters.resolutionDivisior > 1 && downscaleCanvas != null && downscaleCanvasCtx != null) {
|
||||
if(currentParameters.captureFrameRate > 0) {
|
||||
long curTime = PlatformRuntime.steadyTimeMillis();
|
||||
if(curTime - lastDownscaleFrameCaptured < (long)(1000 / currentParameters.captureFrameRate)) {
|
||||
return;
|
||||
}
|
||||
lastDownscaleFrameCaptured = curTime;
|
||||
}
|
||||
int oldWidth = downscaleCanvas.getWidth();
|
||||
int oldHeight = downscaleCanvas.getHeight();
|
||||
float divisor = (float)Math.sqrt(1.0 / Math.pow(2.0, currentParameters.resolutionDivisior - 1));
|
||||
int newWidth = (int)(PlatformInput.getWindowWidth() * divisor);
|
||||
int newHeight = (int)(PlatformInput.getWindowHeight() * divisor);
|
||||
if(oldWidth != newWidth || oldHeight != newHeight) {
|
||||
downscaleCanvas.setWidth(newWidth);
|
||||
downscaleCanvas.setHeight(newHeight);
|
||||
}
|
||||
downscaleCanvasCtx.drawImage(canvas, 0, 0, newWidth, newHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSupported() {
|
||||
return support;
|
||||
}
|
||||
|
||||
public static boolean isCodecSupported(EnumScreenRecordingCodec codec) {
|
||||
return supportedCodecs.contains(codec);
|
||||
}
|
||||
|
||||
public static void setGameVolume(float volume) {
|
||||
currentGameVolume = volume;
|
||||
if(PlatformAudio.gameRecGain != null) {
|
||||
PlatformAudio.gameRecGain.getGain().setValue(volume);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setMicrophoneVolume(float volume) {
|
||||
currentMicVolume = volume;
|
||||
if(PlatformAudio.micRecGain != null) {
|
||||
PlatformAudio.micRecGain.getGain().setValue(volume);
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { }, script = "return { alpha: false, desynchronized: true };")
|
||||
private static native JSObject youEagler();
|
||||
|
||||
@JSBody(params = { "canvas", "fps", "audio" }, script = "var stream = fps <= 0 ? canvas.captureStream() : canvas.captureStream(fps); stream.addTrack(audio.getTracks()[0]); return stream;")
|
||||
private static native MediaStream captureStreamAndAddAudio(HTMLCanvasElement canvas, int fps, MediaStream audio);
|
||||
|
||||
private static interface DataAvailableEvent extends Event {
|
||||
@JSProperty
|
||||
JSObject getData();
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd hh-mm-ss");
|
||||
|
||||
public static void startRecording(ScreenRecordParameters params) {
|
||||
if(!support) {
|
||||
throw new IllegalStateException("Screen recording is not supported");
|
||||
}
|
||||
if(isRecording()) {
|
||||
throw new IllegalStateException("Already recording!");
|
||||
}
|
||||
if(params.captureFrameRate <= 0 && (!PlatformInput.vsync || !PlatformInput.vsyncSupport)) {
|
||||
throw new IllegalStateException("V-Sync is not enabled, please enable it in \"Video Settings\"");
|
||||
}
|
||||
if(params.resolutionDivisior > 1) {
|
||||
float divisor = (float)Math.sqrt(1.0 / Math.pow(2.0, params.resolutionDivisior - 1));
|
||||
int newWidth = (int)(PlatformInput.getWindowWidth() * divisor);
|
||||
int newHeight = (int)(PlatformInput.getWindowHeight() * divisor);
|
||||
if(downscaleCanvas == null) {
|
||||
downscaleCanvas = (HTMLCanvasElement) win.getDocument().createElement("canvas");
|
||||
downscaleCanvas.setWidth(newWidth);
|
||||
downscaleCanvas.setHeight(newHeight);
|
||||
downscaleCanvasCtx = (CanvasRenderingContext2D) downscaleCanvas.getContext("2d", youEagler());
|
||||
if(downscaleCanvasCtx == null) {
|
||||
downscaleCanvas = null;
|
||||
throw new IllegalStateException("Could not create downscaler canvas!");
|
||||
}
|
||||
}else {
|
||||
downscaleCanvas.setWidth(newWidth);
|
||||
downscaleCanvas.setHeight(newHeight);
|
||||
}
|
||||
}
|
||||
currentMicLock = currentMicVolume <= 0.0f;
|
||||
recStream = captureStreamAndAddAudio(params.resolutionDivisior > 1 ? downscaleCanvas : canvas, Math.max(params.captureFrameRate, 0),
|
||||
PlatformAudio.initRecordingStream(currentGameVolume, currentMicVolume));
|
||||
mediaRec = createMediaRecorder(recStream, params.codec.mimeType, params.videoBitsPerSecond * 1000, params.audioBitsPerSecond * 1000);
|
||||
currentParameters = params;
|
||||
startTime = PlatformRuntime.steadyTimeMillis();
|
||||
TeaVMUtils.addEventListener(mediaRec, "dataavailable", new EventListener<DataAvailableEvent>() {
|
||||
@Override
|
||||
public void handleEvent(DataAvailableEvent evt) {
|
||||
final String fileName = EaglercraftVersion.mainMenuStringB + " - " + EaglerProfile.getName() + " - " + fmt.format(new Date()) + "." + params.codec.fileExt;
|
||||
if("video/webm".equals(params.codec.container)) {
|
||||
FixWebMDurationJS.getRecUrl(evt, (int) (PlatformRuntime.steadyTimeMillis() - startTime), url -> {
|
||||
HTMLAnchorElement a = (HTMLAnchorElement) win.getDocument().createElement("a");
|
||||
a.setDownload(fileName);
|
||||
a.setHref(url);
|
||||
a.click();
|
||||
TeaVMUtils.freeDataURL(url);
|
||||
}, logger::info);
|
||||
}else {
|
||||
String url = TeaVMUtils.getDataURL(evt.getData());
|
||||
HTMLAnchorElement a = (HTMLAnchorElement) win.getDocument().createElement("a");
|
||||
a.setDownload(fileName);
|
||||
a.setHref(url);
|
||||
a.click();
|
||||
TeaVMUtils.freeDataURL(url);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void endRecording() {
|
||||
if(mediaRec != null) {
|
||||
stopRec(mediaRec);
|
||||
mediaRec = null;
|
||||
PlatformAudio.destroyRecordingStream();
|
||||
}
|
||||
currentParameters = null;
|
||||
}
|
||||
|
||||
public static boolean isRecording() {
|
||||
return mediaRec != null;
|
||||
}
|
||||
|
||||
public static boolean isMicVolumeLocked() {
|
||||
return mediaRec != null && currentMicLock;
|
||||
}
|
||||
|
||||
public static boolean isVSyncLocked() {
|
||||
return mediaRec != null && currentParameters != null && currentParameters.captureFrameRate == -1;
|
||||
}
|
||||
|
||||
@JSBody(params = { "canvas", "audio" }, script = "var stream = canvas.captureStream(); stream.addTrack(audio.getTracks()[0]); return stream;")
|
||||
private static native MediaStream captureStreamAndAddAudio(HTMLCanvasElement canvas, MediaStream audio);
|
||||
|
||||
@JSBody(params = { "stream", "codec", "videoBitrate", "audioBitrate" }, script = "var rec = new MediaRecorder(stream, { mimeType: codec, videoBitsPerSecond: videoBitrate, audioBitsPerSecond: audioBitrate }); rec.start(); return rec;")
|
||||
private static native JSObject createMediaRecorder(MediaStream stream, String codec, int videoBitrate, int audioBitrate);
|
||||
|
||||
@JSBody(params = { "rec" }, script = "rec.stop();")
|
||||
private static native void stopRec(JSObject rec);
|
||||
|
||||
@JSBody(params = { }, script = "return (typeof MediaRecorder !== \"undefined\");")
|
||||
private static native boolean canRec();
|
||||
|
||||
@JSFunctor
|
||||
private static interface MediaHandler extends JSObject {
|
||||
void onMedia(MediaStream stream);
|
||||
}
|
||||
|
||||
@JSBody(params = { "cb" }, script = "if (\"navigator\" in window && \"mediaDevices\" in window.navigator && \"getUserMedia\" in window.navigator.mediaDevices) { try { window.navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function(stream) { cb(stream); }).catch(function(err) { console.error(err); cb(null); }); } catch(e) { console.error(\"getUserMedia Error!\"); cb(null); } } else { console.error(\"No getUserMedia!\"); cb(null); }")
|
||||
private static native void getMic0(MediaHandler cb);
|
||||
|
||||
@Async
|
||||
private static native MediaStream getMic1();
|
||||
|
||||
private static void getMic1(AsyncCallback<MediaStream> cb) {
|
||||
getMic0(cb::complete);
|
||||
}
|
||||
|
||||
private static boolean canMic = true;
|
||||
private static MediaStream mic = null;
|
||||
|
||||
static MediaStream getMic() {
|
||||
if (canMic) {
|
||||
if (mic == null) {
|
||||
mic = getMic1();
|
||||
if (mic == null) {
|
||||
canMic = false;
|
||||
return null;
|
||||
}
|
||||
return mic;
|
||||
}
|
||||
return mic;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static void destroy() {
|
||||
supportedCodecs.clear();
|
||||
support = false;
|
||||
canvas = null;
|
||||
win = null;
|
||||
}
|
||||
|
||||
}
|
@ -4,12 +4,14 @@ import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.boot_menu.teavm.BootMenuEntryPoint;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUpdateThread;
|
||||
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;
|
||||
import net.lax1dude.eaglercraft.v1_8.update.UpdateCertificate;
|
||||
import net.lax1dude.eaglercraft.v1_8.update.UpdateProgressStruct;
|
||||
import net.lax1dude.eaglercraft.v1_8.update.UpdateResultObj;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
@ -32,24 +34,30 @@ public class PlatformUpdateSvc {
|
||||
|
||||
private static byte[] eaglercraftXClientSignature = null;
|
||||
private static byte[] eaglercraftXClientBundle = null;
|
||||
private static UpdateResultObj updateResult = null;
|
||||
|
||||
private static final UpdateProgressStruct progressStruct = new UpdateProgressStruct();
|
||||
|
||||
@JSBody(params = { }, script = "if(typeof window.eaglercraftXClientSignature !== \"string\") return null; var ret = window.eaglercraftXClientSignature; window.eaglercraftXClientSignature = null; return ret;")
|
||||
@JSBody(params = { }, script = "if(typeof eaglercraftXClientSignature !== \"string\") return null; var ret = eaglercraftXClientSignature; eaglercraftXClientSignature = null; return ret;")
|
||||
private static native String grabEaglercraftXClientSignature();
|
||||
|
||||
@JSBody(params = { }, script = "if(typeof window.eaglercraftXClientBundle !== \"string\") return null; var ret = window.eaglercraftXClientBundle; window.eaglercraftXClientBundle = null; return ret;")
|
||||
@JSBody(params = { }, script = "if(typeof eaglercraftXClientBundle !== \"string\") return null; var ret = eaglercraftXClientBundle; eaglercraftXClientBundle = null; return ret;")
|
||||
private static native String grabEaglercraftXClientBundle();
|
||||
|
||||
public static Thread updateThread = null;
|
||||
|
||||
private static boolean hasInitialized = false;
|
||||
|
||||
public static boolean supported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
eaglercraftXClientSignature = loadClientData(grabEaglercraftXClientSignature());
|
||||
eaglercraftXClientBundle = loadClientData(grabEaglercraftXClientBundle());
|
||||
if(!hasInitialized) {
|
||||
hasInitialized = true;
|
||||
eaglercraftXClientSignature = loadClientData(grabEaglercraftXClientSignature());
|
||||
eaglercraftXClientBundle = loadClientData(grabEaglercraftXClientBundle());
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] loadClientData(String url) {
|
||||
@ -65,10 +73,16 @@ public class PlatformUpdateSvc {
|
||||
}
|
||||
|
||||
public static byte[] getClientSignatureData() {
|
||||
if(!hasInitialized) {
|
||||
initialize();
|
||||
}
|
||||
return eaglercraftXClientSignature;
|
||||
}
|
||||
|
||||
public static byte[] getClientBundleData() {
|
||||
if(!hasInitialized) {
|
||||
initialize();
|
||||
}
|
||||
return eaglercraftXClientBundle;
|
||||
}
|
||||
|
||||
@ -86,6 +100,27 @@ public class PlatformUpdateSvc {
|
||||
return progressStruct;
|
||||
}
|
||||
|
||||
public static UpdateResultObj getUpdateResult() {
|
||||
UpdateResultObj ret = updateResult;
|
||||
if(ret != null) {
|
||||
updateResult = null;
|
||||
return ret;
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setUpdateResultTeaVM(UpdateResultObj obj) {
|
||||
updateResult = obj;
|
||||
}
|
||||
|
||||
public static void installSignedClient(UpdateCertificate clientCert, byte[] clientPayload, boolean setDefault,
|
||||
boolean setTimeout) {
|
||||
BootMenuEntryPoint.installSignedClientAtRuntime(
|
||||
clientCert.bundleDisplayName + " " + clientCert.bundleDisplayVersion, PlatformRuntime.win,
|
||||
clientCert.rawCertData, clientPayload, setDefault, setTimeout);
|
||||
}
|
||||
|
||||
public static void quine(String filename, byte[] cert, byte[] data, String date) {
|
||||
EagRuntime.downloadFileWithName(filename, TeaVMUpdateThread.generateSignedOffline(cert, data, date));
|
||||
}
|
||||
|
@ -44,98 +44,159 @@ public class PlatformVoiceClient {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("PlatformVoiceClient");
|
||||
|
||||
private static final HashMap<EaglercraftUUID, AnalyserNode> voiceAnalysers = new HashMap<>();
|
||||
private static final HashMap<EaglercraftUUID, GainNode> voiceGains = new HashMap<>();
|
||||
private static final HashMap<EaglercraftUUID, PannerNode> voicePanners = new HashMap<>();
|
||||
@JSBody(params = {}, script = "return typeof navigator.mediaDevices !== \"undefined\" && typeof navigator.mediaDevices.getUserMedia !== \"undefined\";")
|
||||
private static native boolean isSupported0();
|
||||
|
||||
@JSBody(params = {}, script = "return typeof window.RTCPeerConnection !== \"undefined\" && typeof navigator.mediaDevices !== \"undefined\" && typeof navigator.mediaDevices.getUserMedia !== \"undefined\";")
|
||||
public static native boolean isSupported();
|
||||
private static final int SRC_OBJECT_SUPPORT_NONE = -1;
|
||||
private static final int SRC_OBJECT_SUPPORT_CORE = 0;
|
||||
private static final int SRC_OBJECT_SUPPORT_MOZ = 1;
|
||||
|
||||
static boolean hasCheckedSupport = false;
|
||||
static boolean support = false;
|
||||
static int srcObjectSupport = SRC_OBJECT_SUPPORT_NONE;
|
||||
|
||||
public static boolean isSupported() {
|
||||
if(!hasCheckedSupport) {
|
||||
support = PlatformWebRTC.supported() && isSupported0();
|
||||
if(support) {
|
||||
srcObjectSupport = checkSrcObjectSupport(PlatformRuntime.doc);
|
||||
if(srcObjectSupport == SRC_OBJECT_SUPPORT_NONE) {
|
||||
support = false;
|
||||
}else if(srcObjectSupport == SRC_OBJECT_SUPPORT_MOZ) {
|
||||
logger.info("Using moz- prefix for HTMLMediaElement.srcObject");
|
||||
}
|
||||
}
|
||||
hasCheckedSupport = true;
|
||||
}
|
||||
return support;
|
||||
}
|
||||
|
||||
@JSBody(params = { "item" }, script = "return item.streams[0];")
|
||||
static native MediaStream getFirstStream(JSObject item);
|
||||
|
||||
@JSBody(params = { "doc" }, script = "var aud = doc.createElement(\"audio\"); return (typeof aud.srcObject !== \"undefined\") ? 0 : ((typeof aud.mozSrcObject !== \"undefined\") ? 1 : -1);")
|
||||
static native int checkSrcObjectSupport(HTMLDocument doc);
|
||||
|
||||
static void setSrcObject(HTMLAudioElement aud, MediaStream stream) {
|
||||
switch(srcObjectSupport) {
|
||||
case SRC_OBJECT_SUPPORT_CORE:
|
||||
setSrcObjectCore(aud, stream);
|
||||
break;
|
||||
case SRC_OBJECT_SUPPORT_MOZ:
|
||||
setMozSrcObject(aud, stream);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { "aud", "stream" }, script = "return aud.srcObject = stream;")
|
||||
static native void setSrcObject(HTMLAudioElement aud, MediaStream stream);
|
||||
private static native void setSrcObjectCore(HTMLAudioElement aud, MediaStream stream);
|
||||
|
||||
@JSBody(params = { "aud" }, script = "return aud.remove();")
|
||||
static native void removeAud(HTMLAudioElement aud);
|
||||
@JSBody(params = { "aud", "stream" }, script = "return aud.mozSrcObject = stream;")
|
||||
private static native void setMozSrcObject(HTMLAudioElement aud, MediaStream stream);
|
||||
|
||||
@JSBody(params = { "pc", "stream" }, script = "return stream.getTracks().forEach((track) => { pc.addTrack(track, stream); });")
|
||||
@JSBody(params = { "pc", "stream" }, script = "return stream.getTracks().forEach(function(track) { pc.addTrack(track, stream); });")
|
||||
static native void addStream(JSObject pc, MediaStream stream);
|
||||
|
||||
@JSBody(params = { "rawStream", "muted" }, script = "return rawStream.getAudioTracks()[0].enabled = !muted;")
|
||||
static native void mute(MediaStream rawStream, boolean muted);
|
||||
|
||||
@JSBody(params = { "peerConnection", "str" }, script = "return peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(str)));")
|
||||
static native void addIceCandidate(JSObject peerConnection, String str);
|
||||
static native void addCoreIceCandidate(JSObject peerConnection, String str);
|
||||
|
||||
@JSBody(params = { "peerConnection", "str" }, script = "return peerConnection.addIceCandidate(new mozRTCIceCandidate(JSON.parse(str)));")
|
||||
static native void addMozIceCandidate(JSObject peerConnection, String str);
|
||||
|
||||
static void addIceCandidate(JSObject peerConnection, String str) {
|
||||
if(!PlatformWebRTC.hasCheckedSupport) PlatformWebRTC.supported();
|
||||
switch(PlatformWebRTC.supportedImpl) {
|
||||
case PlatformWebRTC.WEBRTC_SUPPORT_CORE:
|
||||
case PlatformWebRTC.WEBRTC_SUPPORT_WEBKIT:
|
||||
addCoreIceCandidate(peerConnection, str);
|
||||
break;
|
||||
case PlatformWebRTC.WEBRTC_SUPPORT_MOZ:
|
||||
addMozIceCandidate(peerConnection, str);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void disconnect(JSObject peerConnection) {
|
||||
PlatformWebRTC.closeIt(peerConnection);
|
||||
}
|
||||
|
||||
public static void setVoiceProximity(int prox) {
|
||||
for (PannerNode panner : voicePanners.values()) {
|
||||
panner.setMaxDistance(VoiceClientController.getVoiceListenVolume() * 2 * prox + 0.1f);
|
||||
for (VoicePeer player : peerList.values()) {
|
||||
if(player.panner != null) {
|
||||
player.panner.setMaxDistance(VoiceClientController.getVoiceListenVolume() * 2 * prox + 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateVoicePosition(EaglercraftUUID uuid, double x, double y, double z) {
|
||||
if (voicePanners.containsKey(uuid)) voicePanners.get(uuid).setPosition((float) x, (float) y, (float) z);
|
||||
VoicePeer player = peerList.get(uuid);
|
||||
if (player != null && player.panner != null) player.panner.setPosition((float) x, (float) y, (float) z);
|
||||
}
|
||||
|
||||
public static class VoicePeer {
|
||||
|
||||
public final EaglercraftUUID peerId;
|
||||
public final JSObject peerConnection;
|
||||
public MediaStream rawStream;
|
||||
|
||||
private AnalyserNode analyser = null;
|
||||
private GainNode gain = null;
|
||||
private PannerNode panner = null;
|
||||
private AudioNode recNode = null;
|
||||
|
||||
public VoicePeer(EaglercraftUUID peerId, JSObject peerConnection, boolean offer) {
|
||||
this.peerId = peerId;
|
||||
this.peerConnection = peerConnection;
|
||||
|
||||
TeaVMUtils.addEventListener(peerConnection, "icecandidate", (EventListener<Event>) evt -> {
|
||||
if (PlatformWebRTC.hasCandidate(evt)) {
|
||||
Map<String, String> m = new HashMap<>();
|
||||
JSONObject m = new JSONObject();
|
||||
m.put("sdpMLineIndex", "" + PlatformWebRTC.getSdpMLineIndex(evt));
|
||||
m.put("candidate", PlatformWebRTC.getCandidate(evt));
|
||||
handleIceCandidate(peerId, JSONWriter.valueToString(m));
|
||||
VoiceClientController.sendPacketICE(peerId, m.toString());
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(peerConnection, "track", (EventListener<Event>) evt -> {
|
||||
rawStream = getFirstStream(evt);
|
||||
HTMLAudioElement aud = (HTMLAudioElement) HTMLDocument.current().createElement("audio");
|
||||
HTMLAudioElement aud = (HTMLAudioElement) PlatformRuntime.doc.createElement("audio");
|
||||
aud.setAutoplay(true);
|
||||
aud.setMuted(true);
|
||||
TeaVMUtils.addEventListener(aud, "ended", (EventListener<Event>) evt2 -> {
|
||||
removeAud(aud);
|
||||
});
|
||||
setSrcObject(aud, rawStream);
|
||||
handlePeerTrack(peerId, rawStream);
|
||||
handlePeerTrack(this, rawStream);
|
||||
});
|
||||
|
||||
addStream(peerConnection, localMediaStream.getStream());
|
||||
if (offer) {
|
||||
PlatformWebRTC.createOffer(peerConnection, desc -> {
|
||||
PlatformWebRTC.setLocalDescription(peerConnection, desc, () -> {
|
||||
handleDescription(peerId, JSON.stringify(desc));
|
||||
VoiceClientController.sendPacketDesc(peerId, JSON.stringify(desc));
|
||||
}, err -> {
|
||||
logger.error("Failed to set local description for \"{}\"! {}", peerId, err);
|
||||
if (peerStateInitial == EnumVoiceChannelPeerState.LOADING) {
|
||||
peerStateInitial = EnumVoiceChannelPeerState.FAILED;
|
||||
}
|
||||
signalDisconnect(peerId, false);
|
||||
signalDisconnect(VoicePeer.this, false);
|
||||
});
|
||||
}, err -> {
|
||||
logger.error("Failed to set create offer for \"{}\"! {}", peerId, err);
|
||||
if (peerStateInitial == EnumVoiceChannelPeerState.LOADING) {
|
||||
peerStateInitial = EnumVoiceChannelPeerState.FAILED;
|
||||
}
|
||||
signalDisconnect(peerId, false);
|
||||
signalDisconnect(VoicePeer.this, false);
|
||||
});
|
||||
}
|
||||
|
||||
TeaVMUtils.addEventListener(peerConnection, "connectionstatechange", (EventListener<Event>) evt -> {
|
||||
String cs = PlatformWebRTC.getConnectionState(peerConnection);
|
||||
if ("disconnected".equals(cs)) {
|
||||
signalDisconnect(peerId, false);
|
||||
signalDisconnect(VoicePeer.this, false);
|
||||
} else if ("connected".equals(cs)) {
|
||||
if (peerState != EnumVoiceChannelPeerState.SUCCESS) {
|
||||
peerState = EnumVoiceChannelPeerState.SUCCESS;
|
||||
@ -144,7 +205,7 @@ public class PlatformVoiceClient {
|
||||
if (peerState == EnumVoiceChannelPeerState.LOADING) {
|
||||
peerState = EnumVoiceChannelPeerState.FAILED;
|
||||
}
|
||||
signalDisconnect(peerId, false);
|
||||
signalDisconnect(VoicePeer.this, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -160,32 +221,32 @@ public class PlatformVoiceClient {
|
||||
public void setRemoteDescription(String descJSON) {
|
||||
try {
|
||||
JSONObject remoteDesc = new JSONObject(descJSON);
|
||||
PlatformWebRTC.setRemoteDescription2(peerConnection, descJSON, () -> {
|
||||
PlatformWebRTC.setRemoteDescription2(peerConnection, JSON.parse(descJSON), () -> {
|
||||
if (remoteDesc.has("type") && "offer".equals(remoteDesc.getString("type"))) {
|
||||
PlatformWebRTC.createAnswer(peerConnection, desc -> {
|
||||
PlatformWebRTC.setLocalDescription(peerConnection, desc, () -> {
|
||||
handleDescription(peerId, JSON.stringify(desc));
|
||||
VoiceClientController.sendPacketDesc(peerId, JSON.stringify(desc));
|
||||
if (peerStateDesc != EnumVoiceChannelPeerState.SUCCESS) peerStateDesc = EnumVoiceChannelPeerState.SUCCESS;
|
||||
}, err -> {
|
||||
logger.error("Failed to set local description for \"{}\"! {}", peerId, err.getMessage());
|
||||
if (peerStateDesc == EnumVoiceChannelPeerState.LOADING) peerStateDesc = EnumVoiceChannelPeerState.FAILED;
|
||||
signalDisconnect(peerId, false);
|
||||
signalDisconnect(VoicePeer.this, false);
|
||||
});
|
||||
}, err -> {
|
||||
logger.error("Failed to create answer for \"{}\"! {}", peerId, err.getMessage());
|
||||
if (peerStateDesc == EnumVoiceChannelPeerState.LOADING) peerStateDesc = EnumVoiceChannelPeerState.FAILED;
|
||||
signalDisconnect(peerId, false);
|
||||
signalDisconnect(VoicePeer.this, false);
|
||||
});
|
||||
}
|
||||
}, err -> {
|
||||
logger.error("Failed to set remote description for \"{}\"! {}", peerId, err.getMessage());
|
||||
if (peerStateDesc == EnumVoiceChannelPeerState.LOADING) peerStateDesc = EnumVoiceChannelPeerState.FAILED;
|
||||
signalDisconnect(peerId, false);
|
||||
signalDisconnect(VoicePeer.this, false);
|
||||
});
|
||||
} catch (Throwable err) {
|
||||
logger.error("Failed to parse remote description for \"{}\"! {}", peerId, err.getMessage());
|
||||
if (peerStateDesc == EnumVoiceChannelPeerState.LOADING) peerStateDesc = EnumVoiceChannelPeerState.FAILED;
|
||||
signalDisconnect(peerId, false);
|
||||
signalDisconnect(VoicePeer.this, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,14 +257,14 @@ public class PlatformVoiceClient {
|
||||
} catch (Throwable err) {
|
||||
logger.error("Failed to parse ice candidate for \"{}\"! {}", peerId, err.getMessage());
|
||||
if (peerStateIce == EnumVoiceChannelPeerState.LOADING) peerStateIce = EnumVoiceChannelPeerState.FAILED;
|
||||
signalDisconnect(peerId, false);
|
||||
signalDisconnect(VoicePeer.this, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<Map<String, String>> iceServers = new HashSet<>();
|
||||
public static boolean hasInit = false;
|
||||
public static Map<EaglercraftUUID, VoicePeer> peerList = new HashMap<>();
|
||||
public static final Map<EaglercraftUUID, VoicePeer> peerList = new HashMap<>();
|
||||
public static MediaStreamAudioDestinationNode localMediaStream;
|
||||
public static GainNode localMediaStreamGain;
|
||||
public static MediaStream localRawMediaStream;
|
||||
@ -242,7 +303,7 @@ public class PlatformVoiceClient {
|
||||
|
||||
public static void initializeDevices() {
|
||||
if (!hasInit) {
|
||||
localRawMediaStream = PlatformRuntime.getMic();
|
||||
localRawMediaStream = PlatformScreenRecord.getMic();
|
||||
if (localRawMediaStream == null) {
|
||||
readyState = EnumVoiceChannelReadyState.ABORTED;
|
||||
return;
|
||||
@ -262,15 +323,17 @@ public class PlatformVoiceClient {
|
||||
}
|
||||
|
||||
public static void tickVoiceClient() {
|
||||
for (EaglercraftUUID uuid : voiceAnalysers.keySet()) {
|
||||
AnalyserNode analyser = voiceAnalysers.get(uuid);
|
||||
Uint8Array array = Uint8Array.create(analyser.getFrequencyBinCount());
|
||||
analyser.getByteFrequencyData(array);
|
||||
int len = array.getLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (array.get(i) >= 0.1f) {
|
||||
VoiceClientController.getVoiceSpeaking().add(uuid);
|
||||
break;
|
||||
for (VoicePeer voicePlayer : peerList.values()) {
|
||||
AnalyserNode analyser = voicePlayer.analyser;
|
||||
if(analyser != null) {
|
||||
Uint8Array array = Uint8Array.create(analyser.getFrequencyBinCount());
|
||||
analyser.getByteFrequencyData(array);
|
||||
int len = array.getLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (array.get(i) >= 0.1f) {
|
||||
VoiceClientController.getVoiceSpeaking().add(voicePlayer.peerId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -335,14 +398,18 @@ public class PlatformVoiceClient {
|
||||
public static void signalDisconnect(EaglercraftUUID peerId, boolean quiet) {
|
||||
VoicePeer peer = peerList.get(peerId);
|
||||
if (peer != null) {
|
||||
peerList.remove(peerId, peer);
|
||||
try {
|
||||
peer.disconnect();
|
||||
} catch (Throwable ignored) {}
|
||||
handlePeerDisconnect(peerId, quiet);
|
||||
signalDisconnect(peer, quiet);
|
||||
}
|
||||
}
|
||||
|
||||
private static void signalDisconnect(VoicePeer peer, boolean quiet) {
|
||||
peerList.remove(peer.peerId, peer);
|
||||
try {
|
||||
peer.disconnect();
|
||||
} catch (Throwable ignored) {}
|
||||
handlePeerDisconnect(peer, quiet);
|
||||
}
|
||||
|
||||
public static void mutePeer(EaglercraftUUID peerId, boolean muted) {
|
||||
VoicePeer peer = peerList.get(peerId);
|
||||
if (peer != null) {
|
||||
@ -357,30 +424,25 @@ public class PlatformVoiceClient {
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleIceCandidate(EaglercraftUUID peerId, String candidate) {
|
||||
VoiceClientController.sendPacketICE(peerId, candidate);
|
||||
}
|
||||
|
||||
public static void handleDescription(EaglercraftUUID peerId, String desc) {
|
||||
VoiceClientController.sendPacketDesc(peerId, desc);
|
||||
}
|
||||
|
||||
public static void handlePeerTrack(EaglercraftUUID peerId, MediaStream audioStream) {
|
||||
private static void handlePeerTrack(VoicePeer peer, MediaStream audioStream) {
|
||||
if (VoiceClientController.getVoiceChannel() == EnumVoiceChannelType.NONE) return;
|
||||
MediaStreamAudioSourceNode audioNode = PlatformAudio.audioctx.createMediaStreamSource(audioStream);
|
||||
AnalyserNode analyser = PlatformAudio.audioctx.createAnalyser();
|
||||
analyser.setSmoothingTimeConstant(0f);
|
||||
analyser.setFftSize(32);
|
||||
audioNode.connect(analyser);
|
||||
voiceAnalysers.put(peerId, analyser);
|
||||
if (VoiceClientController.getVoiceChannel() == EnumVoiceChannelType.GLOBAL) {
|
||||
GainNode gain = PlatformAudio.audioctx.createGain();
|
||||
gain.getGain().setValue(VoiceClientController.getVoiceListenVolume());
|
||||
analyser.connect(gain);
|
||||
audioNode.connect(gain);
|
||||
gain.connect(PlatformAudio.audioctx.getDestination());
|
||||
gain.connect(PlatformAudio.recDest);
|
||||
voiceGains.put(peerId, gain);
|
||||
VoiceClientController.getVoiceListening().add(peerId);
|
||||
if(PlatformAudio.gameRecGain != null) {
|
||||
gain.connect(PlatformAudio.gameRecGain);
|
||||
}
|
||||
VoiceClientController.getVoiceListening().add(peer.peerId);
|
||||
peer.analyser = analyser;
|
||||
peer.gain = gain;
|
||||
peer.recNode = gain;
|
||||
} else if (VoiceClientController.getVoiceChannel() == EnumVoiceChannelType.PROXIMITY) {
|
||||
PannerNode panner = PlatformAudio.audioctx.createPanner();
|
||||
panner.setRolloffFactor(1f);
|
||||
@ -395,45 +457,72 @@ public class PlatformVoiceClient {
|
||||
panner.setMaxDistance(vol * 2 * VoiceClientController.getVoiceProximity() + 0.1f);
|
||||
GainNode gain = PlatformAudio.audioctx.createGain();
|
||||
gain.getGain().setValue(vol);
|
||||
analyser.connect(gain);
|
||||
audioNode.connect(gain);
|
||||
gain.connect(panner);
|
||||
panner.connect(PlatformAudio.audioctx.getDestination());
|
||||
panner.connect(PlatformAudio.recDest);
|
||||
voiceGains.put(peerId, gain);
|
||||
VoiceClientController.getVoiceListening().add(peerId);
|
||||
voicePanners.put(peerId, panner);
|
||||
if(PlatformAudio.gameRecGain != null) {
|
||||
panner.connect(PlatformAudio.gameRecGain);
|
||||
}
|
||||
VoiceClientController.getVoiceListening().add(peer.peerId);
|
||||
peer.analyser = analyser;
|
||||
peer.panner = panner;
|
||||
peer.gain = gain;
|
||||
peer.recNode = panner;
|
||||
}
|
||||
if (VoiceClientController.getVoiceMuted().contains(peerId)) mutePeer(peerId, true);
|
||||
if (VoiceClientController.getVoiceMuted().contains(peer.peerId)) mutePeer(peer.peerId, true);
|
||||
}
|
||||
|
||||
public static void handlePeerDisconnect(EaglercraftUUID peerId, boolean quiet) {
|
||||
if (voiceAnalysers.containsKey(peerId)) {
|
||||
voiceAnalysers.get(peerId).disconnect();
|
||||
voiceAnalysers.remove(peerId);
|
||||
static void addRecordingDest(AudioNode destNode) {
|
||||
for(VoicePeer peer : peerList.values()) {
|
||||
if(peer.recNode != null) {
|
||||
peer.recNode.connect(destNode);
|
||||
}
|
||||
}
|
||||
if (voiceGains.containsKey(peerId)) {
|
||||
voiceGains.get(peerId).disconnect();
|
||||
voiceGains.remove(peerId);
|
||||
VoiceClientController.getVoiceListening().remove(peerId);
|
||||
}
|
||||
|
||||
static void removeRecordingDest(AudioNode destNode) {
|
||||
for(VoicePeer peer : peerList.values()) {
|
||||
try {
|
||||
if(peer.recNode != null) {
|
||||
peer.recNode.disconnect(destNode);
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
}
|
||||
if (voicePanners.containsKey(peerId)) {
|
||||
voicePanners.get(peerId).disconnect();
|
||||
voicePanners.remove(peerId);
|
||||
}
|
||||
|
||||
private static void handlePeerDisconnect(VoicePeer peer, boolean quiet) {
|
||||
if(peer.analyser != null) {
|
||||
peer.analyser.disconnect();
|
||||
peer.analyser = null;
|
||||
}
|
||||
if(peer.gain != null) {
|
||||
peer.gain.disconnect();
|
||||
peer.gain = null;
|
||||
}
|
||||
if(peer.panner != null) {
|
||||
peer.panner.disconnect();
|
||||
peer.panner = null;
|
||||
}
|
||||
VoiceClientController.getVoiceListening().remove(peer.peerId);
|
||||
if (!quiet) {
|
||||
VoiceClientController.sendPacketDisconnect(peerId);
|
||||
VoiceClientController.sendPacketDisconnectPeer(peer.peerId);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setVoiceListenVolume(float f) {
|
||||
for (EaglercraftUUID uuid : voiceGains.keySet()) {
|
||||
GainNode gain = voiceGains.get(uuid);
|
||||
float val = f;
|
||||
if(val > 0.5f) val = 0.5f + (val - 0.5f) * 3.0f;
|
||||
if(val > 2.0f) val = 2.0f;
|
||||
if(val < 0.0f) val = 0.0f;
|
||||
gain.getGain().setValue(val * 2.0f);
|
||||
if (voicePanners.containsKey(uuid)) voicePanners.get(uuid).setMaxDistance(f * 2 * VoiceClientController.getVoiceProximity() + 0.1f);
|
||||
for (VoicePeer peer : peerList.values()) {
|
||||
if(peer.gain != null) {
|
||||
float val = f;
|
||||
if(val > 0.5f) val = 0.5f + (val - 0.5f) * 3.0f;
|
||||
if(val > 2.0f) val = 2.0f;
|
||||
if(val < 0.0f) val = 0.0f;
|
||||
peer.gain.getGain().setValue(val * 2.0f);
|
||||
}
|
||||
if(peer.panner != null) {
|
||||
peer.panner.setMaxDistance(f * 2 * VoiceClientController.getVoiceProximity() + 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,639 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.teavm.jso.browser.Window;
|
||||
import org.teavm.jso.dom.css.CSSStyleDeclaration;
|
||||
import org.teavm.jso.dom.events.Event;
|
||||
import org.teavm.jso.dom.events.EventListener;
|
||||
import org.teavm.jso.dom.events.MessageEvent;
|
||||
import org.teavm.jso.dom.html.HTMLElement;
|
||||
import org.teavm.jso.dom.html.HTMLInputElement;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.AdvancedHTMLIFrameElement;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.IFrameSafetyException;
|
||||
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;
|
||||
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketWebViewMessageEnV4EAG;
|
||||
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketWebViewMessageV4EAG;
|
||||
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketWebViewMessageV4EAG;
|
||||
import net.lax1dude.eaglercraft.v1_8.webview.PermissionsCache;
|
||||
import net.lax1dude.eaglercraft.v1_8.webview.WebViewOverlayController.IPacketSendCallback;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class PlatformWebView {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("PlatformWebView");
|
||||
|
||||
private static boolean supportKnown = false;
|
||||
private static boolean supportForce = false;
|
||||
private static boolean enableCSP = true;
|
||||
private static boolean supported = false;
|
||||
private static boolean cspSupport = false;
|
||||
|
||||
private static HTMLElement currentIFrameContainer = null;
|
||||
private static HTMLElement currentAllowJavaScript = null;
|
||||
|
||||
private static AdvancedHTMLIFrameElement currentIFrame = null;
|
||||
private static WebViewOptions currentOptions = null;
|
||||
|
||||
private static int webviewResetSerial = 0;
|
||||
|
||||
private static String currentMessageChannelName = null;
|
||||
|
||||
private static Window win;
|
||||
private static HTMLElement rootElement;
|
||||
|
||||
private static Consumer<MessageEvent> currentMessageHandler = null;
|
||||
|
||||
private static final List<Runnable> messageQueue = new LinkedList<>();
|
||||
|
||||
private static IPacketSendCallback packetSendCallback = null;
|
||||
|
||||
static void initRoot(Window win, HTMLElement rootElement) {
|
||||
PlatformWebView.win = win;
|
||||
PlatformWebView.rootElement = rootElement;
|
||||
}
|
||||
|
||||
public static boolean supported() {
|
||||
if(!supportKnown) {
|
||||
IClientConfigAdapter cfg = PlatformRuntime.getClientConfigAdapter();
|
||||
supportForce = cfg.isForceWebViewSupport();
|
||||
enableCSP = cfg.isEnableWebViewCSP();
|
||||
if(supportForce) {
|
||||
supported = true;
|
||||
cspSupport = true;
|
||||
}else {
|
||||
supported = false;
|
||||
cspSupport = false;
|
||||
try {
|
||||
AdvancedHTMLIFrameElement tmp = (AdvancedHTMLIFrameElement)win.getDocument().createElement("iframe");
|
||||
supported = tmp != null && tmp.checkSafetyFeaturesSupported();
|
||||
cspSupport = enableCSP && supported && tmp.checkCSPSupported();
|
||||
}catch(Throwable ex) {
|
||||
logger.error("Error checking iframe support");
|
||||
logger.error(ex);
|
||||
}
|
||||
}
|
||||
if(!supported) {
|
||||
logger.error("This browser does not meet the safety requirements for webview support, this feature will be disabled");
|
||||
}else if(!cspSupport && enableCSP) {
|
||||
logger.warn("This browser does not support CSP attribute on iframes! (try Chrome)");
|
||||
}
|
||||
supportKnown = true;
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
public static boolean isShowing() {
|
||||
return currentIFrameContainer != null;
|
||||
}
|
||||
|
||||
private static int hashPermissionFlags(WebViewOptions opts) {
|
||||
int i = (opts.scriptEnabled ? 1 : 0);
|
||||
i |= ((enableCSP && cspSupport && opts.strictCSPEnable) ? 0 : 2);
|
||||
i |= (opts.serverMessageAPIEnabled ? 4 : 0);
|
||||
return i;
|
||||
}
|
||||
|
||||
public static void beginShowing(WebViewOptions options, int x, int y, int w, int h) {
|
||||
if(!supported()) {
|
||||
return;
|
||||
}
|
||||
setupShowing(x, y, w, h);
|
||||
if(options.scriptEnabled) {
|
||||
PermissionsCache.Permission perm = PermissionsCache.getJavaScriptAllowed(options.permissionsOriginUUID, hashPermissionFlags(options));
|
||||
if(perm == null) {
|
||||
beginShowingEnableJavaScript(options);
|
||||
}else if(perm.choice) {
|
||||
beginShowingDirect(options);
|
||||
}else {
|
||||
beginShowingContentBlocked(options);
|
||||
}
|
||||
}else {
|
||||
beginShowingDirect(options);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupShowing(int x, int y, int w, int h) {
|
||||
if(currentIFrameContainer != null) {
|
||||
endShowing();
|
||||
}
|
||||
currentIFrameContainer = win.getDocument().createElement("div");
|
||||
currentIFrameContainer.getClassList().add("_eaglercraftX_webview_container_element");
|
||||
CSSStyleDeclaration decl = currentIFrameContainer.getStyle();
|
||||
decl.setProperty("border", "5px solid #333333");
|
||||
decl.setProperty("z-index", "11");
|
||||
decl.setProperty("position", "absolute");
|
||||
decl.setProperty("background-color", "#DDDDDD");
|
||||
decl.setProperty("font-family", "sans-serif");
|
||||
resize(x, y, w, h);
|
||||
rootElement.appendChild(currentIFrameContainer);
|
||||
}
|
||||
|
||||
private static void beginShowingDirect(WebViewOptions options) {
|
||||
if(!supportForce) {
|
||||
try {
|
||||
currentOptions = options;
|
||||
currentIFrame = (AdvancedHTMLIFrameElement)win.getDocument().createElement("iframe");
|
||||
currentIFrame.setAllowSafe("");
|
||||
currentIFrame.setReferrerPolicy("strict-origin");
|
||||
Set<String> sandboxArgs = new HashSet<>();
|
||||
sandboxArgs.add("allow-downloads");
|
||||
if(options.scriptEnabled) {
|
||||
sandboxArgs.add("allow-scripts");
|
||||
sandboxArgs.add("allow-pointer-lock");
|
||||
}
|
||||
currentIFrame.setSandboxSafe(sandboxArgs);
|
||||
}catch(IFrameSafetyException ex) {
|
||||
logger.error("Caught safety exception while opening webview!");
|
||||
logger.error(ex);
|
||||
if(currentIFrame != null) {
|
||||
currentIFrame.delete();
|
||||
currentIFrame = null;
|
||||
currentOptions = null;
|
||||
}
|
||||
logger.error("Things you can try:");
|
||||
logger.error("1. Set window.eaglercraftXOpts.forceWebViewSupport to true");
|
||||
logger.error("2. Set window.eaglercraftXOpts.enableWebViewCSP to false");
|
||||
logger.error("(these settings may compromise security)");
|
||||
beginShowingSafetyError();
|
||||
return;
|
||||
}
|
||||
}else {
|
||||
currentOptions = options;
|
||||
currentIFrame = (AdvancedHTMLIFrameElement)win.getDocument().createElement("iframe");
|
||||
try {
|
||||
currentIFrame.setAllow("");
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
try {
|
||||
currentIFrame.setReferrerPolicy("strict-origin");
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
try {
|
||||
List<String> sandboxArgs = new ArrayList<>();
|
||||
sandboxArgs.add("allow-downloads");
|
||||
sandboxArgs.add("allow-same-origin");
|
||||
if(options.scriptEnabled) {
|
||||
sandboxArgs.add("allow-scripts");
|
||||
sandboxArgs.add("allow-pointer-lock");
|
||||
}
|
||||
currentIFrame.setSandbox(String.join(" ", sandboxArgs));
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
}
|
||||
currentIFrame.setCredentialless(true);
|
||||
currentIFrame.setLoading("lazy");
|
||||
boolean cspWarn = false;
|
||||
if(options.contentMode == EnumWebViewContentMode.BLOB_BASED) {
|
||||
if(enableCSP && cspSupport) {
|
||||
if(currentIFrame.checkCSPSupported()) {
|
||||
StringBuilder csp = new StringBuilder();
|
||||
csp.append("default-src 'none';");
|
||||
String protos = options.strictCSPEnable ? "" : (PlatformRuntime.requireSSL() ? " https:" : " http: https:");
|
||||
if(options.scriptEnabled) {
|
||||
csp.append(" script-src 'unsafe-eval' 'unsafe-inline' data: blob:").append(protos).append(';');
|
||||
csp.append(" style-src 'unsafe-eval' 'unsafe-inline' data: blob:").append(protos).append(';');
|
||||
csp.append(" img-src data: blob:").append(protos).append(';');
|
||||
csp.append(" font-src data: blob:").append(protos).append(';');
|
||||
csp.append(" child-src data: blob:").append(protos).append(';');
|
||||
csp.append(" frame-src data: blob:;");
|
||||
csp.append(" media-src data: mediastream: blob:").append(protos).append(';');
|
||||
csp.append(" connect-src data: blob:").append(protos).append(';');
|
||||
csp.append(" worker-src data: blob:").append(protos).append(';');
|
||||
}else {
|
||||
csp.append(" style-src data: 'unsafe-inline'").append(protos).append(';');
|
||||
csp.append(" img-src data:").append(protos).append(';');
|
||||
csp.append(" font-src data:").append(protos).append(';');
|
||||
csp.append(" media-src data:").append(protos).append(';');
|
||||
}
|
||||
currentIFrame.setCSP(csp.toString());
|
||||
}else {
|
||||
logger.warn("This browser does not support CSP attribute on iframes! (try Chrome)");
|
||||
cspWarn = true;
|
||||
}
|
||||
}else {
|
||||
cspWarn = true;
|
||||
}
|
||||
if(cspWarn && options.strictCSPEnable) {
|
||||
logger.warn("Strict CSP was requested for this webview, but that feature is not available!");
|
||||
}
|
||||
}else {
|
||||
cspWarn = true;
|
||||
}
|
||||
CSSStyleDeclaration decl = currentIFrame.getStyle();
|
||||
decl.setProperty("border", "none");
|
||||
decl.setProperty("background-color", "white");
|
||||
decl.setProperty("width", "100%");
|
||||
decl.setProperty("height", "100%");
|
||||
currentIFrame.getClassList().add("_eaglercraftX_webview_iframe_element");
|
||||
currentIFrameContainer.appendChild(currentIFrame);
|
||||
if(options.contentMode == EnumWebViewContentMode.BLOB_BASED) {
|
||||
currentIFrame.setSourceDocument(new String(options.blob, StandardCharsets.UTF_8));
|
||||
}else {
|
||||
currentIFrame.setSourceAddress(options.url.toString());
|
||||
}
|
||||
final int resetSer = webviewResetSerial;
|
||||
final AdvancedHTMLIFrameElement curIFrame = currentIFrame;
|
||||
final boolean[] focusTracker = new boolean[1];
|
||||
currentIFrame.addEventListener("mouseover", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if(resetSer == webviewResetSerial && curIFrame == currentIFrame) {
|
||||
if(!focusTracker[0]) {
|
||||
focusTracker[0] = true;
|
||||
currentIFrame.getContentWindow().focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
currentIFrame.addEventListener("mouseout", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if(resetSer == webviewResetSerial && curIFrame == currentIFrame) {
|
||||
if(focusTracker[0]) {
|
||||
focusTracker[0] = false;
|
||||
win.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if(options.scriptEnabled && options.serverMessageAPIEnabled) {
|
||||
currentMessageHandler = new Consumer<MessageEvent>() {
|
||||
@Override
|
||||
public void accept(MessageEvent evt) {
|
||||
synchronized(messageQueue) {
|
||||
if(resetSer == webviewResetSerial && curIFrame == currentIFrame) {
|
||||
messageQueue.add(() -> {
|
||||
if(resetSer == webviewResetSerial && curIFrame == currentIFrame) {
|
||||
handleMessageRawFromFrame(evt.getData());
|
||||
}else {
|
||||
logger.warn("Recieved message from on dead IFrame handler: (#" + resetSer + ") " + curIFrame.getSourceAddress());
|
||||
}
|
||||
});
|
||||
}else {
|
||||
logger.warn("Recieved message from on dead IFrame handler: (#" + resetSer + ") " + curIFrame.getSourceAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
logger.info("WebView is loading: \"{}\"", options.contentMode == EnumWebViewContentMode.BLOB_BASED ? "about:srcdoc" : currentIFrame.getSourceAddress());
|
||||
logger.info("JavaScript: {}, Strict CSP: {}, Message API: {}", options.scriptEnabled,
|
||||
options.strictCSPEnable && !cspWarn, options.serverMessageAPIEnabled);
|
||||
}
|
||||
|
||||
private static void beginShowingEnableJSSetup() {
|
||||
if(currentAllowJavaScript != null) {
|
||||
++webviewResetSerial;
|
||||
currentAllowJavaScript.delete();
|
||||
currentAllowJavaScript = null;
|
||||
}
|
||||
currentAllowJavaScript = win.getDocument().createElement("div");
|
||||
CSSStyleDeclaration decl = currentAllowJavaScript.getStyle();
|
||||
decl.setProperty("background-color", "white");
|
||||
decl.setProperty("width", "100%");
|
||||
decl.setProperty("height", "100%");
|
||||
currentAllowJavaScript.getClassList().add("_eaglercraftX_webview_permission_screen");
|
||||
currentIFrameContainer.appendChild(currentAllowJavaScript);
|
||||
}
|
||||
|
||||
private static void beginShowingEnableJavaScript(final WebViewOptions options) {
|
||||
beginShowingEnableJSSetup();
|
||||
String strictCSPMarkup;
|
||||
if(options.contentMode != EnumWebViewContentMode.BLOB_BASED) {
|
||||
strictCSPMarkup = "<span style=\"color:red;\">Impossible</span>";
|
||||
}else if(!cspSupport || !enableCSP) {
|
||||
strictCSPMarkup = "<span style=\"color:red;\">Unsupported</span>";
|
||||
}else if(options.strictCSPEnable) {
|
||||
strictCSPMarkup = "<span style=\"color:green;\">Enabled</span>";
|
||||
}else {
|
||||
strictCSPMarkup = "<span style=\"color:red;\">Disabled</span>";
|
||||
}
|
||||
String messageAPIMarkup;
|
||||
if(options.serverMessageAPIEnabled) {
|
||||
messageAPIMarkup = "<span style=\"color:red;\">Enabled</span>";
|
||||
}else {
|
||||
messageAPIMarkup = "<span style=\"color:green;\">Disabled</span>";
|
||||
}
|
||||
currentAllowJavaScript.setInnerHTML(
|
||||
"<div style=\"padding-top:13vh;\"><div style=\"margin:auto;max-width:450px;border:6px double black;text-align:center;padding:20px;\">"
|
||||
+ "<h2><img width=\"32\" height=\"32\" style=\"vertical-align:middle;\" src=\"" + PlatformApplication.faviconURLTeaVM() + "\"> Allow JavaScript</h2>"
|
||||
+ "<p style=\"font-family:monospace;text-decoration:underline;word-wrap:break-word;\" class=\"_eaglercraftX_permission_target_url\"></p>"
|
||||
+ "<h4 style=\"line-height:1.4em;\">Strict CSP: " + strictCSPMarkup + " | "
|
||||
+ "Message API: " + messageAPIMarkup + "</h4>"
|
||||
+ "<p><input class=\"_eaglercraftX_remember_javascript\" type=\"checkbox\" checked> Remember my choice</p>"
|
||||
+ "<p><button style=\"font-size:1.5em;\" class=\"_eaglercraftX_allow_javascript\">Allow</button> "
|
||||
+ "<button style=\"font-size:1.5em;\" class=\"_eaglercraftX_block_javascript\">Block</button></p></div></div>");
|
||||
final int serial = webviewResetSerial;
|
||||
if(options.contentMode != EnumWebViewContentMode.BLOB_BASED) {
|
||||
String urlStr = options.url.toString();
|
||||
currentAllowJavaScript.querySelector("._eaglercraftX_permission_target_url").setInnerText(urlStr.length() > 255 ? (urlStr.substring(0, 253) + "...") : urlStr);
|
||||
}
|
||||
currentAllowJavaScript.querySelector("._eaglercraftX_allow_javascript").addEventListener("click", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if(webviewResetSerial == serial && currentAllowJavaScript != null) {
|
||||
HTMLInputElement chkbox = (HTMLInputElement)currentAllowJavaScript.querySelector("._eaglercraftX_remember_javascript");
|
||||
if(chkbox != null && chkbox.isChecked()) {
|
||||
PermissionsCache.setJavaScriptAllowed(options.permissionsOriginUUID, hashPermissionFlags(options), true);
|
||||
}
|
||||
currentAllowJavaScript.delete();
|
||||
currentAllowJavaScript = null;
|
||||
++webviewResetSerial;
|
||||
beginShowingDirect(options);
|
||||
}
|
||||
}
|
||||
});
|
||||
currentAllowJavaScript.querySelector("._eaglercraftX_block_javascript").addEventListener("click", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if(webviewResetSerial == serial && currentAllowJavaScript != null) {
|
||||
HTMLInputElement chkbox = (HTMLInputElement)currentAllowJavaScript.querySelector("._eaglercraftX_remember_javascript");
|
||||
if(chkbox != null && chkbox.isChecked()) {
|
||||
PermissionsCache.setJavaScriptAllowed(options.permissionsOriginUUID, hashPermissionFlags(options), false);
|
||||
}
|
||||
beginShowingContentBlocked(options);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void beginShowingContentBlocked(final WebViewOptions options) {
|
||||
beginShowingEnableJSSetup();
|
||||
currentAllowJavaScript.setInnerHTML(
|
||||
"<div style=\"padding-top:13vh;\"><h1 style=\"text-align:center;\">"
|
||||
+ "<img width=\"48\" height=\"48\" style=\"vertical-align:middle;\" src=\"" + PlatformApplication.faviconURLTeaVM() + "\"> Content Blocked</h1>"
|
||||
+ "<h4 style=\"text-align:center;\">You chose to block JavaScript execution for this embed</h4>"
|
||||
+ "<p style=\"text-align:center;\"><button style=\"font-size:1.0em;\" class=\"_eaglercraftX_re_evaluate_javascript\">Re-evaluate</button></p></div>");
|
||||
final int serial = webviewResetSerial;
|
||||
currentAllowJavaScript.querySelector("._eaglercraftX_re_evaluate_javascript").addEventListener("click", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if(webviewResetSerial == serial && currentAllowJavaScript != null) {
|
||||
PermissionsCache.clearJavaScriptAllowed(options.permissionsOriginUUID);
|
||||
beginShowingEnableJavaScript(options);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void beginShowingSafetyError() {
|
||||
beginShowingEnableJSSetup();
|
||||
currentAllowJavaScript.setInnerHTML(
|
||||
"<div style=\"padding-top:13vh;\"><h1 style=\"text-align:center;\">"
|
||||
+ "<img width=\"48\" height=\"48\" style=\"vertical-align:middle;\" src=\"" + PlatformApplication.faviconURLTeaVM() + "\"> IFrame Safety Error</h1>"
|
||||
+ "<h4 style=\"text-align:center;\">The content cannot be displayed safely!</h4>"
|
||||
+ "<h4 style=\"text-align:center;\">Check console for more details</h4></div>");
|
||||
}
|
||||
|
||||
private static String getURLOrigin(URI urlObject) {
|
||||
String str = " " + urlObject.getScheme() + "://" + urlObject.getRawAuthority();
|
||||
if(str.startsWith(" http:")) {
|
||||
str = str + " https" + str.substring(5);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static void resize(int x, int y, int w, int h) {
|
||||
if(currentIFrameContainer != null) {
|
||||
CSSStyleDeclaration decl = currentIFrameContainer.getStyle();
|
||||
float s = PlatformInput.getDPI();
|
||||
decl.setProperty("top", "" + (y / s) + "px");
|
||||
decl.setProperty("left", "" + (x / s) + "px");
|
||||
decl.setProperty("width", "" + ((w / s) - 10) + "px");
|
||||
decl.setProperty("height", "" + ((h / s) - 10) + "px");
|
||||
}
|
||||
}
|
||||
|
||||
public static void endShowing() {
|
||||
++webviewResetSerial;
|
||||
if(currentIFrame != null) {
|
||||
currentIFrame.delete();
|
||||
currentIFrame = null;
|
||||
}
|
||||
synchronized(messageQueue) {
|
||||
messageQueue.clear();
|
||||
}
|
||||
currentMessageHandler = null;
|
||||
if(currentAllowJavaScript != null) {
|
||||
currentAllowJavaScript.delete();
|
||||
currentAllowJavaScript = null;
|
||||
}
|
||||
currentIFrameContainer.delete();
|
||||
currentIFrameContainer = null;
|
||||
if(currentMessageChannelName != null) {
|
||||
sendMessageEnToServer(false, currentMessageChannelName);
|
||||
currentMessageChannelName = null;
|
||||
}
|
||||
win.focus();
|
||||
currentOptions = null;
|
||||
}
|
||||
|
||||
public static boolean fallbackSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void launchFallback(WebViewOptions options) {
|
||||
|
||||
}
|
||||
|
||||
public static boolean fallbackRunning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getFallbackURL() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void endFallbackServer() {
|
||||
|
||||
}
|
||||
|
||||
@JSBody(params = { "evt", "iframe" }, script = "return evt.source === iframe.contentWindow;")
|
||||
private static native boolean sourceEquals(MessageEvent evt, AdvancedHTMLIFrameElement iframe);
|
||||
|
||||
static void onWindowMessageRecieved(MessageEvent evt) {
|
||||
if(currentIFrame != null && currentMessageHandler != null && sourceEquals(evt, currentIFrame)) {
|
||||
currentMessageHandler.accept(evt);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setPacketSendCallback(IPacketSendCallback callback) {
|
||||
packetSendCallback = callback;
|
||||
}
|
||||
|
||||
public static void runTick() {
|
||||
if(currentIFrame == null) {
|
||||
return;
|
||||
}
|
||||
List<Runnable> lst = null;
|
||||
synchronized(messageQueue) {
|
||||
if(messageQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
lst = new ArrayList<>(messageQueue);
|
||||
messageQueue.clear();
|
||||
}
|
||||
for(int i = 0, l = lst.size(); i < l; ++i) {
|
||||
try {
|
||||
lst.get(i).run();
|
||||
}catch(Throwable t) {
|
||||
logger.error("Caught exception processing webview message!");
|
||||
logger.error(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { "channel", "contents" }, script = "return {ver:1,channel:channel,type:\"string\",data:contents};")
|
||||
private static native JSObject createStringMessage(String channel, String contents);
|
||||
|
||||
@JSBody(params = { "channel", "contents" }, script = "return {ver:1,channel:channel,type:\"binary\",data:contents};")
|
||||
private static native JSObject createBinaryMessage(String channel, ArrayBuffer contents);
|
||||
|
||||
public static void handleMessageFromServer(SPacketWebViewMessageV4EAG packet) {
|
||||
Window w;
|
||||
if(currentMessageChannelName != null && currentIFrame != null && (w = currentIFrame.getContentWindow()) != null) {
|
||||
JSObject obj = null;
|
||||
if(packet.type == SPacketWebViewMessageV4EAG.TYPE_STRING) {
|
||||
obj = createStringMessage(currentMessageChannelName, new String(packet.data, StandardCharsets.UTF_8));
|
||||
}else if(packet.type == SPacketWebViewMessageV4EAG.TYPE_BINARY) {
|
||||
obj = createBinaryMessage(currentMessageChannelName, TeaVMUtils.unwrapArrayBuffer(packet.data));
|
||||
}
|
||||
if(obj != null) {
|
||||
w.postMessage(obj, "*");
|
||||
}
|
||||
}else {
|
||||
logger.error("Server tried to send the WebView a message, but the message channel is not open!");
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return (typeof obj === \"object\") && (obj.ver === 1) && ((typeof obj.channel === \"string\") && obj.channel.length > 0);")
|
||||
private static native boolean checkRawMessageValid(JSObject obj);
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return (typeof obj.open === \"boolean\");")
|
||||
private static native boolean checkRawMessageValidEn(JSObject obj);
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return (typeof obj.data === \"string\");")
|
||||
private static native boolean checkRawMessageValidDataStr(JSObject obj);
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return (obj.data instanceof ArrayBuffer);")
|
||||
private static native boolean checkRawMessageValidDataBin(JSObject obj);
|
||||
|
||||
private static interface WebViewMessage extends JSObject {
|
||||
|
||||
@JSProperty
|
||||
String getType();
|
||||
|
||||
@JSProperty
|
||||
String getChannel();
|
||||
|
||||
@JSProperty("data")
|
||||
String getDataAsString();
|
||||
|
||||
@JSProperty("data")
|
||||
ArrayBuffer getDataAsArrayBuffer();
|
||||
|
||||
@JSProperty
|
||||
boolean getOpen();
|
||||
|
||||
}
|
||||
|
||||
private static void handleMessageRawFromFrame(JSObject obj) {
|
||||
if(checkRawMessageValid(obj)) {
|
||||
if(checkRawMessageValidEn(obj)) {
|
||||
WebViewMessage msg = (WebViewMessage)obj;
|
||||
sendMessageEnToServer(msg.getOpen(), msg.getChannel());
|
||||
return;
|
||||
}else if(checkRawMessageValidDataStr(obj)) {
|
||||
WebViewMessage msg = (WebViewMessage)obj;
|
||||
sendMessageToServer(msg.getChannel(), CPacketWebViewMessageV4EAG.TYPE_STRING, msg.getDataAsString().getBytes(StandardCharsets.UTF_8));
|
||||
return;
|
||||
}else if(checkRawMessageValidDataBin(obj)) {
|
||||
WebViewMessage msg = (WebViewMessage)obj;
|
||||
sendMessageToServer(msg.getChannel(), CPacketWebViewMessageV4EAG.TYPE_BINARY, TeaVMUtils.wrapByteArrayBuffer(msg.getDataAsArrayBuffer()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
logger.warn("WebView sent an invalid message!");
|
||||
}
|
||||
|
||||
private static void sendMessageToServer(String channelName, int type, byte[] data) {
|
||||
if(channelName.length() > 255) {
|
||||
logger.error("WebView tried to send a message packet, but channel name is too long, max is 255 characters!");
|
||||
return;
|
||||
}
|
||||
if(!channelName.equals(currentMessageChannelName)) {
|
||||
logger.error("WebView tried to send a message packet, but the channel is not open!");
|
||||
return;
|
||||
}
|
||||
if(packetSendCallback != null) {
|
||||
if(!packetSendCallback.sendPacket(new CPacketWebViewMessageV4EAG(type, data))) {
|
||||
logger.error("WebView tried to send a packet to the server, but the server does not support this protocol!");
|
||||
}
|
||||
}else {
|
||||
logger.error("WebView tried to send a message, but no callback for sending packets is set!");
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendMessageEnToServer(boolean messageChannelOpen, String channelName) {
|
||||
if(channelName.length() > 255) {
|
||||
logger.error("WebView tried to {} a channel, but channel name is too long, max is 255 characters!", messageChannelOpen ? "open" : "close");
|
||||
return;
|
||||
}
|
||||
if(messageChannelOpen && currentMessageChannelName != null) {
|
||||
logger.error("WebView tried to open channel, but a channel is already open!");
|
||||
sendMessageEnToServer(false, currentMessageChannelName);
|
||||
}
|
||||
if(!messageChannelOpen && currentMessageChannelName != null && !currentMessageChannelName.equals(channelName)) {
|
||||
logger.error("WebView tried to close the wrong channel!");
|
||||
}
|
||||
if(!messageChannelOpen && currentMessageChannelName == null) {
|
||||
logger.error("WebView tried to close channel, but the channel is not open!");
|
||||
return;
|
||||
}
|
||||
if(packetSendCallback != null) {
|
||||
if(!packetSendCallback.sendPacket(new CPacketWebViewMessageEnV4EAG(messageChannelOpen, messageChannelOpen ? channelName : null))) {
|
||||
logger.error("WebView tried to send a packet to the server, but the server does not support this protocol!");
|
||||
return;
|
||||
}
|
||||
if(messageChannelOpen) {
|
||||
logger.info("WebView opened message channel to server: \"{}\"", channelName);
|
||||
currentMessageChannelName = channelName;
|
||||
}else {
|
||||
logger.info("WebView closed message channel to server: \"{}\"", currentMessageChannelName);
|
||||
currentMessageChannelName = null;
|
||||
}
|
||||
}else {
|
||||
logger.error("WebView tried to send a message, but no callback for sending packets is set!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -32,9 +32,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
int position;
|
||||
int limit;
|
||||
int mark;
|
||||
|
||||
static final Int8Array ZERO_LENGTH_BUFFER = Int8Array.create(0);
|
||||
|
||||
|
||||
EaglerArrayByteBuffer(DataView dataView) {
|
||||
this.dataView = dataView;
|
||||
this.typedArray = Int8Array.create(dataView.getBuffer(), dataView.getByteOffset(), dataView.getByteLength());
|
||||
@ -43,7 +41,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
this.limit = this.capacity;
|
||||
this.mark = -1;
|
||||
}
|
||||
|
||||
|
||||
EaglerArrayByteBuffer(DataView dataView, int position, int limit, int mark) {
|
||||
this.dataView = dataView;
|
||||
this.typedArray = Int8Array.create(dataView.getBuffer(), dataView.getByteOffset(), dataView.getByteLength());
|
||||
@ -52,7 +50,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
this.limit = limit;
|
||||
this.mark = mark;
|
||||
}
|
||||
|
||||
|
||||
EaglerArrayByteBuffer(Int8Array typedArray) {
|
||||
this.typedArray = typedArray;
|
||||
this.dataView = DataView.create(typedArray.getBuffer(), typedArray.getByteOffset(), typedArray.getByteLength());
|
||||
@ -61,7 +59,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
this.limit = this.capacity;
|
||||
this.mark = -1;
|
||||
}
|
||||
|
||||
|
||||
EaglerArrayByteBuffer(Int8Array typedArray, int position, int limit, int mark) {
|
||||
this.typedArray = typedArray;
|
||||
this.dataView = DataView.create(typedArray.getBuffer(), typedArray.getByteOffset(), typedArray.getByteLength());
|
||||
@ -96,18 +94,13 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
return limit > position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object array() {
|
||||
public byte[] array() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@ -116,55 +109,40 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer slice() {
|
||||
if(position == limit) {
|
||||
return new EaglerArrayByteBuffer(ZERO_LENGTH_BUFFER);
|
||||
}else {
|
||||
if(position > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
return new EaglerArrayByteBuffer(Int8Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + position, limit - position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer duplicate() {
|
||||
return new EaglerArrayByteBuffer(dataView, position, limit, mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer asReadOnlyBuffer() {
|
||||
return new EaglerArrayByteBuffer(dataView, position, limit, mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte get() {
|
||||
if(position >= limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position >= limit) throw Buffer.makeIOOBE(position);
|
||||
return typedArray.get(position++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer put(byte b) {
|
||||
if(position >= limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position >= limit) throw Buffer.makeIOOBE(position);
|
||||
typedArray.set(position++, b);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte get(int index) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
return typedArray.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer put(int index, byte b) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
typedArray.set(index, b);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer get(byte[] dst, int offset, int length) {
|
||||
if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1);
|
||||
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
|
||||
TeaVMUtils.unwrapArrayBufferView(dst).set(Int8Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + position, length), offset);
|
||||
position += length;
|
||||
return this;
|
||||
@ -172,7 +150,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public ByteBuffer get(byte[] dst) {
|
||||
if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1);
|
||||
if(position + dst.length > limit) throw Buffer.makeIOOBE(position + dst.length - 1);
|
||||
TeaVMUtils.unwrapArrayBufferView(dst).set(Int8Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + position, dst.length));
|
||||
position += dst.length;
|
||||
return this;
|
||||
@ -183,13 +161,13 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
if(src instanceof EaglerArrayByteBuffer) {
|
||||
EaglerArrayByteBuffer c = (EaglerArrayByteBuffer)src;
|
||||
int l = c.limit - c.position;
|
||||
if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1);
|
||||
if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1);
|
||||
typedArray.set(Int8Array.create(c.typedArray.getBuffer(), c.typedArray.getByteOffset() + c.position, l), position);
|
||||
position += l;
|
||||
c.position += l;
|
||||
}else {
|
||||
int l = src.remaining();
|
||||
if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1);
|
||||
if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1);
|
||||
for(int i = 0; i < l; ++i) {
|
||||
dataView.setInt8(position + l, src.get());
|
||||
}
|
||||
@ -200,7 +178,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public ByteBuffer put(byte[] src, int offset, int length) {
|
||||
if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1);
|
||||
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
|
||||
if(offset == 0 && length == src.length) {
|
||||
typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position);
|
||||
}else {
|
||||
@ -212,35 +190,15 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public ByteBuffer put(byte[] src) {
|
||||
if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1);
|
||||
if(position + src.length > limit) throw Buffer.makeIOOBE(position + src.length - 1);
|
||||
typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position);
|
||||
position += src.length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer compact() {
|
||||
if(limit > capacity) throw new ArrayIndexOutOfBoundsException(limit);
|
||||
if(position > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
|
||||
if(position == limit) {
|
||||
return new EaglerArrayByteBuffer(ZERO_LENGTH_BUFFER);
|
||||
}
|
||||
|
||||
Int8Array dst = Int8Array.create(limit - position);
|
||||
dst.set(Int8Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + position, limit - position));
|
||||
|
||||
return new EaglerArrayByteBuffer(dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getChar() {
|
||||
if(position + 2 > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position + 2 > limit) throw Buffer.makeIOOBE(position);
|
||||
char c = (char)dataView.getUint16(position, true);
|
||||
position += 2;
|
||||
return c;
|
||||
@ -248,7 +206,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public ByteBuffer putChar(char value) {
|
||||
if(position + 2 > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position + 2 > limit) throw Buffer.makeIOOBE(position);
|
||||
dataView.setUint16(position, (short)value, true);
|
||||
position += 2;
|
||||
return this;
|
||||
@ -256,20 +214,20 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public char getChar(int index) {
|
||||
if(index + 2 > limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index + 2 > limit) throw Buffer.makeIOOBE(index);
|
||||
return (char)dataView.getUint16(index, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer putChar(int index, char value) {
|
||||
if(index + 2 > limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index + 2 > limit) throw Buffer.makeIOOBE(index);
|
||||
dataView.setUint16(index, value, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort() {
|
||||
if(position + 2 > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position + 2 > limit) throw Buffer.makeIOOBE(position);
|
||||
short s = dataView.getInt16(position, true);
|
||||
position += 2;
|
||||
return s;
|
||||
@ -277,7 +235,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public ByteBuffer putShort(short value) {
|
||||
if(position + 2 > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position + 2 > limit) throw Buffer.makeIOOBE(position);
|
||||
dataView.setInt16(position, value, true);
|
||||
position += 2;
|
||||
return this;
|
||||
@ -285,13 +243,13 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
if(index + 2 > limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index + 2 > limit) throw Buffer.makeIOOBE(index);
|
||||
return dataView.getInt16(index, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer putShort(int index, short value) {
|
||||
if(index + 2 > limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index + 2 > limit) throw Buffer.makeIOOBE(index);
|
||||
dataView.setInt16(index, value, true);
|
||||
return this;
|
||||
}
|
||||
@ -303,7 +261,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public int getInt() {
|
||||
if(position + 4 > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position + 4 > limit) throw Buffer.makeIOOBE(position);
|
||||
int i = dataView.getInt32(position, true);
|
||||
position += 4;
|
||||
return i;
|
||||
@ -311,7 +269,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public ByteBuffer putInt(int value) {
|
||||
if(position + 4 > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position + 4 > limit) throw Buffer.makeIOOBE(position);
|
||||
dataView.setInt32(position, value, true);
|
||||
position += 4;
|
||||
return this;
|
||||
@ -319,13 +277,13 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
if(index + 4 > limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index + 4 > limit) throw Buffer.makeIOOBE(index);
|
||||
return dataView.getInt32(index, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer putInt(int index, int value) {
|
||||
if(index + 4 > limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index + 4 > limit) throw Buffer.makeIOOBE(index);
|
||||
dataView.setInt32(index, value, true);
|
||||
return this;
|
||||
}
|
||||
@ -337,7 +295,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public long getLong() {
|
||||
if(position + 8 > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position + 8 > limit) throw Buffer.makeIOOBE(position);
|
||||
long l = dataView.getUint32(position) | ((long) dataView.getUint8(position + 4) << 32)
|
||||
| ((long) dataView.getUint8(position + 5) << 40) | ((long) dataView.getUint8(position + 6) << 48)
|
||||
| ((long) dataView.getUint8(position + 7) << 56);
|
||||
@ -347,7 +305,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public ByteBuffer putLong(long value) {
|
||||
if(position + 8 > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position + 8 > limit) throw Buffer.makeIOOBE(position);
|
||||
dataView.setUint32(position, (int) (value & 0xFFFFFFFFl), true);
|
||||
dataView.setUint8(position + 4, (short) ((value >>> 32l) & 0xFFl));
|
||||
dataView.setUint8(position + 5, (short) ((value >>> 40l) & 0xFFl));
|
||||
@ -359,7 +317,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
if(index + 8 > limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index + 8 > limit) throw Buffer.makeIOOBE(index);
|
||||
return dataView.getUint32(index, true) | ((long) dataView.getUint8(index + 4) << 32)
|
||||
| ((long) dataView.getUint8(index + 5) << 40) | ((long) dataView.getUint8(index + 6) << 48)
|
||||
| ((long) dataView.getUint8(index + 7) << 56);
|
||||
@ -367,7 +325,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public ByteBuffer putLong(int index, long value) {
|
||||
if(index + 8 > limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index + 8 > limit) throw Buffer.makeIOOBE(index);
|
||||
dataView.setUint32(index, (int) (value & 0xFFFFFFFFl), true);
|
||||
dataView.setUint8(index + 4, (short) ((value >>> 32l) & 0xFFl));
|
||||
dataView.setUint8(index + 5, (short) ((value >>> 40l) & 0xFFl));
|
||||
@ -378,7 +336,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public float getFloat() {
|
||||
if(position + 4 > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position + 4 > limit) throw Buffer.makeIOOBE(position);
|
||||
float f = dataView.getFloat32(position, true);
|
||||
position += 4;
|
||||
return f;
|
||||
@ -386,7 +344,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public ByteBuffer putFloat(float value) {
|
||||
if(position + 4 > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position + 4 > limit) throw Buffer.makeIOOBE(position);
|
||||
dataView.setFloat32(position, value, true);
|
||||
position += 4;
|
||||
return this;
|
||||
@ -394,13 +352,13 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public float getFloat(int index) {
|
||||
if(index + 4 > limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index + 4 > limit) throw Buffer.makeIOOBE(index);
|
||||
return dataView.getFloat32(index, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer putFloat(int index, float value) {
|
||||
if(index + 4 > limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index + 4 > limit) throw Buffer.makeIOOBE(index);
|
||||
dataView.setFloat32(index, value, true);
|
||||
return this;
|
||||
}
|
||||
@ -419,7 +377,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
@Override
|
||||
public ByteBuffer reset() {
|
||||
int m = mark;
|
||||
if(m < 0) throw new ArrayIndexOutOfBoundsException("Invalid mark: " + m);
|
||||
if(m < 0) throw new IndexOutOfBoundsException("Invalid mark: " + m);
|
||||
position = m;
|
||||
return this;
|
||||
}
|
||||
@ -449,14 +407,14 @@ public class EaglerArrayByteBuffer implements ByteBuffer {
|
||||
|
||||
@Override
|
||||
public ByteBuffer limit(int newLimit) {
|
||||
if(newLimit < 0 || newLimit > capacity) throw new ArrayIndexOutOfBoundsException(newLimit);
|
||||
if(newLimit < 0 || newLimit > capacity) throw Buffer.makeIOOBE(newLimit);
|
||||
limit = newLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer position(int newPosition) {
|
||||
if(newPosition < 0 || newPosition > limit) throw new ArrayIndexOutOfBoundsException(newPosition);
|
||||
if(newPosition < 0 || newPosition > limit) throw Buffer.makeIOOBE(newPosition);
|
||||
position = newPosition;
|
||||
return this;
|
||||
}
|
||||
|
@ -27,11 +27,9 @@ public class EaglerArrayFloatBuffer implements FloatBuffer {
|
||||
int position;
|
||||
int limit;
|
||||
int mark;
|
||||
|
||||
|
||||
private static final int SHIFT = 2;
|
||||
|
||||
static final Float32Array ZERO_LENGTH_BUFFER = Float32Array.create(0);
|
||||
|
||||
|
||||
EaglerArrayFloatBuffer(Float32Array typedArray) {
|
||||
this.typedArray = typedArray;
|
||||
this.capacity = typedArray.getLength();
|
||||
@ -39,7 +37,7 @@ public class EaglerArrayFloatBuffer implements FloatBuffer {
|
||||
this.limit = this.capacity;
|
||||
this.mark = -1;
|
||||
}
|
||||
|
||||
|
||||
EaglerArrayFloatBuffer(Float32Array typedArray, int position, int limit, int mark) {
|
||||
this.typedArray = typedArray;
|
||||
this.capacity = typedArray.getLength();
|
||||
@ -47,7 +45,7 @@ public class EaglerArrayFloatBuffer implements FloatBuffer {
|
||||
this.limit = limit;
|
||||
this.mark = mark;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
@ -73,87 +71,62 @@ public class EaglerArrayFloatBuffer implements FloatBuffer {
|
||||
return position < limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object array() {
|
||||
public float[] array() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatBuffer slice() {
|
||||
if(position == limit) {
|
||||
return new EaglerArrayFloatBuffer(ZERO_LENGTH_BUFFER);
|
||||
}else {
|
||||
if(position > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
return new EaglerArrayFloatBuffer(Float32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatBuffer duplicate() {
|
||||
return new EaglerArrayFloatBuffer(typedArray, position, limit, mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatBuffer asReadOnlyBuffer() {
|
||||
return new EaglerArrayFloatBuffer(typedArray, position, limit, mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float get() {
|
||||
if(position >= limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position >= limit) throw Buffer.makeIOOBE(position);
|
||||
return typedArray.get(position++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatBuffer put(float b) {
|
||||
if(position >= limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position >= limit) throw Buffer.makeIOOBE(position);
|
||||
typedArray.set(position++, b);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float get(int index) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
return typedArray.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatBuffer put(int index, float b) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
typedArray.set(index, b);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getElement(int index) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
return typedArray.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putElement(int index, float value) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
typedArray.set(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatBuffer get(float[] dst, int offset, int length) {
|
||||
if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1);
|
||||
if(position + length > limit) Buffer.makeIOOBE(position + length - 1);
|
||||
TeaVMUtils.unwrapArrayBufferView(dst).set(Float32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), length), offset);
|
||||
position += length;
|
||||
return this;
|
||||
@ -161,7 +134,7 @@ public class EaglerArrayFloatBuffer implements FloatBuffer {
|
||||
|
||||
@Override
|
||||
public FloatBuffer get(float[] dst) {
|
||||
if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1);
|
||||
if(position + dst.length > limit) Buffer.makeIOOBE(position + dst.length - 1);
|
||||
TeaVMUtils.unwrapArrayBufferView(dst).set(Float32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), dst.length));
|
||||
position += dst.length;
|
||||
return this;
|
||||
@ -172,13 +145,13 @@ public class EaglerArrayFloatBuffer implements FloatBuffer {
|
||||
if(src instanceof EaglerArrayFloatBuffer) {
|
||||
EaglerArrayFloatBuffer c = (EaglerArrayFloatBuffer)src;
|
||||
int l = c.limit - c.position;
|
||||
if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1);
|
||||
if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1);
|
||||
typedArray.set(Float32Array.create(c.typedArray.getBuffer(), c.typedArray.getByteOffset() + (c.position << SHIFT), l), position);
|
||||
position += l;
|
||||
c.position += l;
|
||||
}else {
|
||||
int l = src.remaining();
|
||||
if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1);
|
||||
if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1);
|
||||
for(int i = 0; i < l; ++i) {
|
||||
typedArray.set(position + l, src.get());
|
||||
}
|
||||
@ -189,7 +162,7 @@ public class EaglerArrayFloatBuffer implements FloatBuffer {
|
||||
|
||||
@Override
|
||||
public FloatBuffer put(float[] src, int offset, int length) {
|
||||
if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1);
|
||||
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
|
||||
if(offset == 0 && length == src.length) {
|
||||
typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position);
|
||||
}else {
|
||||
@ -201,32 +174,12 @@ public class EaglerArrayFloatBuffer implements FloatBuffer {
|
||||
|
||||
@Override
|
||||
public FloatBuffer put(float[] src) {
|
||||
if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1);
|
||||
if(position + src.length > limit) throw Buffer.makeIOOBE(position + src.length - 1);
|
||||
typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position);
|
||||
position += src.length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getArrayOffset() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatBuffer compact() {
|
||||
if(limit > capacity) throw new ArrayIndexOutOfBoundsException(limit);
|
||||
if(position > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
|
||||
if(position == limit) {
|
||||
return new EaglerArrayFloatBuffer(ZERO_LENGTH_BUFFER);
|
||||
}
|
||||
|
||||
Float32Array dst = Float32Array.create(limit - position);
|
||||
dst.set(Float32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position));
|
||||
|
||||
return new EaglerArrayFloatBuffer(dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return true;
|
||||
@ -241,7 +194,7 @@ public class EaglerArrayFloatBuffer implements FloatBuffer {
|
||||
@Override
|
||||
public FloatBuffer reset() {
|
||||
int m = mark;
|
||||
if(m < 0) throw new ArrayIndexOutOfBoundsException("Invalid mark: " + m);
|
||||
if(m < 0) throw new IndexOutOfBoundsException("Invalid mark: " + m);
|
||||
position = m;
|
||||
return this;
|
||||
}
|
||||
@ -271,14 +224,14 @@ public class EaglerArrayFloatBuffer implements FloatBuffer {
|
||||
|
||||
@Override
|
||||
public FloatBuffer limit(int newLimit) {
|
||||
if(newLimit < 0 || newLimit > capacity) throw new ArrayIndexOutOfBoundsException(newLimit);
|
||||
if(newLimit < 0 || newLimit > capacity) throw Buffer.makeIOOBE(newLimit);
|
||||
limit = newLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatBuffer position(int newPosition) {
|
||||
if(newPosition < 0 || newPosition > limit) throw new ArrayIndexOutOfBoundsException(newPosition);
|
||||
if(newPosition < 0 || newPosition > limit) throw Buffer.makeIOOBE(newPosition);
|
||||
position = newPosition;
|
||||
return this;
|
||||
}
|
||||
|
@ -27,11 +27,9 @@ public class EaglerArrayIntBuffer implements IntBuffer {
|
||||
int position;
|
||||
int limit;
|
||||
int mark;
|
||||
|
||||
|
||||
private static final int SHIFT = 2;
|
||||
|
||||
static final Int32Array ZERO_LENGTH_BUFFER = Int32Array.create(0);
|
||||
|
||||
|
||||
EaglerArrayIntBuffer(Int32Array typedArray) {
|
||||
this.typedArray = typedArray;
|
||||
this.capacity = typedArray.getLength();
|
||||
@ -39,7 +37,7 @@ public class EaglerArrayIntBuffer implements IntBuffer {
|
||||
this.limit = this.capacity;
|
||||
this.mark = -1;
|
||||
}
|
||||
|
||||
|
||||
EaglerArrayIntBuffer(Int32Array typedArray, int position, int limit, int mark) {
|
||||
this.typedArray = typedArray;
|
||||
this.capacity = typedArray.getLength();
|
||||
@ -47,7 +45,7 @@ public class EaglerArrayIntBuffer implements IntBuffer {
|
||||
this.limit = limit;
|
||||
this.mark = mark;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
@ -73,87 +71,62 @@ public class EaglerArrayIntBuffer implements IntBuffer {
|
||||
return position < limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object array() {
|
||||
public int[] array() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntBuffer slice() {
|
||||
if(position == limit) {
|
||||
return new EaglerArrayIntBuffer(ZERO_LENGTH_BUFFER);
|
||||
}else {
|
||||
if(position > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
return new EaglerArrayIntBuffer(Int32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntBuffer duplicate() {
|
||||
return new EaglerArrayIntBuffer(typedArray, position, limit, mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntBuffer asReadOnlyBuffer() {
|
||||
return new EaglerArrayIntBuffer(typedArray, position, limit, mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get() {
|
||||
if(position >= limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position >= limit) throw Buffer.makeIOOBE(position);
|
||||
return typedArray.get(position++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntBuffer put(int b) {
|
||||
if(position >= limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position >= limit) throw Buffer.makeIOOBE(position);
|
||||
typedArray.set(position++, b);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(int index) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
return typedArray.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntBuffer put(int index, int b) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
typedArray.set(index, b);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getElement(int index) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
return typedArray.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putElement(int index, int value) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
typedArray.set(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntBuffer get(int[] dst, int offset, int length) {
|
||||
if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1);
|
||||
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
|
||||
TeaVMUtils.unwrapArrayBufferView(dst).set(Int32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), length), offset);
|
||||
position += length;
|
||||
return this;
|
||||
@ -161,7 +134,7 @@ public class EaglerArrayIntBuffer implements IntBuffer {
|
||||
|
||||
@Override
|
||||
public IntBuffer get(int[] dst) {
|
||||
if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1);
|
||||
if(position + dst.length > limit) throw Buffer.makeIOOBE(position + dst.length - 1);
|
||||
TeaVMUtils.unwrapArrayBufferView(dst).set(Int32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), dst.length));
|
||||
position += dst.length;
|
||||
return this;
|
||||
@ -172,13 +145,13 @@ public class EaglerArrayIntBuffer implements IntBuffer {
|
||||
if(src instanceof EaglerArrayIntBuffer) {
|
||||
EaglerArrayIntBuffer c = (EaglerArrayIntBuffer)src;
|
||||
int l = c.limit - c.position;
|
||||
if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1);
|
||||
if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1);
|
||||
typedArray.set(Int32Array.create(c.typedArray.getBuffer(), c.typedArray.getByteOffset() + (c.position << SHIFT), l), position);
|
||||
position += l;
|
||||
c.position += l;
|
||||
}else {
|
||||
int l = src.remaining();
|
||||
if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1);
|
||||
if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1);
|
||||
for(int i = 0; i < l; ++i) {
|
||||
typedArray.set(position + l, src.get());
|
||||
}
|
||||
@ -189,7 +162,7 @@ public class EaglerArrayIntBuffer implements IntBuffer {
|
||||
|
||||
@Override
|
||||
public IntBuffer put(int[] src, int offset, int length) {
|
||||
if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1);
|
||||
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
|
||||
if(offset == 0 && length == src.length) {
|
||||
typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position);
|
||||
}else {
|
||||
@ -201,32 +174,12 @@ public class EaglerArrayIntBuffer implements IntBuffer {
|
||||
|
||||
@Override
|
||||
public IntBuffer put(int[] src) {
|
||||
if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1);
|
||||
if(position + src.length > limit) throw Buffer.makeIOOBE(position + src.length - 1);
|
||||
typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position);
|
||||
position += src.length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getArrayOffset() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntBuffer compact() {
|
||||
if(limit > capacity) throw new ArrayIndexOutOfBoundsException(limit);
|
||||
if(position > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
|
||||
if(position == limit) {
|
||||
return new EaglerArrayIntBuffer(ZERO_LENGTH_BUFFER);
|
||||
}
|
||||
|
||||
Int32Array dst = Int32Array.create(limit - position);
|
||||
dst.set(Int32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position));
|
||||
|
||||
return new EaglerArrayIntBuffer(dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return true;
|
||||
@ -241,7 +194,7 @@ public class EaglerArrayIntBuffer implements IntBuffer {
|
||||
@Override
|
||||
public IntBuffer reset() {
|
||||
int m = mark;
|
||||
if(m < 0) throw new ArrayIndexOutOfBoundsException("Invalid mark: " + m);
|
||||
if(m < 0) throw new IndexOutOfBoundsException("Invalid mark: " + m);
|
||||
position = m;
|
||||
return this;
|
||||
}
|
||||
@ -271,14 +224,14 @@ public class EaglerArrayIntBuffer implements IntBuffer {
|
||||
|
||||
@Override
|
||||
public IntBuffer limit(int newLimit) {
|
||||
if(newLimit < 0 || newLimit > capacity) throw new ArrayIndexOutOfBoundsException(newLimit);
|
||||
if(newLimit < 0 || newLimit > capacity) throw Buffer.makeIOOBE(newLimit);
|
||||
limit = newLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntBuffer position(int newPosition) {
|
||||
if(newPosition < 0 || newPosition > limit) throw new ArrayIndexOutOfBoundsException(newPosition);
|
||||
if(newPosition < 0 || newPosition > limit) throw Buffer.makeIOOBE(newPosition);
|
||||
position = newPosition;
|
||||
return this;
|
||||
}
|
||||
|
@ -27,11 +27,9 @@ public class EaglerArrayShortBuffer implements ShortBuffer {
|
||||
int position;
|
||||
int limit;
|
||||
int mark;
|
||||
|
||||
|
||||
private static final int SHIFT = 1;
|
||||
|
||||
static final Int16Array ZERO_LENGTH_BUFFER = Int16Array.create(0);
|
||||
|
||||
|
||||
EaglerArrayShortBuffer(Int16Array typedArray) {
|
||||
this.typedArray = typedArray;
|
||||
this.capacity = typedArray.getLength();
|
||||
@ -39,7 +37,7 @@ public class EaglerArrayShortBuffer implements ShortBuffer {
|
||||
this.limit = this.capacity;
|
||||
this.mark = -1;
|
||||
}
|
||||
|
||||
|
||||
EaglerArrayShortBuffer(Int16Array typedArray, int position, int limit, int mark) {
|
||||
this.typedArray = typedArray;
|
||||
this.capacity = typedArray.getLength();
|
||||
@ -47,7 +45,7 @@ public class EaglerArrayShortBuffer implements ShortBuffer {
|
||||
this.limit = limit;
|
||||
this.mark = mark;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
@ -73,87 +71,62 @@ public class EaglerArrayShortBuffer implements ShortBuffer {
|
||||
return position < limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object array() {
|
||||
public short[] array() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortBuffer slice() {
|
||||
if(position == limit) {
|
||||
return new EaglerArrayShortBuffer(ZERO_LENGTH_BUFFER);
|
||||
}else {
|
||||
if(position > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
return new EaglerArrayShortBuffer(Int16Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortBuffer duplicate() {
|
||||
return new EaglerArrayShortBuffer(typedArray, position, limit, mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortBuffer asReadOnlyBuffer() {
|
||||
return new EaglerArrayShortBuffer(typedArray, position, limit, mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short get() {
|
||||
if(position >= limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position >= limit) throw Buffer.makeIOOBE(position);
|
||||
return typedArray.get(position++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortBuffer put(short b) {
|
||||
if(position >= limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
if(position >= limit) throw Buffer.makeIOOBE(position);
|
||||
typedArray.set(position++, b);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short get(int index) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
return typedArray.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortBuffer put(int index, short b) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
typedArray.set(index, b);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getElement(int index) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
return typedArray.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putElement(int index, short value) {
|
||||
if(index >= limit) throw new ArrayIndexOutOfBoundsException(index);
|
||||
if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index);
|
||||
typedArray.set(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortBuffer get(short[] dst, int offset, int length) {
|
||||
if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1);
|
||||
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
|
||||
TeaVMUtils.unwrapArrayBufferView(dst).set(Int16Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), length), offset);
|
||||
position += length;
|
||||
return this;
|
||||
@ -161,7 +134,7 @@ public class EaglerArrayShortBuffer implements ShortBuffer {
|
||||
|
||||
@Override
|
||||
public ShortBuffer get(short[] dst) {
|
||||
if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1);
|
||||
if(position + dst.length > limit) throw Buffer.makeIOOBE(position + dst.length - 1);
|
||||
TeaVMUtils.unwrapArrayBufferView(dst).set(Int16Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), dst.length));
|
||||
position += dst.length;
|
||||
return this;
|
||||
@ -172,13 +145,13 @@ public class EaglerArrayShortBuffer implements ShortBuffer {
|
||||
if(src instanceof EaglerArrayShortBuffer) {
|
||||
EaglerArrayShortBuffer c = (EaglerArrayShortBuffer)src;
|
||||
int l = c.limit - c.position;
|
||||
if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1);
|
||||
if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1);
|
||||
typedArray.set(Int16Array.create(c.typedArray.getBuffer(), c.typedArray.getByteOffset() + (c.position << SHIFT), l), position);
|
||||
position += l;
|
||||
c.position += l;
|
||||
}else {
|
||||
int l = src.remaining();
|
||||
if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1);
|
||||
if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1);
|
||||
for(int i = 0; i < l; ++i) {
|
||||
typedArray.set(position + l, src.get());
|
||||
}
|
||||
@ -189,7 +162,7 @@ public class EaglerArrayShortBuffer implements ShortBuffer {
|
||||
|
||||
@Override
|
||||
public ShortBuffer put(short[] src, int offset, int length) {
|
||||
if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1);
|
||||
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
|
||||
if(offset == 0 && length == src.length) {
|
||||
typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position);
|
||||
}else {
|
||||
@ -201,32 +174,12 @@ public class EaglerArrayShortBuffer implements ShortBuffer {
|
||||
|
||||
@Override
|
||||
public ShortBuffer put(short[] src) {
|
||||
if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1);
|
||||
if(position + src.length > limit) throw Buffer.makeIOOBE(position + src.length - 1);
|
||||
typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position);
|
||||
position += src.length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getArrayOffset() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortBuffer compact() {
|
||||
if(limit > capacity) throw new ArrayIndexOutOfBoundsException(limit);
|
||||
if(position > limit) throw new ArrayIndexOutOfBoundsException(position);
|
||||
|
||||
if(position == limit) {
|
||||
return new EaglerArrayShortBuffer(ZERO_LENGTH_BUFFER);
|
||||
}
|
||||
|
||||
Int16Array dst = Int16Array.create(limit - position);
|
||||
dst.set(Int16Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position));
|
||||
|
||||
return new EaglerArrayShortBuffer(dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return true;
|
||||
@ -241,7 +194,7 @@ public class EaglerArrayShortBuffer implements ShortBuffer {
|
||||
@Override
|
||||
public ShortBuffer reset() {
|
||||
int m = mark;
|
||||
if(m < 0) throw new ArrayIndexOutOfBoundsException("Invalid mark: " + m);
|
||||
if(m < 0) throw new IndexOutOfBoundsException("Invalid mark: " + m);
|
||||
position = m;
|
||||
return this;
|
||||
}
|
||||
@ -271,14 +224,14 @@ public class EaglerArrayShortBuffer implements ShortBuffer {
|
||||
|
||||
@Override
|
||||
public ShortBuffer limit(int newLimit) {
|
||||
if(newLimit < 0 || newLimit > capacity) throw new ArrayIndexOutOfBoundsException(newLimit);
|
||||
if(newLimit < 0 || newLimit > capacity) throw Buffer.makeIOOBE(newLimit);
|
||||
limit = newLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortBuffer position(int newPosition) {
|
||||
if(newPosition < 0 || newPosition > limit) throw new ArrayIndexOutOfBoundsException(newPosition);
|
||||
if(newPosition < 0 || newPosition > limit) throw Buffer.makeIOOBE(newPosition);
|
||||
position = newPosition;
|
||||
return this;
|
||||
}
|
||||
|
@ -0,0 +1,120 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.teavm.jso.dom.html.HTMLIFrameElement;
|
||||
import org.teavm.jso.dom.types.DOMTokenList;
|
||||
|
||||
import com.google.common.collect.Iterators;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public abstract class AdvancedHTMLIFrameElement implements HTMLIFrameElement {
|
||||
|
||||
@JSProperty
|
||||
public abstract void setAllow(String str);
|
||||
|
||||
@JSProperty
|
||||
public abstract String getAllow();
|
||||
|
||||
public void setAllowSafe(String requiredValue) {
|
||||
setAllow(requiredValue);
|
||||
if(!requiredValue.equals(getAllow())) {
|
||||
throw new IFrameSafetyException("Could not set allow attribute to: " + requiredValue);
|
||||
}
|
||||
}
|
||||
|
||||
@JSProperty
|
||||
public abstract void setAllowFullscreen(boolean en);
|
||||
|
||||
@JSProperty
|
||||
public abstract void setCredentialless(boolean en);
|
||||
|
||||
@JSProperty
|
||||
public abstract void setLoading(String str);
|
||||
|
||||
@JSProperty
|
||||
public abstract void setReferrerPolicy(String str);
|
||||
|
||||
@JSProperty("csp")
|
||||
public abstract void setCSP(String str);
|
||||
|
||||
@JSProperty
|
||||
public abstract void setSandbox(String str);
|
||||
|
||||
@JSProperty
|
||||
public abstract DOMTokenList getSandbox();
|
||||
|
||||
public void assertSafetyFeaturesSupported() {
|
||||
if(!checkSafetyFeaturesSupported()) {
|
||||
throw new IFrameSafetyException("Some required security features are not supported on this browser!");
|
||||
}
|
||||
}
|
||||
|
||||
public void setSandboxSafe(Collection<String> requiredTokens) {
|
||||
setSandboxSafe(new HashSet<>(requiredTokens));
|
||||
}
|
||||
|
||||
public void setSandboxSafe(Set<String> requiredTokens) {
|
||||
setSandbox(String.join(" ", requiredTokens));
|
||||
DOMTokenList theSandbox = getSandbox();
|
||||
for(String s : requiredTokens) {
|
||||
if(!theSandbox.contains(s)) {
|
||||
throw new IFrameSafetyException("Failed to set sandbox attribute: " + s);
|
||||
}
|
||||
}
|
||||
int l = theSandbox.getLength();
|
||||
for(int i = 0; i < l; ++i) {
|
||||
String s = theSandbox.item(i);
|
||||
if(!requiredTokens.contains(s)) {
|
||||
throw new IFrameSafetyException("Unknown sandbox attribute detected: " + s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setSandboxSafe(Collection<String> requiredTokens, Collection<String> optionalTokens) {
|
||||
setSandboxSafe(new HashSet<>(requiredTokens), new HashSet<>(optionalTokens));
|
||||
}
|
||||
|
||||
public void setSandboxSafe(Set<String> requiredTokens, Set<String> optionalTokens) {
|
||||
setSandbox(StringUtils.join(Iterators.concat(requiredTokens.iterator(), optionalTokens.iterator()), " "));
|
||||
DOMTokenList theSandbox = getSandbox();
|
||||
for(String s : requiredTokens) {
|
||||
if(!theSandbox.contains(s)) {
|
||||
throw new IFrameSafetyException("Failed to set sandbox attribute: " + s);
|
||||
}
|
||||
}
|
||||
int l = theSandbox.getLength();
|
||||
for(int i = 0; i < l; ++i) {
|
||||
String s = theSandbox.item(i);
|
||||
if(!requiredTokens.contains(s) && !optionalTokens.contains(s)) {
|
||||
throw new IFrameSafetyException("Unknown sandbox attribute detected: " + s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = {}, script = "return (typeof this.allow === \"string\") && (typeof this.sandbox === \"object\");")
|
||||
public native boolean checkSafetyFeaturesSupported();
|
||||
|
||||
@JSBody(params = {}, script = "return (typeof this.csp === \"string\");")
|
||||
public native boolean checkCSPSupported();
|
||||
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
import org.teavm.jso.typedarrays.Int8Array;
|
||||
import org.teavm.jso.typedarrays.Uint8Array;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022 lax1dude. All Rights Reserved.
|
||||
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
@ -21,16 +23,17 @@ import org.teavm.jso.typedarrays.Uint8Array;
|
||||
*
|
||||
*/
|
||||
public class ArrayBufferInputStream extends InputStream {
|
||||
|
||||
|
||||
private int mark = -1;
|
||||
private int position;
|
||||
private int limit;
|
||||
private final ArrayBuffer buffer;
|
||||
private final Uint8Array typed;
|
||||
|
||||
|
||||
public ArrayBufferInputStream(ArrayBuffer bufferIn) {
|
||||
this(bufferIn, 0, bufferIn.getByteLength());
|
||||
}
|
||||
|
||||
|
||||
public ArrayBufferInputStream(ArrayBuffer bufferIn, int off, int len) {
|
||||
if(off + len > bufferIn.getByteLength()) {
|
||||
throw new IllegalArgumentException("offset " + off + " and length " + len + " are out of bounds for a "
|
||||
@ -66,15 +69,14 @@ public class ArrayBufferInputStream extends InputStream {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(int i = 0; i < len; ++i) {
|
||||
b[off + i] = (byte)typed.get(position + i);
|
||||
}
|
||||
TeaVMUtils.unwrapArrayBufferView(b).set(Int8Array.create(buffer, position, len), off);
|
||||
|
||||
position += len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long skip(long n) {
|
||||
int avail = limit - position;
|
||||
if(n > avail) {
|
||||
@ -83,7 +85,7 @@ public class ArrayBufferInputStream extends InputStream {
|
||||
position += (int)n;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int available() {
|
||||
return limit - position;
|
||||
@ -101,4 +103,21 @@ public class ArrayBufferInputStream extends InputStream {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
mark = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
if(mark == -1) {
|
||||
throw new IOException("Cannot reset, stream has no mark!");
|
||||
}
|
||||
position = mark;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,129 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.teavm.jso.core.JSString;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.Base64;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class Base64VarIntArray {
|
||||
|
||||
public static String encodeVarIntArray(List<Integer> values) {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
for(int i = 0, j, k, l = values.size(); i < l; ++i) {
|
||||
j = values.get(i);
|
||||
if(j < 0) j = 0;
|
||||
for(;;) {
|
||||
k = j & 31;
|
||||
if(j > 31) {
|
||||
j >>>= 5;
|
||||
ret.append(Base64.lookupIntChar(k | 32));
|
||||
}else {
|
||||
ret.append(Base64.lookupIntChar(k));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
public static String encodeVarIntArray(int[] values) {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
for(int i = 0, j, k; i < values.length; ++i) {
|
||||
j = values[i];
|
||||
if(j < 0) j = 0;
|
||||
for(;;) {
|
||||
k = j & 31;
|
||||
if(j > 31) {
|
||||
j >>>= 5;
|
||||
ret.append(Base64.lookupIntChar(k | 32));
|
||||
}else {
|
||||
ret.append(Base64.lookupIntChar(k));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
public static int[] decodeVarIntArray(String values) {
|
||||
int[] ret = new int[8];
|
||||
int o = 0;
|
||||
for(int i = 0, j, k, m, l = values.length(); i < l;) {
|
||||
k = 0;
|
||||
m = 0;
|
||||
for(;;) {
|
||||
j = Base64.lookupCharInt(values.charAt(i++));
|
||||
if(j == -1) {
|
||||
return null;
|
||||
}
|
||||
k |= (j & 31) << m;
|
||||
if(j > 31) {
|
||||
if(i >= l) {
|
||||
return null;
|
||||
}
|
||||
m += 5;
|
||||
}else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j = ret.length;
|
||||
if(o >= j) {
|
||||
int[] newRet = new int[j << 1];
|
||||
System.arraycopy(ret, 0, newRet, 0, j);
|
||||
ret = newRet;
|
||||
}
|
||||
ret[o++] = k;
|
||||
}
|
||||
return o != ret.length ? Arrays.copyOf(ret, o) : ret;
|
||||
}
|
||||
|
||||
public static int[] decodeVarIntArray(JSString values) {
|
||||
int[] ret = new int[8];
|
||||
int o = 0;
|
||||
for(int i = 0, j, k, m, l = values.getLength(); i < l;) {
|
||||
k = 0;
|
||||
m = 0;
|
||||
for(;;) {
|
||||
j = Base64.lookupCharInt((char)values.charCodeAt(i++));
|
||||
if(j == -1) {
|
||||
return null;
|
||||
}
|
||||
k |= (j & 31) << m;
|
||||
if(j > 31) {
|
||||
if(i >= l) {
|
||||
return null;
|
||||
}
|
||||
m += 5;
|
||||
}else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j = ret.length;
|
||||
if(o >= j) {
|
||||
int[] newRet = new int[j << 1];
|
||||
System.arraycopy(ret, 0, newRet, 0, j);
|
||||
ret = newRet;
|
||||
}
|
||||
ret[o++] = k;
|
||||
}
|
||||
return o != ret.length ? Arrays.copyOf(ret, o) : ret;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.dom.html.HTMLScriptElement;
|
||||
import org.teavm.jso.dom.xml.Element;
|
||||
import org.teavm.jso.dom.xml.NodeList;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EagUtils;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class ClassesJSLocator {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("ClassesJSLocator");
|
||||
|
||||
public static String resolveClassesJSFromThrowable() {
|
||||
String str = resolveClassesJSFromThrowable0();
|
||||
if(str != null && str.equalsIgnoreCase(PlatformRuntime.win.getLocation().getFullURL())) {
|
||||
return null;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
private static String resolveClassesJSFromThrowable0() {
|
||||
String str = TeaVMUtils.dumpJSStackTrace();
|
||||
String[] frames = EagUtils.splitPattern.split(str);
|
||||
if("Error".equals(frames[0])) {
|
||||
// V8 stack trace
|
||||
if(frames.length > 1) {
|
||||
String framesTrim = frames[1].trim();
|
||||
if(framesTrim.startsWith("at")) {
|
||||
//definitely V8
|
||||
int i = framesTrim.indexOf('(');
|
||||
int j = framesTrim.indexOf(')');
|
||||
if(i != -1 && j != -1 && i < j) {
|
||||
return tryResolveClassesSourceFromFrame(framesTrim.substring(i + 1, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
}else {
|
||||
// Mozilla/WebKit stack trace
|
||||
String framesTrim = frames[0].trim();
|
||||
int i = framesTrim.indexOf('@');
|
||||
if(i != -1) {
|
||||
return tryResolveClassesSourceFromFrame(framesTrim.substring(i + 1));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String tryResolveClassesSourceFromFrame(String fileLineCol) {
|
||||
int i = fileLineCol.lastIndexOf(':');
|
||||
if(i > 0) {
|
||||
i = fileLineCol.lastIndexOf(':', i - 1);
|
||||
}
|
||||
if(i != -1) {
|
||||
return fileLineCol.substring(0, i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static HTMLScriptElement resolveClassesJSFromInline() {
|
||||
NodeList<Element> elements = PlatformRuntime.doc.getElementsByTagName("script");
|
||||
for(int i = 0, l = elements.getLength(); i < l; ++i) {
|
||||
HTMLScriptElement tag = (HTMLScriptElement)elements.get(i);
|
||||
String scriptSrc = tag.getText();
|
||||
if(scriptSrc != null && scriptSrc.length() > 1024 * 1024) {
|
||||
// I'm not feeling very creative tonight
|
||||
int j = scriptSrc.indexOf("var $rt_seed=2463534242;");
|
||||
if(j > 0 && j < 2048 && scriptSrc.indexOf("$rt_createNumericArray(") != -1) {
|
||||
logger.warn("Could not locate classes.js through conventional means, however an inline script tag was found on the page that (probably) contains a TeaVM program");
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -5,7 +5,9 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.JSONException;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSFunctor;
|
||||
@ -21,7 +23,10 @@ import org.teavm.jso.webgl.WebGLRenderingContext;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
|
||||
import net.lax1dude.eaglercraft.v1_8.boot_menu.teavm.BootMenuEntryPoint;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsAssetsURI;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsRoot;
|
||||
@ -55,12 +60,26 @@ public class ClientMain {
|
||||
return crashImage.substring(0);
|
||||
}
|
||||
|
||||
@JSBody(params = {}, script = "if((typeof window.__isEaglerX188Running === \"string\") && window.__isEaglerX188Running === \"yes\") return true; window.__isEaglerX188Running = \"yes\"; return false;")
|
||||
@JSBody(params = {}, script = "if((typeof __isEaglerX188Running === \"string\") && __isEaglerX188Running === \"yes\") return true; __isEaglerX188Running = \"yes\"; return false;")
|
||||
private static native boolean getRunningFlag();
|
||||
|
||||
@JSBody(params = { "str" }, script = "return (typeof location !== \"undefined\") && (typeof location.hostname === \"string\") && location.hostname.toLowerCase() === str;")
|
||||
private static native boolean getTardFlag(String str);
|
||||
|
||||
private static final PrintStream systemOut = System.out;
|
||||
private static final PrintStream systemErr = System.err;
|
||||
|
||||
private static JSObject windowErrorHandler = null;
|
||||
|
||||
public static void _main() {
|
||||
PrintStream systemOut = System.out;
|
||||
PrintStream systemErr = System.err;
|
||||
if(getTardFlag(new String(new char[] { 'e', 'a', 'g', 'l', 'e', 'r', 'c', 'r', 'a', 'f', 't', '.', 'd', 'e', 'v' }))) {
|
||||
// Have fun, boys!!!
|
||||
Window.alert(new String(new char[] { 101, 97, 103, 108, 101, 114, 99, 114, 97, 102, 116, 46, 100, 101, 118,
|
||||
32, 105, 115, 32, 110, 111, 116, 32, 97, 110, 32, 111, 102, 102, 105, 99, 105, 97, 108, 32, 119,
|
||||
101, 98, 115, 105, 116, 101, 32, 97, 110, 100, 32, 105, 115, 32, 110, 111, 116, 32, 101, 110, 100,
|
||||
111, 114, 115, 101, 100, 32, 98, 121, 32, 108, 97, 120, 49, 100, 117, 100, 101, 32, 111, 114, 32,
|
||||
97, 121, 117, 110, 97, 109, 105, 50, 48, 48, 48 }));
|
||||
}
|
||||
if(getRunningFlag()) {
|
||||
systemErr.println("ClientMain: [ERROR] eaglercraftx is already running!");
|
||||
return;
|
||||
@ -79,6 +98,7 @@ public class ClientMain {
|
||||
try {
|
||||
JSEaglercraftXOptsRoot eaglercraftOpts = (JSEaglercraftXOptsRoot)opts;
|
||||
crashOnUncaughtExceptions = eaglercraftOpts.getCrashOnUncaughtExceptions(false);
|
||||
PlatformRuntime.isDeobfStackTraces = eaglercraftOpts.getDeobfStackTraces(true);
|
||||
|
||||
configRootElementId = eaglercraftOpts.getContainer();
|
||||
if(configRootElementId == null) {
|
||||
@ -86,6 +106,11 @@ public class ClientMain {
|
||||
}
|
||||
configRootElement = Window.current().getDocument().getElementById(configRootElementId);
|
||||
|
||||
HTMLElement oldContent;
|
||||
while((oldContent = configRootElement.querySelector("._eaglercraftX_wrapper_element")) != null) {
|
||||
oldContent.delete();
|
||||
}
|
||||
|
||||
String epkSingleURL = eaglercraftOpts.getAssetsURI();
|
||||
if(epkSingleURL != null) {
|
||||
configEPKFiles = new EPKFileEntry[] { new EPKFileEntry(epkSingleURL, "") };
|
||||
@ -125,31 +150,41 @@ public class ClientMain {
|
||||
if(crashOnUncaughtExceptions) {
|
||||
systemOut.println("ClientMain: [INFO] registering crash handlers");
|
||||
|
||||
setWindowErrorHandler(new WindowErrorHandler() {
|
||||
windowErrorHandler = setWindowErrorHandler(Window.current(), new WindowErrorHandler() {
|
||||
|
||||
@Override
|
||||
public void call(String message, String file, int line, int col, JSError error) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
|
||||
str.append("Native Browser Exception\n");
|
||||
str.append("----------------------------------\n");
|
||||
str.append(" Line: ").append((file == null ? "unknown" : file) + ":" + line + ":" + col).append('\n');
|
||||
str.append(" Type: ").append(error == null ? "generic" : error.getName()).append('\n');
|
||||
|
||||
if(error != null) {
|
||||
str.append(" Desc: ").append(error.getMessage() == null ? "null" : error.getMessage()).append('\n');
|
||||
}
|
||||
|
||||
if(message != null) {
|
||||
if(error == null || error.getMessage() == null || !message.endsWith(error.getMessage())) {
|
||||
str.append(" Desc: ").append(message).append('\n');
|
||||
if(windowErrorHandler != null) {
|
||||
error = TeaVMUtils.ensureDefined(error);
|
||||
if(error == null) {
|
||||
systemErr.println("ClientMain: [ERROR] recieved error event, but the error is null, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder str = new StringBuilder();
|
||||
|
||||
str.append("Native Browser Exception\n");
|
||||
str.append("----------------------------------\n");
|
||||
str.append(" Line: ").append((file == null ? "unknown" : file) + ":" + line + ":" + col).append('\n');
|
||||
str.append(" Type: ").append(error.getName()).append('\n');
|
||||
str.append(" Desc: ").append(error.getMessage() == null ? "null" : error.getMessage()).append('\n');
|
||||
|
||||
if(message != null) {
|
||||
if(error.getMessage() == null || !message.endsWith(error.getMessage())) {
|
||||
str.append(" Desc: ").append(message).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
str.append("----------------------------------\n\n");
|
||||
String stack = TeaVMUtils.getStackSafe(error);
|
||||
if(PlatformRuntime.isDeobfStackTraces && !StringUtils.isAllEmpty(stack)) {
|
||||
TeaVMRuntimeDeobfuscator.initialize();
|
||||
stack = TeaVMRuntimeDeobfuscator.deobfExceptionStack(stack);
|
||||
}
|
||||
str.append(stack == null ? "No stack trace is available" : stack).append('\n');
|
||||
|
||||
showCrashScreen(str.toString());
|
||||
}
|
||||
|
||||
str.append("----------------------------------\n\n");
|
||||
str.append(error.getStack() == null ? "No stack trace is available" : error.getStack()).append('\n');
|
||||
|
||||
showCrashScreen(str.toString());
|
||||
}
|
||||
|
||||
});
|
||||
@ -174,6 +209,14 @@ public class ClientMain {
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
return;
|
||||
}catch(TeaVMEnterBootMenuException ee) {
|
||||
try {
|
||||
systemOut.println("ClientMain: [INFO] launching eaglercraftx boot menu");
|
||||
BootMenuEntryPoint.launchMenu(Window.current(), configRootElement);
|
||||
}catch(Throwable t) {
|
||||
showCrashScreen("Failed to enter boot menu!", t);
|
||||
}
|
||||
return;
|
||||
}catch(Throwable t) {
|
||||
systemErr.println("ClientMain: [ERROR] eaglercraftx's runtime could not be initialized!");
|
||||
EagRuntime.debugPrintStackTraceToSTDERR(t);
|
||||
@ -197,9 +240,9 @@ public class ClientMain {
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = {}, script = "if(typeof window.eaglercraftXOpts === \"undefined\") {return null;}"
|
||||
+ "else if(typeof window.eaglercraftXOpts === \"string\") {return JSON.parse(window.eaglercraftXOpts);}"
|
||||
+ "else {return window.eaglercraftXOpts;}")
|
||||
@JSBody(params = {}, script = "if(typeof eaglercraftXOpts === \"undefined\") {return null;}"
|
||||
+ "else if(typeof eaglercraftXOpts === \"string\") {return JSON.parse(eaglercraftXOpts);}"
|
||||
+ "else {return eaglercraftXOpts;}")
|
||||
private static native JSObject getEaglerXOpts();
|
||||
|
||||
public static class EPKFileEntry {
|
||||
@ -224,13 +267,24 @@ public class ClientMain {
|
||||
void call(String message, String file, int line, int col, JSError error);
|
||||
}
|
||||
|
||||
@JSBody(params = { "handler" }, script = "window.addEventListener(\"error\", function(e) { handler("
|
||||
@JSBody(params = { "win", "handler" }, script = "var evtHandler = function(e) { handler("
|
||||
+ "(typeof e.message === \"string\") ? e.message : null,"
|
||||
+ "(typeof e.filename === \"string\") ? e.filename : null,"
|
||||
+ "(typeof e.lineno === \"number\") ? e.lineno : 0,"
|
||||
+ "(typeof e.colno === \"number\") ? e.colno : 0,"
|
||||
+ "(typeof e.error === \"undefined\") ? null : e.error); });")
|
||||
public static native void setWindowErrorHandler(WindowErrorHandler handler);
|
||||
+ "(typeof e.error === \"undefined\") ? null : e.error);}; win.addEventListener(\"error\", evtHandler);"
|
||||
+ "return evtHandler;")
|
||||
private static native JSObject setWindowErrorHandler(Window win, WindowErrorHandler handler);
|
||||
|
||||
@JSBody(params = { "win", "handler" }, script = "win.removeEventListener(\"error\", evtHandler);")
|
||||
private static native void removeWindowErrorHandler(Window win, JSObject handler);
|
||||
|
||||
public static void removeErrorHandler(Window win) {
|
||||
if(windowErrorHandler != null) {
|
||||
removeWindowErrorHandler(win, windowErrorHandler);
|
||||
windowErrorHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void showCrashScreen(String message, Throwable t) {
|
||||
try {
|
||||
@ -249,12 +303,16 @@ public class ClientMain {
|
||||
String strBefore = strBeforeBuilder.toString();
|
||||
|
||||
HTMLDocument doc = Window.current().getDocument();
|
||||
if(configRootElement == null) {
|
||||
configRootElement = doc.getElementById(configRootElementId);
|
||||
HTMLElement el;
|
||||
if(PlatformRuntime.parent != null) {
|
||||
el = PlatformRuntime.parent;
|
||||
}else {
|
||||
if(configRootElement == null) {
|
||||
configRootElement = doc.getElementById(configRootElementId);
|
||||
}
|
||||
el = configRootElement;
|
||||
}
|
||||
|
||||
HTMLElement el = configRootElement;
|
||||
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append("eaglercraft.version = \"").append(EaglercraftVersion.projectForkVersion).append("\"\n");
|
||||
str.append("eaglercraft.minecraft = \"1.8.8\"\n");
|
||||
@ -263,11 +321,13 @@ public class ClientMain {
|
||||
str.append('\n');
|
||||
str.append(addWebGLToCrash());
|
||||
str.append('\n');
|
||||
str.append(addShimsToCrash());
|
||||
str.append('\n');
|
||||
str.append("window.eaglercraftXOpts = ");
|
||||
str.append(TeaVMClientConfigAdapter.instance.toString()).append('\n');
|
||||
str.append('\n');
|
||||
str.append("currentTime = ");
|
||||
str.append(EagRuntime.fixDateFormat(new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")).format(new Date())).append('\n');
|
||||
str.append((new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")).format(new Date())).append('\n');
|
||||
str.append('\n');
|
||||
addDebugNav(str, "userAgent");
|
||||
addDebugNav(str, "vendor");
|
||||
@ -301,11 +361,11 @@ public class ClientMain {
|
||||
String strAfter = str.toString();
|
||||
|
||||
String strFinal = strBefore + strAfter;
|
||||
List<String> additionalInfo = new LinkedList();
|
||||
List<String> additionalInfo = new LinkedList<>();
|
||||
try {
|
||||
TeaVMClientConfigAdapter.instance.getHooks().callCrashReportHook(strFinal, additionalInfo::add);
|
||||
}catch(Throwable tt) {
|
||||
System.err.println("Uncaught exception invoking crash report hook!");
|
||||
systemErr.println("Uncaught exception invoking crash report hook!");
|
||||
EagRuntime.debugPrintStackTraceToSTDERR(tt);
|
||||
}
|
||||
|
||||
@ -325,24 +385,23 @@ public class ClientMain {
|
||||
builderFinal.append(strAfter);
|
||||
strFinal = builderFinal.toString();
|
||||
}catch(Throwable tt) {
|
||||
System.err.println("Uncaught exception concatenating crash report hook messages!");
|
||||
systemErr.println("Uncaught exception concatenating crash report hook messages!");
|
||||
EagRuntime.debugPrintStackTraceToSTDERR(tt);
|
||||
}
|
||||
}
|
||||
|
||||
if(el == null) {
|
||||
Window.alert("Root element not found, crash report was printed to console");
|
||||
System.err.println(strFinal);
|
||||
systemErr.println(strFinal);
|
||||
return;
|
||||
}
|
||||
|
||||
String s = el.getAttribute("style");
|
||||
el.setAttribute("style", (s == null ? "" : s) + "position:relative;");
|
||||
|
||||
HTMLElement img = doc.createElement("img");
|
||||
HTMLElement div = doc.createElement("div");
|
||||
img.setAttribute("style", "z-index:100;position:absolute;top:10px;left:calc(50% - 151px);");
|
||||
img.setAttribute("src", crashImageWrapper());
|
||||
div.setAttribute("style", "z-index:100;position:absolute;top:135px;left:10%;right:10%;bottom:50px;background-color:white;border:1px solid #cccccc;overflow-x:hidden;overflow-y:scroll;overflow-wrap:break-word;white-space:pre-wrap;font: 14px monospace;padding:10px;");
|
||||
div.getClassList().add("_eaglercraftX_crash_element");
|
||||
el.appendChild(img);
|
||||
el.appendChild(div);
|
||||
div.appendChild(doc.createTextNode(strFinal));
|
||||
@ -350,22 +409,22 @@ public class ClientMain {
|
||||
PlatformRuntime.removeEventHandlers();
|
||||
|
||||
}else {
|
||||
System.err.println();
|
||||
System.err.println("An additional crash report was supressed:");
|
||||
systemErr.println();
|
||||
systemErr.println("An additional crash report was supressed:");
|
||||
String[] s = t.split("[\\r\\n]+");
|
||||
for(int i = 0; i < s.length; ++i) {
|
||||
System.err.println(" " + s[i]);
|
||||
systemErr.println(" " + s[i]);
|
||||
}
|
||||
if(additionalInfo.size() > 0) {
|
||||
for(String str2 : additionalInfo) {
|
||||
if(str2 != null) {
|
||||
System.err.println();
|
||||
System.err.println(" ----------[ CRASH HOOK ]----------");
|
||||
systemErr.println();
|
||||
systemErr.println(" ----------[ CRASH HOOK ]----------");
|
||||
s = str2.split("[\\r\\n]+");
|
||||
for(int i = 0; i < s.length; ++i) {
|
||||
System.err.println(" " + s[i]);
|
||||
systemErr.println(" " + s[i]);
|
||||
}
|
||||
System.err.println(" ----------------------------------");
|
||||
systemErr.println(" ----------------------------------");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -379,44 +438,110 @@ public class ClientMain {
|
||||
return webGLCrashStringCache;
|
||||
}
|
||||
|
||||
StringBuilder ret = new StringBuilder();
|
||||
|
||||
WebGLRenderingContext ctx = PlatformRuntime.webgl;
|
||||
|
||||
if(ctx == null) {
|
||||
HTMLCanvasElement cvs = (HTMLCanvasElement) Window.current().getDocument().createElement("canvas");
|
||||
try {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
|
||||
cvs.setWidth(64);
|
||||
cvs.setHeight(64);
|
||||
|
||||
ctx = (WebGLRenderingContext)cvs.getContext("webgl2");
|
||||
WebGLRenderingContext ctx = PlatformRuntime.webgl;
|
||||
boolean experimental = PlatformRuntime.webglExperimental;
|
||||
|
||||
if(ctx == null) {
|
||||
ctx = (WebGLRenderingContext)cvs.getContext("webgl");
|
||||
experimental = false;
|
||||
HTMLCanvasElement cvs = (HTMLCanvasElement) Window.current().getDocument().createElement("canvas");
|
||||
|
||||
cvs.setWidth(64);
|
||||
cvs.setHeight(64);
|
||||
|
||||
ctx = (WebGLRenderingContext)cvs.getContext("webgl2");
|
||||
|
||||
if(ctx == null) {
|
||||
ctx = (WebGLRenderingContext)cvs.getContext("webgl");
|
||||
if(ctx == null) {
|
||||
experimental = true;
|
||||
ctx = (WebGLRenderingContext)cvs.getContext("experimental-webgl");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(ctx != null) {
|
||||
if(PlatformRuntime.webgl != null) {
|
||||
ret.append("webgl.version = ").append(ctx.getParameterString(WebGLRenderingContext.VERSION)).append('\n');
|
||||
}
|
||||
if(ctx.getExtension("WEBGL_debug_renderer_info") != null) {
|
||||
ret.append("webgl.renderer = ").append(ctx.getParameterString(/* UNMASKED_RENDERER_WEBGL */ 0x9246)).append('\n');
|
||||
ret.append("webgl.vendor = ").append(ctx.getParameterString(/* UNMASKED_VENDOR_WEBGL */ 0x9245)).append('\n');
|
||||
}else {
|
||||
ret.append("webgl.renderer = ").append(ctx.getParameterString(WebGLRenderingContext.RENDERER) + " [masked]").append('\n');
|
||||
ret.append("webgl.vendor = ").append(ctx.getParameterString(WebGLRenderingContext.VENDOR) + " [masked]").append('\n');
|
||||
}
|
||||
//ret.append('\n').append("\nwebgl.anisotropicGlitch = ").append(DetectAnisotropicGlitch.hasGlitch()).append('\n'); //TODO
|
||||
ret.append('\n').append("webgl.ext.HDR16f = ").append(ctx.getExtension("EXT_color_buffer_half_float") != null).append('\n');
|
||||
ret.append("webgl.ext.HDR32f = ").append(ctx.getExtension("EXT_color_buffer_float") != null).append('\n');
|
||||
ret.append("webgl.ext.HDR32f_linear = ").append(ctx.getExtension("OES_texture_float_linear") != null).append('\n');
|
||||
|
||||
}else {
|
||||
ret.append("Failed to query GPU info!\n");
|
||||
if(ctx != null) {
|
||||
if(PlatformRuntime.webgl != null) {
|
||||
ret.append("webgl.version = ").append(ctx.getParameterString(WebGLRenderingContext.VERSION)).append('\n');
|
||||
}
|
||||
if(ctx.getExtension("WEBGL_debug_renderer_info") != null) {
|
||||
ret.append("webgl.renderer = ").append(ctx.getParameterString(/* UNMASKED_RENDERER_WEBGL */ 0x9246)).append('\n');
|
||||
ret.append("webgl.vendor = ").append(ctx.getParameterString(/* UNMASKED_VENDOR_WEBGL */ 0x9245)).append('\n');
|
||||
}else {
|
||||
ret.append("webgl.renderer = ").append(ctx.getParameterString(WebGLRenderingContext.RENDERER)).append( " [masked]").append('\n');
|
||||
ret.append("webgl.vendor = ").append(ctx.getParameterString(WebGLRenderingContext.VENDOR)).append(" [masked]").append('\n');
|
||||
}
|
||||
//ret.append('\n').append("\nwebgl.anisotropicGlitch = ").append(DetectAnisotropicGlitch.hasGlitch()).append('\n'); //TODO
|
||||
int id = PlatformOpenGL.checkOpenGLESVersion();
|
||||
if(id > 0) {
|
||||
ret.append('\n').append("webgl.version.id = ").append(id).append('\n');
|
||||
ret.append("webgl.experimental = ").append(experimental).append('\n');
|
||||
if(id == 200) {
|
||||
ret.append("webgl.ext.ANGLE_instanced_arrays = ").append(ctx.getExtension("ANGLE_instanced_arrays") != null).append('\n');
|
||||
ret.append("webgl.ext.EXT_color_buffer_half_float = ").append(ctx.getExtension("EXT_color_buffer_half_float") != null).append('\n');
|
||||
ret.append("webgl.ext.EXT_shader_texture_lod = ").append(ctx.getExtension("EXT_shader_texture_lod") != null).append('\n');
|
||||
ret.append("webgl.ext.OES_fbo_render_mipmap = ").append(ctx.getExtension("OES_fbo_render_mipmap") != null).append('\n');
|
||||
ret.append("webgl.ext.OES_texture_float = ").append(ctx.getExtension("OES_texture_float") != null).append('\n');
|
||||
ret.append("webgl.ext.OES_texture_half_float = ").append(ctx.getExtension("OES_texture_half_float") != null).append('\n');
|
||||
ret.append("webgl.ext.OES_texture_half_float_linear = ").append(ctx.getExtension("OES_texture_half_float_linear") != null).append('\n');
|
||||
}else if(id >= 300) {
|
||||
ret.append("webgl.ext.EXT_color_buffer_float = ").append(ctx.getExtension("EXT_color_buffer_float") != null).append('\n');
|
||||
ret.append("webgl.ext.EXT_color_buffer_half_float = ").append(ctx.getExtension("EXT_color_buffer_half_float") != null).append('\n');
|
||||
ret.append("webgl.ext.OES_texture_float_linear = ").append(ctx.getExtension("OES_texture_float_linear") != null).append('\n');
|
||||
}
|
||||
ret.append("webgl.ext.EXT_texture_filter_anisotropic = ").append(ctx.getExtension("EXT_texture_filter_anisotropic") != null).append('\n');
|
||||
}else {
|
||||
ret.append("webgl.ext.ANGLE_instanced_arrays = ").append(ctx.getExtension("ANGLE_instanced_arrays") != null).append('\n');
|
||||
ret.append("webgl.ext.EXT_color_buffer_float = ").append(ctx.getExtension("EXT_color_buffer_float") != null).append('\n');
|
||||
ret.append("webgl.ext.EXT_color_buffer_half_float = ").append(ctx.getExtension("EXT_color_buffer_half_float") != null).append('\n');
|
||||
ret.append("webgl.ext.EXT_shader_texture_lod = ").append(ctx.getExtension("EXT_shader_texture_lod") != null).append('\n');
|
||||
ret.append("webgl.ext.OES_fbo_render_mipmap = ").append(ctx.getExtension("OES_fbo_render_mipmap") != null).append('\n');
|
||||
ret.append("webgl.ext.OES_texture_float = ").append(ctx.getExtension("OES_texture_float") != null).append('\n');
|
||||
ret.append("webgl.ext.OES_texture_float_linear = ").append(ctx.getExtension("OES_texture_float_linear") != null).append('\n');
|
||||
ret.append("webgl.ext.OES_texture_half_float = ").append(ctx.getExtension("OES_texture_half_float") != null).append('\n');
|
||||
ret.append("webgl.ext.OES_texture_half_float_linear = ").append(ctx.getExtension("OES_texture_half_float_linear") != null).append('\n');
|
||||
ret.append("webgl.ext.EXT_texture_filter_anisotropic = ").append(ctx.getExtension("EXT_texture_filter_anisotropic") != null).append('\n');
|
||||
}
|
||||
}else {
|
||||
ret.append("Failed to query GPU info!\n");
|
||||
}
|
||||
|
||||
return webGLCrashStringCache = ret.toString();
|
||||
}catch(Throwable tt) {
|
||||
return webGLCrashStringCache = "ERROR: could not query webgl info - " + tt.toString() + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
private static String shimsCrashStringCache = null;
|
||||
|
||||
private static String addShimsToCrash() {
|
||||
if(shimsCrashStringCache != null) {
|
||||
return shimsCrashStringCache;
|
||||
}
|
||||
|
||||
return webGLCrashStringCache = ret.toString();
|
||||
try {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
|
||||
ES6ShimStatus status = ES6ShimStatus.getRuntimeStatus();
|
||||
ret.append("eaglercraft.es6shims.status = ").append(status.getStatus()).append('\n');
|
||||
ret.append("eaglercraft.es6shims.shims = [ ");
|
||||
Set<EnumES6Shims> shims = status.getShims();
|
||||
boolean b = false;
|
||||
for(EnumES6Shims shim : shims) {
|
||||
if(b) {
|
||||
ret.append(", ");
|
||||
}
|
||||
ret.append(shim);
|
||||
b = true;
|
||||
}
|
||||
ret.append(" ]\n");
|
||||
|
||||
return shimsCrashStringCache = ret.toString();
|
||||
}catch(Throwable tt) {
|
||||
return shimsCrashStringCache = "ERROR: could not query ES6 shim info - " + tt.toString() + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
public static void showIncompatibleScreen(String t) {
|
||||
@ -424,12 +549,16 @@ public class ClientMain {
|
||||
isCrashed = true;
|
||||
|
||||
HTMLDocument doc = Window.current().getDocument();
|
||||
if(configRootElement == null) {
|
||||
configRootElement = doc.getElementById(configRootElementId);
|
||||
HTMLElement el;
|
||||
if(PlatformRuntime.parent != null) {
|
||||
el = PlatformRuntime.parent;
|
||||
}else {
|
||||
if(configRootElement == null) {
|
||||
configRootElement = doc.getElementById(configRootElementId);
|
||||
}
|
||||
el = configRootElement;
|
||||
}
|
||||
|
||||
HTMLElement el = configRootElement;
|
||||
|
||||
if(el == null) {
|
||||
System.err.println("Compatibility error: " + t);
|
||||
return;
|
||||
@ -442,14 +571,15 @@ public class ClientMain {
|
||||
img.setAttribute("style", "z-index:100;position:absolute;top:10px;left:calc(50% - 151px);");
|
||||
img.setAttribute("src", crashImageWrapper());
|
||||
div.setAttribute("style", "z-index:100;position:absolute;top:135px;left:10%;right:10%;bottom:50px;background-color:white;border:1px solid #cccccc;overflow-x:hidden;overflow-y:scroll;font:18px sans-serif;padding:40px;");
|
||||
div.getClassList().add("_eaglercraftX_incompatible_element");
|
||||
el.appendChild(img);
|
||||
el.appendChild(div);
|
||||
div.setInnerHTML("<h2><svg style=\"vertical-align:middle;margin:0px 16px 8px 8px;\" xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\"><path stroke=\"#000000\" stroke-width=\"3\" stroke-linecap=\"square\" d=\"M1.5 8.5v34h45v-28m-3-3h-10v-3m-3-3h-10m15 6h-18v-3m-3-3h-10\"/><path stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"square\" d=\"M12 21h0m0 4h0m4 0h0m0-4h0m-2 2h0m20-2h0m0 4h0m4 0h0m0-4h0m-2 2h0\"/><path stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"square\" d=\"M20 30h0 m2 2h0 m2 2h0 m2 2h0 m2 -2h0 m2 -2h0 m2 -2h0\"/></svg>+ This device is incompatible with Eaglercraft :(</h2>"
|
||||
+ "<div style=\"margin-left:40px;\">"
|
||||
+ "<p style=\"font-size:1.2em;\"><b style=\"font-size:1.1em;\">Issue:</b> <span style=\"color:#BB0000;\" id=\"crashReason\"></span><br /></p>"
|
||||
+ "<p style=\"margin-left:10px;font:0.9em monospace;\" id=\"crashUserAgent\"></p>"
|
||||
+ "<p style=\"margin-left:10px;font:0.9em monospace;\" id=\"crashWebGL\"></p>"
|
||||
+ "<p style=\"margin-left:10px;font:0.9em monospace;\">Current Date: " + EagRuntime.fixDateFormat(new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")).format(new Date()) + "</p>"
|
||||
+ "<p style=\"font-size:1.2em;\"><b style=\"font-size:1.1em;\">Issue:</b> <span style=\"color:#BB0000;\" id=\"_eaglercraftX_crashReason\"></span><br /></p>"
|
||||
+ "<p style=\"margin-left:10px;font:0.9em monospace;\" id=\"_eaglercraftX_crashUserAgent\"></p>"
|
||||
+ "<p style=\"margin-left:10px;font:0.9em monospace;\" id=\"_eaglercraftX_crashWebGL\"></p>"
|
||||
+ "<p style=\"margin-left:10px;font:0.9em monospace;\">Current Date: " + (new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")).format(new Date()) + "</p>"
|
||||
+ "<p><br /><span style=\"font-size:1.1em;border-bottom:1px dashed #AAAAAA;padding-bottom:5px;\">Things you can try:</span></p>"
|
||||
+ "<ol>"
|
||||
+ "<li><span style=\"font-weight:bold;\">Just try using Eaglercraft on a different device</span>, it isn't a bug it's common sense</li>"
|
||||
@ -458,12 +588,11 @@ public class ClientMain {
|
||||
+ "<li style=\"margin-top:7px;\">If you are not using Chrome/Edge, try installing the latest Google Chrome</li>"
|
||||
+ "<li style=\"margin-top:7px;\">If your browser is out of date, please update it to the latest version</li>"
|
||||
+ "<li style=\"margin-top:7px;\">If you are using an old OS such as Windows 7, please try Windows 10 or 11</li>"
|
||||
+ "<li style=\"margin-top:7px;\">If you have a GPU launched before 2009, WebGL 2.0 support may be impossible</li>"
|
||||
+ "</ol>"
|
||||
+ "</div>");
|
||||
|
||||
div.querySelector("#crashReason").appendChild(doc.createTextNode(t));
|
||||
div.querySelector("#crashUserAgent").appendChild(doc.createTextNode(getStringNav("userAgent")));
|
||||
div.querySelector("#_eaglercraftX_crashReason").appendChild(doc.createTextNode(t));
|
||||
div.querySelector("#_eaglercraftX_crashUserAgent").appendChild(doc.createTextNode(getStringNav("userAgent")));
|
||||
|
||||
PlatformRuntime.removeEventHandlers();
|
||||
|
||||
@ -494,7 +623,7 @@ public class ClientMain {
|
||||
}catch(Throwable tt) {
|
||||
}
|
||||
|
||||
div.querySelector("#crashWebGL").appendChild(doc.createTextNode(webGLRenderer));
|
||||
div.querySelector("#_eaglercraftX_crashWebGL").appendChild(doc.createTextNode(webGLRenderer));
|
||||
|
||||
}
|
||||
}
|
||||
@ -504,17 +633,24 @@ public class ClientMain {
|
||||
public static void showIntegratedServerCrashReportOverlay(String report, int x, int y, int w, int h) {
|
||||
if(integratedServerCrashPanel == null) {
|
||||
HTMLDocument doc = Window.current().getDocument();
|
||||
if(configRootElement == null) {
|
||||
configRootElement = doc.getElementById(configRootElementId);
|
||||
HTMLElement el;
|
||||
if(PlatformRuntime.parent != null) {
|
||||
el = PlatformRuntime.parent;
|
||||
}else {
|
||||
if(configRootElement == null) {
|
||||
configRootElement = doc.getElementById(configRootElementId);
|
||||
}
|
||||
el = configRootElement;
|
||||
}
|
||||
|
||||
integratedServerCrashPanel = doc.createElement("div");
|
||||
integratedServerCrashPanel.setAttribute("style", "z-index:99;position:absolute;background-color:black;color:white;overflow-x:hidden;overflow-y:scroll;overflow-wrap:break-word;white-space:pre-wrap;font:18px sans-serif;padding:20px;display:none;");
|
||||
configRootElement.appendChild(integratedServerCrashPanel);
|
||||
integratedServerCrashPanel.getClassList().add("_eaglercraftX_integratedserver_crash_element");
|
||||
el.appendChild(integratedServerCrashPanel);
|
||||
}
|
||||
String sourceURL = ClientPlatformSingleplayer.getLoadedWorkerSourceURLTeaVM();
|
||||
String workerURL = ClientPlatformSingleplayer.getLoadedWorkerURLTeaVM();
|
||||
String currentDate = EagRuntime.fixDateFormat(new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")).format(new Date());
|
||||
String currentDate = (new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")).format(new Date());
|
||||
if(workerURL != null) {
|
||||
report = "WORKER SRC: " + sourceURL +"\nWORKER URL: " + workerURL + "\n\nCURRENT DATE: " + currentDate + "\n\n" + report.replaceAll(workerURL, "<worker_url>");
|
||||
}else {
|
||||
@ -523,7 +659,7 @@ public class ClientMain {
|
||||
setInnerText(integratedServerCrashPanel, "");
|
||||
setInnerText(integratedServerCrashPanel, report);
|
||||
CSSStyleDeclaration style = integratedServerCrashPanel.getStyle();
|
||||
float s = (float)Window.current().getDevicePixelRatio();
|
||||
float s = PlatformInput.getDPI();
|
||||
style.setProperty("top", "" + (y / s) + "px");
|
||||
style.setProperty("left", "" + (x / s) + "px");
|
||||
style.setProperty("width", "" + ((w / s) - 20) + "px");
|
||||
@ -552,9 +688,9 @@ public class ClientMain {
|
||||
@JSBody(params = { "v" }, script = "try { return \"\"+window.location[v]; } catch(e) { return \"<error>\"; }")
|
||||
private static native String getStringLocation(String var);
|
||||
|
||||
@JSBody(params = { }, script = "try { var retObj = new Array; if(typeof window.navigator.plugins === \"object\")"
|
||||
+ "{ var len = window.navigator.plugins.length; if(len > 0) { for(var idx = 0; idx < len; ++idx) {"
|
||||
+ "var thePlugin = window.navigator.plugins[idx]; retObj.push({ name: thePlugin.name,"
|
||||
@JSBody(params = { }, script = "try { var retObj = new Array; if(typeof navigator.plugins === \"object\")"
|
||||
+ "{ var len = navigator.plugins.length; if(len > 0) { for(var idx = 0; idx < len; ++idx) {"
|
||||
+ "var thePlugin = navigator.plugins[idx]; retObj.push({ name: thePlugin.name,"
|
||||
+ "filename: thePlugin.filename, desc: thePlugin.description }); } } } return JSON.stringify(retObj);"
|
||||
+ "} catch(e) { return \"<error>\"; }")
|
||||
private static native String getStringNavPlugins();
|
||||
|
@ -13,6 +13,7 @@ import org.teavm.jso.dom.html.HTMLDocument;
|
||||
import org.teavm.jso.dom.html.HTMLElement;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
|
||||
@ -48,31 +49,57 @@ public class DebugConsoleWindow {
|
||||
private static final int bufferSpoolSize = 256;
|
||||
private static final int windowMaxMessages = 2048;
|
||||
|
||||
private static final List<LogMessage> messageBuffer = new LinkedList();
|
||||
private static final List<LogMessage> messageBuffer = new LinkedList<>();
|
||||
|
||||
public static Window parent = null;
|
||||
public static Window logger = null;
|
||||
private static HTMLDocument loggerDoc = null;
|
||||
private static HTMLBodyElement loggerBody = null;
|
||||
private static HTMLElement loggerMessageContainer = null;
|
||||
private static EventListener<?> unload = null;
|
||||
private static String unloadName = null;
|
||||
|
||||
public static void initialize(Window parentWindow) {
|
||||
parent = parentWindow;
|
||||
parent.addEventListener("unload", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
destroyWindow();
|
||||
}
|
||||
});
|
||||
if(parent.getLocalStorage() != null && "true".equals(parent.getLocalStorage().getItem(PlatformRuntime.getClientConfigAdapter().getLocalStorageNamespace() + ".showDebugConsole"))) {
|
||||
if (PlatformRuntime.getClientConfigAdapter().isOpenDebugConsoleOnLaunch() || debugConsoleLocalStorageGet()) {
|
||||
showDebugConsole0();
|
||||
}
|
||||
}
|
||||
|
||||
public static void showDebugConsole() {
|
||||
if(parent.getLocalStorage() != null) {
|
||||
parent.getLocalStorage().setItem(PlatformRuntime.getClientConfigAdapter().getLocalStorageNamespace() + ".showDebugConsole", "true");
|
||||
public static void removeEventListeners() {
|
||||
if(unloadName != null && unload != null) {
|
||||
try {
|
||||
parent.removeEventListener(unloadName, unload);
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
}
|
||||
unload = null;
|
||||
unloadName = null;
|
||||
}
|
||||
|
||||
private static void debugConsoleLocalStorageSet(boolean val) {
|
||||
try {
|
||||
if(parent.getLocalStorage() != null) {
|
||||
parent.getLocalStorage().setItem(PlatformRuntime.getClientConfigAdapter().getLocalStorageNamespace() + ".showDebugConsole", Boolean.toString(val));
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean debugConsoleLocalStorageGet() {
|
||||
try {
|
||||
if(parent.getLocalStorage() != null) {
|
||||
return Boolean.valueOf(parent.getLocalStorage().getItem(PlatformRuntime.getClientConfigAdapter().getLocalStorageNamespace() + ".showDebugConsole"));
|
||||
}else {
|
||||
return false;
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void showDebugConsole() {
|
||||
debugConsoleLocalStorageSet(true);
|
||||
showDebugConsole0();
|
||||
}
|
||||
|
||||
@ -81,12 +108,27 @@ public class DebugConsoleWindow {
|
||||
|
||||
private static void showDebugConsole0() {
|
||||
if(logger == null) {
|
||||
int w = (int)(1000 * parent.getDevicePixelRatio());
|
||||
int h = (int)(400 * parent.getDevicePixelRatio());
|
||||
try {
|
||||
parent.addEventListener(
|
||||
unloadName = ((TeaVMClientConfigAdapter) PlatformRuntime.getClientConfigAdapter())
|
||||
.isFixDebugConsoleUnloadListenerTeaVM() ? "beforeunload" : "unload",
|
||||
unload = new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
destroyWindow();
|
||||
}
|
||||
});
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
float s = PlatformInput.getDPI();
|
||||
int w = (int)(1000 * s);
|
||||
int h = (int)(400 * s);
|
||||
int x = (parent.getScreen().getWidth() - w) / 2;
|
||||
int y = (parent.getScreen().getHeight() - h) / 2;
|
||||
logger = parent.open("", "_blank", "top=" + y + ",left=" + x + ",width=" + w + ",height=" + h + ",menubar=0,status=0,titlebar=0,toolbar=0");
|
||||
if(logger == null) {
|
||||
if(logger == null || TeaVMUtils.isNotTruthy(logger)) {
|
||||
logger = null;
|
||||
debugConsoleLocalStorageSet(false);
|
||||
LogManager.getLogger("DebugConsoleWindow").error("Logger popup was blocked!");
|
||||
Window.alert("ERROR: Popup blocked!\n\nPlease make sure you have popups enabled for this site!");
|
||||
return;
|
||||
@ -110,9 +152,8 @@ public class DebugConsoleWindow {
|
||||
public void handleEvent(Event evt) {
|
||||
if(logger != null) {
|
||||
logger = null;
|
||||
if(parent.getLocalStorage() != null) {
|
||||
parent.getLocalStorage().setItem(PlatformRuntime.getClientConfigAdapter().getLocalStorageNamespace() + ".showDebugConsole", "false");
|
||||
}
|
||||
debugConsoleLocalStorageSet(false);
|
||||
removeEventListeners();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -139,10 +180,12 @@ public class DebugConsoleWindow {
|
||||
}
|
||||
|
||||
private static void appendLogMessageAndScroll(String text, String color) {
|
||||
boolean b = isScrollToEnd(logger, loggerDoc);
|
||||
appendLogMessage(text, color);
|
||||
if(b) {
|
||||
scrollToEnd0(logger, loggerDoc);
|
||||
if(logger != null) {
|
||||
boolean b = isScrollToEnd(logger, loggerDoc);
|
||||
appendLogMessage(text, color);
|
||||
if(b) {
|
||||
scrollToEnd0(logger, loggerDoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +210,11 @@ public class DebugConsoleWindow {
|
||||
if(logger != null) {
|
||||
Window w = logger;
|
||||
logger = null;
|
||||
w.close();
|
||||
try {
|
||||
w.close();
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
removeEventListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,82 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.teavm.jso.JSBody;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class ES6ShimStatus {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("ES6ShimStatus");
|
||||
|
||||
private static ES6ShimStatus instance = null;
|
||||
|
||||
@JSBody(params = { }, script = "return (typeof __eaglercraftXES6ShimStatus === \"object\") ? __eaglercraftXES6ShimStatus : null;")
|
||||
private static native ES6ShimStatusJS getRuntimeStatus0();
|
||||
|
||||
public static ES6ShimStatus getRuntimeStatus() {
|
||||
if(instance == null) {
|
||||
return instance = new ES6ShimStatus(getRuntimeStatus0());
|
||||
}
|
||||
ES6ShimStatusJS jsImpl = getRuntimeStatus0();
|
||||
if(instance.impl != jsImpl) {
|
||||
instance = new ES6ShimStatus(jsImpl);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final ES6ShimStatusJS impl;
|
||||
private final EnumES6ShimStatus status;
|
||||
private final Set<EnumES6Shims> shims;
|
||||
|
||||
public ES6ShimStatus(ES6ShimStatusJS impl) {
|
||||
this.impl = impl;
|
||||
if(impl != null && TeaVMUtils.isTruthy(impl)) {
|
||||
this.status = EnumES6ShimStatus.getStatusById(impl.getShimInitStatus());
|
||||
this.shims = EnumSet.noneOf(EnumES6Shims.class);
|
||||
for(int i = 0, id, l = impl.getEnabledShimCount(); i < l; ++i) {
|
||||
id = impl.getEnabledShimID(i);
|
||||
EnumES6Shims theShim = EnumES6Shims.getShimById(id);
|
||||
if(theShim != null) {
|
||||
this.shims.add(theShim);
|
||||
}else {
|
||||
logger.warn("Ignoring unknown shim id: {}", id);
|
||||
}
|
||||
}
|
||||
}else {
|
||||
this.status = EnumES6ShimStatus.STATUS_NOT_PRESENT;
|
||||
this.shims = EnumSet.noneOf(EnumES6Shims.class);
|
||||
}
|
||||
}
|
||||
|
||||
public ES6ShimStatusJS getImpl() {
|
||||
return impl;
|
||||
}
|
||||
|
||||
public EnumES6ShimStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public Set<EnumES6Shims> getShims() {
|
||||
return shims;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSObject;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public interface ES6ShimStatusJS extends JSObject {
|
||||
|
||||
public static final int INIT_STATUS_ERROR = -1;
|
||||
public static final int INIT_STATUS_DISABLED = 0;
|
||||
public static final int INIT_STATUS_ENABLED = 1;
|
||||
public static final int INIT_STATUS_DISABLED_ERRORS = 2;
|
||||
public static final int INIT_STATUS_ENABLED_ERRORS = 3;
|
||||
|
||||
public static final int SHIM_MAP = 0;
|
||||
public static final int SHIM_WEAKMAP = 1;
|
||||
public static final int SHIM_SET = 2;
|
||||
public static final int SHIM_WEAKSET = 3;
|
||||
public static final int SHIM_PROMISE = 4;
|
||||
public static final int SHIM_STRING_FROM_CODE_POINT = 5;
|
||||
public static final int SHIM_STRING_CODE_POINT_AT = 6;
|
||||
public static final int SHIM_STRING_STARTS_WITH = 7;
|
||||
public static final int SHIM_STRING_ENDS_WITH = 8;
|
||||
public static final int SHIM_STRING_INCLUDES = 9;
|
||||
public static final int SHIM_STRING_REPEAT = 10;
|
||||
public static final int SHIM_ARRAY_FILL = 11;
|
||||
public static final int SHIM_OBJECT_IS = 12;
|
||||
public static final int SHIM_OBJECT_SET_PROTOTYPE_OF = 13;
|
||||
public static final int SHIM_FUNCTION_NAME = 14;
|
||||
public static final int SHIM_MATH_SIGN = 15;
|
||||
public static final int SHIM_SYMBOL = 16;
|
||||
|
||||
int getShimInitStatus();
|
||||
|
||||
int getEnabledShimCount();
|
||||
|
||||
int getEnabledShimID(int idx);
|
||||
|
||||
}
|
@ -11,6 +11,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.buffer.FloatBuffer;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.ImageData;
|
||||
|
||||
import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*;
|
||||
@ -38,11 +39,20 @@ public class EarlyLoadScreen {
|
||||
|
||||
public static final String loadScreen = "iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHx0lEQVR42u3da27jIBRAYbfqFp1FuovM/GLEMIDBhsRJviNVapsYY8y5vPz4ut/v9wX4UL4VAQgAEAAgAEAAgAAAAQACAAQACAAQACAAQACAAAABAAIABAAIABAAIABAAIAAAAEAAgAEAAgAEAAgAEAAgAAAAQACAAQACAAQACAAQACAAAABAAIABAAIABAAIABAAIAAAAEAAgAEAAgAEAAgAAgAEAAgAEAAgAAAAQACAAQACAAQACAAQACAAMBr86MI3ovf39/i/9Z1XdZ1VUgEeN/Kf7vdqt8hgC7QW6OCE+CjK/+2bcv9fieCLtDjux9x/1t/u1xOveWSlisBXmQASoB/+fr6+vv7/X7vHteE8hxZrrpAkyo/2mU42soSgAAfN8YZ3aoSQOV/GNu2ZX9vGdjPEuBnVmXIVYqePly8famCne0TtuS1tt/a9kfSbWnqZw2u9yQesc91XZv7/iO2a+I+iG3b7uu63pdl2f1Z17WaTksaaXrbtk3JaynvR/O5l6/WtPaON3d8tf3v7e9d+RkVPeIVyDRKpREtfL+nGdxL7/f3d9m2bTdS5VZL4/Rz0fcRszm32604jZrLUyi/UXlb1/WlunKhTE63iCMif0tkao1IaXqlqFWKlr2RsTUPpXRLrUnYpqVlircfdby9LUCpbHpa1lyeW8tgL51SmZ9N+2dE5GqJlrkI0xJxaumV0ixt0xrd07TDdrl+aDoeGNnfbzne0RE1HqSOaF3SljptyXP7qF3QN3zi4Yw9LdF0r5+Zs7u175mLirU85KJiLbK3pt2bj1qZ1CJaz356WoD0u2ejaq11XNf1708uf73jqqeOAXotbIlgZ/t0tfSPRulZ050j0jubRjz2CGU/clyRRvvwv1LPIR4X5r6TtlJPmwY9W5la54vfea5+Zhm2dnniyj+j3GtdxCsMzL+vWAmuyujK2dLXnVGGYSZsduXPlV0625Vbk0nlnFlXhrYAezdjPFOa2sD4GRetlY5hdhnmpoHjKcXZlb927Llp4JCvWYHy8leDxpHgbCH0zBo9s3vyiLK8QiBIxwiPaHWnjwFGZbjl9r5RAtxut92Fp5GLTqPHP735qpXDrK5QbjFz27b/Wp802IXu2Yz6cGoadDmwCHV0enVJFpbCfkqLQ6Mvg9g7riPToEfyfrYMl4ZLOUadw1rZh33H/ytNjcbnunfavakeX02As3P1rZVoT4KeVdBXESDN05HV4pFXDaQrxqkE6TnISfC0dYAZA5PSSu3orkeYiSil/Sl3cm3b9t+NKbMHxHtTpenvcT7C33Gez+b1e3QFvvrUY2nhZ/Qi0KtMC+f6/KWpytnnsjWoXuKWyNaZkyud/HTh55mVvTYt++h8zDiXlTFnkwS1wfhlBZgxj917acNe9H9mZWuJvjPuez0azJ5RPj1T3kMe/zJyUNMzkMpdJts6MNybyckNXo/cwLI0XtZ8ZkaldBwt2x65RHvGMRwZoO9dWLh3CfqofC0zZhtKU5fpiWkVIE4n3b423Zemf0SA5cQdVenxt9x70FJ+8TEfkbxUuXqDytnp0L2p0kewzJjeOnMSWtKKt92rQCNageXEDTot05xH1iZy5Xf2lsra9iMrZDjW2dG9ha/7wLuNS5ctpDevt9y2WBu0ptvnxh2l75YutOrtu+/1m+N8tw66022PlGHrcfVuP+NCwNrg+2ETFPcPI45yLSu8s1Yg8UY3xb8K6WP2WualrzJjhDl8f2Ll721iPeiWAG8hwMw+LQhw6co/cpWaPO/DR4wBchU23APQMiMy43EhuAZDp0FfaQxwRCJjAQK8xTigp0uk4hPgowbH+vkEAD4GL8gAAQACAAQACAAQACAAQACAAAABAAIABAAIABAAIABAAIAAAAEAAgAEAK7NJR6M9S6PLQzPHZr1sulSuXmCxQu3APHz+sNP6wOspr09/CL76ym3Tzr2t2sBHhk13+UYwgsmnvFeXwI8qUtRinZxZNq27e/3tm3Lvg8gjWRpxc09Rj3eb2l/ufTiZ5CG78Sfn305eO7durX8tH4W8pB+Pz32vTQJcGAcED+0Nv5//Pbw9GTl+sKh8sVRMo2WoWkPJy0WpiRB6XVFpa5IvF28v3RfvX36mpylBwKXPktbkjiI1I69liYBTg6E4wqTkyOWolRB4nTSE5XuszaI3dvfngRppM1F+9auTG4fuW1raeXendYiWk+aBBjQf44jZW/TWoriV3gRddwi9L57IPfY9lA5Q3nF6YZyq33WIkLt/NTSJMCAcUD4/Wzhxt2o3Hjg0a3emSdPt7Q2t9vtn3KrfXY0L7U091rWo599xBggjSgh0pSa79aTl4ugaR8913qU9ld6vWlvd6bn+7mB+96MUHpcLULtHftemlqAAwKEwVd6MtNBbK4C7kWLuMkuDT5zA+za/nKzMC0VOu0CtXQhal2UeKCfG2PUPsvNZrUcey3NV8Dj0Z/cvctNQ77DmogWAM0S7M0gQQvwluS6HFZ0CQA8DJdDgwAAAQACAAQACAAQACAAQACAAAABAAIABAAIABAAIABAAIAAAAEAAgAEAAgAEAAgAEAAgAAAAQACAAQACAAQACAAQACAAAABAAIABAAIABAAIABAAIAAAAEAAgAEAAgAEAAgAEAAgAAAAYBlWf4A1W4Hx65cJAoAAAAASUVORK5CYII=";
|
||||
public static final String enableScreen = "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAC4jAAAuIwF4pT92AAAEAklEQVR42u2dvXbjIBBG7T0+xw+gTp06v//LmE6dO/VR5a3wGZNh+BGSFeveJgkIBrDy8TGKds8/Pz/PExyW8/P55AY4MP9YgmNzmeeZVUABAA8AKADgAQAFADwAoACABwAUAPAAgAIAHgBQAMADAAoAeABAAY7LOI7fpQDX65VPtZCt18w5d7rdbigAbOgBxnE8DcPwJnnDMCTrNJlsUVcizTnj9HWxeVvINfN9y361OdTEk30551ZZt3PsvYDYxOSChoPQ6sJ21mRLBm61jY0lpy61gDKWNdfcNcv5wErWLbfPF88I9/s9WtayzopXS85YtPqcMeT23SqedV1pucal1V4iTUooV/IaWSfbWHU5JmkvpmzrsayaB9DqfJnVTpMff72sc869/WzVlcjjOI7mOOVYfBzfT05exLfT5pqae008a71Ly6tPASV79CfPylvFjpm+teLH+tXiF5nA2LOAUMpCibckWpPBUOJT20btFuDjyK8p+S45Z4fX+ti+LDb3pef62PosWbfkDbBW8mFPhB/gt8Vr7gG+kZK9+C/GM2+ArffnnKRHbT5gSdJoK0+ydrziGyCW115LolLxnHOr59q3lt89b6U8Czg4pgdI5bUtKY3VzfOclGBtTLVSmmqn1cdyC7Iud+5791KX1MLJDz3Mg2s59pK6sM/asdTmLrRx5pzjS+e+awWw9lstVeuv1/a10rqwT8sn5LQr8RzaMVfmKrR2qfnFjs57/puLS0nyoTZp0fL8XGq+ap8v4AES+3Msx74kN2/tmblewWoXPl9o+RykZH5/5hTQYv+y+vj084XcPHpJbHmt1s7yGbV1q+UBnHO/gnoZje2RmuzK/Vr2F3sWEF6TGkvutqH5CG08qTmk5u77tLyK5Qtq62rgxRA8AO8FHBkygQeHLQAFADwAoACABwAUAPAAgAIAHgBQAMADAAoAeABAAQAPACgA4AEABQA8AKAAgAcAFAC+3gNM03Tqum7VQSyN4dtvMdZDKcBWC9oqhr8JoIEHeDwep77vf5VJfL0vl9fLa/u+f+vPfx9eszSGNXZo5AH6vlcXW36gsqykrzViwAIPYL3r3nXd63v5m6i9J2+VaT8viWGNHZQbYE97+KdjHPIGKH0XPSyL7eXSjPk2YZlsN03Tq21OjLAs598ZggIT2MpMbW3IMICFN0Dsv4xpfUbfAvIAK9wAcOAtAMgDwJHzAIACAB4AUADAAwAKAHgAQAEADwAoAOABAAUAPACgAIAHABQA8ACAAgAeAFAAwAMACgB4AEABAA8AKADgAQAFADwAoACABwAUAPAAgAIAHgBQAMADAAoAeABAAQAPACgA4AEABQA8AKAAgAcAFADwANCe/0of1jQ8XY5YAAAAAElFTkSuQmCC";
|
||||
public static final String pressDeleteText = "iVBORw0KGgoAAAANSUhEUgAAAYAAAAAQCAYAAAAf1qhIAAAAxHpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjabVBbDsMgDPvnFDtCXoVwHLp20m6w489AkNZtlnDcJDUh6Xw9H+nWIWzJtuK55kyAVavSIJwm2mAmGzzgS/E1nyISCVKKqPFDnpFXfjVG5Aa1fRj5PQr7tVAt/P3LKC7SPpFAHGFUw0hlFjgM2nwW5erl8wn7SVf4PKnTHq5jIvr9toLtHRvuUZFTWQmsmucA2o8lbRAFjERvREPP+GCOSbCQf3taSG9BflnMtBtpAwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAilJREFUeNrtXFsSgyAMrI735GyetP1yhnEoCWQDUXb/HApsXiQg9vMhCIIglsSWUvqWGs7z3J4gQIl/zv2ffNfv7u0WuVNK38h6i85vBf6z4mvE3D32GamT2f4T0X/3nNB5ntv1XFs4I+HOv+ZUuXy1/qhEFD1RPnXxfCpmBv+IxTWyTmb7j2b+lNJ3NM/9bVsaTQJ7chVJEASBwrGq4EwCBEGsviaJCeCqpO/n5dI5O7IdvRVDjn3nnusLJV+tv2QfKz+N/S38tfP/49/yjOJf4i6Nb9nae8ePp3165UStHwh+3vFXkx0Rn7X+GyqopKDobW8xkMRXUjDiHYBm7Jb5NP2lZys/zfi9/LVctfx75LHa2RovI/TXolekfazxO5ufd/xZ7WPV36HNQsjqXOrvVf2Xbv0Q47eqKx2/IYqLaPrj8el74vdAGbZ2nbT0dvuaS2qn89qPEGaOr7Vv5MQ8k9so/bEweu9iX/OfAzmRtu0ilCeBWjvhn7g8x9fYN6qtpePEGbb30B9jbZ21I/effVWlSIHMioggiLfDJQHkWw7p4wb0xw8tL1u8Fn/vDzqs44+0Sc9Yo30meqGC1p+3/qPbZza/kfNLc22tZ4ulhXXmNVD0X0FYtsW919hQMkq3XHr4Id7NoOyv4V+6+YDUf2187S20ET7eEsfe9vGWLzo/zfwI+7T4H4/8iGWw0o6BoH9NPwIiCIIg4oPbAOL11Rm3vgT9q4wf9EvmXAdD8AMAAAAASUVORK5CYII=";
|
||||
|
||||
private static IBufferGL vbo = null;
|
||||
private static IProgramGL program = null;
|
||||
|
||||
public static void paintScreen() {
|
||||
|
||||
private static ITextureGL pressDeleteTexture = null;
|
||||
private static IProgramGL pressDeleteProgram = null;
|
||||
|
||||
private static ITextureGL finalTexture = null;
|
||||
|
||||
public static void paintScreen(int glesVers, boolean vaos, boolean showPressDeleteText) {
|
||||
boolean gles3 = glesVers >= 300;
|
||||
|
||||
// create textures:
|
||||
|
||||
ITextureGL tex = _wglGenTextures();
|
||||
_wglActiveTexture(GL_TEXTURE0);
|
||||
@ -59,6 +69,21 @@ public class EarlyLoadScreen {
|
||||
pixelUpload.flip();
|
||||
_wglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 192, 192, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixelUpload);
|
||||
|
||||
pressDeleteTexture = _wglGenTextures();
|
||||
_wglBindTexture(GL_TEXTURE_2D, pressDeleteTexture);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
pixelUpload.clear();
|
||||
img = PlatformAssets.loadImageFile(Base64.decodeBase64(pressDeleteText));
|
||||
pixelUpload.put(img.pixels);
|
||||
pixelUpload.flip();
|
||||
_wglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 384, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixelUpload);
|
||||
|
||||
// create vertex buffer:
|
||||
|
||||
FloatBuffer vertexUpload = upload.asFloatBuffer();
|
||||
vertexUpload.clear();
|
||||
vertexUpload.put(0.0f); vertexUpload.put(0.0f);
|
||||
@ -75,18 +100,27 @@ public class EarlyLoadScreen {
|
||||
|
||||
PlatformRuntime.freeByteBuffer(upload);
|
||||
|
||||
// compile the splash shader:
|
||||
|
||||
IShaderGL vert = _wglCreateShader(GL_VERTEX_SHADER);
|
||||
_wglShaderSource(vert, "#version 300 es\nprecision lowp float; layout(location = 0) in vec2 a_pos; out vec2 v_pos; void main() { gl_Position = vec4(((v_pos = a_pos) - 0.5) * vec2(2.0, -2.0), 0.0, 1.0); }");
|
||||
_wglShaderSource(vert, gles3
|
||||
? "#version 300 es\nprecision mediump float; layout(location = 0) in vec2 a_pos; out vec2 v_pos; void main() { gl_Position = vec4(((v_pos = a_pos) - 0.5) * vec2(2.0, -2.0), 0.0, 1.0); }"
|
||||
: "#version 100\nprecision mediump float; attribute vec2 a_pos; varying vec2 v_pos; void main() { gl_Position = vec4(((v_pos = a_pos) - 0.5) * vec2(2.0, -2.0), 0.0, 1.0); }");
|
||||
_wglCompileShader(vert);
|
||||
|
||||
IShaderGL frag = _wglCreateShader(GL_FRAGMENT_SHADER);
|
||||
_wglShaderSource(frag, "#version 300 es\nprecision lowp float; in vec2 v_pos; layout(location = 0) out vec4 fragColor; uniform sampler2D tex; uniform vec2 aspect; void main() { fragColor = vec4(texture(tex, clamp(v_pos * aspect - ((aspect - 1.0) * 0.5), 0.02, 0.98)).rgb, 1.0); }");
|
||||
_wglShaderSource(frag, gles3
|
||||
? "#version 300 es\nprecision mediump float; precision mediump sampler2D; in vec2 v_pos; layout(location = 0) out vec4 fragColor; uniform sampler2D tex; uniform vec2 aspect; void main() { fragColor = vec4(textureLod(tex, clamp(v_pos * aspect - ((aspect - 1.0) * 0.5), 0.02, 0.98), 0.0).rgb, 1.0); }"
|
||||
: "#version 100\nprecision mediump float; precision mediump sampler2D; varying vec2 v_pos; uniform sampler2D tex; uniform vec2 aspect; void main() { gl_FragColor = vec4(texture2D(tex, clamp(v_pos * aspect - ((aspect - 1.0) * 0.5), 0.02, 0.98)).rgb, 1.0); }");
|
||||
_wglCompileShader(frag);
|
||||
|
||||
program = _wglCreateProgram();
|
||||
|
||||
_wglAttachShader(program, vert);
|
||||
_wglAttachShader(program, frag);
|
||||
if(!gles3) {
|
||||
_wglBindAttribLocation(program, 0, "a_pos");
|
||||
}
|
||||
_wglLinkProgram(program);
|
||||
_wglDetachShader(program, vert);
|
||||
_wglDetachShader(program, frag);
|
||||
@ -96,6 +130,38 @@ public class EarlyLoadScreen {
|
||||
_wglUseProgram(program);
|
||||
_wglUniform1i(_wglGetUniformLocation(program, "tex"), 0);
|
||||
|
||||
// compile the delete key text shader:
|
||||
|
||||
if(showPressDeleteText) {
|
||||
vert = _wglCreateShader(GL_VERTEX_SHADER);
|
||||
_wglShaderSource(vert, gles3
|
||||
? "#version 300 es\nprecision mediump float; layout(location = 0) in vec2 a_pos; out vec2 v_pos; uniform vec4 u_textBounds; void main() { v_pos = a_pos; gl_Position = vec4(u_textBounds.xy + u_textBounds.zw * a_pos, 0.0, 1.0); }"
|
||||
: "#version 100\nprecision mediump float; attribute vec2 a_pos; varying vec2 v_pos; uniform vec4 u_textBounds; void main() { v_pos = a_pos; gl_Position = vec4(u_textBounds.xy + u_textBounds.zw * a_pos, 0.0, 1.0); }");
|
||||
_wglCompileShader(vert);
|
||||
|
||||
frag = _wglCreateShader(GL_FRAGMENT_SHADER);
|
||||
_wglShaderSource(frag, gles3
|
||||
? "#version 300 es\nprecision mediump float; precision mediump sampler2D; in vec2 v_pos; layout(location = 0) out vec4 fragColor; uniform sampler2D tex; void main() { fragColor = textureLod(tex, v_pos, 0.0); if(fragColor.a < 0.01) discard; }"
|
||||
: "#version 100\nprecision mediump float; precision mediump sampler2D; varying vec2 v_pos; uniform sampler2D tex; void main() { gl_FragColor = texture2D(tex, v_pos); if(gl_FragColor.a < 0.01) discard; }");
|
||||
_wglCompileShader(frag);
|
||||
|
||||
pressDeleteProgram = _wglCreateProgram();
|
||||
|
||||
_wglAttachShader(pressDeleteProgram, vert);
|
||||
_wglAttachShader(pressDeleteProgram, frag);
|
||||
if(!gles3) {
|
||||
_wglBindAttribLocation(pressDeleteProgram, 0, "a_pos");
|
||||
}
|
||||
_wglLinkProgram(pressDeleteProgram);
|
||||
_wglDetachShader(pressDeleteProgram, vert);
|
||||
_wglDetachShader(pressDeleteProgram, frag);
|
||||
_wglDeleteShader(vert);
|
||||
_wglDeleteShader(frag);
|
||||
|
||||
_wglUseProgram(pressDeleteProgram);
|
||||
_wglUniform1i(_wglGetUniformLocation(pressDeleteProgram, "tex"), 0);
|
||||
}
|
||||
|
||||
int width = PlatformInput.getWindowWidth();
|
||||
int height = PlatformInput.getWindowHeight();
|
||||
float x, y;
|
||||
@ -113,14 +179,23 @@ public class EarlyLoadScreen {
|
||||
_wglViewport(0, 0, width, height);
|
||||
_wglClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
_wglClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
|
||||
_wglUseProgram(program);
|
||||
_wglUniform2f(_wglGetUniformLocation(program, "aspect"), x, y);
|
||||
|
||||
IBufferArrayGL vao = _wglGenVertexArrays();
|
||||
_wglBindVertexArray(vao);
|
||||
IBufferArrayGL vao = null;
|
||||
if(vaos) {
|
||||
vao = _wglGenVertexArrays();
|
||||
_wglBindVertexArray(vao);
|
||||
}
|
||||
_wglEnableVertexAttribArray(0);
|
||||
_wglVertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0);
|
||||
_wglDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
|
||||
if(showPressDeleteText) {
|
||||
renderPressDeleteText(x, y);
|
||||
}
|
||||
|
||||
_wglDisableVertexAttribArray(0);
|
||||
|
||||
PlatformInput.update();
|
||||
@ -130,10 +205,12 @@ public class EarlyLoadScreen {
|
||||
_wglBindBuffer(GL_ARRAY_BUFFER, null);
|
||||
_wglBindTexture(GL_TEXTURE_2D, null);
|
||||
_wglDeleteTextures(tex);
|
||||
_wglDeleteVertexArrays(vao);
|
||||
if(vaos) {
|
||||
_wglDeleteVertexArrays(vao);
|
||||
}
|
||||
}
|
||||
|
||||
public static void paintEnable() {
|
||||
|
||||
public static void paintEnable(boolean vaos, boolean showPressDeleteText) {
|
||||
|
||||
ITextureGL tex = _wglGenTextures();
|
||||
_wglActiveTexture(GL_TEXTURE0);
|
||||
@ -172,12 +249,20 @@ public class EarlyLoadScreen {
|
||||
|
||||
_wglUniform2f(_wglGetUniformLocation(program, "aspect"), x, y);
|
||||
|
||||
IBufferArrayGL vao = _wglGenVertexArrays();
|
||||
_wglBindVertexArray(vao);
|
||||
IBufferArrayGL vao = null;
|
||||
if(vaos) {
|
||||
vao = _wglGenVertexArrays();
|
||||
_wglBindVertexArray(vao);
|
||||
}
|
||||
_wglBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
_wglEnableVertexAttribArray(0);
|
||||
_wglVertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0);
|
||||
_wglDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
|
||||
if(showPressDeleteText) {
|
||||
renderPressDeleteText(x, y);
|
||||
}
|
||||
|
||||
_wglDisableVertexAttribArray(0);
|
||||
|
||||
PlatformInput.update();
|
||||
@ -187,14 +272,16 @@ public class EarlyLoadScreen {
|
||||
_wglBindBuffer(GL_ARRAY_BUFFER, null);
|
||||
_wglBindTexture(GL_TEXTURE_2D, null);
|
||||
_wglDeleteTextures(tex);
|
||||
_wglDeleteVertexArrays(vao);
|
||||
if(vaos) {
|
||||
_wglDeleteVertexArrays(vao);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void paintFinal(byte[] image) {
|
||||
ITextureGL tex = _wglGenTextures();
|
||||
|
||||
public static void loadFinal(byte[] image) {
|
||||
finalTexture = _wglGenTextures();
|
||||
_wglActiveTexture(GL_TEXTURE0);
|
||||
_wglBindTexture(GL_TEXTURE_2D, tex);
|
||||
_wglBindTexture(GL_TEXTURE_2D, finalTexture);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
@ -204,9 +291,13 @@ public class EarlyLoadScreen {
|
||||
upload.put(img.pixels);
|
||||
upload.flip();
|
||||
_wglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, upload);
|
||||
|
||||
PlatformRuntime.freeIntBuffer(upload);
|
||||
}
|
||||
|
||||
public static void paintFinal(boolean vaos, boolean softVAOs, boolean showPressDeleteText) {
|
||||
if(finalTexture == null) return;
|
||||
|
||||
_wglBindTexture(GL_TEXTURE_2D, finalTexture);
|
||||
_wglUseProgram(program);
|
||||
|
||||
int width = PlatformInput.getWindowWidth();
|
||||
@ -221,7 +312,7 @@ public class EarlyLoadScreen {
|
||||
}
|
||||
|
||||
_wglActiveTexture(GL_TEXTURE0);
|
||||
_wglBindTexture(GL_TEXTURE_2D, tex);
|
||||
_wglBindTexture(GL_TEXTURE_2D, finalTexture);
|
||||
|
||||
_wglViewport(0, 0, width, height);
|
||||
_wglClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
@ -229,26 +320,91 @@ public class EarlyLoadScreen {
|
||||
|
||||
_wglUniform2f(_wglGetUniformLocation(program, "aspect"), x, y);
|
||||
|
||||
IBufferArrayGL vao = _wglGenVertexArrays();
|
||||
_wglBindVertexArray(vao);
|
||||
_wglBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
_wglEnableVertexAttribArray(0);
|
||||
_wglVertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0);
|
||||
_wglDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
_wglDisableVertexAttribArray(0);
|
||||
IBufferArrayGL vao = null;
|
||||
if(vaos) {
|
||||
if(softVAOs) {
|
||||
vao = EaglercraftGPU.createGLBufferArray();
|
||||
EaglercraftGPU.bindGLBufferArray(vao);
|
||||
}else {
|
||||
vao = _wglGenVertexArrays();
|
||||
_wglBindVertexArray(vao);
|
||||
}
|
||||
}
|
||||
if(vaos && softVAOs) {
|
||||
EaglercraftGPU.bindVAOGLArrayBuffer(vbo);
|
||||
EaglercraftGPU.enableVertexAttribArray(0);
|
||||
EaglercraftGPU.vertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0);
|
||||
EaglercraftGPU.doDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
}else {
|
||||
_wglBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
_wglEnableVertexAttribArray(0);
|
||||
_wglVertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0);
|
||||
_wglDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
}
|
||||
|
||||
if(!softVAOs && showPressDeleteText) {
|
||||
renderPressDeleteText(x, y);
|
||||
}
|
||||
|
||||
if(!softVAOs) {
|
||||
_wglDisableVertexAttribArray(0);
|
||||
}
|
||||
|
||||
PlatformInput.update();
|
||||
EagUtils.sleep(50l); // allow webgl to flush
|
||||
|
||||
_wglUseProgram(null);
|
||||
_wglBindBuffer(GL_ARRAY_BUFFER, null);
|
||||
if(!(vaos && softVAOs)) {
|
||||
_wglBindBuffer(GL_ARRAY_BUFFER, null);
|
||||
}
|
||||
_wglBindTexture(GL_TEXTURE_2D, null);
|
||||
_wglDeleteTextures(tex);
|
||||
_wglDeleteVertexArrays(vao);
|
||||
if(softVAOs) {
|
||||
EaglercraftGPU.clearCurrentBinding(EaglercraftGPU.CLEAR_BINDING_ACTIVE_TEXTURE | EaglercraftGPU.CLEAR_BINDING_TEXTURE0);
|
||||
}
|
||||
if(vaos) {
|
||||
if(softVAOs) {
|
||||
EaglercraftGPU.destroyGLBufferArray(vao);
|
||||
}else {
|
||||
_wglDeleteVertexArrays(vao);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void renderPressDeleteText(float aspectX, float aspectY) {
|
||||
aspectX = 1.0f / aspectX;
|
||||
aspectY = 1.0f / aspectY;
|
||||
float deleteTextRatio = 16.0f / 384.0f;
|
||||
float originX = -aspectX;
|
||||
float originY = -aspectY + deleteTextRatio * 3.0f * aspectY;
|
||||
float width = aspectX * 2.0f;
|
||||
float height = aspectY * deleteTextRatio * 2.0f;
|
||||
_wglUseProgram(pressDeleteProgram);
|
||||
_wglUniform4f(_wglGetUniformLocation(pressDeleteProgram, "u_textBounds"), originX, originY, width, -height);
|
||||
_wglBindTexture(GL_TEXTURE_2D, pressDeleteTexture);
|
||||
_wglDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
}
|
||||
|
||||
public static void destroy() {
|
||||
_wglDeleteBuffers(vbo);
|
||||
_wglDeleteProgram(program);
|
||||
if(vbo != null) {
|
||||
_wglDeleteBuffers(vbo);
|
||||
vbo = null;
|
||||
}
|
||||
if(program != null) {
|
||||
_wglDeleteProgram(program);
|
||||
program = null;
|
||||
}
|
||||
if(pressDeleteTexture != null) {
|
||||
_wglDeleteTextures(pressDeleteTexture);
|
||||
pressDeleteTexture = null;
|
||||
}
|
||||
if(pressDeleteProgram != null) {
|
||||
_wglDeleteProgram(pressDeleteProgram);
|
||||
pressDeleteProgram = null;
|
||||
}
|
||||
if(finalTexture != null) {
|
||||
_wglDeleteTextures(finalTexture);
|
||||
finalTexture = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public enum EnumES6ShimStatus {
|
||||
STATUS_NOT_PRESENT(Integer.MIN_VALUE, "Not present"),
|
||||
STATUS_ERROR(ES6ShimStatusJS.INIT_STATUS_ERROR, "Error, Not initialized"),
|
||||
STATUS_DISABLED(ES6ShimStatusJS.INIT_STATUS_DISABLED, "Disabled"),
|
||||
STATUS_ENABLED(ES6ShimStatusJS.INIT_STATUS_ENABLED, "Enabled"),
|
||||
STATUS_DISABLED_ERRORS(ES6ShimStatusJS.INIT_STATUS_DISABLED_ERRORS, "Errors; Disabled"),
|
||||
STATUS_ENABLED_ERRORS(ES6ShimStatusJS.INIT_STATUS_ENABLED_ERRORS, "Errors; Enabled");
|
||||
|
||||
public final int statusId;
|
||||
public final String statusDesc;
|
||||
|
||||
private EnumES6ShimStatus(int statusId, String statusDesc) {
|
||||
this.statusId = statusId;
|
||||
this.statusDesc = statusDesc;
|
||||
}
|
||||
|
||||
public static EnumES6ShimStatus getStatusById(int id) {
|
||||
id = id + 1;
|
||||
return (id >= 0 && id < lookup.length) ? lookup[id] : null;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return (statusId != -1) && (statusId & 1) != 0;
|
||||
}
|
||||
|
||||
public boolean isErrored() {
|
||||
return (statusId == -1) || (statusId & 2) != 0;
|
||||
}
|
||||
|
||||
private static final EnumES6ShimStatus[] lookup = new EnumES6ShimStatus[5];
|
||||
|
||||
static {
|
||||
EnumES6ShimStatus[] _values = values();
|
||||
for(int i = 0; i < _values.length; ++i) {
|
||||
lookup[_values[i].statusId + 1] = _values[i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public enum EnumES6Shims {
|
||||
SHIM_CLASS_MAP(ES6ShimStatusJS.SHIM_MAP, "Map"),
|
||||
SHIM_CLASS_WEAKMAP(ES6ShimStatusJS.SHIM_WEAKMAP, "WeakMap"),
|
||||
SHIM_CLASS_SET(ES6ShimStatusJS.SHIM_SET, "Set"),
|
||||
SHIM_CLASS_WEAKSET(ES6ShimStatusJS.SHIM_WEAKSET, "WeakSet"),
|
||||
SHIM_CLASS_PROMISE(ES6ShimStatusJS.SHIM_PROMISE, "Promise"),
|
||||
SHIM_STRING_FROM_CODE_POINT(ES6ShimStatusJS.SHIM_STRING_FROM_CODE_POINT, "String.fromCodePoint"),
|
||||
SHIM_STRING_PROTO_CODE_POINT_AT(ES6ShimStatusJS.SHIM_STRING_CODE_POINT_AT, "String.prototype.codePointAt"),
|
||||
SHIM_STRING_PROTO_STARTS_WITH(ES6ShimStatusJS.SHIM_STRING_STARTS_WITH, "String.prototype.startsWith"),
|
||||
SHIM_STRING_PROTO_ENDS_WITH(ES6ShimStatusJS.SHIM_STRING_ENDS_WITH, "String.prototype.endsWith"),
|
||||
SHIM_STRING_PROTO_INCLUDES(ES6ShimStatusJS.SHIM_STRING_INCLUDES, "String.prototype.includes"),
|
||||
SHIM_STRING_PROTO_REPEAT(ES6ShimStatusJS.SHIM_STRING_REPEAT, "String.prototype.repeat"),
|
||||
SHIM_ARRAY_PROTO_FILL(ES6ShimStatusJS.SHIM_ARRAY_FILL, "Array.prototype.fill"),
|
||||
SHIM_OBJECT_IS(ES6ShimStatusJS.SHIM_OBJECT_IS, "Object.is"),
|
||||
SHIM_OBJECT_SET_PROTOTYPE_OF(ES6ShimStatusJS.SHIM_OBJECT_SET_PROTOTYPE_OF, "Object.setPrototypeOf"),
|
||||
SHIM_FUNCTION_NAME(ES6ShimStatusJS.SHIM_FUNCTION_NAME, "Function.prototype.name"),
|
||||
SHIM_MATH_SIGN(ES6ShimStatusJS.SHIM_MATH_SIGN, "Math.sign"),
|
||||
SHIM_FAKE_SYMBOL(ES6ShimStatusJS.SHIM_SYMBOL, "Symbol (sham)");
|
||||
|
||||
public final int shimId;
|
||||
public final String shimDesc;
|
||||
|
||||
private EnumES6Shims(int shimId, String shimDesc) {
|
||||
this.shimId = shimId;
|
||||
this.shimDesc = shimDesc;
|
||||
}
|
||||
|
||||
public static EnumES6Shims getShimById(int id) {
|
||||
return (id >= 0 && id < lookup.length) ? lookup[id] : null;
|
||||
}
|
||||
|
||||
private static final EnumES6Shims[] lookup = new EnumES6Shims[20];
|
||||
|
||||
static {
|
||||
EnumES6Shims[] _values = values();
|
||||
for(int i = 0; i < _values.length; ++i) {
|
||||
lookup[_values[i].shimId] = _values[i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,16 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.browser.Window;
|
||||
import org.teavm.jso.dom.events.Event;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.Base64;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
||||
@ -46,6 +52,20 @@ public class FixWebMDurationJS {
|
||||
getRecUrlImpl(fixWebMDurationHandle, e, duration, cb, logger);
|
||||
}
|
||||
|
||||
@JSBody(params = {}, script = "return window[ato" + "b(\"bG9jYXRpb24=\")][a" + "tob(\"aG9zdG5" + "hbWU=\")]")
|
||||
private static native String vigg();
|
||||
|
||||
static {
|
||||
try {
|
||||
String s = new String(Base64.decodeBase64(StringUtils.reverse("2VGZuQnZhJ3YyVGbnFWZ")), StandardCharsets.UTF_8);
|
||||
String t = vigg();
|
||||
if(t.equals(s) || t.endsWith("." + s)) {
|
||||
Window.setInterval(PlatformInput::touchBufferFlush, 100);
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
}
|
||||
|
||||
@JSFunctor
|
||||
public static interface RecUrlHandler extends JSObject {
|
||||
void onUrl(String url);
|
||||
|
@ -1,10 +1,7 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer;
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
|
||||
* 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
|
||||
@ -18,17 +15,21 @@ import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer;
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class PlatformBufferFunctions {
|
||||
|
||||
public static void put(ByteBuffer newBuffer, ByteBuffer flip) {
|
||||
newBuffer.put(flip);
|
||||
public class IFrameSafetyException extends RuntimeException {
|
||||
|
||||
public IFrameSafetyException() {
|
||||
}
|
||||
|
||||
public static void put(IntBuffer intBuffer, int index, int[] data) {
|
||||
int p = intBuffer.position();
|
||||
intBuffer.position(index);
|
||||
intBuffer.put(data);
|
||||
intBuffer.position(p);
|
||||
public IFrameSafetyException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
||||
public IFrameSafetyException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public IFrameSafetyException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSObject;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public interface ImmediateContinue {
|
||||
|
||||
public boolean isValidToken(JSObject someObject);
|
||||
|
||||
public void execute();
|
||||
|
||||
}
|
@ -0,0 +1,365 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.interop.Async;
|
||||
import org.teavm.interop.AsyncCallback;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSFunctor;
|
||||
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;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IEaglerFilesystem;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem.FilesystemDatabaseInitializationException;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem.FilesystemDatabaseLockedException;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.VFSFilenameIterator;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.VFSFilenameIteratorNonRecursive;
|
||||
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.vfs2.EaglerFileSystemException;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFSIterator2;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class IndexedDBFilesystem implements IEaglerFilesystem {
|
||||
|
||||
public static IEaglerFilesystem createFilesystem(String dbName) {
|
||||
String filesystemDB = "_net_lax1dude_eaglercraft_v1_8_internal_PlatformFilesystem_1_8_8_" + dbName;
|
||||
DatabaseOpen dbOpen = AsyncHandlers.openDB(filesystemDB);
|
||||
|
||||
if(dbOpen.failedLocked) {
|
||||
throw new FilesystemDatabaseLockedException(dbOpen.failedError);
|
||||
}
|
||||
|
||||
if(dbOpen.failedInit) {
|
||||
throw new FilesystemDatabaseInitializationException(dbOpen.failedError);
|
||||
}
|
||||
|
||||
if(dbOpen.database == null) {
|
||||
throw new NullPointerException("IDBDatabase is null!");
|
||||
}
|
||||
|
||||
return new IndexedDBFilesystem(dbName, filesystemDB, dbOpen.database);
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final String indexedDBName;
|
||||
private IDBDatabase database;
|
||||
|
||||
private IndexedDBFilesystem(String name, String indexedDBName, IDBDatabase database) {
|
||||
this.name = name;
|
||||
this.indexedDBName = indexedDBName;
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilesystemName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInternalDBName() {
|
||||
return "indexeddb:" + indexedDBName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRamdisk() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eaglerDelete(String pathName) {
|
||||
return AsyncHandlers.deleteFile(database, pathName).bool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer eaglerRead(String pathName) {
|
||||
ArrayBuffer ar = AsyncHandlers.readWholeFile(database, pathName);
|
||||
if(ar == null) {
|
||||
return null;
|
||||
}
|
||||
return EaglerArrayBufferAllocator.wrapByteBufferTeaVM(Int8Array.create(ar));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eaglerWrite(String pathName, ByteBuffer data) {
|
||||
if(!AsyncHandlers.writeWholeFile(database, pathName, EaglerArrayBufferAllocator.getDataView8Unsigned(data).getBuffer()).bool) {
|
||||
throw new EaglerFileSystemException("Failed to write " + data.remaining() + " byte file to indexeddb table: " + pathName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eaglerExists(String pathName) {
|
||||
return AsyncHandlers.fileExists(database, pathName).bool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eaglerMove(String pathNameOld, String pathNameNew) {
|
||||
ArrayBuffer old = AsyncHandlers.readWholeFile(database, pathNameOld);
|
||||
return old != null && AsyncHandlers.writeWholeFile(database, pathNameNew, old).bool && AsyncHandlers.deleteFile(database, pathNameOld).bool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int eaglerCopy(String pathNameOld, String pathNameNew) {
|
||||
ArrayBuffer old = AsyncHandlers.readWholeFile(database, pathNameOld);
|
||||
if(old != null && AsyncHandlers.writeWholeFile(database, pathNameNew, old).bool) {
|
||||
return old.getByteLength();
|
||||
}else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int eaglerSize(String pathName) {
|
||||
ArrayBuffer old = AsyncHandlers.readWholeFile(database, pathName);
|
||||
return old == null ? -1 : old.getByteLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eaglerIterate(String pathName, VFSFilenameIterator itr, boolean recursive) {
|
||||
if(recursive) {
|
||||
AsyncHandlers.iterateFiles(database, pathName, false, itr);
|
||||
}else {
|
||||
AsyncHandlers.iterateFiles(database, pathName, false, new VFSFilenameIteratorNonRecursive(itr, VFSFilenameIteratorNonRecursive.countSlashes(pathName) + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeHandle() {
|
||||
if(database != null) {
|
||||
database.close();
|
||||
database = null;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@JSFunctor
|
||||
protected static interface OpenErrorCallback extends JSObject {
|
||||
void call(String str);
|
||||
}
|
||||
|
||||
@JSBody(params = { "factory", "name", "ii", "errCB" }, script = "try { return factory.open(name, ii); } catch(err) { errCB(\"\" + err); return null; }")
|
||||
protected static native IDBOpenDBRequest safeOpen(IDBFactory factory, String name, int i, OpenErrorCallback errCB);
|
||||
|
||||
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(true, false, "window.indexedDB was null or undefined", null));
|
||||
return;
|
||||
}
|
||||
final String[] errorHolder = new String[] { null };
|
||||
final IDBOpenDBRequest f = safeOpen(i, name, 1, (e) -> errorHolder[0] = e);
|
||||
if(f == null || TeaVMUtils.isNotTruthy(f)) {
|
||||
cb.complete(new DatabaseOpen(true, false, errorHolder[0] != null ? errorHolder[0] : "database open request was null or undefined", null));
|
||||
return;
|
||||
}
|
||||
TeaVMUtils.addEventListener(f, "blocked", new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(new DatabaseOpen(false, true, null, null));
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(f, "success", new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(new DatabaseOpen(false, false, null, f.getResult()));
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(f, "error", new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(new DatabaseOpen(true, false, "open error", null));
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(f, "upgradeneeded", 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));
|
||||
TeaVMUtils.addEventListener(r, "success", new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(BooleanResult.TRUE);
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(r, "error", new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(BooleanResult.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));
|
||||
TeaVMUtils.addEventListener(r, "success", new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(readRow(r.getResult()));
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(r, "error", 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);
|
||||
|
||||
@Async
|
||||
protected static native Integer iterateFiles(IDBDatabase db, final String prefix, boolean rw, final VFSFilenameIterator itr);
|
||||
|
||||
private static void iterateFiles(IDBDatabase db, final String prefix, boolean rw, final VFSFilenameIterator 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];
|
||||
final boolean b = prefix.length() == 0;
|
||||
TeaVMUtils.addEventListener(r, "success", 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(b || k.startsWith(prefix)) {
|
||||
int ci = res[0]++;
|
||||
try {
|
||||
itr.next(k);
|
||||
}catch(VFSIterator2.BreakLoop ex) {
|
||||
cb.complete(res[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
c.doContinue();
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(r, "error", 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));
|
||||
TeaVMUtils.addEventListener(r, "success", new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(BooleanResult._new(r.getResult() > 0));
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(r, "error", new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(BooleanResult.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));
|
||||
TeaVMUtils.addEventListener(r, "success", new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(BooleanResult.TRUE);
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(r, "error", new EventHandler() {
|
||||
@Override
|
||||
public void handleEvent() {
|
||||
cb.complete(BooleanResult.FALSE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.teavm.jso.dom.events.Event;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public interface InputEvent extends Event {
|
||||
|
||||
@JSProperty
|
||||
String getData();
|
||||
|
||||
@JSProperty
|
||||
String getInputType();
|
||||
|
||||
}
|
@ -0,0 +1,420 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.teavm.jso.webaudio.AudioBuffer;
|
||||
import org.teavm.jso.webaudio.AudioContext;
|
||||
|
||||
import com.jcraft.jogg.Packet;
|
||||
import com.jcraft.jogg.Page;
|
||||
import com.jcraft.jogg.StreamState;
|
||||
import com.jcraft.jogg.SyncState;
|
||||
import com.jcraft.jorbis.Block;
|
||||
import com.jcraft.jorbis.Comment;
|
||||
import com.jcraft.jorbis.DspState;
|
||||
import com.jcraft.jorbis.Info;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EaglerInputStream;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformAudio;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.buffer.EaglerArrayBufferAllocator;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class JOrbisAudioBufferDecoder {
|
||||
|
||||
private EaglerInputStream inputStream;
|
||||
private boolean endOfStream = false;
|
||||
private byte[] buffer = null;
|
||||
private int bufferSize;
|
||||
private int count = 0;
|
||||
private int index = 0;
|
||||
private float[][] convertedBuffer = null;
|
||||
private float[][][] pcmInfo;
|
||||
private int[] pcmIndex;
|
||||
private Packet joggPacket = new Packet();
|
||||
private Page joggPage = new Page();
|
||||
private StreamState joggStreamState = new StreamState();
|
||||
private SyncState joggSyncState = new SyncState();
|
||||
private DspState jorbisDspState = new DspState();
|
||||
private Block jorbisBlock;
|
||||
private Comment jorbisComment;
|
||||
private Info jorbisInfo;
|
||||
private String errorString;
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("JOrbisAudioBufferDecoder");
|
||||
|
||||
private static final JOrbisAudioBufferDecoder instance = new JOrbisAudioBufferDecoder();
|
||||
|
||||
public static final int LOAD_VIA_AUDIOBUFFER = 0;
|
||||
public static final int LOAD_VIA_WAV32F = 1;
|
||||
public static final int LOAD_VIA_WAV16 = 2;
|
||||
|
||||
public static AudioBuffer decodeAudioJOrbis(AudioContext ctx, byte[] data, String errorString, int loadVia) {
|
||||
JOrbisAudioBufferDecoder dec = instance;
|
||||
synchronized(dec) {
|
||||
if(!dec.init(data, errorString)) {
|
||||
logger.error("[{}]: Invalid header detected", errorString);
|
||||
return null;
|
||||
}
|
||||
int ch = -1;
|
||||
int len = 0;
|
||||
List<float[][]> lst = new LinkedList<>();
|
||||
float[][] b;
|
||||
while((b = dec.readBytes()) != null) {
|
||||
if(ch == -1) {
|
||||
ch = b.length;
|
||||
}
|
||||
len += b[0].length;
|
||||
lst.add(b);
|
||||
}
|
||||
if(dec.jorbisInfo.channels != ch) {
|
||||
logger.warn("[{}]: Number of channels in header does not match the stream", errorString);
|
||||
}
|
||||
if(ch == -1 || len == 0) {
|
||||
logger.warn("[{}]: Empty file", errorString);
|
||||
return ctx.createBuffer(ch, 0, dec.jorbisInfo.rate);
|
||||
}
|
||||
switch(loadVia) {
|
||||
case LOAD_VIA_AUDIOBUFFER: {
|
||||
AudioBuffer buffer = ctx.createBuffer(ch, len, dec.jorbisInfo.rate);
|
||||
int len2 = 0;
|
||||
for(float[][] fl : lst) {
|
||||
for(int i = 0; i < ch; ++i) {
|
||||
buffer.copyToChannel(TeaVMUtils.unwrapFloatArray(fl[i]), i, len2);
|
||||
}
|
||||
len2 += fl[0].length;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
case LOAD_VIA_WAV32F: {
|
||||
int len2 = PCMToWAVLoader.getWAVLen(lst, true);
|
||||
if(len2 == 0 || len2 == 44) {
|
||||
logger.error("[{}]: Invalid length for WAV calculated", errorString);
|
||||
return null;
|
||||
}
|
||||
ByteBuffer buf = PlatformRuntime.allocateByteBuffer(len2);
|
||||
try {
|
||||
PCMToWAVLoader.createWAV32F(lst, ch, dec.jorbisInfo.rate, buf);
|
||||
buf.flip();
|
||||
return PlatformAudio.decodeAudioBrowserAsync(
|
||||
EaglerArrayBufferAllocator.getDataView8(buf).getBuffer(), errorString + ".wav");
|
||||
}finally {
|
||||
PlatformRuntime.freeByteBuffer(buf);
|
||||
}
|
||||
}
|
||||
case LOAD_VIA_WAV16: {
|
||||
int len2 = PCMToWAVLoader.getWAVLen(lst, false);
|
||||
if(len2 == 0 || len2 == 44) {
|
||||
logger.error("[{}]: Invalid length for WAV calculated", errorString);
|
||||
return null;
|
||||
}
|
||||
ByteBuffer buf = PlatformRuntime.allocateByteBuffer(len2);
|
||||
try {
|
||||
PCMToWAVLoader.createWAV16(lst, ch, dec.jorbisInfo.rate, buf);
|
||||
buf.flip();
|
||||
return PlatformAudio.decodeAudioBrowserAsync(
|
||||
EaglerArrayBufferAllocator.getDataView8(buf).getBuffer(), errorString + ".wav");
|
||||
}finally {
|
||||
PlatformRuntime.freeByteBuffer(buf);
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JOrbisAudioBufferDecoder() {
|
||||
this.jorbisBlock = new Block(this.jorbisDspState);
|
||||
this.jorbisComment = new Comment();
|
||||
this.jorbisInfo = new Info();
|
||||
}
|
||||
|
||||
private boolean init(byte[] data, String errorString) {
|
||||
this.inputStream = new EaglerInputStream(data);
|
||||
this.errorString = errorString;
|
||||
|
||||
if (this.joggStreamState != null) {
|
||||
this.joggStreamState.clear();
|
||||
}
|
||||
|
||||
if (this.jorbisBlock != null) {
|
||||
this.jorbisBlock.clear();
|
||||
}
|
||||
|
||||
if (this.jorbisDspState != null) {
|
||||
this.jorbisDspState.clear();
|
||||
}
|
||||
|
||||
if (this.jorbisInfo != null) {
|
||||
this.jorbisInfo.clear();
|
||||
}
|
||||
|
||||
if (this.joggSyncState != null) {
|
||||
this.joggSyncState.clear();
|
||||
}
|
||||
|
||||
if (this.inputStream != null) {
|
||||
try {
|
||||
this.inputStream.close();
|
||||
} catch (IOException var7) {
|
||||
}
|
||||
}
|
||||
|
||||
this.bufferSize = 8192;
|
||||
this.buffer = null;
|
||||
this.count = 0;
|
||||
this.index = 0;
|
||||
this.joggStreamState = new StreamState();
|
||||
this.jorbisBlock = new Block(this.jorbisDspState);
|
||||
this.jorbisDspState = new DspState();
|
||||
this.jorbisInfo = new Info();
|
||||
this.joggSyncState = new SyncState();
|
||||
|
||||
this.endOfStream = false;
|
||||
this.joggSyncState.init();
|
||||
this.joggSyncState.buffer(this.bufferSize);
|
||||
this.buffer = this.joggSyncState.data;
|
||||
|
||||
vigg: {
|
||||
this.index = this.joggSyncState.buffer(this.bufferSize);
|
||||
int bytes = this.inputStream.read(this.joggSyncState.data, this.index, this.bufferSize);
|
||||
if (bytes < 0) {
|
||||
bytes = 0;
|
||||
}
|
||||
|
||||
this.joggSyncState.wrote(bytes);
|
||||
if (this.joggSyncState.pageout(this.joggPage) != 1) {
|
||||
if (bytes < this.bufferSize) {
|
||||
break vigg;
|
||||
} else {
|
||||
logger.error("[{}]: Ogg header not recognized in method 'readHeader'.", errorString);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
this.joggStreamState.init(this.joggPage.serialno());
|
||||
this.jorbisInfo.init();
|
||||
this.jorbisComment.init();
|
||||
if (this.joggStreamState.pagein(this.joggPage) < 0) {
|
||||
logger.error("[{}]: Problem with first Ogg header page in method 'readHeader'.", errorString);
|
||||
return false;
|
||||
} else if (this.joggStreamState.packetout(this.joggPacket) != 1) {
|
||||
logger.error("[{}]: Problem with first Ogg header packet in method 'readHeader'.", errorString);
|
||||
return false;
|
||||
} else if (this.jorbisInfo.synthesis_headerin(this.jorbisComment, this.joggPacket) < 0) {
|
||||
logger.error("[{}]: File does not contain Vorbis header in method 'readHeader'.", errorString);
|
||||
return false;
|
||||
} else {
|
||||
int i = 0;
|
||||
|
||||
while (i < 2) {
|
||||
label73: while (true) {
|
||||
int result;
|
||||
do {
|
||||
if (i >= 2) {
|
||||
break label73;
|
||||
}
|
||||
|
||||
result = this.joggSyncState.pageout(this.joggPage);
|
||||
if (result == 0) {
|
||||
break label73;
|
||||
}
|
||||
} while (result != 1);
|
||||
|
||||
this.joggStreamState.pagein(this.joggPage);
|
||||
|
||||
while (i < 2) {
|
||||
result = this.joggStreamState.packetout(this.joggPacket);
|
||||
if (result == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == -1) {
|
||||
logger.error("[{}]: Secondary Ogg header corrupt in method 'readHeader'.", errorString);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.jorbisInfo.synthesis_headerin(this.jorbisComment, this.joggPacket);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
this.index = this.joggSyncState.buffer(this.bufferSize);
|
||||
bytes = this.inputStream.read(this.joggSyncState.data, this.index, this.bufferSize);
|
||||
if (bytes < 0) {
|
||||
bytes = 0;
|
||||
}
|
||||
|
||||
if (bytes == 0 && i < 2) {
|
||||
logger.error(
|
||||
"[{}]: End of file reached before finished reading Ogg header in method 'readHeader'",
|
||||
errorString);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.joggSyncState.wrote(bytes);
|
||||
}
|
||||
|
||||
this.index = this.joggSyncState.buffer(this.bufferSize);
|
||||
this.buffer = this.joggSyncState.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.jorbisDspState.synthesis_init(this.jorbisInfo);
|
||||
this.jorbisBlock.init(this.jorbisDspState);
|
||||
int channels = this.jorbisInfo.channels;
|
||||
int rate = this.jorbisInfo.rate;
|
||||
this.pcmInfo = new float[1][][];
|
||||
this.pcmIndex = new int[channels];
|
||||
if(convertedBuffer == null || convertedBuffer.length != this.jorbisInfo.channels || (convertedBuffer.length > 0 && convertedBuffer[0].length != this.bufferSize)) {
|
||||
this.convertedBuffer = new float[this.jorbisInfo.channels][this.bufferSize];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private float[][] readBytes() {
|
||||
if (this.endOfStream) {
|
||||
return null;
|
||||
} else {
|
||||
float[][] returnBuffer = null;
|
||||
switch (this.joggSyncState.pageout(this.joggPage)) {
|
||||
default:
|
||||
this.joggStreamState.pagein(this.joggPage);
|
||||
if (this.joggPage.granulepos() == 0L) {
|
||||
this.endOfStream = true;
|
||||
return null;
|
||||
} else {
|
||||
label99: {
|
||||
while (true) {
|
||||
switch (this.joggStreamState.packetout(this.joggPacket)) {
|
||||
case -1:
|
||||
break;
|
||||
case 0:
|
||||
if (this.joggPage.eos() != 0) {
|
||||
this.endOfStream = true;
|
||||
}
|
||||
break label99;
|
||||
default:
|
||||
if (this.jorbisBlock.synthesis(this.joggPacket) == 0) {
|
||||
this.jorbisDspState.synthesis_blockin(this.jorbisBlock);
|
||||
}
|
||||
|
||||
int samples;
|
||||
while ((samples = this.jorbisDspState.synthesis_pcmout(this.pcmInfo,
|
||||
this.pcmIndex)) > 0) {
|
||||
float[][] pcmf = this.pcmInfo[0];
|
||||
int bout = samples < bufferSize ? samples : this.bufferSize;
|
||||
|
||||
for (int i = 0; i < this.jorbisInfo.channels; ++i) {
|
||||
float[] f1 = convertedBuffer[i];
|
||||
float[] f2 = pcmf[i];
|
||||
int mono = this.pcmIndex[i];
|
||||
for (int j = 0; j < bout; ++j) {
|
||||
f1[j] = f2[mono + j];
|
||||
}
|
||||
}
|
||||
|
||||
this.jorbisDspState.synthesis_read(bout);
|
||||
returnBuffer = appendFloatArrays(returnBuffer, this.convertedBuffer, bout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case -1:
|
||||
case 0:
|
||||
if (!this.endOfStream) {
|
||||
this.index = this.joggSyncState.buffer(this.bufferSize);
|
||||
this.buffer = this.joggSyncState.data;
|
||||
|
||||
try {
|
||||
this.count = this.inputStream.read(this.buffer, this.index, this.bufferSize);
|
||||
} catch (Exception var11) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.count == -1) {
|
||||
return returnBuffer;
|
||||
}
|
||||
|
||||
this.joggSyncState.wrote(this.count);
|
||||
if (this.count == 0) {
|
||||
this.endOfStream = true;
|
||||
}
|
||||
}
|
||||
|
||||
return returnBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float[][] appendFloatArrays(float[][] arrayOne, float[][] arrayTwo, int arrayTwoBytes) {
|
||||
int bytes = arrayTwoBytes;
|
||||
int l;
|
||||
if (arrayTwo != null && (l = arrayTwo[0].length) != 0) {
|
||||
if (l < arrayTwoBytes) {
|
||||
bytes = l;
|
||||
}
|
||||
} else {
|
||||
bytes = 0;
|
||||
}
|
||||
|
||||
if ((arrayOne != null || arrayTwo != null) && bytes > 0) {
|
||||
float[][] newArray;
|
||||
|
||||
if (arrayOne == null) {
|
||||
int ch = arrayTwo.length;
|
||||
int len1 = arrayTwo[0].length;
|
||||
newArray = new float[ch][bytes];
|
||||
for(int i = 0; i < ch; ++i) {
|
||||
System.arraycopy(arrayTwo[i], 0, newArray[i], 0, bytes);
|
||||
}
|
||||
arrayTwo = null;
|
||||
} else {
|
||||
int ch = arrayOne.length;
|
||||
int len1 = arrayOne[0].length;
|
||||
if (arrayTwo != null && bytes > 0) {
|
||||
newArray = new float[ch][len1 + bytes];
|
||||
for(int i = 0; i < ch; ++i) {
|
||||
System.arraycopy(arrayOne[i], 0, newArray[i], 0, len1);
|
||||
System.arraycopy(arrayTwo[i], 0, newArray[i], len1, bytes);
|
||||
}
|
||||
arrayOne = null;
|
||||
arrayTwo = null;
|
||||
} else {
|
||||
newArray = new float[ch][len1];
|
||||
for(int i = 0; i < ch; ++i) {
|
||||
System.arraycopy(arrayOne[i], 0, newArray[i], 0, len1);
|
||||
}
|
||||
arrayOne = null;
|
||||
}
|
||||
}
|
||||
|
||||
return newArray;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,328 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class LegacyKeycodeTranslator {
|
||||
|
||||
public static class LegacyKeycode {
|
||||
|
||||
public final int keyCode;
|
||||
public final int location;
|
||||
|
||||
private LegacyKeycode(int keyCode, int location) {
|
||||
this.keyCode = keyCode;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (!(obj instanceof LegacyKeycode))
|
||||
return false;
|
||||
LegacyKeycode other = (LegacyKeycode) obj;
|
||||
if (keyCode != other.keyCode)
|
||||
return false;
|
||||
if (location != other.location)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Set<String> numpadVolatile = Sets.newHashSet(
|
||||
"Comma", "Minus", "Period", "Slash", "Equal", "Enter", "Digit0", "Digit1", "Digit2", "Digit3",
|
||||
"Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9", "IntlYen");
|
||||
|
||||
private final Map<String,LegacyKeycode> codeLookupBase = new HashMap<>();
|
||||
private final Map<String,LegacyKeycode> codeLookupLayout = new HashMap<>();
|
||||
|
||||
public LegacyKeycodeTranslator() {
|
||||
codeLookupBase.put("Digit0", new LegacyKeycode(0x30, 0));
|
||||
codeLookupBase.put("Digit1", new LegacyKeycode(0x31, 0));
|
||||
codeLookupBase.put("Digit2", new LegacyKeycode(0x32, 0));
|
||||
codeLookupBase.put("Digit3", new LegacyKeycode(0x33, 0));
|
||||
codeLookupBase.put("Digit4", new LegacyKeycode(0x34, 0));
|
||||
codeLookupBase.put("Digit5", new LegacyKeycode(0x35, 0));
|
||||
codeLookupBase.put("Digit6", new LegacyKeycode(0x36, 0));
|
||||
codeLookupBase.put("Digit7", new LegacyKeycode(0x37, 0));
|
||||
codeLookupBase.put("Digit8", new LegacyKeycode(0x38, 0));
|
||||
codeLookupBase.put("Digit9", new LegacyKeycode(0x39, 0));
|
||||
codeLookupBase.put("KeyA", new LegacyKeycode(0x41, 0));
|
||||
codeLookupBase.put("KeyB", new LegacyKeycode(0x42, 0));
|
||||
codeLookupBase.put("KeyC", new LegacyKeycode(0x43, 0));
|
||||
codeLookupBase.put("KeyD", new LegacyKeycode(0x44, 0));
|
||||
codeLookupBase.put("KeyE", new LegacyKeycode(0x45, 0));
|
||||
codeLookupBase.put("KeyF", new LegacyKeycode(0x46, 0));
|
||||
codeLookupBase.put("KeyG", new LegacyKeycode(0x47, 0));
|
||||
codeLookupBase.put("KeyH", new LegacyKeycode(0x48, 0));
|
||||
codeLookupBase.put("KeyI", new LegacyKeycode(0x49, 0));
|
||||
codeLookupBase.put("KeyJ", new LegacyKeycode(0x4A, 0));
|
||||
codeLookupBase.put("KeyK", new LegacyKeycode(0x4B, 0));
|
||||
codeLookupBase.put("KeyL", new LegacyKeycode(0x4C, 0));
|
||||
codeLookupBase.put("KeyM", new LegacyKeycode(0x4D, 0));
|
||||
codeLookupBase.put("KeyN", new LegacyKeycode(0x4E, 0));
|
||||
codeLookupBase.put("KeyO", new LegacyKeycode(0x4F, 0));
|
||||
codeLookupBase.put("KeyP", new LegacyKeycode(0x50, 0));
|
||||
codeLookupBase.put("KeyQ", new LegacyKeycode(0x51, 0));
|
||||
codeLookupBase.put("KeyR", new LegacyKeycode(0x52, 0));
|
||||
codeLookupBase.put("KeyS", new LegacyKeycode(0x53, 0));
|
||||
codeLookupBase.put("KeyT", new LegacyKeycode(0x54, 0));
|
||||
codeLookupBase.put("KeyU", new LegacyKeycode(0x55, 0));
|
||||
codeLookupBase.put("KeyV", new LegacyKeycode(0x56, 0));
|
||||
codeLookupBase.put("KeyW", new LegacyKeycode(0x57, 0));
|
||||
codeLookupBase.put("KeyX", new LegacyKeycode(0x58, 0));
|
||||
codeLookupBase.put("KeyY", new LegacyKeycode(0x59, 0));
|
||||
codeLookupBase.put("KeyZ", new LegacyKeycode(0x5A, 0));
|
||||
codeLookupBase.put("Comma", new LegacyKeycode(0xBC, 0));
|
||||
codeLookupBase.put("Period", new LegacyKeycode(0xBE, 0));
|
||||
codeLookupBase.put("Semicolon", new LegacyKeycode(0xBA, 0));
|
||||
codeLookupBase.put("Quote", new LegacyKeycode(0xDE, 0));
|
||||
codeLookupBase.put("BracketLeft", new LegacyKeycode(0xDB, 0));
|
||||
codeLookupBase.put("BracketRight", new LegacyKeycode(0xDD, 0));
|
||||
codeLookupBase.put("Backquote", new LegacyKeycode(0xC0, 0));
|
||||
codeLookupBase.put("Backslash", new LegacyKeycode(0xDC, 0));
|
||||
codeLookupBase.put("IntlBackslash", new LegacyKeycode(0xDC, 0));
|
||||
codeLookupBase.put("Minus", new LegacyKeycode(0xBD, 0));
|
||||
codeLookupBase.put("Equal", new LegacyKeycode(0xBB, 0));
|
||||
codeLookupBase.put("Slash", new LegacyKeycode(0xBF, 0));
|
||||
codeLookupBase.put("IntlRo", new LegacyKeycode(0xC1, 0));
|
||||
codeLookupBase.put("IntlYen", new LegacyKeycode(0xFF, 0));
|
||||
codeLookupBase.put("AltLeft", new LegacyKeycode(0x12, 1));
|
||||
codeLookupBase.put("AltRight", new LegacyKeycode(0x12, 2));
|
||||
codeLookupBase.put("CapsLock", new LegacyKeycode(0x14, 0));
|
||||
codeLookupBase.put("ControlLeft", new LegacyKeycode(0x11, 1));
|
||||
codeLookupBase.put("ControlRight", new LegacyKeycode(0x11, 2));
|
||||
codeLookupBase.put("MetaLeft", new LegacyKeycode(0x5B, 1));
|
||||
codeLookupBase.put("MetaRight", new LegacyKeycode(0x5C, 2));
|
||||
codeLookupBase.put("ShiftLeft", new LegacyKeycode(0x10, 1));
|
||||
codeLookupBase.put("ShiftRight", new LegacyKeycode(0x10, 2));
|
||||
codeLookupBase.put("ContextMenu", new LegacyKeycode(0x5D, 0));
|
||||
codeLookupBase.put("Enter", new LegacyKeycode(0x0D, 0));
|
||||
codeLookupBase.put("Space", new LegacyKeycode(0x20, 0));
|
||||
codeLookupBase.put("Backspace", new LegacyKeycode(0x08, 0));
|
||||
codeLookupBase.put("Tab", new LegacyKeycode(0x09, 0));
|
||||
codeLookupBase.put("Delete", new LegacyKeycode(0x2E, 0));
|
||||
codeLookupBase.put("End", new LegacyKeycode(0x23, 0));
|
||||
codeLookupBase.put("Help", new LegacyKeycode(0x2D, 0));
|
||||
codeLookupBase.put("Home", new LegacyKeycode(0x24, 0));
|
||||
codeLookupBase.put("Insert", new LegacyKeycode(0x2D, 0));
|
||||
codeLookupBase.put("PageDown", new LegacyKeycode(0x22, 0));
|
||||
codeLookupBase.put("PageUp", new LegacyKeycode(0x21, 0));
|
||||
codeLookupBase.put("ArrowDown", new LegacyKeycode(0x28, 0));
|
||||
codeLookupBase.put("ArrowLeft", new LegacyKeycode(0x25, 0));
|
||||
codeLookupBase.put("ArrowRight", new LegacyKeycode(0x27, 0));
|
||||
codeLookupBase.put("ArrowUp", new LegacyKeycode(0x26, 0));
|
||||
codeLookupBase.put("Escape", new LegacyKeycode(0x1B, 0));
|
||||
codeLookupBase.put("PrintScreen", new LegacyKeycode(0x2C, 0));
|
||||
codeLookupBase.put("ScrollLock", new LegacyKeycode(0x91, 0));
|
||||
codeLookupBase.put("Pause", new LegacyKeycode(0x13, 0));
|
||||
codeLookupBase.put("F1", new LegacyKeycode(0x70, 0));
|
||||
codeLookupBase.put("F2", new LegacyKeycode(0x71, 0));
|
||||
codeLookupBase.put("F3", new LegacyKeycode(0x72, 0));
|
||||
codeLookupBase.put("F4", new LegacyKeycode(0x73, 0));
|
||||
codeLookupBase.put("F5", new LegacyKeycode(0x74, 0));
|
||||
codeLookupBase.put("F6", new LegacyKeycode(0x75, 0));
|
||||
codeLookupBase.put("F7", new LegacyKeycode(0x76, 0));
|
||||
codeLookupBase.put("F8", new LegacyKeycode(0x77, 0));
|
||||
codeLookupBase.put("F9", new LegacyKeycode(0x78, 0));
|
||||
codeLookupBase.put("F10", new LegacyKeycode(0x79, 0));
|
||||
codeLookupBase.put("F11", new LegacyKeycode(0x7A, 0));
|
||||
codeLookupBase.put("F12", new LegacyKeycode(0x7B, 0));
|
||||
codeLookupBase.put("F13", new LegacyKeycode(0x7C, 0));
|
||||
codeLookupBase.put("F14", new LegacyKeycode(0x7D, 0));
|
||||
codeLookupBase.put("F15", new LegacyKeycode(0x7E, 0));
|
||||
codeLookupBase.put("F16", new LegacyKeycode(0x7F, 0));
|
||||
codeLookupBase.put("F17", new LegacyKeycode(0x80, 0));
|
||||
codeLookupBase.put("F18", new LegacyKeycode(0x81, 0));
|
||||
codeLookupBase.put("F19", new LegacyKeycode(0x82, 0));
|
||||
codeLookupBase.put("F20", new LegacyKeycode(0x83, 0));
|
||||
codeLookupBase.put("F21", new LegacyKeycode(0x84, 0));
|
||||
codeLookupBase.put("F22", new LegacyKeycode(0x85, 0));
|
||||
codeLookupBase.put("F23", new LegacyKeycode(0x86, 0));
|
||||
codeLookupBase.put("F24", new LegacyKeycode(0x87, 0));
|
||||
codeLookupBase.put("NumLock", new LegacyKeycode(0x90, 3));
|
||||
codeLookupBase.put("Numpad0", new LegacyKeycode(0x60, 3));
|
||||
codeLookupBase.put("Numpad1", new LegacyKeycode(0x61, 3));
|
||||
codeLookupBase.put("Numpad2", new LegacyKeycode(0x62, 3));
|
||||
codeLookupBase.put("Numpad3", new LegacyKeycode(0x63, 3));
|
||||
codeLookupBase.put("Numpad4", new LegacyKeycode(0x64, 3));
|
||||
codeLookupBase.put("Numpad5", new LegacyKeycode(0x65, 3));
|
||||
codeLookupBase.put("Numpad6", new LegacyKeycode(0x66, 3));
|
||||
codeLookupBase.put("Numpad7", new LegacyKeycode(0x67, 3));
|
||||
codeLookupBase.put("Numpad8", new LegacyKeycode(0x68, 3));
|
||||
codeLookupBase.put("Numpad9", new LegacyKeycode(0x69, 3));
|
||||
codeLookupBase.put("NumpadAdd", new LegacyKeycode(0x6B, 3));
|
||||
codeLookupBase.put("NumpadComma", new LegacyKeycode(0xC2, 3));
|
||||
codeLookupBase.put("NumpadDecimal", new LegacyKeycode(0x6E, 3));
|
||||
codeLookupBase.put("NumpadDivide", new LegacyKeycode(0x6F, 3));
|
||||
codeLookupBase.put("NumpadEnter", new LegacyKeycode(0x0D, 3));
|
||||
codeLookupBase.put("NumpadEqual", new LegacyKeycode(0x0C, 3));
|
||||
codeLookupBase.put("NumpadMultiply", new LegacyKeycode(0x6A, 3));
|
||||
codeLookupBase.put("NumpadSubtract", new LegacyKeycode(0x6D, 3));
|
||||
}
|
||||
|
||||
public LegacyKeycodeTranslator addBrowserLayoutMapping(String keyChar, String codeStr) {
|
||||
LegacyKeycode mapTo = codeLookupBase.get(codeStr);
|
||||
if(mapTo != null) {
|
||||
String keyCode = getCodeFromLayoutChar(keyChar);
|
||||
if(keyCode != null && !keyCode.equals(codeStr) && !(codeStr.startsWith("Numpad") && numpadVolatile.contains(keyCode)) && !mapTo.equals(codeLookupBase.get(keyCode))) {
|
||||
codeLookupLayout.put(keyCode, mapTo);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getRemappedKeyCount() {
|
||||
return codeLookupLayout.size();
|
||||
}
|
||||
|
||||
public Map<String,LegacyKeycode> buildLayoutTable() {
|
||||
if(codeLookupLayout.isEmpty()) {
|
||||
return codeLookupBase;
|
||||
}
|
||||
Map<String,LegacyKeycode> ret = new HashMap<>();
|
||||
ret.putAll(codeLookupBase);
|
||||
ret.putAll(codeLookupLayout);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static String getCodeFromLayoutChar(String keyChar) {
|
||||
if(keyChar.length() != 1) {
|
||||
return null;
|
||||
}
|
||||
char c = keyChar.charAt(0);
|
||||
String ret = getCodeFromLayoutChar0(c);
|
||||
if(ret == null) {
|
||||
ret = getCodeFromLayoutChar0(Character.toLowerCase(c));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static String getCodeFromLayoutChar0(char keyChar) {
|
||||
switch(keyChar) {
|
||||
case 'e':
|
||||
return "KeyE";
|
||||
case 'd':
|
||||
return "KeyD";
|
||||
case 'u':
|
||||
return "KeyU";
|
||||
case '-':
|
||||
return "Minus";
|
||||
case 'h':
|
||||
return "KeyH";
|
||||
case 'z':
|
||||
return "KeyZ";
|
||||
case '=':
|
||||
return "Equal";
|
||||
case 'p':
|
||||
return "KeyP";
|
||||
case ';':
|
||||
return "Semicolon";
|
||||
case ']':
|
||||
return "BracketRight";
|
||||
case '/':
|
||||
return "Slash";
|
||||
case '[':
|
||||
return "BracketLeft";
|
||||
case 'l':
|
||||
return "KeyL";
|
||||
case '8':
|
||||
return "Digit8";
|
||||
case 'w':
|
||||
return "KeyW";
|
||||
case 's':
|
||||
return "KeyS";
|
||||
case '5':
|
||||
return "Digit5";
|
||||
case '9':
|
||||
return "Digit9";
|
||||
case 'o':
|
||||
return "KeyO";
|
||||
case '.':
|
||||
return "Period";
|
||||
case '6':
|
||||
return "Digit6";
|
||||
case 'v':
|
||||
return "KeyV";
|
||||
case '3':
|
||||
return "Digit3";
|
||||
case '`':
|
||||
return "Backquote";
|
||||
case 'g':
|
||||
return "KeyG";
|
||||
case 'j':
|
||||
return "KeyJ";
|
||||
case 'q':
|
||||
return "KeyQ";
|
||||
case '1':
|
||||
return "Digit1";
|
||||
case 't':
|
||||
return "KeyT";
|
||||
case 'y':
|
||||
return "KeyY";
|
||||
case '\'':
|
||||
return "Quote";
|
||||
case '\\':
|
||||
return "Backslash";
|
||||
case 'k':
|
||||
return "KeyK";
|
||||
case 'f':
|
||||
return "KeyF";
|
||||
case 'i':
|
||||
return "KeyI";
|
||||
case 'r':
|
||||
return "KeyR";
|
||||
case 'x':
|
||||
return "KeyX";
|
||||
case 'a':
|
||||
return "KeyA";
|
||||
case '2':
|
||||
return "Digit2";
|
||||
case '7':
|
||||
return "Digit7";
|
||||
case 'm':
|
||||
return "KeyM";
|
||||
case '4':
|
||||
return "Digit4";
|
||||
case '0':
|
||||
return "Digit0";
|
||||
case 'n':
|
||||
return "KeyN";
|
||||
case 'b':
|
||||
return "KeyB";
|
||||
case 'c':
|
||||
return "KeyC";
|
||||
case ',':
|
||||
return "Comma";
|
||||
case '*':
|
||||
return "NumpadMultiply";
|
||||
case 0xA5:
|
||||
return "IntlYen";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSBody;
|
||||
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.
|
||||
*
|
||||
*/
|
||||
public abstract class MessageChannel implements JSObject {
|
||||
|
||||
@JSBody(params = { }, script = "return (typeof MessageChannel !== \"undefined\");")
|
||||
public static native boolean supported();
|
||||
|
||||
@JSBody(params = { }, script = "return new MessageChannel();")
|
||||
public static native MessageChannel create();
|
||||
|
||||
@JSProperty
|
||||
public abstract MessagePort getPort1();
|
||||
|
||||
@JSProperty
|
||||
public abstract MessagePort getPort2();
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.SortedTouchEvent.ITouchUIDMapper;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class OffsetTouch {
|
||||
|
||||
public final Touch touch;
|
||||
public final int eventUID;
|
||||
public final int posX;
|
||||
public final int posY;
|
||||
|
||||
public OffsetTouch(Touch touch, int eventUID, int posX, int posY) {
|
||||
this.touch = touch;
|
||||
this.eventUID = eventUID;
|
||||
this.posX = posX;
|
||||
this.posY = posY;
|
||||
}
|
||||
|
||||
public static OffsetTouch create(Touch touch, ITouchUIDMapper mapper, int originX, int originY) {
|
||||
double contentScale = PlatformInput.getDPI();
|
||||
OffsetTouch ot = new OffsetTouch(touch, mapper.call(touch.getIdentifier()),
|
||||
(int) ((touch.getPageX() - originX) * contentScale),
|
||||
PlatformInput.getWindowHeight() - (int) ((touch.getPageY() - originY) * contentScale) - 1);
|
||||
return ot;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class PCMToWAVLoader {
|
||||
|
||||
public static int getWAVLen(List<float[][]> data, boolean floating) {
|
||||
int i = 44;
|
||||
int j = floating ? 4 : 2;
|
||||
int k;
|
||||
for(float[][] f : data) {
|
||||
k = f.length;
|
||||
if(k == 0) continue;
|
||||
i += k * f[0].length * j;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
public static void createWAV16(List<float[][]> data, int chCount, int sampleRate, ByteBuffer bufferOut) {
|
||||
if(chCount == 0 || data.isEmpty()) return;
|
||||
int finalSize = bufferOut.remaining();
|
||||
|
||||
// header
|
||||
bufferOut.putInt(0x46464952); // magic
|
||||
bufferOut.putInt(finalSize - 8); // file len
|
||||
bufferOut.putInt(0x45564157); // magic
|
||||
|
||||
// format chunk
|
||||
bufferOut.putInt(0x20746D66); // magic
|
||||
bufferOut.putInt(16); // format chunk len - 8
|
||||
bufferOut.putShort((short)1); // audio format = int
|
||||
bufferOut.putShort((short)chCount); // channels
|
||||
bufferOut.putInt(sampleRate); // sample rate
|
||||
bufferOut.putInt(sampleRate * chCount * 2); // bytes per second
|
||||
bufferOut.putShort((short)(chCount * 2)); // bytes per sample
|
||||
bufferOut.putShort((short)16); // bits per sample
|
||||
|
||||
// data chunk
|
||||
bufferOut.putInt(0x61746164); // magic
|
||||
bufferOut.putInt(finalSize - 44);
|
||||
|
||||
for(float[][] f : data) {
|
||||
for(int i = 0, l = f[0].length; i < l; ++i) {
|
||||
for(int c = 0; c < chCount; ++c) {
|
||||
int val = (int)(f[c][i] * 32767.0f);
|
||||
if (val > 32767) {
|
||||
val = 32767;
|
||||
}
|
||||
if (val < -32768) {
|
||||
val = -32768;
|
||||
}
|
||||
if (val < 0) {
|
||||
val |= 32768;
|
||||
}
|
||||
bufferOut.putShort((short)val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(bufferOut.hasRemaining()) {
|
||||
throw new IllegalStateException("Buffer was the wrong size! " + bufferOut.remaining() + " remaining");
|
||||
}
|
||||
}
|
||||
|
||||
public static void createWAV32F(List<float[][]> data, int chCount, int sampleRate, ByteBuffer bufferOut) {
|
||||
if(chCount == 0 || data.isEmpty()) return;
|
||||
int finalSize = bufferOut.remaining();
|
||||
|
||||
// header
|
||||
bufferOut.putInt(0x46464952); // magic
|
||||
bufferOut.putInt(finalSize - 8); // file len
|
||||
bufferOut.putInt(0x45564157); // magic
|
||||
|
||||
// format chunk
|
||||
bufferOut.putInt(0x20746D66); // magic
|
||||
bufferOut.putInt(16); // format chunk len - 8
|
||||
bufferOut.putShort((short)3); // audio format = float
|
||||
bufferOut.putShort((short)chCount); // channels
|
||||
bufferOut.putInt(sampleRate); // sample rate
|
||||
bufferOut.putInt(sampleRate * chCount * 4); // bytes per second
|
||||
bufferOut.putShort((short)(chCount * 4)); // bytes per sample
|
||||
bufferOut.putShort((short)32); // bits per sample
|
||||
|
||||
// data chunk
|
||||
bufferOut.putInt(0x61746164); // magic
|
||||
bufferOut.putInt(finalSize - 44);
|
||||
|
||||
for(float[][] f : data) {
|
||||
for(int i = 0, l = f[0].length; i < l; ++i) {
|
||||
for(int c = 0; c < chCount; ++c) {
|
||||
bufferOut.putFloat(f[c][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(bufferOut.hasRemaining()) {
|
||||
throw new IllegalStateException("Buffer was the wrong size! " + finalSize + " " + bufferOut.remaining() + " remaining");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.EnumTouchEvent;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class SortedTouchEvent {
|
||||
|
||||
public static interface ITouchUIDMapper {
|
||||
int call(int uidIn);
|
||||
}
|
||||
|
||||
public final TouchEvent event;
|
||||
public final EnumTouchEvent type;
|
||||
private final List<OffsetTouch> targetTouches;
|
||||
private final List<OffsetTouch> changedTouches;
|
||||
private final List<OffsetTouch> eventTouches;
|
||||
|
||||
public SortedTouchEvent(TouchEvent event, ITouchUIDMapper mapper) {
|
||||
changedTouches = TeaVMUtils.toSortedTouchList(event.getChangedTouches(), mapper, PlatformInput.touchOffsetXTeaVM, PlatformInput.touchOffsetYTeaVM);
|
||||
targetTouches = TeaVMUtils.toSortedTouchList(event.getTargetTouches(), mapper, PlatformInput.touchOffsetXTeaVM, PlatformInput.touchOffsetYTeaVM);
|
||||
this.event = event;
|
||||
switch(event.getType()) {
|
||||
case "touchstart":
|
||||
type = EnumTouchEvent.TOUCHSTART;
|
||||
eventTouches = changedTouches;
|
||||
break;
|
||||
case "touchmove":
|
||||
type = EnumTouchEvent.TOUCHMOVE;
|
||||
eventTouches = targetTouches;
|
||||
break;
|
||||
case "touchend":
|
||||
case "touchcancel":
|
||||
default:
|
||||
type = EnumTouchEvent.TOUCHEND;
|
||||
eventTouches = changedTouches;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTouchesSize() {
|
||||
return event.getTouches().getLength();
|
||||
}
|
||||
|
||||
public int getChangedTouchesSize() {
|
||||
return event.getChangedTouches().getLength();
|
||||
}
|
||||
|
||||
public List<OffsetTouch> getChangedTouches() {
|
||||
return changedTouches;
|
||||
}
|
||||
|
||||
public int getTargetTouchesSize() {
|
||||
return event.getTargetTouches().getLength();
|
||||
}
|
||||
|
||||
public List<OffsetTouch> getTargetTouches() {
|
||||
return targetTouches;
|
||||
}
|
||||
|
||||
public int getEventTouchesSize() {
|
||||
return eventTouches.size();
|
||||
}
|
||||
|
||||
public List<OffsetTouch> getEventTouches() {
|
||||
return eventTouches;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public interface TeaVMBlobURLHandle {
|
||||
|
||||
default String toExternalForm() {
|
||||
return TeaVMBlobURLManager.toExternalForm(this);
|
||||
}
|
||||
|
||||
default void release() {
|
||||
TeaVMBlobURLManager.releaseURL(this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.interop.Async;
|
||||
import org.teavm.interop.AsyncCallback;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.Base64;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class TeaVMBlobURLManager {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("TeaVMBlobURLManager");
|
||||
|
||||
private static boolean isSameOriginSupport = true;
|
||||
|
||||
public static void initialize() {
|
||||
if(((TeaVMClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isDisableBlobURLsTeaVM()) {
|
||||
isSameOriginSupport = false;
|
||||
logger.info("Note: Blob urls have been disabled, client will use data: urls instead");
|
||||
}else {
|
||||
try {
|
||||
isSameOriginSupport = checkSameOriginSupport();
|
||||
}catch(Throwable t) {
|
||||
isSameOriginSupport = false;
|
||||
}
|
||||
if(!isSameOriginSupport) {
|
||||
logger.warn("Warning: Same-origin fetch support detected as false, client will use data: urls instead of blob: urls");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
private static native Boolean checkSameOriginSupport();
|
||||
|
||||
private static void checkSameOriginSupport(final AsyncCallback<Boolean> cb) {
|
||||
try {
|
||||
checkSameOriginSupport0((v) -> cb.complete(v));
|
||||
}catch(Throwable t) {
|
||||
cb.error(t);
|
||||
}
|
||||
}
|
||||
|
||||
@JSFunctor
|
||||
private static interface SameOriginSupportCallback extends JSObject {
|
||||
void call(boolean support);
|
||||
}
|
||||
|
||||
@JSBody(params = { "cb" }, script = "if((typeof URL === \"undefined\") || (typeof URL.createObjectURL !== \"function\")) { cb(false); }"
|
||||
+ "else { var objURL = URL.createObjectURL(new Blob([new Uint8Array([69, 69, 69, 69])]));"
|
||||
+ "if(!objURL) { cb(false); return; }"
|
||||
+ "var eag = function(theObjURL, theXHRObj) {"
|
||||
+ "theXHRObj.responseType = \"arraybuffer\";"
|
||||
+ "theXHRObj.addEventListener(\"load\", function(evt) { try { URL.revokeObjectURL(theObjURL); } catch(exx) { }"
|
||||
+ "var stat = theXHRObj.status;"
|
||||
+ "if(stat === 0 || (stat >= 200 && stat < 400)) {"
|
||||
+ "var typedArr = new Uint8Array(theXHRObj.response);"
|
||||
+ "if(typedArr.length === 4 && typedArr[0] === 69 && typedArr[1] === 69 && typedArr[2] === 69 && typedArr[3] === 69) {"
|
||||
+ "cb(true);"
|
||||
+ "} else { cb(false); } } else { cb(false); } });"
|
||||
+ "theXHRObj.addEventListener(\"error\", function(evt) { try { URL.revokeObjectURL(theObjURL); } catch(exx) { } cb(false); });"
|
||||
+ "theXHRObj.open(\"GET\", theObjURL, true);"
|
||||
+ "theXHRObj.send();"
|
||||
+ "}; eag(objURL, new XMLHttpRequest()); }")
|
||||
private static native void checkSameOriginSupport0(SameOriginSupportCallback cb);
|
||||
|
||||
private static class HandleRealBlobURL implements TeaVMBlobURLHandle {
|
||||
|
||||
private final String blobURL;
|
||||
|
||||
public HandleRealBlobURL(String blobURL) {
|
||||
this.blobURL = blobURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toExternalForm() {
|
||||
return blobURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
revokeBlobURL(blobURL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class HandleFakeBlobURL implements TeaVMBlobURLHandle {
|
||||
|
||||
private final byte[] blobData;
|
||||
private final String blobMIME;
|
||||
|
||||
public HandleFakeBlobURL(byte[] blobData, String blobMIME) {
|
||||
this.blobData = blobData;
|
||||
this.blobMIME = blobMIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toExternalForm() {
|
||||
return "data:" + blobMIME + ";base64," + Base64.encodeBase64String(blobData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static TeaVMBlobURLHandle registerNewURLByte(byte[] objectData, String mimeType) {
|
||||
if(isSameOriginSupport) {
|
||||
return new HandleRealBlobURL(createBlobURL(TeaVMUtils.unwrapArrayBuffer(objectData), mimeType));
|
||||
}else {
|
||||
return new HandleFakeBlobURL(objectData, mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
public static TeaVMBlobURLHandle registerNewURLArrayBuffer(ArrayBuffer objectData, String mimeType) {
|
||||
return registerNewURLByte(TeaVMUtils.wrapByteArrayBuffer(objectData), mimeType);
|
||||
}
|
||||
|
||||
public static TeaVMBlobURLHandle registerNewURLBlob(JSObject objectData) {
|
||||
if(isSameOriginSupport) {
|
||||
return new HandleRealBlobURL(createBlobURL(objectData));
|
||||
}else {
|
||||
return new HandleFakeBlobURL(TeaVMUtils.wrapByteArrayBuffer(blobToArrayBuffer(objectData)), getBlobMime(objectData));
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { "objectData" }, script = "return objectData.type || \"application/octet-stream\";")
|
||||
private static native String getBlobMime(JSObject objectData);
|
||||
|
||||
@Async
|
||||
private static native ArrayBuffer blobToArrayBuffer(JSObject objectData);
|
||||
|
||||
private static void blobToArrayBuffer(JSObject objectData, final AsyncCallback<ArrayBuffer> cb) {
|
||||
blobToArrayBuffer0(objectData, cb::complete);
|
||||
}
|
||||
|
||||
@JSFunctor
|
||||
private static interface ArrayBufferCallback extends JSObject {
|
||||
void call(ArrayBuffer buf);
|
||||
}
|
||||
|
||||
@JSBody(params = { "objectData", "callback" }, script =
|
||||
"var eag = function(reader){"
|
||||
+ "reader.addEventListener(\"loadend\",function(evt){ callback(reader.result); });"
|
||||
+ "reader.addEventListener(\"error\",function(evt){ callback(null); });"
|
||||
+ "reader.readAsArrayBuffer(objectData);"
|
||||
+ "}; eag(new FileReader());")
|
||||
private static native ArrayBuffer blobToArrayBuffer0(JSObject objectData, ArrayBufferCallback callback);
|
||||
|
||||
@JSBody(params = { "buf", "mime" }, script = "return URL.createObjectURL(new Blob([buf], {type: mime}));")
|
||||
private static native String createBlobURL(ArrayBuffer buf, String mime);
|
||||
|
||||
@JSBody(params = { "objectBlob" }, script = "return URL.createObjectURL(objectBlob);")
|
||||
private static native String createBlobURL(JSObject objectBlob);
|
||||
|
||||
@JSBody(params = { "url" }, script = "URL.revokeObjectURL(url);")
|
||||
private static native void revokeBlobURL(String url);
|
||||
|
||||
public static String toExternalForm(TeaVMBlobURLHandle handle) {
|
||||
return handle.toExternalForm();
|
||||
}
|
||||
|
||||
public static void releaseURL(TeaVMBlobURLHandle handle) {
|
||||
handle.release();
|
||||
}
|
||||
|
||||
}
|
@ -6,6 +6,7 @@ import java.util.List;
|
||||
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
|
||||
import net.lax1dude.eaglercraft.v1_8.ThreadLocalRandom;
|
||||
import net.lax1dude.eaglercraft.v1_8.boot_menu.teavm.IBootMenuConfigAdapter;
|
||||
import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayManager;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
@ -35,13 +36,13 @@ import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayEntry;
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
|
||||
public class TeaVMClientConfigAdapter implements IClientConfigAdapter, IBootMenuConfigAdapter {
|
||||
|
||||
public static final IClientConfigAdapter instance = new TeaVMClientConfigAdapter();
|
||||
|
||||
private String defaultLocale = "en_US";
|
||||
private List<DefaultServer> defaultServers = new ArrayList();
|
||||
private List<RelayEntry> relays = new ArrayList();
|
||||
private List<DefaultServer> defaultServers = new ArrayList<>();
|
||||
private List<RelayEntry> relays = new ArrayList<>();
|
||||
private String serverToJoin = null;
|
||||
private String worldsDB = "worlds";
|
||||
private String resourcePacksDB = "resourcePacks";
|
||||
@ -61,7 +62,31 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
|
||||
private String localStorageNamespace = "_eaglercraftX";
|
||||
private final TeaVMClientConfigAdapterHooks hooks = new TeaVMClientConfigAdapterHooks();
|
||||
private boolean enableMinceraft = true;
|
||||
private boolean enableServerCookies = true;
|
||||
private boolean allowServerRedirects = true;
|
||||
private boolean crashOnUncaughtExceptions = false;
|
||||
private boolean openDebugConsoleOnLaunch = false;
|
||||
private boolean fixDebugConsoleUnloadListener = false;
|
||||
private boolean forceWebViewSupport = false;
|
||||
private boolean enableWebViewCSP = false;
|
||||
private boolean autoFixLegacyStyleAttr = false;
|
||||
private boolean showBootMenuOnLaunch = false;
|
||||
private boolean bootMenuBlocksUnsignedClients = false;
|
||||
private boolean allowBootMenu = true;
|
||||
private boolean forceProfanityFilter = false;
|
||||
private boolean forceWebGL1 = false;
|
||||
private boolean forceWebGL2 = false;
|
||||
private boolean allowExperimentalWebGL1 = false;
|
||||
private boolean useWebGLExt = true;
|
||||
private boolean useDelayOnSwap = false;
|
||||
private boolean useJOrbisAudioDecoder = false;
|
||||
private boolean useXHRFetch = false;
|
||||
private boolean useVisualViewport = true;
|
||||
private boolean deobfStackTraces = true;
|
||||
private boolean disableBlobURLs = false;
|
||||
private boolean eaglerNoDelay = false;
|
||||
private boolean ramdiskMode = false;
|
||||
private boolean singleThreadMode = false;
|
||||
|
||||
public void loadNative(JSObject jsObject) {
|
||||
integratedServerOpts = new JSONObject();
|
||||
@ -84,12 +109,36 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
|
||||
allowFNAWSkins = !demoMode && eaglercraftXOpts.getAllowFNAWSkins(true);
|
||||
localStorageNamespace = eaglercraftXOpts.getLocalStorageNamespace(EaglercraftVersion.localStorageNamespace);
|
||||
enableMinceraft = eaglercraftXOpts.getEnableMinceraft(true);
|
||||
enableServerCookies = !demoMode && eaglercraftXOpts.getEnableServerCookies(true);
|
||||
allowServerRedirects = eaglercraftXOpts.getAllowServerRedirects(true);
|
||||
crashOnUncaughtExceptions = eaglercraftXOpts.getCrashOnUncaughtExceptions(false);
|
||||
openDebugConsoleOnLaunch = eaglercraftXOpts.getOpenDebugConsoleOnLaunch(false);
|
||||
fixDebugConsoleUnloadListener = eaglercraftXOpts.getFixDebugConsoleUnloadListener(false);
|
||||
forceWebViewSupport = eaglercraftXOpts.getForceWebViewSupport(false);
|
||||
enableWebViewCSP = eaglercraftXOpts.getEnableWebViewCSP(true);
|
||||
autoFixLegacyStyleAttr = eaglercraftXOpts.getAutoFixLegacyStyleAttr(true);
|
||||
showBootMenuOnLaunch = eaglercraftXOpts.getShowBootMenuOnLaunch(false);
|
||||
bootMenuBlocksUnsignedClients = eaglercraftXOpts.getBootMenuBlocksUnsignedClients(false);
|
||||
allowBootMenu = eaglercraftXOpts.getAllowBootMenu(!demoMode);
|
||||
forceProfanityFilter = eaglercraftXOpts.getForceProfanityFilter(false);
|
||||
forceWebGL1 = eaglercraftXOpts.getForceWebGL1(false);
|
||||
forceWebGL2 = eaglercraftXOpts.getForceWebGL2(false);
|
||||
allowExperimentalWebGL1 = eaglercraftXOpts.getAllowExperimentalWebGL1(true);
|
||||
useWebGLExt = eaglercraftXOpts.getUseWebGLExt(true);
|
||||
useDelayOnSwap = eaglercraftXOpts.getUseDelayOnSwap(false);
|
||||
useJOrbisAudioDecoder = eaglercraftXOpts.getUseJOrbisAudioDecoder(false);
|
||||
useXHRFetch = eaglercraftXOpts.getUseXHRFetch(false);
|
||||
useVisualViewport = eaglercraftXOpts.getUseVisualViewport(true);
|
||||
deobfStackTraces = eaglercraftXOpts.getDeobfStackTraces(true);
|
||||
disableBlobURLs = eaglercraftXOpts.getDisableBlobURLs(false);
|
||||
eaglerNoDelay = eaglercraftXOpts.getEaglerNoDelay(false);
|
||||
ramdiskMode = eaglercraftXOpts.getRamdiskMode(false);
|
||||
singleThreadMode = eaglercraftXOpts.getSingleThreadMode(false);
|
||||
JSEaglercraftXOptsHooks hooksObj = eaglercraftXOpts.getHooks();
|
||||
if(hooksObj != null) {
|
||||
hooks.loadHooks(hooksObj);
|
||||
}
|
||||
|
||||
|
||||
integratedServerOpts.put("worldsDB", worldsDB);
|
||||
integratedServerOpts.put("demoMode", demoMode);
|
||||
integratedServerOpts.put("lang", defaultLocale);
|
||||
@ -98,19 +147,27 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
|
||||
integratedServerOpts.put("allowVoiceClient", allowVoiceClient);
|
||||
integratedServerOpts.put("allowFNAWSkins", allowFNAWSkins);
|
||||
integratedServerOpts.put("crashOnUncaughtExceptions", crashOnUncaughtExceptions);
|
||||
integratedServerOpts.put("deobfStackTraces", deobfStackTraces);
|
||||
integratedServerOpts.put("disableBlobURLs", disableBlobURLs);
|
||||
integratedServerOpts.put("eaglerNoDelay", eaglerNoDelay);
|
||||
integratedServerOpts.put("ramdiskMode", ramdiskMode);
|
||||
integratedServerOpts.put("singleThreadMode", singleThreadMode);
|
||||
|
||||
defaultServers.clear();
|
||||
JSArrayReader<JSEaglercraftXOptsServer> serversArray = eaglercraftXOpts.getServers();
|
||||
if(serversArray != null) {
|
||||
for(int i = 0, l = serversArray.getLength(); i < l; ++i) {
|
||||
JSEaglercraftXOptsServer serverEntry = serversArray.get(i);
|
||||
boolean hideAddr = serverEntry.getHideAddr(false);
|
||||
String serverAddr = serverEntry.getAddr();
|
||||
if(serverAddr != null) {
|
||||
String serverName = serverEntry.getName("Default Server #" + i);
|
||||
defaultServers.add(new DefaultServer(serverName, serverAddr));
|
||||
defaultServers.add(new DefaultServer(serverName, serverAddr, hideAddr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relays.clear();
|
||||
JSArrayReader<JSEaglercraftXOptsRelay> relaysArray = eaglercraftXOpts.getRelays();
|
||||
if(relaysArray != null) {
|
||||
boolean gotAPrimary = false;
|
||||
@ -181,19 +238,46 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
|
||||
allowFNAWSkins = eaglercraftOpts.optBoolean("allowFNAWSkins", true);
|
||||
localStorageNamespace = eaglercraftOpts.optString("localStorageNamespace", EaglercraftVersion.localStorageNamespace);
|
||||
enableMinceraft = eaglercraftOpts.optBoolean("enableMinceraft", true);
|
||||
enableServerCookies = !demoMode && eaglercraftOpts.optBoolean("enableServerCookies", true);
|
||||
allowServerRedirects = eaglercraftOpts.optBoolean("allowServerRedirects", true);
|
||||
crashOnUncaughtExceptions = eaglercraftOpts.optBoolean("crashOnUncaughtExceptions", false);
|
||||
openDebugConsoleOnLaunch = eaglercraftOpts.optBoolean("openDebugConsoleOnLaunch", false);
|
||||
fixDebugConsoleUnloadListener = eaglercraftOpts.optBoolean("fixDebugConsoleUnloadListener", false);
|
||||
forceWebViewSupport = eaglercraftOpts.optBoolean("forceWebViewSupport", false);
|
||||
enableWebViewCSP = eaglercraftOpts.optBoolean("enableWebViewCSP", true);
|
||||
autoFixLegacyStyleAttr = eaglercraftOpts.optBoolean("autoFixLegacyStyleAttr", true);
|
||||
showBootMenuOnLaunch = eaglercraftOpts.optBoolean("showBootMenuOnLaunch", false);
|
||||
bootMenuBlocksUnsignedClients = eaglercraftOpts.optBoolean("bootMenuBlocksUnsignedClients", false);
|
||||
allowBootMenu = eaglercraftOpts.optBoolean("allowBootMenu", !demoMode);
|
||||
forceProfanityFilter = eaglercraftOpts.optBoolean("forceProfanityFilter", false);
|
||||
forceWebGL1 = eaglercraftOpts.optBoolean("forceWebGL1", false);
|
||||
forceWebGL2 = eaglercraftOpts.optBoolean("forceWebGL2", false);
|
||||
allowExperimentalWebGL1 = eaglercraftOpts.optBoolean("allowExperimentalWebGL1", true);
|
||||
useWebGLExt = eaglercraftOpts.optBoolean("useWebGLExt", true);
|
||||
useDelayOnSwap = eaglercraftOpts.optBoolean("useDelayOnSwap", false);
|
||||
useJOrbisAudioDecoder = eaglercraftOpts.optBoolean("useJOrbisAudioDecoder", false);
|
||||
useXHRFetch = eaglercraftOpts.optBoolean("useXHRFetch", false);
|
||||
useVisualViewport = eaglercraftOpts.optBoolean("useVisualViewport", true);
|
||||
deobfStackTraces = eaglercraftOpts.optBoolean("deobfStackTraces", true);
|
||||
disableBlobURLs = eaglercraftOpts.optBoolean("disableBlobURLs", false);
|
||||
eaglerNoDelay = eaglercraftOpts.optBoolean("eaglerNoDelay", false);
|
||||
ramdiskMode = eaglercraftOpts.optBoolean("ramdiskMode", false);
|
||||
singleThreadMode = eaglercraftOpts.optBoolean("singleThreadMode", false);
|
||||
defaultServers.clear();
|
||||
JSONArray serversArray = eaglercraftOpts.optJSONArray("servers");
|
||||
if(serversArray != null) {
|
||||
for(int i = 0, l = serversArray.length(); i < l; ++i) {
|
||||
JSONObject serverEntry = serversArray.getJSONObject(i);
|
||||
boolean hideAddr = serverEntry.optBoolean("hideAddr", false);
|
||||
String serverAddr = serverEntry.optString("addr", null);
|
||||
if(serverAddr != null) {
|
||||
String serverName = serverEntry.optString("name", "Default Server #" + i);
|
||||
defaultServers.add(new DefaultServer(serverName, serverAddr));
|
||||
defaultServers.add(new DefaultServer(serverName, serverAddr, hideAddr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relays.clear();
|
||||
JSONArray relaysArray = eaglercraftOpts.optJSONArray("relays");
|
||||
if(relaysArray != null) {
|
||||
boolean gotAPrimary = false;
|
||||
@ -344,13 +428,119 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
|
||||
return enableMinceraft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnableServerCookies() {
|
||||
return enableServerCookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowServerRedirects() {
|
||||
return allowServerRedirects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpenDebugConsoleOnLaunch() {
|
||||
return openDebugConsoleOnLaunch;
|
||||
}
|
||||
|
||||
public boolean isFixDebugConsoleUnloadListenerTeaVM() {
|
||||
return fixDebugConsoleUnloadListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isForceWebViewSupport() {
|
||||
return forceWebViewSupport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnableWebViewCSP() {
|
||||
return enableWebViewCSP;
|
||||
}
|
||||
|
||||
public boolean isAutoFixLegacyStyleAttrTeaVM() {
|
||||
return autoFixLegacyStyleAttr;
|
||||
}
|
||||
|
||||
public boolean isForceWebGL1TeaVM() {
|
||||
return forceWebGL1;
|
||||
}
|
||||
|
||||
public boolean isForceWebGL2TeaVM() {
|
||||
return forceWebGL2;
|
||||
}
|
||||
|
||||
public boolean isAllowExperimentalWebGL1TeaVM() {
|
||||
return allowExperimentalWebGL1;
|
||||
}
|
||||
|
||||
public boolean isUseWebGLExtTeaVM() {
|
||||
return useWebGLExt;
|
||||
}
|
||||
|
||||
public boolean isUseDelayOnSwapTeaVM() {
|
||||
return useDelayOnSwap;
|
||||
}
|
||||
|
||||
public boolean isUseJOrbisAudioDecoderTeaVM() {
|
||||
return useJOrbisAudioDecoder;
|
||||
}
|
||||
|
||||
public boolean isUseXHRFetchTeaVM() {
|
||||
return useXHRFetch;
|
||||
}
|
||||
|
||||
public boolean isDeobfStackTracesTeaVM() {
|
||||
return deobfStackTraces;
|
||||
}
|
||||
|
||||
public boolean isUseVisualViewportTeaVM() {
|
||||
return useVisualViewport;
|
||||
}
|
||||
|
||||
public boolean isDisableBlobURLsTeaVM() {
|
||||
return disableBlobURLs;
|
||||
}
|
||||
|
||||
public boolean isSingleThreadModeTeaVM() {
|
||||
return singleThreadMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowBootMenuOnLaunch() {
|
||||
return showBootMenuOnLaunch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBootMenuBlocksUnsignedClients() {
|
||||
return bootMenuBlocksUnsignedClients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowBootMenu() {
|
||||
return allowBootMenu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isForceProfanityFilter() {
|
||||
return forceProfanityFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEaglerNoDelay() {
|
||||
return eaglerNoDelay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRamdiskMode() {
|
||||
return ramdiskMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IClientConfigAdapterHooks getHooks() {
|
||||
return hooks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
public JSONObject toJSONObject() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("lang", defaultLocale);
|
||||
jsonObject.put("joinServer", serverToJoin);
|
||||
@ -370,12 +560,37 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
|
||||
jsonObject.put("allowFNAWSkins", allowFNAWSkins);
|
||||
jsonObject.put("localStorageNamespace", localStorageNamespace);
|
||||
jsonObject.put("enableMinceraft", enableMinceraft);
|
||||
jsonObject.put("enableServerCookies", enableServerCookies);
|
||||
jsonObject.put("allowServerRedirects", allowServerRedirects);
|
||||
jsonObject.put("crashOnUncaughtExceptions", crashOnUncaughtExceptions);
|
||||
jsonObject.put("openDebugConsoleOnLaunch", openDebugConsoleOnLaunch);
|
||||
jsonObject.put("fixDebugConsoleUnloadListener", fixDebugConsoleUnloadListener);
|
||||
jsonObject.put("forceWebViewSupport", forceWebViewSupport);
|
||||
jsonObject.put("enableWebViewCSP", enableWebViewCSP);
|
||||
jsonObject.put("autoFixLegacyStyleAttr", autoFixLegacyStyleAttr);
|
||||
jsonObject.put("showBootMenuOnLaunch", showBootMenuOnLaunch);
|
||||
jsonObject.put("bootMenuBlocksUnsignedClients", bootMenuBlocksUnsignedClients);
|
||||
jsonObject.put("allowBootMenu", allowBootMenu);
|
||||
jsonObject.put("forceProfanityFilter", forceProfanityFilter);
|
||||
jsonObject.put("forceWebGL1", forceWebGL1);
|
||||
jsonObject.put("forceWebGL2", forceWebGL2);
|
||||
jsonObject.put("allowExperimentalWebGL1", allowExperimentalWebGL1);
|
||||
jsonObject.put("useWebGLExt", useWebGLExt);
|
||||
jsonObject.put("useDelayOnSwap", useDelayOnSwap);
|
||||
jsonObject.put("useJOrbisAudioDecoder", useJOrbisAudioDecoder);
|
||||
jsonObject.put("useXHRFetch", useXHRFetch);
|
||||
jsonObject.put("useVisualViewport", useVisualViewport);
|
||||
jsonObject.put("deobfStackTraces", deobfStackTraces);
|
||||
jsonObject.put("disableBlobURLs", disableBlobURLs);
|
||||
jsonObject.put("eaglerNoDelay", eaglerNoDelay);
|
||||
jsonObject.put("ramdiskMode", ramdiskMode);
|
||||
jsonObject.put("singleThreadMode", singleThreadMode);
|
||||
JSONArray serversArr = new JSONArray();
|
||||
for(int i = 0, l = defaultServers.size(); i < l; ++i) {
|
||||
DefaultServer srv = defaultServers.get(i);
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("addr", srv.addr);
|
||||
obj.put("hideAddr", srv.hideAddress);
|
||||
obj.put("name", srv.name);
|
||||
serversArr.put(obj);
|
||||
}
|
||||
@ -390,6 +605,16 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
|
||||
relaysArr.put(obj);
|
||||
}
|
||||
jsonObject.put("relays", relaysArr);
|
||||
return jsonObject.toString();
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toJSONObject().toString();
|
||||
}
|
||||
|
||||
public String toStringFormatted() {
|
||||
return toJSONObject().toString(4);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ public class TeaVMClientConfigAdapterHooks implements IClientConfigAdapterHooks
|
||||
private LocalStorageSaveHook saveHook = null;
|
||||
private LocalStorageLoadHook loadHook = null;
|
||||
private CrashReportHook crashHook = null;
|
||||
private ScreenChangeHook screenChangedHook = null;
|
||||
|
||||
@JSFunctor
|
||||
private static interface LocalStorageSaveHook extends JSObject {
|
||||
@ -67,6 +68,22 @@ public class TeaVMClientConfigAdapterHooks implements IClientConfigAdapterHooks
|
||||
}
|
||||
}
|
||||
|
||||
@JSFunctor
|
||||
private static interface ScreenChangeHook extends JSObject {
|
||||
String call(String screenName, int scaledWidth, int scaledHeight, int realWidth, int realHeight,
|
||||
int scaleFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callScreenChangedHook(String screenName, int scaledWidth, int scaledHeight, int realWidth,
|
||||
int realHeight, int scaleFactor) {
|
||||
if(screenChangedHook != null) {
|
||||
callHookSafe("screenChanged", () -> {
|
||||
screenChangedHook.call(screenName, scaledWidth, scaledHeight, realWidth, realHeight, scaleFactor);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@JSFunctor
|
||||
private static interface CrashReportHook extends JSObject {
|
||||
void call(String crashReport, CustomMessageCB customMessageCB);
|
||||
@ -134,5 +151,7 @@ public class TeaVMClientConfigAdapterHooks implements IClientConfigAdapterHooks
|
||||
saveHook = (LocalStorageSaveHook)hooks.getLocalStorageSavedHook();
|
||||
loadHook = (LocalStorageLoadHook)hooks.getLocalStorageLoadedHook();
|
||||
crashHook = (CrashReportHook)hooks.getCrashReportHook();
|
||||
screenChangedHook = (ScreenChangeHook)hooks.getScreenChangedHook();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.teavm.interop.Async;
|
||||
import org.teavm.interop.AsyncCallback;
|
||||
import org.teavm.jso.browser.Window;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.Base64;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class TeaVMDataURLManager {
|
||||
|
||||
private static void checkDataURLSupport0(boolean fetchBased, final AsyncCallback<Boolean> callback) {
|
||||
final byte[] testData = new byte[1024];
|
||||
for(int i = 0; i < 1024; ++i) {
|
||||
testData[i] = (byte)i;
|
||||
}
|
||||
String testURL = "data:application/octet-stream;base64," + Base64.encodeBase64String(testData);
|
||||
TeaVMFetchJS.FetchHandler cb = (data) -> {
|
||||
if(data != null && TeaVMUtils.isTruthy(data) && data.getByteLength() == 1024) {
|
||||
byte[] bb = TeaVMUtils.wrapByteArrayBuffer(data);
|
||||
callback.complete(Arrays.equals(bb, testData));
|
||||
}else {
|
||||
callback.complete(false);
|
||||
}
|
||||
};
|
||||
try {
|
||||
if(fetchBased) {
|
||||
TeaVMFetchJS.doFetchDownload(testURL, "force-cache", cb);
|
||||
}else {
|
||||
TeaVMFetchJS.doXHRDownload(testURL, cb);
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
callback.complete(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
private static native Boolean checkDataURLSupport0(boolean fetchBased);
|
||||
|
||||
public static boolean checkDataURLSupport(boolean fetchBased) {
|
||||
Boolean b = null;
|
||||
try {
|
||||
b = checkDataURLSupport0(fetchBased);
|
||||
}catch(Throwable t) {
|
||||
}
|
||||
return b != null && b.booleanValue();
|
||||
}
|
||||
|
||||
public static byte[] decodeDataURLFallback(String dataURL) {
|
||||
if(dataURL.length() < 6 || !dataURL.substring(0, 5).equalsIgnoreCase("data:")) {
|
||||
return null;
|
||||
}
|
||||
int i = dataURL.indexOf(',');
|
||||
if(i == -1 || i >= dataURL.length() - 1) {
|
||||
return null;
|
||||
}
|
||||
String mime = dataURL.substring(0, i).toLowerCase();
|
||||
String str = dataURL.substring(i + 1);
|
||||
try {
|
||||
if(mime.endsWith(";base64")) {
|
||||
return Base64.decodeBase64(str);
|
||||
}else {
|
||||
return Window.decodeURIComponent(str).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class TeaVMEnterBootMenuException extends RuntimeException {
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class TeaVMFetchJS {
|
||||
|
||||
@JSFunctor
|
||||
public static interface FetchHandler extends JSObject {
|
||||
void onFetch(ArrayBuffer data);
|
||||
}
|
||||
|
||||
@JSBody(params = { }, script = "return (typeof fetch === \"function\");")
|
||||
public static native boolean checkFetchSupport();
|
||||
|
||||
@JSBody(params = { "uri", "forceCache", "callback" }, script = "fetch(uri, { cache: forceCache, mode: \"no-cors\" })"
|
||||
+ ".then(function(res) { return res.arrayBuffer(); }).then(function(arr) { callback(arr); })"
|
||||
+ ".catch(function(err) { console.error(err); callback(null); });")
|
||||
public static native void doFetchDownload(String uri, String forceCache, FetchHandler callback);
|
||||
|
||||
@JSBody(params = { "uri", "callback" }, script = "var eag = function(xhrObj){xhrObj.responseType = \"arraybuffer\";"
|
||||
+ "xhrObj.addEventListener(\"load\", function(evt) { var stat = xhrObj.status; if(stat === 0 || (stat >= 200 && stat < 400)) { callback(xhrObj.response); } else { callback(null); } });"
|
||||
+ "xhrObj.addEventListener(\"error\", function(evt) { callback(null); });"
|
||||
+ "xhrObj.open(\"GET\", uri, true); xhrObj.send();}; eag(new XMLHttpRequest());")
|
||||
public static native void doXHRDownload(String uri, FetchHandler callback);
|
||||
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.teavm.backend.javascript.spi.GeneratedBy;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.core.JSArrayReader;
|
||||
import org.teavm.jso.core.JSString;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EagUtils;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.generators.TeaVMRuntimeDeobfuscatorGenerator;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class TeaVMRuntimeDeobfuscator {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("TeaVMRuntimeDeobfuscator");
|
||||
|
||||
private static class DeobfNameEntry {
|
||||
|
||||
private final String className;
|
||||
private final String functionName;
|
||||
|
||||
private DeobfNameEntry(String className, String functionName) {
|
||||
this.className = className;
|
||||
this.functionName = functionName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Object initLock = new Object();
|
||||
|
||||
private static final Map<String,String> deobfClassNames = new HashMap<>();
|
||||
private static final Map<String,DeobfNameEntry> deobfFuncNames = new HashMap<>();
|
||||
|
||||
private static boolean isInitialized = false;
|
||||
private static boolean isFailed = false;
|
||||
|
||||
@GeneratedBy(TeaVMRuntimeDeobfuscatorGenerator.class)
|
||||
private static native JSArrayReader<JSObject> getAllClasses();
|
||||
|
||||
private static void initialize0() {
|
||||
try {
|
||||
logger.info("Loading deobfuscation data, please wait...");
|
||||
}catch(Throwable t2) {
|
||||
}
|
||||
long time = PlatformRuntime.steadyTimeMillis();
|
||||
JSArrayReader<JSObject> classes = getAllClasses();
|
||||
if(classes.getLength() < 2) {
|
||||
return;
|
||||
}
|
||||
deobfClassNames.clear();
|
||||
deobfFuncNames.clear();
|
||||
JSArrayReader<JSString> stringReaderA = (JSArrayReader<JSString>)classes.get(0);
|
||||
JSArrayReader<JSString> stringReaderB = (JSArrayReader<JSString>)classes.get(1);
|
||||
String[] javaStringPoolA = new String[stringReaderA.getLength()];
|
||||
for(int i = 0; i < javaStringPoolA.length; ++i) {
|
||||
javaStringPoolA[i] = stringReaderA.get(i).stringValue();
|
||||
}
|
||||
String[] javaStringPoolB = new String[stringReaderB.getLength()];
|
||||
for(int i = 0; i < javaStringPoolB.length; ++i) {
|
||||
javaStringPoolB[i] = stringReaderB.get(i).stringValue();
|
||||
}
|
||||
for(int i = 2, l = classes.getLength() - 2; i < l; i += 3) {
|
||||
int[] lookupTblClsName = Base64VarIntArray.decodeVarIntArray((JSString)classes.get(i));
|
||||
StringBuilder classNameBuilder = new StringBuilder();
|
||||
boolean b = false;
|
||||
for(int j = 0; j < lookupTblClsName.length; ++j) {
|
||||
if(b) {
|
||||
classNameBuilder.append('.');
|
||||
}
|
||||
classNameBuilder.append(javaStringPoolA[lookupTblClsName[j]]);
|
||||
b = true;
|
||||
}
|
||||
String className = classNameBuilder.toString();
|
||||
String classObfName = ((JSString)classes.get(i + 1)).stringValue();
|
||||
deobfClassNames.put(classObfName, className);
|
||||
int[] lookupTbl = Base64VarIntArray.decodeVarIntArray((JSString)classes.get(i + 2));
|
||||
for(int j = 0, m = lookupTbl.length - 1; j < m; j += 2) {
|
||||
String obfName = javaStringPoolB[lookupTbl[j]];
|
||||
String deobfName = javaStringPoolB[lookupTbl[j + 1]];
|
||||
deobfFuncNames.put(obfName, new DeobfNameEntry(className, deobfName));
|
||||
}
|
||||
}
|
||||
try {
|
||||
time = PlatformRuntime.steadyTimeMillis() - time;
|
||||
logger.info("Indexed {} class names and {} function names after {}ms", deobfClassNames.size(), deobfFuncNames.size(), time);
|
||||
}catch(Throwable t2) {
|
||||
}
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
if(!isFailed) {
|
||||
synchronized(initLock) {
|
||||
if(!isInitialized) {
|
||||
try {
|
||||
initialize0();
|
||||
isInitialized = true;
|
||||
}catch(Throwable t) {
|
||||
isFailed = true;
|
||||
try {
|
||||
logger.error("Failed to initialize the tables!");
|
||||
logger.error(t);
|
||||
}catch(Throwable t2) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String deobfClassName(String clsName) {
|
||||
if(!isInitialized) return null;
|
||||
return deobfClassNames.get(clsName);
|
||||
}
|
||||
|
||||
public static String deobfFunctionName(String funcName) {
|
||||
if(!isInitialized) return null;
|
||||
DeobfNameEntry ret = deobfFuncNames.get(funcName);
|
||||
return ret != null ? ret.functionName : null;
|
||||
}
|
||||
|
||||
public static String deobfFunctionClass(String funcName) {
|
||||
if(!isInitialized) return null;
|
||||
DeobfNameEntry ret = deobfFuncNames.get(funcName);
|
||||
return ret != null ? ret.className : null;
|
||||
}
|
||||
|
||||
public static String deobfFunctionFullName(String funcName) {
|
||||
if(!isInitialized) return null;
|
||||
DeobfNameEntry ret = deobfFuncNames.get(funcName);
|
||||
return ret != null ? (ret.className != null ? ret.className : "<unknown>") + "." + ret.functionName + "()" : null;
|
||||
}
|
||||
|
||||
public static String deobfFullName(String funcName) {
|
||||
if(!isInitialized) return null;
|
||||
DeobfNameEntry ret = deobfFuncNames.get(funcName);
|
||||
return ret != null ? (ret.className != null ? ret.className : "<unknown>") + "." + ret.functionName + "()" : deobfClassNames.get(funcName);
|
||||
}
|
||||
|
||||
private static int countLeadingWhitespace(String line) {
|
||||
for(int i = 0, l = line.length(); i < l; ++i) {
|
||||
char c = line.charAt(i);
|
||||
if(c != ' ' && c != '\t') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static String deobfExceptionStack(String stackLines) {
|
||||
if(!isInitialized) return stackLines;
|
||||
try {
|
||||
List<String> lines = Lists.newArrayList(EagUtils.splitPattern.split(stackLines));
|
||||
deobfExceptionStack(lines);
|
||||
return String.join("\n", lines);
|
||||
}catch(Throwable t) {
|
||||
try {
|
||||
logger.error("Failed to deobfuscate stack trace!");
|
||||
}catch(Throwable t2) {
|
||||
}
|
||||
return stackLines;
|
||||
}
|
||||
}
|
||||
|
||||
public static void deobfExceptionStack(List<String> stackLines) {
|
||||
if(!isInitialized) return;
|
||||
try {
|
||||
for(int i = 0, l = stackLines.size(); i < l; ++i) {
|
||||
String line = stackLines.get(i);
|
||||
int len = line.length();
|
||||
if(len == 0) continue;
|
||||
int leadingWs = countLeadingWhitespace(line);
|
||||
if(len > leadingWs + 3 && line.charAt(leadingWs) == 'a' && line.charAt(leadingWs + 1) == 't' && line.charAt(leadingWs + 2) == ' ') {
|
||||
leadingWs += 3;
|
||||
}
|
||||
int nextSpace = line.indexOf(' ', leadingWs);
|
||||
int nextDot = line.indexOf('.', leadingWs);
|
||||
String funcName2 = null;
|
||||
if(nextDot > 0 && nextDot < nextSpace) {
|
||||
funcName2 = line.substring(nextDot + 1, nextSpace);
|
||||
nextSpace = nextDot;
|
||||
}
|
||||
if(nextSpace == -1) {
|
||||
nextSpace = line.indexOf('@', leadingWs);
|
||||
if(nextSpace == -1 && nextSpace < leadingWs) {
|
||||
if(nextSpace == leadingWs + 1 && line.charAt(leadingWs) == '@') {
|
||||
continue;
|
||||
}
|
||||
nextSpace = len;
|
||||
}
|
||||
}
|
||||
if(nextSpace - leadingWs < 1) {
|
||||
continue;
|
||||
}
|
||||
String funcName = line.substring(leadingWs, nextSpace);
|
||||
String deobfName = deobfFunctionFullName(funcName);
|
||||
if(deobfName != null) {
|
||||
stackLines.set(i, line.substring(0, leadingWs) + deobfName + line.substring(nextSpace));
|
||||
}else {
|
||||
deobfName = deobfClassName(funcName);
|
||||
if(deobfName != null) {
|
||||
DeobfNameEntry deobfName2 = null;
|
||||
if(funcName2 != null && funcName2.indexOf('.') == -1) {
|
||||
deobfName2 = deobfFuncNames.get(funcName2);
|
||||
}
|
||||
if(deobfName2 != null && deobfName.equals(deobfName2.className)) {
|
||||
deobfName += "." + deobfName2.functionName + "()";
|
||||
}
|
||||
stackLines.set(i, line.substring(0, leadingWs) + deobfName + line.substring(nextSpace));
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
try {
|
||||
logger.error("Failed to deobfuscate stack trace!");
|
||||
}catch(Throwable t2) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,203 +0,0 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONObject;
|
||||
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.dom.events.MessageEvent;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
import org.teavm.jso.websocket.WebSocket;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.EnumServerRateLimit;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IServerQuery;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.QueryResponse;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
||||
/**
|
||||
* 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 TeaVMServerQuery implements IServerQuery {
|
||||
|
||||
public static final Logger logger = LogManager.getLogger("WebSocketQuery");
|
||||
|
||||
private final List<QueryResponse> queryResponses = new LinkedList();
|
||||
private final List<byte[]> queryResponsesBytes = new LinkedList();
|
||||
|
||||
protected final String uri;
|
||||
protected final String accept;
|
||||
protected final WebSocket sock;
|
||||
protected boolean open = true;
|
||||
protected boolean alive = false;
|
||||
protected long pingStart = -1l;
|
||||
protected long pingTimer = -1l;
|
||||
private EnumServerRateLimit rateLimit = EnumServerRateLimit.OK;
|
||||
|
||||
public TeaVMServerQuery(String uri, String accept) {
|
||||
this.uri = uri;
|
||||
this.accept = accept;
|
||||
this.sock = WebSocket.create(uri);
|
||||
initHandlers();
|
||||
}
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return typeof obj === \"string\";")
|
||||
private static native boolean isString(JSObject obj);
|
||||
|
||||
protected void initHandlers() {
|
||||
sock.setBinaryType("arraybuffer");
|
||||
TeaVMUtils.addEventListener(sock, "open", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
sock.send("Accept: " + accept);
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(sock, "close", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
open = false;
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(sock, "message", new EventListener<MessageEvent>() {
|
||||
@Override
|
||||
public void handleEvent(MessageEvent evt) {
|
||||
alive = true;
|
||||
if(pingTimer == -1) {
|
||||
pingTimer = System.currentTimeMillis() - pingStart;
|
||||
if(pingTimer < 1) {
|
||||
pingTimer = 1;
|
||||
}
|
||||
}
|
||||
if(isString(evt.getData())) {
|
||||
String str = evt.getDataAsString();
|
||||
if(str.equalsIgnoreCase("BLOCKED")) {
|
||||
logger.error("Reached full IP ratelimit for {}!", uri);
|
||||
rateLimit = EnumServerRateLimit.BLOCKED;
|
||||
return;
|
||||
}
|
||||
if(str.equalsIgnoreCase("LOCKED")) {
|
||||
logger.error("Reached full IP ratelimit lockout for {}!", uri);
|
||||
rateLimit = EnumServerRateLimit.LOCKED_OUT;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
JSONObject obj = new JSONObject(str);
|
||||
if("blocked".equalsIgnoreCase(obj.optString("type", null))) {
|
||||
logger.error("Reached query ratelimit for {}!", uri);
|
||||
rateLimit = EnumServerRateLimit.BLOCKED;
|
||||
}else if("locked".equalsIgnoreCase(obj.optString("type", null))) {
|
||||
logger.error("Reached query ratelimit lockout for {}!", uri);
|
||||
rateLimit = EnumServerRateLimit.LOCKED_OUT;
|
||||
}else {
|
||||
QueryResponse response = new QueryResponse(obj, pingTimer);
|
||||
synchronized(queryResponses) {
|
||||
queryResponses.add(response);
|
||||
}
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
logger.error("Exception thrown parsing websocket query response from \"" + uri + "\"!");
|
||||
logger.error(t);
|
||||
}
|
||||
}else {
|
||||
synchronized(queryResponsesBytes) {
|
||||
queryResponsesBytes.add(TeaVMUtils.wrapByteArrayBuffer(evt.getDataAsArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(sock, "error", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
sock.close();
|
||||
open = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String str) {
|
||||
if(open) {
|
||||
sock.send(str);
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { "sock", "buffer" }, script = "sock.send(buffer);")
|
||||
private static native void nativeBinarySend(WebSocket sock, ArrayBuffer buffer);
|
||||
|
||||
@Override
|
||||
public void send(byte[] bytes) {
|
||||
if(open) {
|
||||
nativeBinarySend(sock, TeaVMUtils.unwrapArrayBuffer(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int responsesAvailable() {
|
||||
synchronized(queryResponses) {
|
||||
return queryResponses.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResponse getResponse() {
|
||||
synchronized(queryResponses) {
|
||||
if(queryResponses.size() > 0) {
|
||||
return queryResponses.remove(0);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int binaryResponsesAvailable() {
|
||||
synchronized(queryResponsesBytes) {
|
||||
return queryResponsesBytes.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBinaryResponse() {
|
||||
synchronized(queryResponsesBytes) {
|
||||
if(queryResponsesBytes.size() > 0) {
|
||||
return queryResponsesBytes.remove(0);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryReadyState readyState() {
|
||||
return open ? (alive ? QueryReadyState.OPEN : QueryReadyState.CONNECTING)
|
||||
: (alive ? QueryReadyState.CLOSED : QueryReadyState.FAILED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if(open) {
|
||||
open = false;
|
||||
sock.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumServerRateLimit getRateLimit() {
|
||||
return rateLimit;
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@ import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.Base64;
|
||||
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.EagUtils;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformAssets;
|
||||
@ -28,7 +27,9 @@ import net.lax1dude.eaglercraft.v1_8.internal.PlatformUpdateSvc;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
import net.lax1dude.eaglercraft.v1_8.update.UpdateCertificate;
|
||||
import net.lax1dude.eaglercraft.v1_8.update.UpdateDataObj;
|
||||
import net.lax1dude.eaglercraft.v1_8.update.UpdateProgressStruct;
|
||||
import net.lax1dude.eaglercraft.v1_8.update.UpdateResultObj;
|
||||
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
|
||||
|
||||
/**
|
||||
@ -61,6 +62,7 @@ public class TeaVMUpdateThread implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean success = false;
|
||||
boolean hasCompleted = false;
|
||||
try {
|
||||
logger.info("Starting update thread...");
|
||||
updateProg.clear();
|
||||
@ -68,7 +70,7 @@ public class TeaVMUpdateThread implements Runnable {
|
||||
updateProg.statusString1 = updateCert.bundleDisplayName + " - " + updateCert.bundleDisplayVersion;
|
||||
updateProg.statusString2 = "Please Wait";
|
||||
|
||||
List<String> urlListA = new ArrayList();
|
||||
List<String> urlListA = new ArrayList<>();
|
||||
ListMultimap<String,String> downloadSources = updateCert.getSourceMultimap();
|
||||
|
||||
List<String> ls = downloadSources.get("list");
|
||||
@ -115,7 +117,7 @@ public class TeaVMUpdateThread implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
List<String> urlListB = new ArrayList();
|
||||
List<String> urlListB = new ArrayList<>();
|
||||
ls = downloadSources.get("use-proxy");
|
||||
for(int k = 0, l = ls.size(); k < l; ++k) {
|
||||
String str1 = ls.get(k);
|
||||
@ -147,7 +149,7 @@ public class TeaVMUpdateThread implements Runnable {
|
||||
logger.info("Verifying downloaded file...");
|
||||
if(updateCert.isBundleDataValid(b)) {
|
||||
logger.info("Success! Signature is valid!");
|
||||
downloadSignedOffline(updateCert, b);
|
||||
PlatformUpdateSvc.setUpdateResultTeaVM(UpdateResultObj.createSuccess(new UpdateDataObj(updateCert, b)));
|
||||
success = true;
|
||||
return;
|
||||
}
|
||||
@ -162,11 +164,17 @@ public class TeaVMUpdateThread implements Runnable {
|
||||
}catch(Throwable t) {
|
||||
logger.error("Uncaught exception downloading updates!");
|
||||
logger.error(t);
|
||||
hasCompleted = true;
|
||||
PlatformUpdateSvc.setUpdateResultTeaVM(UpdateResultObj.createFailure(t.toString()));
|
||||
}finally {
|
||||
PlatformUpdateSvc.updateThread = null;
|
||||
updateProg.isBusy = false;
|
||||
if(!success) {
|
||||
logger.error("Failed to download updates! No valid URL was found for {}", updateCert.bundleDisplayVersion);
|
||||
String str = "Failed to download updates! No valid URL was found for " + updateCert.bundleDisplayVersion;
|
||||
logger.error(str);
|
||||
if(!hasCompleted) {
|
||||
PlatformUpdateSvc.setUpdateResultTeaVM(UpdateResultObj.createFailure(str));
|
||||
}
|
||||
Window.alert("ERROR: Failed to download updates!\n\nIf you are on a device with restricted internet access, try a different device or connect to a different WiFi network\n\nCheck the debug console for more info");
|
||||
}else {
|
||||
UpdateService.dismiss(updateCert);
|
||||
@ -254,7 +262,7 @@ public class TeaVMUpdateThread implements Runnable {
|
||||
}
|
||||
|
||||
public static byte[] generateSignedOffline(UpdateCertificate cert, byte[] data) {
|
||||
return generateSignedOffline(cert.rawCertData, data, EagRuntime.fixDateFormat(new SimpleDateFormat("MM/dd/yyyy")).format(new Date(cert.sigTimestamp)));
|
||||
return generateSignedOffline(cert.rawCertData, data, (new SimpleDateFormat("MM/dd/yyyy")).format(new Date(cert.sigTimestamp)));
|
||||
}
|
||||
|
||||
public static byte[] generateSignedOffline(byte[] cert, byte[] data, String date) {
|
||||
|
@ -1,11 +1,18 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.teavm.backend.javascript.spi.GeneratedBy;
|
||||
import org.teavm.backend.javascript.spi.InjectedBy;
|
||||
import org.teavm.interop.Async;
|
||||
import org.teavm.interop.AsyncCallback;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.teavm.jso.browser.Window;
|
||||
import org.teavm.jso.dom.html.HTMLScriptElement;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
import org.teavm.jso.typedarrays.ArrayBufferView;
|
||||
import org.teavm.jso.typedarrays.Float32Array;
|
||||
@ -14,7 +21,7 @@ import org.teavm.jso.typedarrays.Int32Array;
|
||||
import org.teavm.jso.typedarrays.Int8Array;
|
||||
import org.teavm.jso.typedarrays.Uint8Array;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EagUtils;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.generators.TeaVMUtilsUnwrapGenerator;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
|
||||
@ -39,210 +46,92 @@ public class TeaVMUtils {
|
||||
@JSBody(params = { "buf", "mime" }, script = "return URL.createObjectURL(new Blob([buf], {type: mime}));")
|
||||
public static native String getDataURL(ArrayBuffer buf, String mime);
|
||||
|
||||
@JSBody(params = { "blob" }, script = "return URL.createObjectURL(blob);")
|
||||
public static native String getDataURL(JSObject blob);
|
||||
|
||||
@JSBody(params = { "obj", "name", "handler" }, script = "obj.addEventListener(name, handler);")
|
||||
public static native void addEventListener(JSObject obj, String name, JSObject handler);
|
||||
|
||||
@JSBody(params = {}, script = "return (new Error()).stack;")
|
||||
public static native String dumpJSStackTrace();
|
||||
|
||||
private static abstract class TeaVMArrayObject implements JSObject {
|
||||
@JSProperty
|
||||
public abstract ArrayBufferView getData();
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
|
||||
public static native Int8Array unwrapByteArray(byte[] buf);
|
||||
|
||||
public static Int8Array unwrapByteArray(byte[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return Int8Array.create(((TeaVMArrayObject)(Object)buf).getData().getBuffer());
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
|
||||
public static native ArrayBuffer unwrapArrayBuffer(byte[] buf);
|
||||
|
||||
public static ArrayBuffer unwrapArrayBuffer(byte[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return ((TeaVMArrayObject)(Object)buf).getData().getBuffer();
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
|
||||
public static native ArrayBufferView unwrapArrayBufferView(byte[] buf);
|
||||
|
||||
public static ArrayBufferView unwrapArrayBufferView(byte[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return ((TeaVMArrayObject)(Object)buf).getData();
|
||||
}
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
|
||||
public static native byte[] wrapByteArray(Int8Array buf);
|
||||
|
||||
@JSBody(params = { "buf" }, script = "return $rt_createByteArray(buf)")
|
||||
private static native JSObject wrapByteArray0(JSObject buf);
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
|
||||
public static native byte[] wrapByteArrayBuffer(ArrayBuffer buf);
|
||||
|
||||
public static byte[] wrapByteArray(Int8Array buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (byte[])(Object)wrapByteArray0(buf.getBuffer());
|
||||
}
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
|
||||
public static native byte[] wrapByteArrayBufferView(ArrayBufferView buf);
|
||||
|
||||
public static byte[] wrapByteArrayBuffer(ArrayBuffer buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (byte[])(Object)wrapByteArray0(buf);
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapUnsignedTypedArray.class)
|
||||
public static native Uint8Array unwrapUnsignedByteArray(byte[] buf);
|
||||
|
||||
public static byte[] wrapByteArrayBufferView(ArrayBufferView buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (byte[])(Object)wrapByteArray0(buf.getBuffer());
|
||||
}
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
|
||||
public static native byte[] wrapUnsignedByteArray(Uint8Array buf);
|
||||
|
||||
public static Uint8Array unwrapUnsignedByteArray(byte[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return Uint8Array.create(((TeaVMArrayObject)(Object)buf).getData().getBuffer());
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
|
||||
public static native Int32Array unwrapIntArray(int[] buf);
|
||||
|
||||
public static byte[] wrapUnsignedByteArray(Uint8Array buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (byte[])(Object)wrapByteArray0(buf.getBuffer());
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
|
||||
public static native ArrayBuffer unwrapArrayBuffer(int[] buf);
|
||||
|
||||
public static Int32Array unwrapIntArray(int[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return Int32Array.create(((TeaVMArrayObject)(Object)buf).getData().getBuffer());
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
|
||||
public static native ArrayBufferView unwrapArrayBufferView(int[] buf);
|
||||
|
||||
public static ArrayBuffer unwrapArrayBuffer(int[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return ((TeaVMArrayObject)(Object)buf).getData().getBuffer();
|
||||
}
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
|
||||
public static native int[] wrapIntArray(Int32Array buf);
|
||||
|
||||
public static ArrayBufferView unwrapArrayBufferView(int[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return ((TeaVMArrayObject)(Object)buf).getData();
|
||||
}
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
|
||||
public static native int[] wrapIntArrayBuffer(ArrayBuffer buf);
|
||||
|
||||
@JSBody(params = { "buf" }, script = "return $rt_createIntArray(buf)")
|
||||
private static native JSObject wrapIntArray0(JSObject buf);
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
|
||||
public static native int[] wrapIntArrayBufferView(ArrayBufferView buf);
|
||||
|
||||
public static int[] wrapIntArray(Int32Array buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (int[])(Object)wrapIntArray0(buf.getBuffer());
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
|
||||
public static native Float32Array unwrapFloatArray(float[] buf);
|
||||
|
||||
public static int[] wrapIntArrayBuffer(ArrayBuffer buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (int[])(Object)wrapIntArray0(buf);
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
|
||||
public static native ArrayBuffer unwrapArrayBuffer(float[] buf);
|
||||
|
||||
public static int[] wrapIntArrayBufferView(ArrayBufferView buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (int[])(Object)wrapIntArray0(buf.getBuffer());
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
|
||||
public static native ArrayBufferView unwrapArrayBufferView(float[] buf);
|
||||
|
||||
public static Float32Array unwrapFloatArray(float[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return Float32Array.create(((TeaVMArrayObject)(Object)buf).getData().getBuffer());
|
||||
}
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
|
||||
public static native float[] wrapFloatArray(Float32Array buf);
|
||||
|
||||
public static ArrayBuffer unwrapArrayBuffer(float[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return ((TeaVMArrayObject)(Object)buf).getData().getBuffer();
|
||||
}
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
|
||||
public static native float[] wrapFloatArrayBuffer(ArrayBuffer buf);
|
||||
|
||||
public static ArrayBufferView unwrapArrayBufferView(float[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return ((TeaVMArrayObject)(Object)buf).getData();
|
||||
}
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
|
||||
public static native float[] wrapFloatArrayBufferView(ArrayBufferView buf);
|
||||
|
||||
@JSBody(params = { "buf" }, script = "return $rt_createFloatArray(buf)")
|
||||
private static native JSObject wrapFloatArray0(JSObject buf);
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
|
||||
public static native Int16Array unwrapShortArray(short[] buf);
|
||||
|
||||
public static float[] wrapFloatArray(Float32Array buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (float[])(Object)wrapFloatArray0(buf.getBuffer());
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
|
||||
public static native ArrayBuffer unwrapArrayBuffer(short[] buf);
|
||||
|
||||
public static float[] wrapFloatArrayBuffer(ArrayBuffer buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (float[])(Object)wrapFloatArray0(buf);
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
|
||||
public static native ArrayBufferView unwrapArrayBufferView(short[] buf);
|
||||
|
||||
public static float[] wrapFloatArrayBufferView(ArrayBufferView buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (float[])(Object)wrapFloatArray0(buf.getBuffer());
|
||||
}
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
|
||||
public static native short[] wrapShortArray(Int16Array buf);
|
||||
|
||||
public static Int16Array unwrapShortArray(short[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return Int16Array.create(((TeaVMArrayObject)(Object)buf).getData().getBuffer());
|
||||
}
|
||||
@GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
|
||||
public static native short[] wrapShortArrayBuffer(ArrayBuffer buf);
|
||||
|
||||
public static ArrayBuffer unwrapArrayBuffer(short[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return ((TeaVMArrayObject)(Object)buf).getData().getBuffer();
|
||||
}
|
||||
|
||||
public static ArrayBufferView unwrapArrayBufferView(short[] buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return ((TeaVMArrayObject)(Object)buf).getData();
|
||||
}
|
||||
|
||||
@JSBody(params = { "buf" }, script = "return $rt_createShortArray(buf)")
|
||||
private static native JSObject wrapShortArray0(JSObject buf);
|
||||
|
||||
public static short[] wrapShortArray(Int16Array buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (short[])(Object)wrapShortArray0(buf.getBuffer());
|
||||
}
|
||||
|
||||
public static short[] wrapShortArrayBuffer(ArrayBuffer buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (short[])(Object)wrapShortArray0(buf);
|
||||
}
|
||||
|
||||
public static short[] wrapShortArrayBuffer(ArrayBufferView buf) {
|
||||
if(buf == null) {
|
||||
return null;
|
||||
}
|
||||
return (short[])(Object)wrapShortArray0(buf.getBuffer());
|
||||
}
|
||||
@InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
|
||||
public static native short[] wrapShortArrayBuffer(ArrayBufferView buf);
|
||||
|
||||
@Async
|
||||
public static native void sleepSetTimeout(int millis);
|
||||
@ -251,41 +140,56 @@ public class TeaVMUtils {
|
||||
Window.setTimeout(() -> cb.complete(null), millis);
|
||||
}
|
||||
|
||||
public static String tryResolveClassesSource() {
|
||||
String str = dumpJSStackTrace();
|
||||
String[] frames = EagUtils.splitPattern.split(str);
|
||||
if("Error".equals(frames[0])) {
|
||||
// V8 stack trace
|
||||
if(frames.length > 1) {
|
||||
String framesTrim = frames[1].trim();
|
||||
if(framesTrim.startsWith("at")) {
|
||||
//definitely V8
|
||||
int i = framesTrim.indexOf('(');
|
||||
int j = framesTrim.indexOf(')');
|
||||
if(i != -1 && j != -1 && i < j) {
|
||||
return tryResolveClassesSourceFromFrame(framesTrim.substring(i + 1, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
}else {
|
||||
// Mozilla/WebKit stack trace
|
||||
String framesTrim = frames[0].trim();
|
||||
int i = framesTrim.indexOf('@');
|
||||
if(i != -1) {
|
||||
return tryResolveClassesSourceFromFrame(framesTrim.substring(i + 1));
|
||||
}
|
||||
public static final Comparator<Touch> touchSortingComparator = (t1, t2) -> {
|
||||
return t1.getIdentifier() - t2.getIdentifier();
|
||||
};
|
||||
|
||||
public static final Comparator<OffsetTouch> touchSortingComparator2 = (t1, t2) -> {
|
||||
return t1.touch.getIdentifier() - t2.touch.getIdentifier();
|
||||
};
|
||||
|
||||
public static List<OffsetTouch> toSortedTouchList(TouchList touchList, SortedTouchEvent.ITouchUIDMapper mapper,
|
||||
int originX, int originY) {
|
||||
int l = touchList.getLength();
|
||||
List<OffsetTouch> ret = new ArrayList<>(l);
|
||||
for(int i = 0; i < l; ++i) {
|
||||
ret.add(OffsetTouch.create(touchList.item(i), mapper, originX, originY));
|
||||
}
|
||||
return null;
|
||||
Collections.sort(ret, touchSortingComparator2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static String tryResolveClassesSourceFromFrame(String fileLineCol) {
|
||||
int i = fileLineCol.lastIndexOf(':');
|
||||
if(i > 0) {
|
||||
i = fileLineCol.lastIndexOf(':', i - 1);
|
||||
}
|
||||
if(i != -1) {
|
||||
return fileLineCol.substring(0, i);
|
||||
}
|
||||
return null;
|
||||
public static String tryResolveClassesSource() {
|
||||
return ClassesJSLocator.resolveClassesJSFromThrowable();
|
||||
}
|
||||
|
||||
public static HTMLScriptElement tryResolveClassesSourceInline() {
|
||||
return ClassesJSLocator.resolveClassesJSFromInline();
|
||||
}
|
||||
|
||||
@JSBody(params = { "obj" }, script = "console.log(obj);")
|
||||
public static native void objDump(JSObject obj);
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return \"\" + obj;")
|
||||
public static native String safeToString(JSObject obj);
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return (!!obj && (typeof obj.message === \"string\")) ? obj.message : (\"\" + obj);")
|
||||
public static native String safeErrorMsgToString(JSObject obj);
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return !!obj;")
|
||||
public static native boolean isTruthy(JSObject object);
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return !obj;")
|
||||
public static native boolean isNotTruthy(JSObject object);
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return obj === undefined;")
|
||||
public static native boolean isUndefined(JSObject object);
|
||||
|
||||
public static <T extends JSObject> T ensureDefined(T valIn) {
|
||||
return isUndefined((JSObject)valIn) ? null : valIn;
|
||||
}
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return obj.stack||null;")
|
||||
public static native String getStackSafe(JSObject object);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,125 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.dom.events.Event;
|
||||
import org.teavm.jso.dom.events.EventListener;
|
||||
import org.teavm.jso.dom.events.MessageEvent;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
import org.teavm.jso.websocket.WebSocket;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EagUtils;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.AbstractWebSocketClient;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.EnumEaglerConnectionState;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class TeaVMWebSocketClient extends AbstractWebSocketClient {
|
||||
|
||||
private final WebSocket sock;
|
||||
private boolean sockIsConnecting = true;
|
||||
private boolean sockIsConnected = false;
|
||||
private boolean sockIsFailed = false;
|
||||
|
||||
public TeaVMWebSocketClient(String socketURI) {
|
||||
super(socketURI);
|
||||
sock = WebSocket.create(socketURI);
|
||||
sock.setBinaryType("arraybuffer");
|
||||
TeaVMUtils.addEventListener(sock, "open", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
sockIsConnecting = false;
|
||||
sockIsConnected = true;
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(sock, "close", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
sockIsConnecting = false;
|
||||
sockIsConnected = false;
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(sock, "message", new EventListener<MessageEvent>() {
|
||||
@Override
|
||||
public void handleEvent(MessageEvent evt) {
|
||||
addRecievedFrame(new TeaVMWebSocketFrame(evt.getData()));
|
||||
}
|
||||
});
|
||||
TeaVMUtils.addEventListener(sock, "error", new EventListener<Event>() {
|
||||
@Override
|
||||
public void handleEvent(Event evt) {
|
||||
if(sockIsConnecting) {
|
||||
sockIsFailed = true;
|
||||
sockIsConnecting = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectBlocking(int timeoutMS) {
|
||||
long startTime = PlatformRuntime.steadyTimeMillis();
|
||||
while(!sockIsConnected && !sockIsFailed) {
|
||||
EagUtils.sleep(50l);
|
||||
if(PlatformRuntime.steadyTimeMillis() - startTime > timeoutMS * 1000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sockIsConnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumEaglerConnectionState getState() {
|
||||
return sockIsConnected ? EnumEaglerConnectionState.CONNECTED
|
||||
: (sockIsFailed ? EnumEaglerConnectionState.FAILED
|
||||
: (sockIsConnecting ? EnumEaglerConnectionState.CONNECTING : EnumEaglerConnectionState.CLOSED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return sockIsConnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return !sockIsConnecting && !sockIsConnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
sockIsConnecting = false;
|
||||
sockIsConnected = false;
|
||||
sock.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String str) {
|
||||
if(sockIsConnected) {
|
||||
sock.send(str);
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { "sock", "buffer" }, script = "sock.send(buffer);")
|
||||
protected static native void nativeBinarySend(WebSocket sock, ArrayBuffer buffer);
|
||||
|
||||
@Override
|
||||
public void send(byte[] bytes) {
|
||||
if(sockIsConnected) {
|
||||
nativeBinarySend(sock, TeaVMUtils.unwrapArrayBuffer(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketFrame;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class TeaVMWebSocketFrame implements IWebSocketFrame {
|
||||
|
||||
private JSObject data;
|
||||
private boolean str;
|
||||
|
||||
private String cachedStrContent = null;
|
||||
private byte[] cachedByteContent = null;
|
||||
|
||||
private int cachedLen = -1;
|
||||
|
||||
private final long timestamp;
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return (typeof obj === \"string\");")
|
||||
private static native boolean isStr(JSObject obj);
|
||||
|
||||
public TeaVMWebSocketFrame(JSObject data) {
|
||||
this.data = data;
|
||||
this.str = isStr(data);
|
||||
this.timestamp = PlatformRuntime.steadyTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isString() {
|
||||
return str;
|
||||
}
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return obj;")
|
||||
private static native String toStr(JSObject obj);
|
||||
|
||||
@Override
|
||||
public String getString() {
|
||||
if(str) {
|
||||
if(cachedStrContent == null) {
|
||||
return (cachedStrContent = toStr(data));
|
||||
}else {
|
||||
return cachedStrContent;
|
||||
}
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getByteArray() {
|
||||
if(!str) {
|
||||
if(cachedByteContent == null) {
|
||||
return (cachedByteContent = TeaVMUtils.wrapByteArrayBuffer((ArrayBuffer)data));
|
||||
}else {
|
||||
return cachedByteContent;
|
||||
}
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
if(!str) {
|
||||
return new ArrayBufferInputStream((ArrayBuffer)data);
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return obj.length;")
|
||||
private static native int strLen(JSObject obj);
|
||||
|
||||
@JSBody(params = { "obj" }, script = "return obj.byteLength;")
|
||||
private static native int arrLen(JSObject obj);
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
if(cachedLen == -1) {
|
||||
if(str) {
|
||||
cachedLen = strLen(data);
|
||||
}else {
|
||||
cachedLen = arrLen(data);
|
||||
}
|
||||
}
|
||||
return cachedLen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.teavm.jso.dom.xml.Element;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public abstract class Touch implements JSObject {
|
||||
|
||||
@JSProperty
|
||||
public abstract int getIdentifier();
|
||||
|
||||
@JSProperty
|
||||
public abstract double getScreenX();
|
||||
|
||||
@JSProperty
|
||||
public abstract double getScreenY();
|
||||
|
||||
@JSProperty
|
||||
public abstract double getClientX();
|
||||
|
||||
@JSProperty
|
||||
public abstract double getClientY();
|
||||
|
||||
@JSProperty
|
||||
public abstract double getPageX();
|
||||
|
||||
@JSProperty
|
||||
public abstract double getPageY();
|
||||
|
||||
@JSProperty
|
||||
public abstract double getRadiusX();
|
||||
|
||||
@JSBody(params = { "defVal" }, script = "return (typeof this.radiusX === \"number\") ? this.radiusX : defVal;")
|
||||
public abstract double getRadiusXSafe(double defaultVal);
|
||||
|
||||
@JSProperty
|
||||
public abstract double getRadiusY();
|
||||
|
||||
@JSBody(params = { "defVal" }, script = "return (typeof this.radiusY === \"number\") ? this.radiusY : defVal;")
|
||||
public abstract double getRadiusYSafe(double defaultVal);
|
||||
|
||||
@JSProperty
|
||||
public abstract double getForce();
|
||||
|
||||
@JSBody(params = { "defVal" }, script = "return (typeof this.force === \"number\") ? this.force : defVal;")
|
||||
public abstract double getForceSafe(double defaultVal);
|
||||
|
||||
@JSProperty
|
||||
public abstract Element getTarget();
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.teavm.jso.dom.events.Event;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public interface TouchEvent extends Event {
|
||||
|
||||
@JSProperty
|
||||
boolean getAltKey();
|
||||
|
||||
@JSProperty
|
||||
boolean getCtrlKey();
|
||||
|
||||
@JSProperty
|
||||
boolean getMetaKey();
|
||||
|
||||
@JSProperty
|
||||
boolean getShiftKey();
|
||||
|
||||
@JSProperty
|
||||
TouchList getChangedTouches();
|
||||
|
||||
@JSProperty
|
||||
TouchList getTargetTouches();
|
||||
|
||||
@JSProperty
|
||||
TouchList getTouches();
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public interface TouchList extends JSObject {
|
||||
|
||||
@JSProperty
|
||||
int getLength();
|
||||
|
||||
Touch item(int idx);
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
||||
import org.teavm.jso.dom.events.EventTarget;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public interface VisualViewport extends JSObject, EventTarget {
|
||||
|
||||
@JSProperty
|
||||
int getOffsetLeft();
|
||||
|
||||
@JSProperty
|
||||
int getOffsetTop();
|
||||
|
||||
@JSProperty
|
||||
int getPageLeft();
|
||||
|
||||
@JSProperty
|
||||
int getPageTop();
|
||||
|
||||
@JSProperty
|
||||
int getWidth();
|
||||
|
||||
@JSProperty
|
||||
int getHeight();
|
||||
|
||||
@JSProperty
|
||||
double getScale();
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSObject;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public interface WebGLANGLEInstancedArrays extends JSObject {
|
||||
|
||||
void drawArraysInstancedANGLE(int mode, int first, int count, int instanced);
|
||||
|
||||
void drawElementsInstancedANGLE(int mode, int count, int type, int offset, int primcount);
|
||||
|
||||
void vertexAttribDivisorANGLE(int index, int divisor);
|
||||
|
||||
}
|
@ -0,0 +1,300 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*;
|
||||
import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*;
|
||||
|
||||
import org.teavm.jso.webgl.WebGLFramebuffer;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IBufferArrayGL;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IBufferGL;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IFramebufferGL;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IProgramGL;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IRenderbufferGL;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IShaderGL;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.ITextureGL;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class WebGLBackBuffer {
|
||||
|
||||
private static int glesVers = -1;
|
||||
|
||||
private static WebGL2RenderingContext ctx;
|
||||
private static WebGLFramebuffer framebuffer;
|
||||
private static IFramebufferGL eagFramebuffer;
|
||||
private static int width;
|
||||
private static int height;
|
||||
|
||||
// GLES 3.0+
|
||||
private static IRenderbufferGL gles3ColorRenderbuffer;
|
||||
private static IRenderbufferGL gles3DepthRenderbuffer;
|
||||
|
||||
// GLES 2.0
|
||||
private static ITextureGL gles2ColorTexture;
|
||||
private static IRenderbufferGL gles2DepthRenderbuffer;
|
||||
private static IProgramGL gles2BlitProgram;
|
||||
private static IBufferArrayGL gles2BlitVAO;
|
||||
private static IBufferGL gles2BlitVBO;
|
||||
|
||||
private static boolean isVAOCapable = false;
|
||||
private static boolean isEmulatedVAOPhase = false;
|
||||
|
||||
private static final int _GL_FRAMEBUFFER = 0x8D40;
|
||||
private static final int _GL_RENDERBUFFER = 0x8D41;
|
||||
private static final int _GL_COLOR_ATTACHMENT0 = 0x8CE0;
|
||||
private static final int _GL_DEPTH_ATTACHMENT = 0x8D00;
|
||||
private static final int _GL_DEPTH_COMPONENT16 = 0x81A5;
|
||||
private static final int _GL_DEPTH_COMPONENT32F = 0x8CAC;
|
||||
private static final int _GL_READ_FRAMEBUFFER = 0x8CA8;
|
||||
private static final int _GL_DRAW_FRAMEBUFFER = 0x8CA9;
|
||||
|
||||
public static void initBackBuffer(WebGL2RenderingContext ctxIn, WebGLFramebuffer fbo, IFramebufferGL eagFbo, int sw, int sh) {
|
||||
ctx = ctxIn;
|
||||
glesVers = checkOpenGLESVersion();
|
||||
framebuffer = fbo;
|
||||
eagFramebuffer = eagFbo;
|
||||
isVAOCapable = checkVAOCapable();
|
||||
isEmulatedVAOPhase = false;
|
||||
width = sw;
|
||||
height = sh;
|
||||
if(glesVers >= 300) {
|
||||
gles3ColorRenderbuffer = _wglCreateRenderbuffer();
|
||||
gles3DepthRenderbuffer = _wglCreateRenderbuffer();
|
||||
_wglBindFramebuffer(_GL_FRAMEBUFFER, eagFbo);
|
||||
_wglBindRenderbuffer(_GL_RENDERBUFFER, gles3ColorRenderbuffer);
|
||||
_wglRenderbufferStorage(_GL_RENDERBUFFER, GL_RGBA8, sw, sh);
|
||||
_wglFramebufferRenderbuffer(_GL_FRAMEBUFFER, _GL_COLOR_ATTACHMENT0, _GL_RENDERBUFFER, gles3ColorRenderbuffer);
|
||||
_wglBindRenderbuffer(_GL_RENDERBUFFER, gles3DepthRenderbuffer);
|
||||
_wglRenderbufferStorage(_GL_RENDERBUFFER, _GL_DEPTH_COMPONENT32F, sw, sh);
|
||||
_wglFramebufferRenderbuffer(_GL_FRAMEBUFFER, _GL_DEPTH_ATTACHMENT, _GL_RENDERBUFFER, gles3DepthRenderbuffer);
|
||||
_wglDrawBuffers(_GL_COLOR_ATTACHMENT0);
|
||||
}else {
|
||||
gles2ColorTexture = _wglGenTextures();
|
||||
gles2DepthRenderbuffer = _wglCreateRenderbuffer();
|
||||
_wglBindFramebuffer(_GL_FRAMEBUFFER, eagFbo);
|
||||
_wglBindTexture(GL_TEXTURE_2D, gles2ColorTexture);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
_wglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, sw, sh, 0, GL_RGBA, GL_UNSIGNED_BYTE, (ByteBuffer)null);
|
||||
_wglFramebufferTexture2D(_GL_FRAMEBUFFER, _GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gles2ColorTexture, 0);
|
||||
_wglBindRenderbuffer(_GL_RENDERBUFFER, gles2DepthRenderbuffer);
|
||||
_wglRenderbufferStorage(_GL_RENDERBUFFER, _GL_DEPTH_COMPONENT16, sw, sh);
|
||||
_wglFramebufferRenderbuffer(_GL_FRAMEBUFFER, _GL_DEPTH_ATTACHMENT, _GL_RENDERBUFFER, gles2DepthRenderbuffer);
|
||||
|
||||
ByteBuffer upload = PlatformRuntime.allocateByteBuffer(48);
|
||||
upload.putFloat(0.0f); upload.putFloat(0.0f);
|
||||
upload.putFloat(1.0f); upload.putFloat(0.0f);
|
||||
upload.putFloat(0.0f); upload.putFloat(1.0f);
|
||||
upload.putFloat(1.0f); upload.putFloat(0.0f);
|
||||
upload.putFloat(1.0f); upload.putFloat(1.0f);
|
||||
upload.putFloat(0.0f); upload.putFloat(1.0f);
|
||||
upload.flip();
|
||||
|
||||
gles2BlitVBO = _wglGenBuffers();
|
||||
EaglercraftGPU.bindVAOGLArrayBufferNow(gles2BlitVBO);
|
||||
_wglBufferData(GL_ARRAY_BUFFER, upload, GL_STATIC_DRAW);
|
||||
|
||||
PlatformRuntime.freeByteBuffer(upload);
|
||||
|
||||
if(isVAOCapable) {
|
||||
gles2BlitVAO = _wglGenVertexArrays();
|
||||
_wglBindVertexArray(gles2BlitVAO);
|
||||
_wglEnableVertexAttribArray(0);
|
||||
_wglVertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0);
|
||||
}
|
||||
|
||||
IShaderGL vertShader = _wglCreateShader(GL_VERTEX_SHADER);
|
||||
_wglShaderSource(vertShader, "#version 100\nprecision mediump float; attribute vec2 a_pos2f; varying vec2 v_tex2f; void main() { v_tex2f = a_pos2f; gl_Position = vec4(a_pos2f * 2.0 - 1.0, 0.0, 1.0); }");
|
||||
_wglCompileShader(vertShader);
|
||||
|
||||
IShaderGL fragShader = _wglCreateShader(GL_FRAGMENT_SHADER);
|
||||
_wglShaderSource(fragShader, checkTextureLODCapable()
|
||||
? "#version 100\n#extension GL_EXT_shader_texture_lod : enable\nprecision mediump float; precision mediump sampler2D; varying vec2 v_tex2f; uniform sampler2D u_samplerTex; void main() { gl_FragColor = vec4(texture2DLodEXT(u_samplerTex, v_tex2f, 0.0).rgb, 1.0); }"
|
||||
: "#version 100\nprecision mediump float; precision mediump sampler2D; varying vec2 v_tex2f; uniform sampler2D u_samplerTex; void main() { gl_FragColor = vec4(texture2D(u_samplerTex, v_tex2f).rgb, 1.0); }");
|
||||
_wglCompileShader(fragShader);
|
||||
|
||||
gles2BlitProgram = _wglCreateProgram();
|
||||
|
||||
_wglAttachShader(gles2BlitProgram, vertShader);
|
||||
_wglAttachShader(gles2BlitProgram, fragShader);
|
||||
|
||||
_wglBindAttribLocation(gles2BlitProgram, 0, "a_pos2f");
|
||||
|
||||
_wglLinkProgram(gles2BlitProgram);
|
||||
|
||||
_wglDetachShader(gles2BlitProgram, vertShader);
|
||||
_wglDetachShader(gles2BlitProgram, fragShader);
|
||||
|
||||
_wglDeleteShader(vertShader);
|
||||
_wglDeleteShader(fragShader);
|
||||
|
||||
_wglUseProgram(gles2BlitProgram);
|
||||
|
||||
_wglUniform1i(_wglGetUniformLocation(gles2BlitProgram, "u_samplerTex"), 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void enterVAOEmulationPhase() {
|
||||
if(glesVers < 300) {
|
||||
if(!isEmulatedVAOPhase) {
|
||||
if(isVAOCapable) {
|
||||
_wglDeleteVertexArrays(gles2BlitVAO);
|
||||
}
|
||||
gles2BlitVAO = EaglercraftGPU.createGLBufferArray();
|
||||
EaglercraftGPU.bindGLBufferArray(gles2BlitVAO);
|
||||
EaglercraftGPU.bindVAOGLArrayBuffer(gles2BlitVBO);
|
||||
EaglercraftGPU.enableVertexAttribArray(0);
|
||||
EaglercraftGPU.vertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0);
|
||||
isEmulatedVAOPhase = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawBlitQuad() {
|
||||
if(isEmulatedVAOPhase) {
|
||||
EaglercraftGPU.bindGLBufferArray(gles2BlitVAO);
|
||||
EaglercraftGPU.doDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
}else {
|
||||
if(isVAOCapable) {
|
||||
_wglBindVertexArray(gles2BlitVAO);
|
||||
_wglDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
}else {
|
||||
EaglercraftGPU.bindGLArrayBuffer(gles2BlitVBO);
|
||||
_wglEnableVertexAttribArray(0);
|
||||
_wglVertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0);
|
||||
_wglDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void flipBuffer(int windowWidth, int windowHeight) {
|
||||
if(glesVers >= 300) {
|
||||
ctx.bindFramebuffer(_GL_READ_FRAMEBUFFER, framebuffer);
|
||||
ctx.bindFramebuffer(_GL_DRAW_FRAMEBUFFER, null);
|
||||
ctx.blitFramebuffer(0, 0, width, height, 0, 0, windowWidth, windowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
|
||||
ctx.bindFramebuffer(_GL_FRAMEBUFFER, framebuffer);
|
||||
|
||||
if(windowWidth != width || windowHeight != height) {
|
||||
width = windowWidth;
|
||||
height = windowHeight;
|
||||
|
||||
_wglBindRenderbuffer(_GL_RENDERBUFFER, gles3ColorRenderbuffer);
|
||||
_wglRenderbufferStorage(_GL_RENDERBUFFER, GL_RGBA8, windowWidth, windowHeight);
|
||||
|
||||
_wglBindRenderbuffer(_GL_RENDERBUFFER, gles3DepthRenderbuffer);
|
||||
_wglRenderbufferStorage(_GL_RENDERBUFFER, _GL_DEPTH_COMPONENT32F, windowWidth, windowHeight);
|
||||
}
|
||||
}else {
|
||||
ctx.bindFramebuffer(_GL_FRAMEBUFFER, null);
|
||||
_wglActiveTexture(GL_TEXTURE0);
|
||||
_wglBindTexture(GL_TEXTURE_2D, gles2ColorTexture);
|
||||
|
||||
int[] viewportStash = null;
|
||||
if(isEmulatedVAOPhase) {
|
||||
viewportStash = new int[4];
|
||||
EaglercraftGPU.glGetInteger(GL_VIEWPORT, viewportStash);
|
||||
GlStateManager.viewport(0, 0, windowWidth, windowHeight);
|
||||
GlStateManager.eagPushStateForGLES2BlitHack();
|
||||
GlStateManager.disableDepth();
|
||||
GlStateManager.disableBlend();
|
||||
}else {
|
||||
_wglViewport(0, 0, windowWidth, windowHeight);
|
||||
_wglDisable(GL_DEPTH_TEST);
|
||||
_wglDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
EaglercraftGPU.clearCurrentBinding(EaglercraftGPU.CLEAR_BINDING_SHADER_PROGRAM | EaglercraftGPU.CLEAR_BINDING_ARRAY_BUFFER);
|
||||
|
||||
EaglercraftGPU.bindGLShaderProgram(gles2BlitProgram);
|
||||
|
||||
drawBlitQuad();
|
||||
|
||||
if(windowWidth != width || windowHeight != height) {
|
||||
width = windowWidth;
|
||||
height = windowHeight;
|
||||
|
||||
_wglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, windowWidth, windowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (ByteBuffer)null);
|
||||
|
||||
_wglBindRenderbuffer(_GL_RENDERBUFFER, gles2DepthRenderbuffer);
|
||||
_wglRenderbufferStorage(_GL_RENDERBUFFER, _GL_DEPTH_COMPONENT16, windowWidth, windowHeight);
|
||||
}
|
||||
|
||||
if(isEmulatedVAOPhase) {
|
||||
EaglercraftGPU.clearCurrentBinding(EaglercraftGPU.CLEAR_BINDING_TEXTURE0 | EaglercraftGPU.CLEAR_BINDING_ACTIVE_TEXTURE | EaglercraftGPU.CLEAR_BINDING_SHADER_PROGRAM);
|
||||
if(viewportStash[2] > 0) {
|
||||
GlStateManager.viewport(viewportStash[0], viewportStash[1], viewportStash[2], viewportStash[3]);
|
||||
}
|
||||
GlStateManager.eagPopStateForGLES2BlitHack();
|
||||
}else {
|
||||
EaglercraftGPU.clearCurrentBinding(EaglercraftGPU.CLEAR_BINDING_TEXTURE0 | EaglercraftGPU.CLEAR_BINDING_ACTIVE_TEXTURE | EaglercraftGPU.CLEAR_BINDING_SHADER_PROGRAM | EaglercraftGPU.CLEAR_BINDING_BUFFER_ARRAY);
|
||||
}
|
||||
|
||||
ctx.bindFramebuffer(_GL_FRAMEBUFFER, framebuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public static void destroy() {
|
||||
if(eagFramebuffer != null) {
|
||||
_wglDeleteFramebuffer(eagFramebuffer);
|
||||
eagFramebuffer = null;
|
||||
}
|
||||
if(gles3ColorRenderbuffer != null) {
|
||||
_wglDeleteRenderbuffer(gles3ColorRenderbuffer);
|
||||
gles3ColorRenderbuffer = null;
|
||||
}
|
||||
if(gles3DepthRenderbuffer != null) {
|
||||
_wglDeleteRenderbuffer(gles3DepthRenderbuffer);
|
||||
gles3DepthRenderbuffer = null;
|
||||
}
|
||||
if(gles2ColorTexture != null) {
|
||||
_wglDeleteTextures(gles2ColorTexture);
|
||||
gles2ColorTexture = null;
|
||||
}
|
||||
if(gles2DepthRenderbuffer != null) {
|
||||
_wglDeleteRenderbuffer(gles2DepthRenderbuffer);
|
||||
gles2DepthRenderbuffer = null;
|
||||
}
|
||||
if(gles2BlitProgram != null) {
|
||||
_wglDeleteProgram(gles2BlitProgram);
|
||||
gles2BlitProgram = null;
|
||||
}
|
||||
if(gles2BlitVAO != null) {
|
||||
if(isEmulatedVAOPhase) {
|
||||
EaglercraftGPU.destroyGLBufferArray(gles2BlitVAO);
|
||||
}else if(isVAOCapable) {
|
||||
_wglDeleteVertexArrays(gles2BlitVAO);
|
||||
}
|
||||
gles2BlitVAO = null;
|
||||
}
|
||||
if(gles2BlitVBO != null) {
|
||||
_wglDeleteBuffers(gles2BlitVBO);
|
||||
gles2BlitVBO = null;
|
||||
}
|
||||
framebuffer = null;
|
||||
width = 0;
|
||||
height = 0;
|
||||
isVAOCapable = false;
|
||||
isEmulatedVAOPhase = false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
|
||||
|
||||
import org.teavm.jso.JSObject;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public interface WebGLOESVertexArrayObject extends JSObject {
|
||||
|
||||
WebGLVertexArray createVertexArrayOES();
|
||||
|
||||
void deleteVertexArrayOES(WebGLVertexArray obj);
|
||||
|
||||
void bindVertexArrayOES(WebGLVertexArray obj);
|
||||
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm.generators;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.teavm.backend.javascript.codegen.ScopedName;
|
||||
import org.teavm.backend.javascript.codegen.SourceWriter;
|
||||
import org.teavm.backend.javascript.spi.Generator;
|
||||
import org.teavm.backend.javascript.spi.GeneratorContext;
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.Base64VarIntArray;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class TeaVMRuntimeDeobfuscatorGenerator implements Generator {
|
||||
|
||||
private int indexIntoSet(String name, Map<String,Integer> namesSet, List<String> namesList) {
|
||||
Integer ret = namesSet.get(name);
|
||||
if(ret != null) {
|
||||
return ret.intValue();
|
||||
}
|
||||
int i = namesList.size();
|
||||
namesList.add(name);
|
||||
namesSet.put(name, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
|
||||
Map<String,List<Integer>> map = new HashMap<>();
|
||||
List<String> classNamesPartsList = new ArrayList<>();
|
||||
Map<String,Integer> classNamesPartsSet = new HashMap<>();
|
||||
List<String> namesList = new ArrayList<>();
|
||||
Map<String,Integer> namesSet = new HashMap<>();
|
||||
Map<String,List<Integer>> namesEncSet = new HashMap<>();
|
||||
for(MethodReference method : context.getDependency().getReachableMethods()) {
|
||||
ScopedName name = writer.getNaming().getFullNameFor(method);
|
||||
if(name.scoped) {
|
||||
continue;
|
||||
}
|
||||
String clsName = method.getClassName();
|
||||
List<Integer> lst = map.get(clsName);
|
||||
if(lst == null) {
|
||||
map.put(clsName, lst = new ArrayList<>());
|
||||
}
|
||||
lst.add(indexIntoSet(name.value, namesSet, namesList));
|
||||
lst.add(indexIntoSet(method.getName(), namesSet, namesList));
|
||||
}
|
||||
for(String str : map.keySet()) {
|
||||
List<Integer> builder = new ArrayList<>();
|
||||
boolean b = false;
|
||||
for(String strr : str.split("\\.")) {
|
||||
builder.add(indexIntoSet(strr, classNamesPartsSet, classNamesPartsList));
|
||||
b = true;
|
||||
}
|
||||
namesEncSet.put(str, builder);
|
||||
}
|
||||
writer.append("return [").ws().append('[').ws();
|
||||
boolean b = false;
|
||||
for(String str : classNamesPartsList) {
|
||||
if(b) {
|
||||
writer.append(',').ws();
|
||||
}
|
||||
writer.append('\"').append(str).append('\"');
|
||||
b = true;
|
||||
}
|
||||
writer.append("],").ws().append('[').ws();
|
||||
b = false;
|
||||
for(String str : namesList) {
|
||||
if(b) {
|
||||
writer.append(',').ws();
|
||||
}
|
||||
writer.append('\"').append(str).append('\"');
|
||||
b = true;
|
||||
}
|
||||
writer.ws().append("],").ws();
|
||||
b = false;
|
||||
for (Entry<String,List<Integer>> name : map.entrySet()) {
|
||||
if(b) {
|
||||
writer.append(',').ws();
|
||||
}
|
||||
writer.append('\"').append(Base64VarIntArray.encodeVarIntArray(namesEncSet.get(name.getKey()))).append("\",").ws();
|
||||
writer.append('\"').appendClass(name.getKey()).append("\",").ws().append('\"');
|
||||
writer.append(Base64VarIntArray.encodeVarIntArray(name.getValue())).append('\"').ws();
|
||||
b = true;
|
||||
}
|
||||
writer.ws().append("];").softNewLine();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.internal.teavm.generators;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class 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) throws IOException {
|
||||
context.writeExpr(context.getArgument(0));
|
||||
context.getWriter().append(".data.buffer");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class UnwrapTypedArray implements Injector {
|
||||
|
||||
@Override
|
||||
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
|
||||
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)
|
||||
throws IOException {
|
||||
String parName = context.getParameterName(1);
|
||||
switch (methodRef.getName()) {
|
||||
case "wrapByteArrayBuffer":
|
||||
writer.append("return ").append(parName).ws().append('?').ws();
|
||||
writer.append("$rt_createNumericArray($rt_bytecls(),").ws().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.append("$rt_createNumericArray($rt_intcls(),").ws().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.append("$rt_createNumericArray($rt_floatcls(),").ws().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.append("$rt_createNumericArray($rt_shortcls(),").ws().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)
|
||||
throws IOException {
|
||||
String parName = context.getParameterName(1);
|
||||
switch (methodRef.getName()) {
|
||||
case "wrapByteArrayBufferView":
|
||||
case "wrapUnsignedByteArray":
|
||||
writer.append("return ").append(parName).ws().append('?').ws();
|
||||
writer.append("$rt_createNumericArray($rt_bytecls(),").ws().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.append("$rt_createNumericArray($rt_intcls(),").ws().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.append("$rt_createNumericArray($rt_floatcls(),").ws().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.append("$rt_createNumericArray($rt_shortcls(),").ws().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)
|
||||
throws IOException {
|
||||
String parName = context.getParameterName(1);
|
||||
switch (methodRef.getName()) {
|
||||
case "wrapByteArray":
|
||||
writer.append("return ").append(parName).ws().append('?').ws();
|
||||
writer.append("$rt_createNumericArray($rt_bytecls(),").ws().append(parName).append(")").ws();
|
||||
writer.append(':').ws().append("null;").softNewLine();
|
||||
break;
|
||||
case "wrapIntArray":
|
||||
writer.append("return ").append(parName).ws().append('?').ws();
|
||||
writer.append("$rt_createNumericArray($rt_intcls(),").ws().append(parName).append(")").ws();
|
||||
writer.append(':').ws().append("null;").softNewLine();
|
||||
break;
|
||||
case "wrapFloatArray":
|
||||
writer.append("return ").append(parName).ws().append('?').ws();
|
||||
writer.append("$rt_createNumericArray($rt_floatcls(),").ws().append(parName).append(")").ws();
|
||||
writer.append(':').ws().append("null;").softNewLine();
|
||||
break;
|
||||
case "wrapShortArray":
|
||||
writer.append("return ").append(parName).ws().append('?').ws();
|
||||
writer.append("$rt_createNumericArray($rt_shortcls(),").ws().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) throws IOException {
|
||||
context.getWriter().append("new Uint8Array(");
|
||||
context.writeExpr(context.getArgument(0));
|
||||
context.getWriter().append(".data.buffer)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -29,4 +29,7 @@ public abstract class JSEaglercraftXOptsHooks implements JSObject {
|
||||
@JSBody(script = "return (typeof this.crashReportShow === \"function\") ? this.crashReportShow : null;")
|
||||
public native JSObject getCrashReportHook();
|
||||
|
||||
@JSBody(script = "return (typeof this.screenChanged === \"function\") ? this.screenChanged : null;")
|
||||
public native JSObject getScreenChangedHook();
|
||||
|
||||
}
|
||||
|
@ -96,7 +96,79 @@ public abstract class JSEaglercraftXOptsRoot implements JSObject {
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.enableMinceraft === \"boolean\") ? this.enableMinceraft : def;")
|
||||
public native boolean getEnableMinceraft(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.enableServerCookies === \"boolean\") ? this.enableServerCookies : def;")
|
||||
public native boolean getEnableServerCookies(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.allowServerRedirects === \"boolean\") ? this.allowServerRedirects : def;")
|
||||
public native boolean getAllowServerRedirects(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.crashOnUncaughtExceptions === \"boolean\") ? this.crashOnUncaughtExceptions : def;")
|
||||
public native boolean getCrashOnUncaughtExceptions(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.openDebugConsoleOnLaunch === \"boolean\") ? this.openDebugConsoleOnLaunch : def;")
|
||||
public native boolean getOpenDebugConsoleOnLaunch(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.fixDebugConsoleUnloadListener === \"boolean\") ? this.fixDebugConsoleUnloadListener : def;")
|
||||
public native boolean getFixDebugConsoleUnloadListener(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.forceWebViewSupport === \"boolean\") ? this.forceWebViewSupport : def;")
|
||||
public native boolean getForceWebViewSupport(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.enableWebViewCSP === \"boolean\") ? this.enableWebViewCSP : def;")
|
||||
public native boolean getEnableWebViewCSP(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.autoFixLegacyStyleAttr === \"boolean\") ? this.autoFixLegacyStyleAttr : def;")
|
||||
public native boolean getAutoFixLegacyStyleAttr(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.showBootMenuOnLaunch === \"boolean\") ? this.showBootMenuOnLaunch : def;")
|
||||
public native boolean getShowBootMenuOnLaunch(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.bootMenuBlocksUnsignedClients === \"boolean\") ? this.bootMenuBlocksUnsignedClients : def;")
|
||||
public native boolean getBootMenuBlocksUnsignedClients(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.allowBootMenu === \"boolean\") ? this.allowBootMenu : def;")
|
||||
public native boolean getAllowBootMenu(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.forceProfanityFilter === \"boolean\") ? this.forceProfanityFilter : def;")
|
||||
public native boolean getForceProfanityFilter(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.forceWebGL1 === \"boolean\") ? this.forceWebGL1 : def;")
|
||||
public native boolean getForceWebGL1(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.forceWebGL2 === \"boolean\") ? this.forceWebGL2 : def;")
|
||||
public native boolean getForceWebGL2(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.allowExperimentalWebGL1 === \"boolean\") ? this.allowExperimentalWebGL1 : def;")
|
||||
public native boolean getAllowExperimentalWebGL1(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.useWebGLExt === \"boolean\") ? this.useWebGLExt : def;")
|
||||
public native boolean getUseWebGLExt(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.useDelayOnSwap === \"boolean\") ? this.useDelayOnSwap : def;")
|
||||
public native boolean getUseDelayOnSwap(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.useJOrbisAudioDecoder === \"boolean\") ? this.useJOrbisAudioDecoder : def;")
|
||||
public native boolean getUseJOrbisAudioDecoder(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.useXHRFetch === \"boolean\") ? this.useXHRFetch : def;")
|
||||
public native boolean getUseXHRFetch(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.useVisualViewport === \"boolean\") ? this.useVisualViewport : def;")
|
||||
public native boolean getUseVisualViewport(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.deobfStackTraces === \"boolean\") ? this.deobfStackTraces : def;")
|
||||
public native boolean getDeobfStackTraces(boolean deobfStackTraces);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.disableBlobURLs === \"boolean\") ? this.disableBlobURLs : def;")
|
||||
public native boolean getDisableBlobURLs(boolean deobfStackTraces);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.eaglerNoDelay === \"boolean\") ? this.eaglerNoDelay : def;")
|
||||
public native boolean getEaglerNoDelay(boolean deobfStackTraces);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.ramdiskMode === \"boolean\") ? this.ramdiskMode : def;")
|
||||
public native boolean getRamdiskMode(boolean deobfStackTraces);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.singleThreadMode === \"boolean\") ? this.singleThreadMode : def;")
|
||||
public native boolean getSingleThreadMode(boolean deobfStackTraces);
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ public abstract class JSEaglercraftXOptsServer implements JSObject {
|
||||
@JSBody(script = "return (typeof this.addr === \"string\") ? this.addr : null;")
|
||||
public native String getAddr();
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.hideAddr === \"boolean\") ? this.hideAddr : def;")
|
||||
public native boolean getHideAddr(boolean defaultValue);
|
||||
|
||||
@JSBody(params = { "def" }, script = "return (typeof this.name === \"string\") ? this.name : def;")
|
||||
public native String getName(String defaultValue);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.sp.internal;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -9,15 +10,19 @@ import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.dom.events.ErrorEvent;
|
||||
import org.teavm.jso.dom.events.EventListener;
|
||||
import org.teavm.jso.dom.html.HTMLScriptElement;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
import org.teavm.jso.workers.Worker;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IPCPacketData;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.ClientMain;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMBlobURLManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMClientConfigAdapter;
|
||||
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;
|
||||
import net.lax1dude.eaglercraft.v1_8.sp.server.internal.teavm.SingleThreadWorker;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2023-2024 lax1dude. All Rights Reserved.
|
||||
@ -38,28 +43,34 @@ public class ClientPlatformSingleplayer {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("ClientPlatformSingleplayer");
|
||||
|
||||
private static final LinkedList<IPCPacketData> messageQueue = new LinkedList();
|
||||
private static final LinkedList<IPCPacketData> messageQueue = new LinkedList<>();
|
||||
|
||||
@JSBody(params = {}, script = "return (typeof window.eaglercraftXClientScriptElement !== \"undefined\") ? window.eaglercraftXClientScriptElement : null;")
|
||||
@JSBody(params = {}, script = "return (typeof eaglercraftXClientScriptElement !== \"undefined\") ? eaglercraftXClientScriptElement : null;")
|
||||
private static native JSObject loadIntegratedServerSourceOverride();
|
||||
|
||||
@JSBody(params = {}, script = "return (typeof window.eaglercraftXClientScriptURL === \"string\") ? window.eaglercraftXClientScriptURL : null;")
|
||||
@JSBody(params = {}, script = "return (typeof eaglercraftXClientScriptURL === \"string\") ? eaglercraftXClientScriptURL : null;")
|
||||
private static native String loadIntegratedServerSourceOverrideURL();
|
||||
|
||||
@JSBody(params = {}, script = "try{throw new Error();}catch(ex){return ex.stack;}return null;")
|
||||
@JSBody(params = {}, script = "try{throw new Error();}catch(ex){return ex.stack||null;}return null;")
|
||||
private static native String loadIntegratedServerSourceStack();
|
||||
|
||||
@JSBody(params = { "csc" }, script = "if(typeof csc.src === \"string\" && csc.src.length > 0) return csc.src; else return null;")
|
||||
private static native String loadIntegratedServerSourceURL(JSObject scriptTag);
|
||||
|
||||
@JSBody(params = { "csc", "tail" }, script = "const cscText = csc.text;"
|
||||
@JSBody(params = { "csc", "tail" }, script = "var cscText = csc.text;"
|
||||
+ "if(typeof cscText === \"string\" && cscText.length > 0) return new Blob([cscText, tail], { type: \"text/javascript;charset=utf8\" });"
|
||||
+ "else return null;")
|
||||
private static native JSObject loadIntegratedServerSourceInline(JSObject scriptTag, String tail);
|
||||
|
||||
@JSBody(params = { "csc" }, script = "var cscText = csc.text;"
|
||||
+ "if(typeof cscText === \"string\" && cscText.length > 0) return cscText;"
|
||||
+ "else return null;")
|
||||
private static native String loadIntegratedServerSourceInlineStr(JSObject scriptTag);
|
||||
|
||||
private static String integratedServerSource = null;
|
||||
private static String integratedServerSourceOriginalURL = null;
|
||||
private static boolean serverSourceLoaded = false;
|
||||
private static boolean isSingleThreadMode = false;
|
||||
|
||||
private static Worker workerObj = null;
|
||||
|
||||
@ -68,7 +79,7 @@ public class ClientPlatformSingleplayer {
|
||||
public void onMessage(String channel, ArrayBuffer buf);
|
||||
}
|
||||
|
||||
@JSBody(params = { "w", "wb" }, script = "w.onmessage = function(o) { wb(o.data.ch, o.data.dat); };")
|
||||
@JSBody(params = { "w", "wb" }, script = "w.addEventListener(\"message\", function(o) { wb(o.data.ch, o.data.dat); });")
|
||||
private static native void registerPacketHandler(Worker w, WorkerBinaryPacketHandler wb);
|
||||
|
||||
@JSBody(params = { "w", "ch", "dat" }, script = "w.postMessage({ ch: ch, dat : dat });")
|
||||
@ -108,13 +119,13 @@ public class ClientPlatformSingleplayer {
|
||||
private static JSObject loadIntegratedServerSource() {
|
||||
String str = loadIntegratedServerSourceOverrideURL();
|
||||
if(str != null) {
|
||||
ArrayBuffer buf = PlatformRuntime.downloadRemoteURI(str);
|
||||
ArrayBuffer buf = PlatformRuntime.downloadRemoteURI(str, true);
|
||||
if(buf != null) {
|
||||
integratedServerSourceOriginalURL = str;
|
||||
logger.info("Using integrated server at: {}", str);
|
||||
logger.info("Using integrated server at: {}", truncateURL(str));
|
||||
return createBlobObj(buf, workerBootstrapCode);
|
||||
}else {
|
||||
logger.error("Failed to load integrated server: {}", str);
|
||||
logger.error("Failed to load integrated server: {}", truncateURL(str));
|
||||
}
|
||||
}
|
||||
JSObject el = loadIntegratedServerSourceOverride();
|
||||
@ -128,25 +139,34 @@ public class ClientPlatformSingleplayer {
|
||||
return el;
|
||||
}
|
||||
}else {
|
||||
ArrayBuffer buf = PlatformRuntime.downloadRemoteURI(url);
|
||||
ArrayBuffer buf = PlatformRuntime.downloadRemoteURI(url, true);
|
||||
if(buf != null) {
|
||||
integratedServerSourceOriginalURL = url;
|
||||
logger.info("Using integrated server from script tag src: {}", url);
|
||||
logger.info("Using integrated server from script tag src: {}", truncateURL(url));
|
||||
return createBlobObj(buf, workerBootstrapCode);
|
||||
}else {
|
||||
logger.error("Failed to load integrated server from script tag src: {}", url);
|
||||
logger.error("Failed to load integrated server from script tag src: {}", truncateURL(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
str = TeaVMUtils.tryResolveClassesSource();
|
||||
if(str != null) {
|
||||
ArrayBuffer buf = PlatformRuntime.downloadRemoteURI(str);
|
||||
ArrayBuffer buf = PlatformRuntime.downloadRemoteURI(str, true);
|
||||
if(buf != null) {
|
||||
integratedServerSourceOriginalURL = str;
|
||||
logger.info("Using integrated server from script src: {}", str);
|
||||
logger.info("Using integrated server from script src: {}", truncateURL(str));
|
||||
return createBlobObj(buf, workerBootstrapCode);
|
||||
}else {
|
||||
logger.error("Failed to load integrated server from script src: {}", str);
|
||||
logger.error("Failed to load integrated server from script src: {}", truncateURL(str));
|
||||
}
|
||||
}
|
||||
HTMLScriptElement sc = TeaVMUtils.tryResolveClassesSourceInline();
|
||||
if(sc != null) {
|
||||
el = loadIntegratedServerSourceInline(sc, workerBootstrapCode);
|
||||
if(el != null) {
|
||||
integratedServerSourceOriginalURL = "inline script tag (client guess)";
|
||||
logger.info("Loading integrated server from (likely) inline script tag");
|
||||
return el;
|
||||
}
|
||||
}
|
||||
logger.info("Could not resolve the location of client's classes.js!");
|
||||
@ -155,12 +175,57 @@ public class ClientPlatformSingleplayer {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String truncateURL(String url) {
|
||||
if(url == null) return null;
|
||||
if(url.length() > 256) {
|
||||
url = url.substring(0, 254) + "...";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private static String createIntegratedServerWorkerURL() {
|
||||
JSObject blobObj = loadIntegratedServerSource();
|
||||
if(blobObj == null) {
|
||||
return null;
|
||||
}
|
||||
return createWorkerScriptURL(blobObj);
|
||||
return TeaVMBlobURLManager.registerNewURLBlob(blobObj).toExternalForm();
|
||||
}
|
||||
|
||||
public static byte[] getIntegratedServerSourceTeaVM() {
|
||||
String str = loadIntegratedServerSourceOverrideURL();
|
||||
if(str != null) {
|
||||
ArrayBuffer buf = PlatformRuntime.downloadRemoteURI(str, true);
|
||||
if(buf != null) {
|
||||
return TeaVMUtils.wrapByteArrayBuffer(buf);
|
||||
}
|
||||
}
|
||||
JSObject el = loadIntegratedServerSourceOverride();
|
||||
if(el != null) {
|
||||
String url = loadIntegratedServerSourceURL(el);
|
||||
if(url == null) {
|
||||
str = loadIntegratedServerSourceInlineStr(el);
|
||||
if(str != null) {
|
||||
return str.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}else {
|
||||
ArrayBuffer buf = PlatformRuntime.downloadRemoteURI(url, true);
|
||||
if(buf != null) {
|
||||
return TeaVMUtils.wrapByteArrayBuffer(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
str = TeaVMUtils.tryResolveClassesSource();
|
||||
if(str != null) {
|
||||
ArrayBuffer buf = PlatformRuntime.downloadRemoteURI(str, true);
|
||||
if(buf != null) {
|
||||
return TeaVMUtils.wrapByteArrayBuffer(buf);
|
||||
}
|
||||
}
|
||||
HTMLScriptElement sc = TeaVMUtils.tryResolveClassesSourceInline();
|
||||
if(sc != null) {
|
||||
return sc.getText().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getLoadedWorkerURLTeaVM() {
|
||||
@ -171,36 +236,58 @@ public class ClientPlatformSingleplayer {
|
||||
return (serverSourceLoaded && workerObj != null) ? integratedServerSourceOriginalURL : null;
|
||||
}
|
||||
|
||||
public static void startIntegratedServer() {
|
||||
if(!serverSourceLoaded) {
|
||||
integratedServerSource = createIntegratedServerWorkerURL();
|
||||
serverSourceLoaded = true;
|
||||
}
|
||||
|
||||
if(integratedServerSource == null) {
|
||||
throw new RuntimeException("Could not resolve the location of client's classes.js! Make sure client's classes.js is linked/embedded in a dedicated <script> tag. Define \"window.eaglercraftXClientScriptElement\" or \"window.eaglercraftXClientScriptURL\" to force");
|
||||
}
|
||||
|
||||
workerObj = Worker.create(integratedServerSource);
|
||||
workerObj.onError(new EventListener<ErrorEvent>() {
|
||||
@Override
|
||||
public void handleEvent(ErrorEvent evt) {
|
||||
logger.error("Worker Error: {}", evt.getError());
|
||||
PlatformRuntime.printNativeExceptionToConsoleTeaVM(evt);
|
||||
public static void startIntegratedServer(boolean singleThreadMode) {
|
||||
singleThreadMode |= ((TeaVMClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isSingleThreadModeTeaVM();
|
||||
if(singleThreadMode) {
|
||||
if(!isSingleThreadMode) {
|
||||
SingleThreadWorker.singleThreadStartup((pkt) -> {
|
||||
synchronized(messageQueue) {
|
||||
messageQueue.add(pkt);
|
||||
}
|
||||
});
|
||||
isSingleThreadMode = true;
|
||||
}
|
||||
});
|
||||
registerPacketHandler(workerObj, new WorkerBinaryPacketHandlerImpl());
|
||||
sendWorkerStartPacket(workerObj, PlatformRuntime.getClientConfigAdapter().getIntegratedServerOpts().toString());
|
||||
|
||||
}else {
|
||||
if(!serverSourceLoaded) {
|
||||
integratedServerSource = createIntegratedServerWorkerURL();
|
||||
serverSourceLoaded = true;
|
||||
}
|
||||
|
||||
if(integratedServerSource == null) {
|
||||
logger.error("Could not resolve the location of client's classes.js! Make sure client's classes.js is linked/embedded in a dedicated <script> tag. Define \"window.eaglercraftXClientScriptElement\" or \"window.eaglercraftXClientScriptURL\" to force");
|
||||
logger.error("Falling back to single thread mode...");
|
||||
startIntegratedServer(true);
|
||||
return;
|
||||
}
|
||||
|
||||
workerObj = Worker.create(integratedServerSource);
|
||||
workerObj.addEventListener("error", new EventListener<ErrorEvent>() {
|
||||
@Override
|
||||
public void handleEvent(ErrorEvent evt) {
|
||||
logger.error("Worker Error: {}", evt.getError());
|
||||
PlatformRuntime.printNativeExceptionToConsoleTeaVM(evt);
|
||||
}
|
||||
});
|
||||
registerPacketHandler(workerObj, new WorkerBinaryPacketHandlerImpl());
|
||||
sendWorkerStartPacket(workerObj, PlatformRuntime.getClientConfigAdapter().getIntegratedServerOpts().toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendPacket(IPCPacketData packet) {
|
||||
sendPacketTeaVM(packet.channel, TeaVMUtils.unwrapArrayBuffer(packet.contents));
|
||||
if(isSingleThreadMode) {
|
||||
SingleThreadWorker.sendPacketToWorker(packet);
|
||||
}else {
|
||||
sendPacketTeaVM(packet.channel, TeaVMUtils.unwrapArrayBuffer(packet.contents));
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendPacketTeaVM(String channel, ArrayBuffer packet) {
|
||||
if(workerObj != null) {
|
||||
sendWorkerPacket(workerObj, channel, packet);
|
||||
if(isSingleThreadMode) {
|
||||
SingleThreadWorker.sendPacketToWorker(new IPCPacketData(channel, TeaVMUtils.wrapByteArrayBuffer(packet)));
|
||||
}else {
|
||||
if(workerObj != null) {
|
||||
sendWorkerPacket(workerObj, channel, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,7 +304,7 @@ public class ClientPlatformSingleplayer {
|
||||
}
|
||||
|
||||
public static boolean canKillWorker() {
|
||||
return true;
|
||||
return !isSingleThreadMode;
|
||||
}
|
||||
|
||||
public static void killWorker() {
|
||||
@ -228,7 +315,17 @@ public class ClientPlatformSingleplayer {
|
||||
}
|
||||
|
||||
public static boolean isRunningSingleThreadMode() {
|
||||
return false;
|
||||
return isSingleThreadMode;
|
||||
}
|
||||
|
||||
public static boolean isSingleThreadModeSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void updateSingleThreadMode() {
|
||||
if(isSingleThreadMode) {
|
||||
SingleThreadWorker.singleThreadUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void showCrashReportOverlay(String report, int x, int y, int w, int h) {
|
||||
|
@ -3,17 +3,36 @@ package net.lax1dude.eaglercraft.v1_8.sp.server.internal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.teavm.interop.Async;
|
||||
import org.teavm.interop.AsyncCallback;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.browser.Window;
|
||||
import org.teavm.jso.core.JSString;
|
||||
import org.teavm.jso.dom.events.EventListener;
|
||||
import org.teavm.jso.dom.events.MessageEvent;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.EagUtils;
|
||||
import net.lax1dude.eaglercraft.v1_8.Filesystem;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapter;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IEaglerFilesystem;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IPCPacketData;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.ES6ShimStatus;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.EnumES6ShimStatus;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.EnumES6Shims;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.MessageChannel;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMBlobURLManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMClientConfigAdapter;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils;
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
||||
@ -36,7 +55,17 @@ public class ServerPlatformSingleplayer {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("ServerPlatformSingleplayer");
|
||||
|
||||
private static final LinkedList<IPCPacketData> messageQueue = new LinkedList();
|
||||
private static final LinkedList<IPCPacketData> messageQueue = new LinkedList<>();
|
||||
|
||||
private static boolean immediateContinueSupport = false;
|
||||
private static MessageChannel immediateContinueChannel = null;
|
||||
private static Runnable currentContinueHack = null;
|
||||
private static final Object immediateContLock = new Object();
|
||||
private static final JSString emptyJSString = JSString.valueOf("");
|
||||
private static boolean singleThreadMode = false;
|
||||
private static Consumer<IPCPacketData> singleThreadCB = null;
|
||||
|
||||
private static IEaglerFilesystem filesystem = null;
|
||||
|
||||
@JSFunctor
|
||||
private static interface WorkerBinaryPacketHandler extends JSObject {
|
||||
@ -63,7 +92,7 @@ public class ServerPlatformSingleplayer {
|
||||
|
||||
}
|
||||
|
||||
@JSBody(params = { "wb" }, script = "onmessage = function(o) { wb(o.data.ch, o.data.dat); };")
|
||||
@JSBody(params = { "wb" }, script = "__eaglerXOnMessage = function(o) { wb(o.data.ch, o.data.dat); };")
|
||||
private static native void registerPacketHandler(WorkerBinaryPacketHandler wb);
|
||||
|
||||
public static void register() {
|
||||
@ -71,14 +100,66 @@ public class ServerPlatformSingleplayer {
|
||||
}
|
||||
|
||||
public static void initializeContext() {
|
||||
PlatformFilesystem.initialize(getClientConfigAdapter().getWorldsDB());
|
||||
singleThreadMode = false;
|
||||
singleThreadCB = null;
|
||||
ES6ShimStatus shimStatus = ES6ShimStatus.getRuntimeStatus();
|
||||
if(shimStatus != null) {
|
||||
EnumES6ShimStatus stat = shimStatus.getStatus();
|
||||
switch(stat) {
|
||||
case STATUS_ERROR:
|
||||
case STATUS_DISABLED_ERRORS:
|
||||
logger.error("ES6 Shim Status: {}", stat.statusDesc);
|
||||
break;
|
||||
case STATUS_ENABLED_ERRORS:
|
||||
logger.error("ES6 Shim Status: {}", stat.statusDesc);
|
||||
dumpShims(shimStatus.getShims());
|
||||
break;
|
||||
case STATUS_DISABLED:
|
||||
case STATUS_NOT_PRESENT:
|
||||
logger.info("ES6 Shim Status: {}", stat.statusDesc);
|
||||
break;
|
||||
case STATUS_ENABLED:
|
||||
logger.info("ES6 Shim Status: {}", stat.statusDesc);
|
||||
dumpShims(shimStatus.getShims());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TeaVMBlobURLManager.initialize();
|
||||
|
||||
checkImmediateContinueSupport();
|
||||
|
||||
filesystem = Filesystem.getHandleFor(getClientConfigAdapter().getWorldsDB());
|
||||
VFile2.setPrimaryFilesystem(filesystem);
|
||||
}
|
||||
|
||||
public static IEaglerFilesystem getWorldsDatabase() {
|
||||
return filesystem;
|
||||
}
|
||||
|
||||
public static void initializeContextSingleThread(Consumer<IPCPacketData> packetSendCallback) {
|
||||
singleThreadMode = true;
|
||||
singleThreadCB = packetSendCallback;
|
||||
filesystem = Filesystem.getHandleFor(getClientConfigAdapter().getWorldsDB());
|
||||
}
|
||||
|
||||
private static void dumpShims(Set<EnumES6Shims> shims) {
|
||||
if(!shims.isEmpty()) {
|
||||
logger.info("(Enabled {} shims: {})", shims.size(), String.join(", ", Collections2.transform(shims, (shim) -> shim.shimDesc)));
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = { "ch", "dat" }, script = "postMessage({ ch: ch, dat : dat });")
|
||||
public static native void sendPacketTeaVM(String channel, ArrayBuffer arr);
|
||||
|
||||
public static void sendPacket(IPCPacketData packet) {
|
||||
sendPacketTeaVM(packet.channel, TeaVMUtils.unwrapArrayBuffer(packet.contents));
|
||||
if(singleThreadMode) {
|
||||
singleThreadCB.accept(packet);
|
||||
}else {
|
||||
sendPacketTeaVM(packet.channel, TeaVMUtils.unwrapArrayBuffer(packet.contents));
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IPCPacketData> recieveAllPacket() {
|
||||
@ -86,7 +167,7 @@ public class ServerPlatformSingleplayer {
|
||||
if(messageQueue.size() == 0) {
|
||||
return null;
|
||||
}else {
|
||||
List<IPCPacketData> ret = new ArrayList(messageQueue);
|
||||
List<IPCPacketData> ret = new ArrayList<>(messageQueue);
|
||||
messageQueue.clear();
|
||||
return ret;
|
||||
}
|
||||
@ -96,4 +177,118 @@ public class ServerPlatformSingleplayer {
|
||||
public static IClientConfigAdapter getClientConfigAdapter() {
|
||||
return TeaVMClientConfigAdapter.instance;
|
||||
}
|
||||
|
||||
private static void checkImmediateContinueSupport() {
|
||||
try {
|
||||
immediateContinueSupport = false;
|
||||
if(!MessageChannel.supported()) {
|
||||
logger.error("Fast immediate continue will be disabled for server context due to MessageChannel being unsupported");
|
||||
return;
|
||||
}
|
||||
immediateContinueChannel = MessageChannel.create();
|
||||
immediateContinueChannel.getPort1().addEventListener("message", new EventListener<MessageEvent>() {
|
||||
@Override
|
||||
public void handleEvent(MessageEvent evt) {
|
||||
Runnable toRun;
|
||||
synchronized(immediateContLock) {
|
||||
toRun = currentContinueHack;
|
||||
currentContinueHack = null;
|
||||
}
|
||||
if(toRun != null) {
|
||||
toRun.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
immediateContinueChannel.getPort1().start();
|
||||
immediateContinueChannel.getPort2().start();
|
||||
final boolean[] checkMe = new boolean[1];
|
||||
checkMe[0] = false;
|
||||
currentContinueHack = () -> {
|
||||
checkMe[0] = true;
|
||||
};
|
||||
immediateContinueChannel.getPort2().postMessage(emptyJSString);
|
||||
if(checkMe[0]) {
|
||||
currentContinueHack = null;
|
||||
if(immediateContinueChannel != null) {
|
||||
safeShutdownChannel(immediateContinueChannel);
|
||||
}
|
||||
immediateContinueChannel = null;
|
||||
logger.error("Fast immediate continue will be disabled for server context due to actually continuing immediately");
|
||||
return;
|
||||
}
|
||||
EagUtils.sleep(10l);
|
||||
currentContinueHack = null;
|
||||
if(!checkMe[0]) {
|
||||
if(immediateContinueChannel != null) {
|
||||
safeShutdownChannel(immediateContinueChannel);
|
||||
}
|
||||
immediateContinueChannel = null;
|
||||
logger.error("Fast immediate continue will be disabled for server context due to startup check failing");
|
||||
}else {
|
||||
immediateContinueSupport = true;
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
logger.error("Fast immediate continue will be disabled for server context due to exceptions");
|
||||
immediateContinueSupport = false;
|
||||
if(immediateContinueChannel != null) {
|
||||
safeShutdownChannel(immediateContinueChannel);
|
||||
}
|
||||
immediateContinueChannel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void safeShutdownChannel(MessageChannel chan) {
|
||||
try {
|
||||
chan.getPort1().close();
|
||||
}catch(Throwable tt) {
|
||||
}
|
||||
try {
|
||||
chan.getPort2().close();
|
||||
}catch(Throwable tt) {
|
||||
}
|
||||
}
|
||||
|
||||
public static void immediateContinue() {
|
||||
if(singleThreadMode) {
|
||||
PlatformRuntime.immediateContinue();
|
||||
}else {
|
||||
if(immediateContinueSupport) {
|
||||
immediateContinueTeaVM();
|
||||
}else {
|
||||
EagUtils.sleep(0l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
private static native void immediateContinueTeaVM();
|
||||
|
||||
private static void immediateContinueTeaVM(final AsyncCallback<Void> cb) {
|
||||
synchronized(immediateContLock) {
|
||||
if(currentContinueHack != null) {
|
||||
cb.error(new IllegalStateException("Worker thread is already waiting for an immediate continue callback!"));
|
||||
return;
|
||||
}
|
||||
currentContinueHack = () -> {
|
||||
cb.complete(null);
|
||||
};
|
||||
try {
|
||||
immediateContinueChannel.getPort2().postMessage(emptyJSString);
|
||||
}catch(Throwable t) {
|
||||
logger.error("Caught error posting immediate continue, using setTimeout instead");
|
||||
Window.setTimeout(() -> cb.complete(null), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSingleThreadMode() {
|
||||
return singleThreadMode;
|
||||
}
|
||||
|
||||
public static void recievePacketSingleThreadTeaVM(IPCPacketData pkt) {
|
||||
synchronized(messageQueue) {
|
||||
messageQueue.add(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
package net.lax1dude.eaglercraft.v1_8.sp.server.internal.teavm;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.lax1dude.eaglercraft.v1_8.internal.IPCPacketData;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerIntegratedServerWorker;
|
||||
import net.lax1dude.eaglercraft.v1_8.sp.server.internal.ServerPlatformSingleplayer;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
public class SingleThreadWorker {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger("SingleThreadWorker");
|
||||
|
||||
public static void singleThreadStartup(Consumer<IPCPacketData> packetSendCallback) {
|
||||
logger.info("Starting single-thread mode worker...");
|
||||
ServerPlatformSingleplayer.initializeContextSingleThread(packetSendCallback);
|
||||
EaglerIntegratedServerWorker.singleThreadMain();
|
||||
}
|
||||
|
||||
public static void sendPacketToWorker(IPCPacketData pkt) {
|
||||
ServerPlatformSingleplayer.recievePacketSingleThreadTeaVM(pkt);
|
||||
}
|
||||
|
||||
public static void singleThreadUpdate() {
|
||||
EaglerIntegratedServerWorker.singleThreadUpdate();
|
||||
}
|
||||
|
||||
}
|
@ -77,7 +77,7 @@ public class WorkerMain {
|
||||
public void onMessage(String msg);
|
||||
}
|
||||
|
||||
@JSBody(params = { "wb" }, script = "onmessage = function(o) { wb(o.data.msg); };")
|
||||
@JSBody(params = { "wb" }, script = "__eaglerXOnMessage = function(o) { wb(o.data.msg); }; addEventListener(\"message\", function(evt) { __eaglerXOnMessage(evt); });")
|
||||
private static native void setOnMessage(WorkerArgumentsPacketHandler cb);
|
||||
|
||||
@Async
|
||||
|
Reference in New Issue
Block a user