Update #52 - Fixed various issues with the client

This commit is contained in:
lax1dude
2025-06-15 21:43:43 -07:00
parent 325a6826bf
commit f3281c037f
94 changed files with 882 additions and 506 deletions

View File

@ -162,6 +162,8 @@ The default eaglercraftXOpts values is this:
- `singleThreadMode:` if the game should run the client and integrated server in the same context instead of creating a worker object
- `enableEPKVersionCheck:` if the game should attempt to bypass the browser's cache and retry downloading assets.epk when its outdated
- `enforceVSync:` (WASM only) if the game should automatically re-enable VSync at launch if its disabled
- `keepAliveHack:` if the game should embed a looping `<audio>` tag to keep the tab from becoming inactive on Chrome
- `finishOnSwap:` if the WebGL `finish` function should be called after every frame when VSync is disabled
- `hooks:` can be used to define JavaScript callbacks for certain events
* `localStorageSaved:` JavaScript callback to save local storage keys (key, data)
* `localStorageLoaded:` JavaScript callback to load local storage keys (key) returns data

View File

@ -1 +1 @@
u51
u52

View File

@ -21,7 +21,17 @@
~ import net.lax1dude.eaglercraft.v1_8.netty.ByteBufInputStream;
~ import net.lax1dude.eaglercraft.v1_8.netty.ByteBufOutputStream;
> CHANGE 98 : 99 @ 98 : 99
> CHANGE 30 : 37 @ 30 : 32
~ public byte[] readByteArray(int maxLen) {
~ int i = this.readVarIntFromBuffer();
~ if (i < 0 || i > maxLen) {
~ throw new DecoderException(
~ "The received array length is longer than maximum allowed (" + i + " > " + maxLen + ")");
~ }
~ byte[] abyte = new byte[i];
> CHANGE 66 : 67 @ 66 : 67
~ public void writeUuid(EaglercraftUUID uuid) {

View File

@ -35,9 +35,9 @@
> CHANGE 3 : 8 @ 3 : 4
~ this.profile = new GameProfile((EaglercraftUUID) null, parPacketBuffer.readStringFromBuffer(16));
~ this.skin = parPacketBuffer.readByteArray();
~ this.cape = parPacketBuffer.readableBytes() > 0 ? parPacketBuffer.readByteArray() : null;
~ this.protocols = parPacketBuffer.readableBytes() > 0 ? parPacketBuffer.readByteArray() : null;
~ this.skin = parPacketBuffer.readByteArray(32768);
~ this.cape = parPacketBuffer.readableBytes() > 0 ? parPacketBuffer.readByteArray(32768) : null;
~ this.protocols = parPacketBuffer.readableBytes() > 0 ? parPacketBuffer.readByteArray(256) : null;
~ this.brandUUID = parPacketBuffer.readableBytes() > 0 ? parPacketBuffer.readUuid() : null;
> INSERT 4 : 8 @ 4

View File

@ -18,7 +18,12 @@
~ // this.verifyTokenEncrypted = CryptManager.encryptData(publicKey, verifyToken);
~ // }
> CHANGE 15 : 18 @ 15 : 18
> CHANGE 2 : 4 @ 2 : 4
~ this.secretKeyEncrypted = parPacketBuffer.readByteArray(1024);
~ this.verifyTokenEncrypted = parPacketBuffer.readByteArray(1024);
> CHANGE 11 : 14 @ 11 : 14
~ // public SecretKey getSecretKey(PrivateKey key) {
~ // return CryptManager.decryptSharedKey(key, this.secretKeyEncrypted);

View File

@ -23,13 +23,14 @@
~ // this.verifyToken = verifyToken;
~ // }
> CHANGE 3 : 6 @ 3 : 4
> CHANGE 3 : 7 @ 3 : 5
~ // this.publicKey =
~ // CryptManager.decodePublicKey(parPacketBuffer.readByteArray());
~ parPacketBuffer.readByteArray(); // skip
~ parPacketBuffer.readByteArray(1024); // skip
~ this.verifyToken = parPacketBuffer.readByteArray(1024);
> CHANGE 4 : 7 @ 4 : 7
> CHANGE 3 : 6 @ 3 : 6
~ // parPacketBuffer.writeString(this.hashedServerId);
~ // parPacketBuffer.writeByteArray(this.publicKey.getEncoded());

View File

@ -17,7 +17,11 @@
+ chunkIn.alfheim$getLightingEngine().processLightUpdates();
> CHANGE 40 : 41 @ 40 : 41
> CHANGE 9 : 10 @ 9 : 10
~ this.extractedData.data = parPacketBuffer.readByteArray(0x1000000);
> CHANGE 30 : 31 @ 30 : 31
~ ArrayList<ExtendedBlockStorage> arraylist = Lists.newArrayList();

View File

@ -9,7 +9,11 @@
+
> CHANGE 64 : 66 @ 64 : 65
> CHANGE 54 : 55 @ 54 : 55
~ this.mapDataBytes = parPacketBuffer.readByteArray(0x400000);
> CHANGE 9 : 11 @ 9 : 10
~ for (int i = 0; i < this.mapVisiblePlayersVec4b.length; ++i) {
~ Vec4b vec4b = this.mapVisiblePlayersVec4b[i];

View File

@ -265,7 +265,7 @@
+ eaglercraft.command.clientStub=This command is client side!
+
> INSERT 163 : 578 @ 163
> INSERT 163 : 574 @ 163
+ eaglercraft.singleplayer.busy.killTask=Cancel Task
+ eaglercraft.singleplayer.busy.cancelWarning=Are you sure?
@ -656,11 +656,7 @@
+ eaglercraft.options.badVideoSettingsDetected.title=Issues Detected
+ eaglercraft.options.badVideoSettingsDetected.0=Some of your video settings may be causing
+ eaglercraft.options.badVideoSettingsDetected.1=the game to lag excessively
+ eaglercraft.options.badVideoSettingsDetected.vsync.0=VSync is disabled, some browsers require
+ eaglercraft.options.badVideoSettingsDetected.vsync.1=VSync to be enabled to hint when the
+ eaglercraft.options.badVideoSettingsDetected.vsync.2=framebuffer has updated. If the game feels
+ eaglercraft.options.badVideoSettingsDetected.vsync.3=significantly slower than is indicated by
+ eaglercraft.options.badVideoSettingsDetected.vsync.4=the FPS counter, you should enable VSync.
+ eaglercraft.options.badVideoSettingsDetected.vsync.0=VSync is disabled, this may cause input lag
+ eaglercraft.options.badVideoSettingsDetected.renderDistance.0=Render distance is %d chunks, most devices
+ eaglercraft.options.badVideoSettingsDetected.renderDistance.1=lag when the render distance is greater
+ eaglercraft.options.badVideoSettingsDetected.renderDistance.2=than 4 chunks

View File

@ -125,7 +125,7 @@ public class EaglerLWJGLFloatBuffer extends FloatBuffer {
@Override
public FloatBuffer get(float[] dst, int offset, int length) {
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
UnsafeMemcpy.memcpyAlignDst(dst, offset << SHIFT, address + (position << SHIFT), length);
UnsafeMemcpy.memcpyAlignDst(dst, offset, address + (position << SHIFT), length);
position += length;
return this;
}
@ -161,7 +161,7 @@ public class EaglerLWJGLFloatBuffer extends FloatBuffer {
@Override
public FloatBuffer put(float[] src, int offset, int length) {
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, offset << SHIFT, length);
UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, offset, length);
position += length;
return this;
}

View File

@ -125,7 +125,7 @@ public class EaglerLWJGLIntBuffer extends IntBuffer {
@Override
public IntBuffer get(int[] dst, int offset, int length) {
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
UnsafeMemcpy.memcpyAlignDst(dst, offset << SHIFT, address + (position << SHIFT), length);
UnsafeMemcpy.memcpyAlignDst(dst, offset, address + (position << SHIFT), length);
position += length;
return this;
}
@ -161,7 +161,7 @@ public class EaglerLWJGLIntBuffer extends IntBuffer {
@Override
public IntBuffer put(int[] src, int offset, int length) {
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, offset << SHIFT, length);
UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, offset, length);
position += length;
return this;
}

View File

@ -125,7 +125,7 @@ public class EaglerLWJGLShortBuffer extends ShortBuffer {
@Override
public ShortBuffer get(short[] dst, int offset, int length) {
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
UnsafeMemcpy.memcpyAlignDst(dst, offset << SHIFT, address + (position << SHIFT), length);
UnsafeMemcpy.memcpyAlignDst(dst, offset, address + (position << SHIFT), length);
position += length;
return this;
}
@ -161,7 +161,7 @@ public class EaglerLWJGLShortBuffer extends ShortBuffer {
@Override
public ShortBuffer put(short[] src, int offset, int length) {
if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1);
UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, offset << SHIFT, length);
UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, offset, length);
position += length;
return this;
}

View File

@ -10,7 +10,7 @@ public class EaglercraftVersion {
/// Customize these to fit your fork:
public static final String projectForkName = "EaglercraftX";
public static final String projectForkVersion = "u51";
public static final String projectForkVersion = "u52";
public static final String projectForkVendor = "lax1dude";
public static final String projectForkURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8";
@ -20,20 +20,20 @@ public class EaglercraftVersion {
public static final String projectOriginName = "EaglercraftX";
public static final String projectOriginAuthor = "lax1dude";
public static final String projectOriginRevision = "1.8";
public static final String projectOriginVersion = "u51";
public static final String projectOriginVersion = "u52";
public static final String projectOriginURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; // rest in peace
// EPK Version Identifier
public static final String EPKVersionIdentifier = "u51"; // Set to null to disable EPK version check
public static final String EPKVersionIdentifier = "u52"; // Set to null to disable EPK version check
// Updating configuration
public static final boolean enableUpdateService = true;
public static final String updateBundlePackageName = "net.lax1dude.eaglercraft.v1_8.client";
public static final int updateBundlePackageVersionInt = 51;
public static final int updateBundlePackageVersionInt = 52;
public static final String updateLatestLocalStorageKey = "latestUpdate_" + updateBundlePackageName;

View File

@ -30,7 +30,7 @@ public class GuiScreenVSyncReEnabled extends GuiScreen {
public void initGui() {
this.buttonList.clear();
this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height / 6 + 136, I18n.format("options.vsyncReEnabled.continue")));
this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height / 6 + 142, I18n.format("options.vsyncReEnabled.continue")));
}
public void drawScreen(int par1, int par2, float par3) {

View File

@ -49,10 +49,6 @@ public class GuiScreenVideoSettingsWarning extends GuiScreen {
if((mask & WARNING_VSYNC) != 0) {
messages.add(null);
messages.add(I18n.format("options.badVideoSettingsDetected.vsync.0"));
messages.add(I18n.format("options.badVideoSettingsDetected.vsync.1"));
messages.add(I18n.format("options.badVideoSettingsDetected.vsync.2"));
messages.add(I18n.format("options.badVideoSettingsDetected.vsync.3"));
messages.add(I18n.format("options.badVideoSettingsDetected.vsync.4"));
}
if((mask & WARNING_RENDER_DISTANCE) != 0) {
messages.add(null);

View File

@ -63,13 +63,13 @@ public class EaglerMeshLoader implements IResourceManagerReloadListener {
int intsOfVertex, intsOfIndex, intsTotal, stride;
try(DataInputStream dis = new DataInputStream(resourceManager.getResource(meshLoc).getInputStream())) {
byte[] header = new byte[8];
dis.read(header);
dis.readFully(header);
if(!Arrays.equals(header, new byte[] { (byte) 33, (byte) 69, (byte) 65, (byte) 71, (byte) 36,
(byte) 109, (byte) 100, (byte) 108 })) {
throw new IOException("File is not an eaglercraft high-poly mesh!");
}
char CT = (char)dis.read();
char CT = (char)dis.readUnsignedByte();
if(CT == 'C') {
meshStruct.hasTexture = false;

View File

@ -493,27 +493,21 @@ public class EaglercraftGPU {
}
public static void enableVertexAttribArray(int index) {
if(emulatedVAOs) {
if(currentVertexArray == null) {
logger.warn("Skipping enable attrib with emulated VAO because no known VAO is bound!");
return;
}
((SoftGLVertexArray)currentVertexArray).enableAttrib(index, true);
}else {
if(!emulatedVAOs) {
_wglEnableVertexAttribArray(index);
}
if (currentVertexArray != null) {
currentVertexArray.setBit(1 << index);
}
}
public static void disableVertexAttribArray(int index) {
if(emulatedVAOs) {
if(currentVertexArray == null) {
logger.warn("Skipping disable attrib with emulated VAO because no known VAO is bound!");
return;
}
((SoftGLVertexArray)currentVertexArray).enableAttrib(index, false);
}else {
if(!emulatedVAOs) {
_wglDisableVertexAttribArray(index);
}
if (currentVertexArray != null) {
currentVertexArray.unsetBit(1 << index);
}
}
public static void vertexAttribPointer(int index, int size, int format, boolean normalized, int stride, int offset) {

View File

@ -74,12 +74,15 @@ class SoftGLVertexArray implements IVertexArrayGL {
}
}
void enableAttrib(int index, boolean en) {
if(en) {
enabled |= (1 << index);
}else {
enabled &= ~(1 << index);
}
@Override
public void setBit(int bit) {
enabled |= bit;
enabledCnt = 32 - Integer.numberOfLeadingZeros(enabled);
}
@Override
public void unsetBit(int bit) {
enabled &= ~bit;
enabledCnt = 32 - Integer.numberOfLeadingZeros(enabled);
}
@ -183,6 +186,11 @@ class SoftGLVertexArray implements IVertexArrayGL {
public void free() {
}
@Override
public int getBits() {
return enabled;
}
static class Attrib {
final IBufferGL buffer;
@ -223,17 +231,4 @@ class SoftGLVertexArray implements IVertexArrayGL {
}
@Override
public int getBits() {
return enabled;
}
@Override
public void setBit(int bit) {
}
@Override
public void unsetBit(int bit) {
}
}

View File

@ -42,14 +42,6 @@ public class StreamBuffer {
}
private static PoolInstance fillPoolInstance() {
PoolInstance ret = pool[poolBufferID++];
if(poolBufferID > poolSize - 1) {
poolBufferID = 0;
}
return ret;
}
private static void resizeInstance(PoolInstance instance, int requiredMemory) {
IBufferGL buffer = instance.vertexBuffer;
if (buffer == null) {
@ -67,8 +59,6 @@ public class StreamBuffer {
protected StreamBufferInstance[] buffers;
protected int currentBufferId = 0;
protected final IStreamBufferInitializer initializer;
public static class StreamBufferInstance {
@ -94,24 +84,17 @@ public class StreamBuffer {
}
public StreamBuffer(IStreamBufferInitializer initializer) {
this(poolSize, initializer);
}
public StreamBuffer(int count, IStreamBufferInitializer initializer) {
if(count > poolSize) {
count = poolSize;
}
this.buffers = new StreamBufferInstance[count];
this.buffers = new StreamBufferInstance[poolSize];
for(int i = 0; i < this.buffers.length; ++i) {
StreamBufferInstance j = new StreamBufferInstance();
j.poolInstance = fillPoolInstance();
j.poolInstance = pool[i];
this.buffers[i] = j;
}
this.initializer = initializer;
}
public StreamBufferInstance getBuffer(int requiredMemory) {
StreamBufferInstance next = buffers[(currentBufferId++) % buffers.length];
StreamBufferInstance next = buffers[poolBufferID++ % buffers.length];
resizeInstance(next.poolInstance, requiredMemory);
if(next.vertexArray == null) {
next.vertexArray = EaglercraftGPU.createGLVertexArray();

View File

@ -0,0 +1,149 @@
/*
* Copyright (c) 2025 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.
*
*/
package net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred;
import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*;
import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*;
import static net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.ExtGLEnums.*;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.Display;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
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.buffer.ByteBuffer;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.opengl.DrawUtils;
import net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU;
import net.lax1dude.eaglercraft.v1_8.opengl.GLSLHeader;
import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager;
import net.lax1dude.eaglercraft.v1_8.opengl.VSHInputLayoutParser;
public class D3DCmpLod0IssueCheck {
private static final Logger logger = LogManager.getLogger("D3DCmpLod0IssueCheck");
public static boolean test() {
String rendererString = EaglercraftGPU.glGetString(GL_RENDERER);
if (rendererString == null || !rendererString.contains(" Direct3D11 ")) {
return false;
}
logger.info("Checking for D3D compiler issue...");
String vshLocalSrc = EagRuntime.getRequiredResourceString("/assets/eagler/glsl/local.vsh");
List<VSHInputLayoutParser.ShaderInput> vertLayout = VSHInputLayoutParser.getShaderInputs(vshLocalSrc);
IShaderGL vert = _wglCreateShader(GL_VERTEX_SHADER);
_wglShaderSource(vert, GLSLHeader.getVertexHeaderCompat(vshLocalSrc, DrawUtils.vertexShaderPrecision));
_wglCompileShader(vert);
if (_wglGetShaderi(vert, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
_wglDeleteShader(vert);
logger.error("Failed to compile vertex shader! This should not happen!");
logger.error("Don't know how to proceed");
return false;
}
IShaderGL frag = _wglCreateShader(GL_FRAGMENT_SHADER);
_wglShaderSource(frag, GLSLHeader.getHeader()
+ EagRuntime.getRequiredResourceString("/assets/eagler/glsl/deferred/check_d3d_cmplod0.fsh"));
_wglCompileShader(frag);
if (_wglGetShaderi(frag, GL_COMPILE_STATUS) != GL_TRUE) {
Display.checkContextLost();
logger.info(_wglGetShaderInfoLog(frag));
_wglDeleteShader(vert);
_wglDeleteShader(frag);
logger.error("Failed to compile fragment shader! This should not happen!");
logger.error("D3D compiler workarounds will be enabled");
return true;
}
IProgramGL program = _wglCreateProgram();
_wglAttachShader(program, vert);
_wglAttachShader(program, frag);
if (EaglercraftGPU.checkOpenGLESVersion() == 200) {
VSHInputLayoutParser.applyLayout(program, vertLayout);
}
_wglLinkProgram(program);
_wglDetachShader(program, vert);
_wglDetachShader(program, frag);
_wglDeleteShader(vert);
_wglDeleteShader(frag);
if (_wglGetProgrami(program, GL_LINK_STATUS) != GL_TRUE) {
Display.checkContextLost();
_wglDeleteProgram(program);
logger.error("Failed to link program! This should not happen!");
logger.error("D3D compiler workarounds will be enabled");
return true;
}
EaglercraftGPU.bindGLShaderProgram(program);
_wglUniform1i(_wglGetUniformLocation(program, "u_inputTexture"), 0);
int emptyTexture = GlStateManager.generateTexture();
GlStateManager.bindTexture(emptyTexture);
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
_wglTexParameteri(GL_TEXTURE_2D, _GL_TEXTURE_COMPARE_FUNC, GL_GREATER);
_wglTexParameteri(GL_TEXTURE_2D, _GL_TEXTURE_COMPARE_MODE, _GL_COMPARE_REF_TO_TEXTURE);
_wglTexImage2D(GL_TEXTURE_2D, 0, _GL_DEPTH_COMPONENT24, 256, 256, 0, _GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, (ByteBuffer)null);
IFramebufferGL fbo = _wglCreateFramebuffer();
_wglBindFramebuffer(_GL_FRAMEBUFFER, fbo);
IRenderbufferGL rbo = _wglCreateRenderbuffer();
_wglBindRenderbuffer(_GL_RENDERBUFFER, rbo);
_wglRenderbufferStorage(_GL_RENDERBUFFER, GL_RGBA8, 256, 256);
_wglFramebufferRenderbuffer(_GL_FRAMEBUFFER, _GL_COLOR_ATTACHMENT0, _GL_RENDERBUFFER, rbo);
_wglDrawBuffers(_GL_COLOR_ATTACHMENT0);
int err = _wglGetError();
if (err != 0) {
logger.error("Ignored OpenGL error while clearing error state: ", EaglercraftGPU.gluErrorString(err));
}
DrawUtils.drawStandardQuad2D();
err = _wglGetError();
GlStateManager.deleteTexture(emptyTexture);
_wglDeleteProgram(program);
_wglDeleteRenderbuffer(rbo);
_wglDeleteFramebuffer(fbo);
_wglBindFramebuffer(_GL_FRAMEBUFFER, null);
if (err != 0) {
logger.error("Using the test shader generated error: {}", EaglercraftGPU.gluErrorString(err));
logger.error("D3D compiler workarounds will be enabled");
return true;
} else {
return false;
}
}
}

View File

@ -1889,45 +1889,9 @@ public class EaglerDeferredPipeline {
matrixCopyBuffer.flip();
_wglUniformMatrix4x2fv(shader_reproject_ssr.uniforms.u_lastInverseProjMatrix4x2f, false, matrixCopyBuffer);
_wglUniform1f(shader_reproject_ssr.uniforms.u_sampleStep1f, 0.125f);
_wglUniform1i(shader_reproject_ssr.uniforms.u_sampleDelta1i, 5);
if(shader_reproject_ssr.uniforms.u_sampleDelta1i != null) {
_wglUniform1i(shader_reproject_ssr.uniforms.u_sampleDelta1i, 5);
DrawUtils.drawStandardQuad2D();
}else {
DrawUtils.drawStandardQuad2D(); // sample 1
_wglBindFramebuffer(_GL_FRAMEBUFFER, reprojectionSSRFramebuffer[0]);
GlStateManager.setActiveTexture(GL_TEXTURE3);
GlStateManager.bindTexture(reprojectionSSRHitVector[1]);
GlStateManager.setActiveTexture(GL_TEXTURE2);
GlStateManager.bindTexture(reprojectionSSRTexture[1]);
DrawUtils.drawStandardQuad2D(); // sample 2
_wglBindFramebuffer(_GL_FRAMEBUFFER, reprojectionSSRFramebuffer[1]);
GlStateManager.setActiveTexture(GL_TEXTURE3);
GlStateManager.bindTexture(reprojectionSSRHitVector[0]);
GlStateManager.setActiveTexture(GL_TEXTURE2);
GlStateManager.bindTexture(reprojectionSSRTexture[0]);
DrawUtils.drawStandardQuad2D(); // sample 3
_wglBindFramebuffer(_GL_FRAMEBUFFER, reprojectionSSRFramebuffer[0]);
GlStateManager.setActiveTexture(GL_TEXTURE3);
GlStateManager.bindTexture(reprojectionSSRHitVector[1]);
GlStateManager.setActiveTexture(GL_TEXTURE2);
GlStateManager.bindTexture(reprojectionSSRTexture[1]);
DrawUtils.drawStandardQuad2D(); // sample 4
_wglBindFramebuffer(_GL_FRAMEBUFFER, reprojectionSSRFramebuffer[1]);
GlStateManager.setActiveTexture(GL_TEXTURE3);
GlStateManager.bindTexture(reprojectionSSRHitVector[0]);
GlStateManager.setActiveTexture(GL_TEXTURE2);
GlStateManager.bindTexture(reprojectionSSRTexture[0]);
DrawUtils.drawStandardQuad2D(); // sample 5
}
DrawUtils.drawStandardQuad2D();
DeferredStateManager.checkGLError("combineGBuffersAndIlluminate(): RUN SCREENSPACE REFLECTIONS ALGORITHM");
}
@ -2900,45 +2864,9 @@ public class EaglerDeferredPipeline {
matrixCopyBuffer.flip();
_wglUniformMatrix4x2fv(shader_reproject_ssr.uniforms.u_lastInverseProjMatrix4x2f, false, matrixCopyBuffer);
_wglUniform1f(shader_reproject_ssr.uniforms.u_sampleStep1f, 0.5f);
_wglUniform1i(shader_reproject_ssr.uniforms.u_sampleDelta1i, 5);
if(shader_reproject_ssr.uniforms.u_sampleDelta1i != null) {
_wglUniform1i(shader_reproject_ssr.uniforms.u_sampleDelta1i, 5);
DrawUtils.drawStandardQuad2D();
}else {
DrawUtils.drawStandardQuad2D(); // sample 1
_wglBindFramebuffer(_GL_FRAMEBUFFER, realisticWaterSSRFramebuffer[0]);
GlStateManager.setActiveTexture(GL_TEXTURE3);
GlStateManager.bindTexture(realisticWaterControlHitVectorTexture[1]);
GlStateManager.setActiveTexture(GL_TEXTURE2);
GlStateManager.bindTexture(realisticWaterControlReflectionTexture[1]);
DrawUtils.drawStandardQuad2D(); // sample 2
_wglBindFramebuffer(_GL_FRAMEBUFFER, realisticWaterSSRFramebuffer[1]);
GlStateManager.setActiveTexture(GL_TEXTURE3);
GlStateManager.bindTexture(realisticWaterControlHitVectorTexture[0]);
GlStateManager.setActiveTexture(GL_TEXTURE2);
GlStateManager.bindTexture(realisticWaterControlReflectionTexture[0]);
DrawUtils.drawStandardQuad2D(); // sample 3
_wglBindFramebuffer(_GL_FRAMEBUFFER, realisticWaterSSRFramebuffer[0]);
GlStateManager.setActiveTexture(GL_TEXTURE3);
GlStateManager.bindTexture(realisticWaterControlHitVectorTexture[1]);
GlStateManager.setActiveTexture(GL_TEXTURE2);
GlStateManager.bindTexture(realisticWaterControlReflectionTexture[1]);
DrawUtils.drawStandardQuad2D(); // sample 4
_wglBindFramebuffer(_GL_FRAMEBUFFER, realisticWaterSSRFramebuffer[1]);
GlStateManager.setActiveTexture(GL_TEXTURE3);
GlStateManager.bindTexture(realisticWaterControlHitVectorTexture[0]);
GlStateManager.setActiveTexture(GL_TEXTURE2);
GlStateManager.bindTexture(realisticWaterControlReflectionTexture[0]);
DrawUtils.drawStandardQuad2D(); // sample 5
}
DrawUtils.drawStandardQuad2D();
DeferredStateManager.checkGLError("endDrawRealisticWaterMask(): RUN SCREENSPACE REFLECTIONS ALGORITHM");

View File

@ -28,6 +28,7 @@ import net.lax1dude.eaglercraft.v1_8.opengl.FixedFunctionShader;
import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager;
import net.lax1dude.eaglercraft.v1_8.opengl.IExtPipelineCompiler;
import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.program.GBufferExtPipelineShader;
import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.program.ShaderCompiler;
import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.program.ShaderSource;
import net.lax1dude.eaglercraft.v1_8.vector.Matrix4f;
import net.lax1dude.eaglercraft.v1_8.vector.Vector4f;
@ -59,6 +60,11 @@ public class GBufferPipelineCompiler implements IExtPipelineCompiler {
userPointer[0] = new GBufferPipelineProgramInstance(stateCoreBits, stateExtBits);
EaglerDeferredConfig conf = Minecraft.getMinecraft().gameSettings.deferredShaderConf;
StringBuilder macros = new StringBuilder();
if (ShaderCompiler.isBrokenD3DCompiler()) {
macros.append("#define CMPLOD0_D3D_WORKAROUND(a, b) texture(a, b)\n");
} else {
macros.append("#define CMPLOD0_D3D_WORKAROUND(a, b) textureLod(a, b, 0.0)\n");
}
if((stateExtBits & STATE_SHADOW_RENDER) != 0) {
if((stateExtBits & STATE_CLIP_PLANE) != 0) {
macros.append("#define STATE_CLIP_PLANE\n");

View File

@ -186,12 +186,12 @@ public class LensFlareMeshRenderer {
_wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
_wglPixelStorei(GL_UNPACK_ALIGNMENT, 1);
int mip = 0;
while(dis.read() == 'E') {
while(dis.readUnsignedByte() == 'E') {
int w = dis.readShort();
int h = dis.readShort();
copyBuffer.clear();
for(int i = 0, l = w * h; i < l; ++i) {
copyBuffer.put((byte)dis.read());
copyBuffer.put((byte)dis.readUnsignedByte());
}
copyBuffer.flip();
_wglTexImage2D(GL_TEXTURE_2D, mip++, _GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, copyBuffer);

View File

@ -54,18 +54,19 @@ public class LightSourceMesh {
destroy();
try (DataInputStream is = new DataInputStream(
Minecraft.getMinecraft().getResourceManager().getResource(meshLocation).getInputStream())) {
if(is.read() != 0xEE || is.read() != 0xAA || is.read() != 0x66 || is.read() != '%') {
if(is.readUnsignedByte() != 0xEE || is.readUnsignedByte() != 0xAA || is.readUnsignedByte() != 0x66
|| is.readUnsignedByte() != '%') {
throw new IOException("Bad file type for: " + meshLocation.toString());
}
byte[] bb = new byte[is.read()];
is.read(bb);
byte[] bb = new byte[is.readUnsignedByte()];
is.readFully(bb);
if(!Arrays.equals(bb, typeBytes)) {
throw new IOException("Bad file type \"" + new String(bb, StandardCharsets.UTF_8) + "\" for: " + meshLocation.toString());
}
int vboLength = is.readInt() * 6;
byte[] readBuffer = new byte[vboLength];
is.read(readBuffer);
is.readFully(readBuffer);
ByteBuffer buf = EagRuntime.allocateByteBuffer(readBuffer.length);
buf.put(readBuffer);
@ -78,7 +79,7 @@ public class LightSourceMesh {
EagRuntime.freeByteBuffer(buf);
int iboLength = meshIndexCount = is.readInt();
int iboType = is.read();
int iboType = is.readUnsignedByte();
iboLength *= iboType;
switch(iboType) {
case 1:
@ -95,7 +96,7 @@ public class LightSourceMesh {
}
readBuffer = new byte[iboLength];
is.read(readBuffer);
is.readFully(readBuffer);
buf = EagRuntime.allocateByteBuffer(readBuffer.length);
buf.put(readBuffer);

View File

@ -63,18 +63,19 @@ public class SkyboxRenderer {
destroy();
try (DataInputStream is = new DataInputStream(
Minecraft.getMinecraft().getResourceManager().getResource(skyboxLocation).getInputStream())) {
if(is.read() != 0xEE || is.read() != 0xAA || is.read() != 0x66 || is.read() != '%') {
if(is.readUnsignedByte() != 0xEE || is.readUnsignedByte() != 0xAA || is.readUnsignedByte() != 0x66
|| is.readUnsignedByte() != '%') {
throw new IOException("Bad file type for: " + skyboxLocation.toString());
}
byte[] bb = new byte[is.read()];
is.read(bb);
is.readFully(bb);
if(!Arrays.equals(bb, new byte[] { 's', 'k', 'y', 'b', 'o', 'x' })) {
throw new IOException("Bad file type \"" + new String(bb, StandardCharsets.UTF_8) + "\" for: " + skyboxLocation.toString());
}
atmosphereLUTWidth = is.readUnsignedShort();
atmosphereLUTHeight = is.readUnsignedShort();
byte[] readBuffer = new byte[atmosphereLUTWidth * atmosphereLUTHeight * 4];
is.read(readBuffer);
is.readFully(readBuffer);
ByteBuffer buf = EagRuntime.allocateByteBuffer(readBuffer.length);
buf.put(readBuffer);
@ -97,7 +98,7 @@ public class SkyboxRenderer {
int vboLength = is.readInt() * 8;
readBuffer = new byte[vboLength];
is.read(readBuffer);
is.readFully(readBuffer);
buf = EagRuntime.allocateByteBuffer(readBuffer.length);
buf.put(readBuffer);
@ -110,7 +111,7 @@ public class SkyboxRenderer {
EagRuntime.freeByteBuffer(buf);
int iboLength = skyboxIndexCount = is.readInt();
int iboType = is.read();
int iboType = is.readUnsignedByte();
iboLength *= iboType;
switch(iboType) {
case 1:
@ -129,7 +130,7 @@ public class SkyboxRenderer {
skyboxIndexStride = iboType;
readBuffer = new byte[iboLength];
is.read(readBuffer);
is.readFully(readBuffer);
buf = EagRuntime.allocateByteBuffer(readBuffer.length);
buf.put(readBuffer);

View File

@ -28,12 +28,27 @@ import net.lax1dude.eaglercraft.v1_8.internal.IShaderGL;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.opengl.GLSLHeader;
import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.D3DCmpLod0IssueCheck;
import net.minecraft.util.ResourceLocation;
public class ShaderCompiler {
private static final Logger logger = LogManager.getLogger("DeferredPipelineCompiler");
private static boolean isBrokenD3DCompiler = false;
private static boolean isBrokenD3DChecked = false;
/**
* Warning: binds a different framebuffer
*/
public static boolean isBrokenD3DCompiler() {
if (!isBrokenD3DChecked) {
isBrokenD3DCompiler = D3DCmpLod0IssueCheck.test();
isBrokenD3DChecked = true;
}
return isBrokenD3DCompiler;
}
public static IShaderGL compileShader(String name, int stage, ResourceLocation filename, String... compileFlags) throws ShaderCompileException {
String src = ShaderSource.getSourceFor(filename);
if(src == null) {
@ -59,6 +74,12 @@ public class ShaderCompiler {
StringBuilder srcCat = new StringBuilder();
srcCat.append(GLSLHeader.getHeader()).append('\n');
if (isBrokenD3DCompiler()) {
srcCat.append("#define CMPLOD0_D3D_WORKAROUND(a, b) texture(a, b)\n");
} else {
srcCat.append("#define CMPLOD0_D3D_WORKAROUND(a, b) textureLod(a, b, 0.0)\n");
}
if(compileFlags != null && compileFlags.size() > 0) {
for(int i = 0, l = compileFlags.size(); i < l; ++i) {
srcCat.append("#define ").append(compileFlags.get(i)).append('\n');

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 lax1dude. All Rights Reserved.
* Copyright (c) 2023-2025 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
@ -16,6 +16,7 @@
package net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.texture;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
@ -34,53 +35,72 @@ public class EaglerBitwisePackedTexture {
}
}
public static ImageData loadTexture(InputStream is, int alpha) throws IOException {
if(is.read() != '%' || is.read() != 'E' || is.read() != 'B' || is.read() != 'P') {
private static int readByte(InputStream is) throws IOException {
int i = is.read();
if (i < 0) {
throw new EOFException();
}
return i;
}
public static ImageData loadTexture(InputStream is) throws IOException {
if(readByte(is) != '%' || readByte(is) != 'E' || readByte(is) != 'B' || readByte(is) != 'P') {
throw new IOException("Not an EBP file!");
}
int v = is.read();
int v = readByte(is);
if(v != 1) {
throw new IOException("Unknown EBP version: " + v);
}
v = is.read();
if(v != 3) {
throw new IOException("Invalid component count: " + v);
int c = readByte(is);
if(c != 3 && c != 4) {
throw new IOException("Invalid component count: " + c);
}
int w = is.read() | (is.read() << 8);
int h = is.read() | (is.read() << 8);
int w = readByte(is) | (readByte(is) << 8);
int h = readByte(is) | (readByte(is) << 8);
ImageData img = new ImageData(w, h, true);
alpha <<= 24;
v = is.read();
v = readByte(is);
if(v == 0) {
for(int i = 0, l = w * h; i < l; ++i) {
img.pixels[i] = is.read() | (is.read() << 8) | (is.read() << 16) | alpha;
if(c == 3) {
for(int i = 0, l = w * h; i < l; ++i) {
img.pixels[i] = readByte(is) | (readByte(is) << 8) | (readByte(is) << 16) | 0xFF000000;
}
}else {
for(int i = 0, l = w * h; i < l; ++i) {
img.pixels[i] = readByte(is) | (readByte(is) << 8) | (readByte(is) << 16) | (readByte(is) << 24);
}
}
}else if(v == 1) {
int paletteSize = is.read();
int[] palette = new int[paletteSize + 1];
palette[0] = alpha;
for(int i = 0; i < paletteSize; ++i) {
palette[i + 1] = is.read() | (is.read() << 8) | (is.read() << 16) | alpha;
int paletteSize = readByte(is) + 1;
int[] palette = new int[paletteSize];
palette[0] = 0xFF000000;
if(c == 3) {
for(int i = 1; i < paletteSize; ++i) {
palette[i] = readByte(is) | (readByte(is) << 8) | (readByte(is) << 16) | 0xFF000000;
}
}else {
for(int i = 1; i < paletteSize; ++i) {
palette[i] = readByte(is) | (readByte(is) << 8) | (readByte(is) << 16) | (readByte(is) << 24);
}
}
int bpp = is.read();
byte[] readSet = new byte[is.read() | (is.read() << 8) | (is.read() << 16)];
is.read(readSet);
int bpp = readByte(is);
byte[] readSet = new byte[readByte(is) | (readByte(is) << 8) | (readByte(is) << 16)];
IOUtils.readFully(is, readSet);
for(int i = 0, l = w * h; i < l; ++i) {
img.pixels[i] = palette[getFromBits(i * bpp, bpp, readSet)];
}
}else {
throw new IOException("Unknown EBP storage type: " + v);
}
if(is.read() != ':' || is.read() != '>') {
if(readByte(is) != ':' || readByte(is) != '>') {
throw new IOException("Invalid footer! (:>)");
}
return img;
}
public static ImageData loadTextureSafe(InputStream is, int alpha) throws IOException {
public static ImageData loadTextureSafe(InputStream is) throws IOException {
ImageData bufferedimage;
try {
bufferedimage = loadTexture(is, alpha);
bufferedimage = loadTexture(is);
} finally {
IOUtils.closeQuietly(is);
}

View File

@ -52,11 +52,13 @@ public class PBRTextureMapUtils {
if(res.getResourcePackName().equals(resourcePack)) {
ImageData toRet = TextureUtil.readBufferedImage(res.getInputStream());
if(ext.equals("_s")) {
for(int i = 0, j; i < toRet.pixels.length; ++i) {
// swap B and A, because labPBR support
int a = (toRet.pixels[i] >>> 24) & 0xFF;
for(int i = 0, j, a, b; i < toRet.pixels.length; ++i) {
j = toRet.pixels[i];
a = (j >>> 24) & 0xFF;
if(a == 0xFF) a = 0;
toRet.pixels[i] = (toRet.pixels[i] & 0x0000FFFF) | Math.min(a << 18, 0xFF0000) | 0xFF000000;
b = (((j >>> 16) & 0xFF) - 65) * 255 / 190;
if(b < 0) b = 0;
toRet.pixels[i] = (j & 0x0000FFFF) | Math.min(a << 18, 0xFF0000) | ((255 - b) << 24);
}
}
return toRet;
@ -74,7 +76,7 @@ public class PBRTextureMapUtils {
}catch(Throwable t) {
}
try {
return EaglerBitwisePackedTexture.loadTextureSafe(resMgr.getResource(new ResourceLocation("eagler:glsl/deferred/assets_pbr/" + fname + ".ebp")).getInputStream(), 255);
return EaglerBitwisePackedTexture.loadTextureSafe(resMgr.getResource(new ResourceLocation("eagler:glsl/deferred/assets_pbr/" + fname + ".ebp")).getInputStream());
}catch(Throwable t) {
// dead code because teavm
t.toString();

View File

@ -70,7 +70,7 @@ public class ClientV5MessageHandler extends ClientV4MessageHandler {
}
public void handleServer(SPacketClientStateFlagV5EAG packet) {
StateFlags.setFlag(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.state);
StateFlags.setFlag(netHandler, new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.state);
}
public void handleServer(SPacketDisplayWebViewURLV5EAG packet) {

View File

@ -17,6 +17,7 @@
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.minecraft.client.network.NetHandlerPlayClient;
public class StateFlags {
@ -29,18 +30,23 @@ public class StateFlags {
public static final EaglercraftUUID DISABLE_SKIN_URL_LOOKUP = new EaglercraftUUID(0xC41D641BE2DA4094L,
0xB1B2DFF2E9D08180L);
public static final EaglercraftUUID SET_MAX_MULTI_PACKET = new EaglercraftUUID(0x877BC5F5A2154DDBL,
0xB493BE790A763E90L);
public static boolean eaglerPlayerFlag = false;
public static boolean eaglerPlayerFlagSupervisor = false;
public static boolean disableSkinURLLookup = false;
public static void setFlag(EaglercraftUUID flag, int value) {
public static void setFlag(NetHandlerPlayClient handler, EaglercraftUUID flag, int value) {
if (flag.equals(EAGLER_PLAYER_FLAG_PRESENT)) {
eaglerPlayerFlag = (value & 1) != 0;
eaglerPlayerFlagSupervisor = (value & 2) != 0;
} else if (flag.equals(DISABLE_SKIN_URL_LOOKUP)) {
disableSkinURLLookup = value != 0;
} else if (flag.equals(SET_MAX_MULTI_PACKET)) {
handler.getEaglerMessageController().setMaxMultiPacket(value);
}
}

View File

@ -57,6 +57,9 @@ public class InjectedMessageController extends MessageController {
byteInputStreamSingleton.feedBuffer(data, offset);
inputStreamSingleton.readByte();
if(data[offset + 1] == (byte) 0xFF) {
if(inputStreamSingleton.available() > 32768) {
throw new IOException("Impossible large multi-packet received: " + inputStreamSingleton.available());
}
inputStreamSingleton.readByte();
int count = inputStreamSingleton.readVarInt();
for(int i = 0, j, k; i < count; ++i) {
@ -134,7 +137,7 @@ public class InjectedMessageController extends MessageController {
lastLen = GamePacketOutputBuffer.getVarIntSize(i) + i;
totalLen += lastLen;
++sendCount;
}while(totalLen < 32760 && sendCount < total - start);
}while(totalLen < 32760 && sendCount < total - start && sendCount < maxMultiPacket);
if(totalLen >= 32760) {
--sendCount;
totalLen -= lastLen;

View File

@ -138,7 +138,7 @@ public class LegacyMessageController extends MessageController {
lastLen = GamePacketOutputBuffer.getVarIntSize(i) + i;
totalLen += lastLen;
++sendCount;
}while(totalLen < 32760 && sendCount < total - start);
}while(totalLen < 32760 && sendCount < total - start && sendCount < maxMultiPacket);
if(totalLen >= 32760) {
--sendCount;
totalLen -= lastLen;

View File

@ -37,6 +37,7 @@ public abstract class MessageController {
protected final int sendDirection;
protected final int receiveDirection;
protected List<GameMessagePacket> sendQueue;
protected int maxMultiPacket = 64;
public MessageController(GamePluginMessageProtocol protocol, GameMessageHandler handler, int direction) {
this.protocol = protocol;
@ -57,6 +58,10 @@ public abstract class MessageController {
return sendQueue != null;
}
public void setMaxMultiPacket(int max) {
this.maxMultiPacket = max;
}
public void sendPacket(GameMessagePacket packet) {
if(sendQueue != null) {
sendQueue.add(packet);

View File

@ -227,9 +227,11 @@ public class PacketBufferInputWrapper implements GamePacketInputBuffer {
}
@Override
public byte[] readByteArrayMC() throws IOException {
public byte[] readByteArrayMC(int maxLen) throws IOException {
try {
return buffer.readByteArray();
return buffer.readByteArray(maxLen);
}catch(DecoderException ex) {
throw new IOException(ex.getMessage());
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}

View File

@ -18,6 +18,7 @@ package net.lax1dude.eaglercraft.v1_8.sp.server.export;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
@ -67,13 +68,13 @@ public class EPKDecompiler implements Closeable {
throw new IOException("Unknown or invalid EPK version: " + vers);
}
IOUtils.skipFully(is, is.read()); // skip filename
IOUtils.skipFully(is, loadByte(is)); // skip filename
IOUtils.skipFully(is, loadShort(is)); // skip comment
IOUtils.skipFully(is, 8); // skip millis date
numFiles = loadInt(is);
char compressionType = (char)is.read();
char compressionType = (char)loadByte(is);
switch(compressionType) {
case 'G':
@ -137,7 +138,7 @@ public class EPKDecompiler implements Closeable {
throw new IOException("File '" + name + "' has an invalid checksum");
}
if(zis.read() != ':') {
if(loadByte(zis) != ':') {
throw new IOException("File '" + name + "' is incomplete");
}
}else {
@ -145,7 +146,7 @@ public class EPKDecompiler implements Closeable {
IOUtils.readFully(zis, data);
}
if(zis.read() != '>') {
if(loadByte(zis) != '>') {
throw new IOException("Object '" + name + "' is incomplete");
}
@ -155,15 +156,23 @@ public class EPKDecompiler implements Closeable {
}
}
public static final int loadShort(InputStream is) throws IOException {
return (is.read() << 8) | is.read();
public static int loadByte(InputStream is) throws IOException {
int i = is.read();
if (i < 0) {
throw new EOFException();
}
return i;
}
public static final int loadInt(InputStream is) throws IOException {
return (is.read() << 24) | (is.read() << 16) | (is.read() << 8) | is.read();
public static int loadShort(InputStream is) throws IOException {
return (loadByte(is) << 8) | loadByte(is);
}
public static final String readASCII(byte[] bytesIn) throws IOException {
public static int loadInt(InputStream is) throws IOException {
return (loadByte(is) << 24) | (loadByte(is) << 16) | (loadByte(is) << 8) | loadByte(is);
}
public static String readASCII(byte[] bytesIn) throws IOException {
char[] charIn = new char[bytesIn.length];
for(int i = 0; i < bytesIn.length; ++i) {
charIn[i] = (char)((int)bytesIn[i] & 0xFF);
@ -171,11 +180,11 @@ public class EPKDecompiler implements Closeable {
return new String(charIn);
}
public static final String readASCII(InputStream bytesIn) throws IOException {
int len = bytesIn.read();
public static String readASCII(InputStream bytesIn) throws IOException {
int len = loadByte(bytesIn);
char[] charIn = new char[len];
for(int i = 0; i < len; ++i) {
charIn[i] = (char)(bytesIn.read() & 0xFF);
charIn[i] = (char)loadByte(bytesIn);
}
return new String(charIn);
}

View File

@ -34,7 +34,7 @@ public interface GamePacketInputBuffer extends DataInput {
String readStringEaglerASCII16() throws IOException;
byte[] readByteArrayMC() throws IOException;
byte[] readByteArrayMC(int maxLen) throws IOException;
int available() throws IOException;

View File

@ -53,7 +53,7 @@ public class CPacketWebViewMessageV4EAG implements GameMessagePacket {
@Override
public void readPacket(GamePacketInputBuffer buffer) throws IOException {
type = buffer.readUnsignedByte();
data = buffer.readByteArrayMC();
data = buffer.readByteArrayMC(32750);
}
@Override

View File

@ -53,7 +53,7 @@ public class SPacketWebViewMessageV4EAG implements GameMessagePacket {
@Override
public void readPacket(GamePacketInputBuffer buffer) throws IOException {
type = buffer.readUnsignedByte();
data = buffer.readByteArrayMC();
data = buffer.readByteArrayMC(32750);
}
@Override

View File

@ -171,8 +171,12 @@ public class SimpleInputBufferImpl extends DataInputStream implements GamePacket
}
@Override
public byte[] readByteArrayMC() throws IOException {
byte[] abyte = new byte[this.readVarInt()];
public byte[] readByteArrayMC(int maxLen) throws IOException {
int i = this.readVarInt();
if (i > maxLen) {
throw new IOException("Byte array is too long: " + i + " > " + maxLen);
}
byte[] abyte = new byte[i];
this.readFully(abyte);
return abyte;
}

View File

@ -1 +1 @@
u51
u52

View File

@ -64,5 +64,8 @@
"eaglerNoDelay": false,
"ramdiskMode": false,
"singleThreadMode": false,
"enableEPKVersionCheck": true
"enableEPKVersionCheck": true,
"enforceVSync": true,
"keepAliveHack": true,
"finishOnSwap": true
}

View File

@ -64,5 +64,8 @@
"eaglerNoDelay": false,
"ramdiskMode": false,
"singleThreadMode": false,
"enableEPKVersionCheck": true
"enableEPKVersionCheck": true,
"enforceVSync": true,
"keepAliveHack": true,
"finishOnSwap": true
}

View File

@ -64,5 +64,8 @@
"eaglerNoDelay": false,
"ramdiskMode": false,
"singleThreadMode": false,
"enableEPKVersionCheck": true
"enableEPKVersionCheck": true,
"enforceVSync": true,
"keepAliveHack": true,
"finishOnSwap": true
}

View File

@ -83,7 +83,6 @@ uniform sampler2D u_metalsLUT;
#define LIB_INCLUDE_PBR_LIGHTING_FUNCTION
#define LIB_INCLUDE_PBR_LIGHTING_PREFETCH
#EAGLER INCLUDE (3) "eagler:glsl/deferred/lib/pbr_lighting.glsl"
#EAGLER INCLUDE (4) "eagler:glsl/deferred/lib/branchless_comparison.glsl"
uniform sampler2D u_irradianceMap;
@ -147,7 +146,7 @@ void main() {
for(;;) {
shadowTexPos4f = u_sunShadowMatrixLOD04f * shadowWorldPos4f;
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy * vec2(1.0, SUN_SHADOW_MAP_FRAC), shadowTexPos4f.z + 0.0001), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy * vec2(1.0, SUN_SHADOW_MAP_FRAC), shadowTexPos4f.z + 0.0001));
break;
}
@ -156,7 +155,7 @@ void main() {
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowTexPos4f.y += 1.0;
shadowTexPos4f.y *= SUN_SHADOW_MAP_FRAC;
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015));
break;
}
#endif
@ -166,7 +165,7 @@ void main() {
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowTexPos4f.y += 2.0;
shadowTexPos4f.y *= SUN_SHADOW_MAP_FRAC;
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015));
}
#endif
break;
@ -176,7 +175,7 @@ void main() {
#ifdef DO_COMPILE_SUN_SHADOWS
lightColor3f *= shadowSample * skyLight;
#endif
vec3 normalWrap3f = normalVector3f * COMPARE_LT_C_C(dot(-worldDirection4f.xyz, normalVector3f), 0.0, -1.0, 1.0);
vec3 normalWrap3f = normalVector3f * (dot(-worldDirection4f.xyz, normalVector3f) < 0.0 ? -1.0 : 1.0);
lightColor3f = eaglercraftLighting(diffuseColor4f.rgb, lightColor3f, -worldDirection4f.xyz, u_sunDirection4f.xyz, normalWrap3f, materialData3f, metalN, metalK) * u_blockSkySunDynamicLightFac4f.z;
}
@ -195,8 +194,8 @@ void main() {
vec4 sample2 = textureLod(u_irradianceMap, irradianceMapSamplePos2f.xz * vec2(0.5, -0.25) + vec2(0.5, 0.75), 0.0);
skyLight += mix(sample1.rgb, sample2.rgb, smoothstep(0.0, 1.0, irradianceMapSamplePos2f.y * -12.5 + 0.5)).rgb;
}else {
irradianceMapSamplePos2f.xz *= vec2(0.5, COMPARE_GT_C_C(irradianceMapSamplePos2f.y, 0.0, 0.25, -0.25));
irradianceMapSamplePos2f.xz += vec2(0.5, COMPARE_GT_C_C(irradianceMapSamplePos2f.y, 0.0, 0.25, 0.75));
irradianceMapSamplePos2f.xz *= vec2(0.5, irradianceMapSamplePos2f.y > 0.0 ? 0.25 : -0.25);
irradianceMapSamplePos2f.xz += vec2(0.5, irradianceMapSamplePos2f.y > 0.0 ? 0.25 : 0.75);
skyLight += textureLod(u_irradianceMap, irradianceMapSamplePos2f.xz, 0.0).rgb;
}
skyLight *= lightmapCoords2f.g * u_sunColor3f_sky1f.w;
@ -211,7 +210,7 @@ void main() {
for(int i = 0; i < safeLightCount; ++i) {
dlightDist3f = worldPosition4f.xyz - u_dynamicLightArray[i].u_lightPosition4f.xyz;
dlightDir3f = normalize(dlightDist3f);
dlightDir3f = dlightDir3f * COMPARE_LT_C_C(dot(dlightDir3f, normalVector3f), 0.0, 1.0, -1.0);
dlightDir3f = dlightDir3f * (dot(dlightDir3f, normalVector3f) < 0.0 ? 1.0 : -1.0);
dlightDir3f = materialData3f.b == 1.0 ? normalVector3f : -dlightDir3f;
if(dot(dlightDir3f, normalVector3f) <= 0.0) {
continue;

View File

@ -1,3 +1,5 @@
#line 2
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
@ -14,22 +16,28 @@
*
*/
// Assuming modern GPUs probably implement clamp, max, and ciel without branches
// Tests for a D3D bug in ANGLE, this only became an issue recently...
// value1 > value2 ? 1.0 : 0.0
#define COMPARE_GT_0_1(value1, value2) clamp(ceil(value1 - value2), 0.0, 1.0)
precision highp float;
precision highp sampler2DShadow;
// value1 > value2 ? N : 0.0
#define COMPARE_GT_0_ANY(value1, value2) max(ceil(value1 - value2), 0.0)
uniform sampler2DShadow u_testSampler;
// value1 < value2 ? 1.0 : 0.0
#define COMPARE_LT_0_1(value1, value2) clamp(ceil(value2 - value1), 0.0, 1.0)
out vec4 fragOut4f;
// value1 < value2 ? N : 0.0
#define COMPARE_LT_0_ANY(value1, value2) max(ceil(value2 - value1), 0.0)
// value1 > value2 ? ifGT : ifLT
#define COMPARE_GT_C_C(value1, value2, ifGT, ifLT) (COMPARE_GT_0_1(value1, value2) * (ifGT - ifLT) + ifLT)
// value1 < value2 ? ifLT : ifGT
#define COMPARE_LT_C_C(value1, value2, ifLT, ifGT) (COMPARE_LT_0_1(value1, value2) * (ifLT - ifGT) + ifGT)
void main() {
float stupid = texture(u_testSampler, vec3(69.0, 69.0, 0.5));
for(;;) {
if (stupid != 69.0) {
stupid = textureLod(u_testSampler, vec3(420.0, 420.0, 0.69), 0.0);
break;
}
if (stupid != 420.0) {
stupid = textureLod(u_testSampler, vec3(69.0, 69.0, 0.420), 0.0);
break;
}
fragOut4f = vec4(0.0);
return;
}
fragOut4f = vec4(stupid);
}

View File

@ -73,15 +73,20 @@ void main() {
vec3 sunDirection = u_sunDirection3f * vec3(1.0, 2.0, 1.0) * 0.025;
float sunVisibility = sample0;
GET_CLOUDS((samplePos + sunDirection), sample1)
vec3 pos = samplePos + sunDirection;
GET_CLOUDS(pos, sample1)
sunVisibility += sample1;
GET_CLOUDS((samplePos + sunDirection * 2.0), sample1)
pos = samplePos + sunDirection * 2.0;
GET_CLOUDS(pos, sample1)
sunVisibility += sample1;
GET_CLOUDS((samplePos + sunDirection * 3.0), sample1)
pos = samplePos + sunDirection * 3.0;
GET_CLOUDS(pos, sample1)
sunVisibility += sample1;
GET_CLOUDS((samplePos + sunDirection * 4.0), sample1)
pos = samplePos + sunDirection * 4.0;
GET_CLOUDS(pos, sample1)
sunVisibility += sample1;
GET_CLOUDS((samplePos + sunDirection * 5.0), sample1)
pos = samplePos + sunDirection * 5.0;
GET_CLOUDS(pos, sample1)
sunVisibility += sample1;
sunVisibility = exp2(-sunVisibility * 50.0);

View File

@ -60,7 +60,6 @@ uniform float u_skyLightFactor1f;
#endif
#EAGLER INCLUDE (3) "eagler:glsl/deferred/lib/pbr_env_map.glsl"
#EAGLER INCLUDE (4) "eagler:glsl/deferred/lib/branchless_comparison.glsl"
void main() {
vec3 diffuseColor3f;
@ -87,7 +86,7 @@ void main() {
#ifdef COMPILE_GLOBAL_AMBIENT_OCCLUSION
vec4 ao = textureLod(u_ssaoTexture, min(v_position2f * u_halfResolutionPixelAlignment2f, 1.0), 0.0);
ao.g = mix(COMPARE_GT_0_1(0.0, ao.b), 1.0, ao.g);
ao.g = ao.b > 0.0 ? ao.g : 1.0;
shadow = mix(shadow, shadow * ao.g, 0.9);
#endif
@ -103,8 +102,8 @@ void main() {
vec4 sample2 = textureLod(u_irradianceMap, irradianceMapSamplePos2f.xz * vec2(0.5, -0.25) + vec2(0.5, 0.75), 0.0);
skyLight += mix(sample1.rgb, sample2.rgb, smoothstep(0.0, 1.0, irradianceMapSamplePos2f.y * -12.5 + 0.5)).rgb;
}else {
irradianceMapSamplePos2f.xz *= vec2(0.5, COMPARE_GT_C_C(irradianceMapSamplePos2f.y, 0.0, 0.25, -0.25));
irradianceMapSamplePos2f.xz += vec2(0.5, COMPARE_GT_C_C(irradianceMapSamplePos2f.y, 0.0, 0.25, 0.75));
irradianceMapSamplePos2f.xz *= vec2(0.5, irradianceMapSamplePos2f.y > 0.0 ? 0.25 : -0.25);
irradianceMapSamplePos2f.xz += vec2(0.5, irradianceMapSamplePos2f.y > 0.0 ? 0.25 : 0.75);
skyLight += textureLod(u_irradianceMap, irradianceMapSamplePos2f.xz, 0.0).rgb;
}
@ -115,8 +114,8 @@ void main() {
vec3 specular = vec3(0.0);
#ifdef COMPILE_ENV_MAP_REFLECTIONS
float f = COMPARE_LT_0_ANY(materialData4f.g, 0.06);
f += COMPARE_LT_0_ANY(materialData4f.r, 0.5);
float f = materialData4f.g < 0.06 ? 1.0 : 0.0;
f += materialData4f.r < 0.5 ? 1.0 : 0.0;
while((materialData4f.a >= 0.5 ? f : -1.0) == 0.0) {
vec4 worldPosition4f = vec4(v_position2f, depth, 1.0) * 2.0 - 1.0;
worldPosition4f = u_inverseProjMatrix4f * worldPosition4f;
@ -138,8 +137,8 @@ void main() {
vec4 sample2 = textureLod(u_environmentMap, reflectDir.xz * vec2(0.5, -0.25) + vec2(0.5, 0.75), 0.0);
envMapSample4f = vec4(mix(sample1.rgb, sample2.rgb, smoothstep(0.0, 1.0, reflectDir.y * -12.5 + 0.5)).rgb, min(sample1.a, sample2.a));
}else {
reflectDir.xz = reflectDir.xz * vec2(0.5, COMPARE_GT_C_C(reflectDir.y, 0.0, 0.25, -0.25));
reflectDir.xz += vec2(0.5, COMPARE_GT_C_C(reflectDir.y, 0.0, 0.25, 0.75));
reflectDir.xz = reflectDir.xz * vec2(0.5, reflectDir.y > 0.0 ? 0.25 : -0.25);
reflectDir.xz += vec2(0.5, reflectDir.y > 0.0 ? 0.25 : 0.75);
envMapSample4f = textureLod(u_environmentMap, reflectDir.xz, 0.0);
}
envMapSample4f.a += min(lightmapCoords2f.g * 2.0, 1.0) * (1.0 - envMapSample4f.a);
@ -153,8 +152,8 @@ void main() {
#ifdef COMPILE_SCREEN_SPACE_REFLECTIONS
#ifndef COMPILE_ENV_MAP_REFLECTIONS
float f = COMPARE_LT_0_ANY(materialData4f.g, 0.06);
f += COMPARE_LT_0_ANY(materialData4f.r, 0.5);
float f = materialData4f.g < 0.06 ? 1.0 : 0.0;
f += materialData4f.r < 0.5 ? 1.0 : 0.0;
if(f == 0.0) {
#else
if((materialData4f.a < 0.5 ? f : -1.0) == 0.0) {

View File

@ -162,7 +162,6 @@ uniform sampler2D u_lightShaftsTexture;
#endif
#EAGLER INCLUDE (4) "eagler:glsl/deferred/lib/pbr_env_map.glsl"
#EAGLER INCLUDE (4) "eagler:glsl/deferred/lib/branchless_comparison.glsl"
#ifdef DO_COMPILE_SUN_SHADOWS
uniform sampler2DShadow u_sunShadowDepthTexture;
@ -179,7 +178,7 @@ vec2(0.675, 0.682));
tmpVec2 = clamp(tmpVec2, vec2(0.001), vec2(0.999));\
tmpVec2.y += lod;\
tmpVec2.y *= SUN_SHADOW_MAP_FRAC;\
accum += textureLod(tex, vec3(tmpVec2, vec3Pos.z + 0.0001), 0.0);
accum += CMPLOD0_D3D_WORKAROUND(tex, vec3(tmpVec2, vec3Pos.z + 0.0001));
#endif
#endif
@ -308,7 +307,7 @@ void main() {
for(;;) {
shadowTexPos4f = u_sunShadowMatrixLOD04f * shadowWorldPos4f;
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy * vec2(1.0, SUN_SHADOW_MAP_FRAC), shadowTexPos4f.z), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy * vec2(1.0, SUN_SHADOW_MAP_FRAC), shadowTexPos4f.z));
#ifdef COMPILE_SUN_SHADOW_SMOOTH
SMOOTH_SHADOW_POISSON_SAMPLE(0, u_sunShadowDepthTexture, 0.0, shadowTexPos4f.xyz, shadowSample, tmpVec2)
SMOOTH_SHADOW_POISSON_SAMPLE(1, u_sunShadowDepthTexture, 0.0, shadowTexPos4f.xyz, shadowSample, tmpVec2)
@ -327,7 +326,7 @@ void main() {
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowTexPos4f.y += 1.0;
shadowTexPos4f.y *= SUN_SHADOW_MAP_FRAC;
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015));
break;
}
#endif
@ -337,7 +336,7 @@ void main() {
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowTexPos4f.y += 2.0;
shadowTexPos4f.y *= SUN_SHADOW_MAP_FRAC;
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015));
}
#endif
break;
@ -355,8 +354,8 @@ void main() {
// =========== ENVIRONMENT MAP =========== //
f = COMPARE_LT_0_ANY(materialData4f.g, 0.06);
f += COMPARE_LT_0_ANY(materialData4f.r, 0.5);
f = materialData4f.g < 0.06 ? 1.0 : 0.0;
f += materialData4f.r < 0.5 ? 1.0 : 0.0;
while(f == 0.0) {
float dst2 = dot(worldPosition4f.xyz, worldPosition4f.xyz);
if(dst2 > 25.0) {
@ -373,8 +372,8 @@ void main() {
vec4 sample2 = textureLod(u_environmentMap, reflectDir.xz * vec2(0.5, -0.25) + vec2(0.5, 0.75), 0.0);
envMapSample4f = vec4(mix(sample1.rgb, sample2.rgb, smoothstep(0.0, 1.0, reflectDir.y * -12.5 + 0.5)).rgb, min(sample1.a, sample2.a));
}else {
reflectDir.xz = reflectDir.xz * vec2(0.5, COMPARE_GT_C_C(reflectDir.y, 0.0, 0.25, -0.25));
reflectDir.xz += vec2(0.5, COMPARE_GT_C_C(reflectDir.y, 0.0, 0.25, 0.75));
reflectDir.xz = reflectDir.xz * vec2(0.5, reflectDir.y > 0.0 ? 0.25 : -0.25);
reflectDir.xz += vec2(0.5, reflectDir.y > 0.0 ? 0.25 : 0.75);
envMapSample4f = textureLod(u_environmentMap, reflectDir.xz, 0.0);
}
envMapSample4f.a += min(lightmapCoords2f.g * 2.0, 1.0) * (1.0 - envMapSample4f.a);
@ -401,8 +400,8 @@ void main() {
vec4 sample2 = textureLod(u_irradianceMap, irradianceMapSamplePos2f.xz * vec2(0.5, -0.25) + vec2(0.5, 0.75), 0.0);
skyLight += mix(sample1.rgb, sample2.rgb, smoothstep(0.0, 1.0, irradianceMapSamplePos2f.y * -12.5 + 0.5)).rgb;
}else {
irradianceMapSamplePos2f.xz *= vec2(0.5, COMPARE_GT_C_C(irradianceMapSamplePos2f.y, 0.0, 0.25, -0.25));
irradianceMapSamplePos2f.xz += vec2(0.5, COMPARE_GT_C_C(irradianceMapSamplePos2f.y, 0.0, 0.25, 0.75));
irradianceMapSamplePos2f.xz *= vec2(0.5, irradianceMapSamplePos2f.y > 0.0 ? 0.25 : -0.25);
irradianceMapSamplePos2f.xz += vec2(0.5, irradianceMapSamplePos2f.y > 0.0 ? 0.25 : 0.75);
skyLight += textureLod(u_irradianceMap, irradianceMapSamplePos2f.xz, 0.0).rgb;
}
skyLight *= lightmapCoords2f.g * u_sunColor3f_sky1f.w;
@ -437,7 +436,7 @@ void main() {
vec4 fogBlend4f = vec4(0.0);
#ifndef COMPILE_ENABLE_TEX_GEN
while(u_fogParameters4f.x > 0.0) {
float atmos = COMPARE_LT_C_C(u_fogParameters4f.x, 4.0, 0.0, 4.0);
float atmos = u_fogParameters4f.x >= 4.0 ? 4.0 : 0.0;
float type = u_fogParameters4f.x - atmos;
fogBlend4f = mix(u_fogColorLight4f, u_fogColorDark4f, lightmapCoords2f.g);

View File

@ -85,8 +85,6 @@ layout(std140) uniform u_worldLightingData {
uniform sampler2D u_environmentMap;
uniform sampler2D u_brdfLUT;
#EAGLER INCLUDE (4) "eagler:glsl/deferred/lib/branchless_comparison.glsl"
#define GLASS_ROUGHNESS 0.15
#define GLASS_F0 0.4
@ -135,7 +133,7 @@ vec2(0.675, 0.682));
tmpVec2 = clamp(tmpVec2, vec2(0.001), vec2(0.999));\
tmpVec2.y += lod;\
tmpVec2.y *= SUN_SHADOW_MAP_FRAC;\
accum += textureLod(tex, vec3(tmpVec2, vec3Pos.z + 0.0001), 0.0);
accum += CMPLOD0_D3D_WORKAROUND(tex, vec3(tmpVec2, vec3Pos.z + 0.0001));
#endif
#endif
@ -193,7 +191,7 @@ void main() {
for(;;) {
shadowTexPos4f = u_sunShadowMatrixLOD04f * shadowWorldPos4f;
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy * vec2(1.0, SUN_SHADOW_MAP_FRAC), shadowTexPos4f.z + 0.0001), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy * vec2(1.0, SUN_SHADOW_MAP_FRAC), shadowTexPos4f.z + 0.0001));
#ifdef COMPILE_SUN_SHADOW_SMOOTH
SMOOTH_SHADOW_POISSON_SAMPLE(0, u_sunShadowDepthTexture, 0.0, shadowTexPos4f.xyz, shadowSample, tmpVec2)
SMOOTH_SHADOW_POISSON_SAMPLE(1, u_sunShadowDepthTexture, 0.0, shadowTexPos4f.xyz, shadowSample, tmpVec2)
@ -212,7 +210,7 @@ void main() {
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowTexPos4f.y += 1.0;
shadowTexPos4f.y *= SUN_SHADOW_MAP_FRAC;
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015));
break;
}
#endif
@ -222,7 +220,7 @@ void main() {
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowTexPos4f.y += 2.0;
shadowTexPos4f.y *= SUN_SHADOW_MAP_FRAC;
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015));
}
#endif
break;
@ -253,8 +251,8 @@ void main() {
vec4 sample2 = textureLod(u_environmentMap, reflectDir.xz * vec2(0.5, -0.25) + vec2(0.5, 0.75), 0.0);
envMapSample4f = vec4(mix(sample1.rgb, sample2.rgb, smoothstep(0.0, 1.0, reflectDir.y * -12.5 + 0.5)).rgb, min(sample1.a, sample2.a));
}else {
reflectDir.xz = reflectDir.xz * vec2(0.5, COMPARE_GT_C_C(reflectDir.y, 0.0, 0.25, -0.25));
reflectDir.xz += vec2(0.5, COMPARE_GT_C_C(reflectDir.y, 0.0 , 0.25, 0.75));
reflectDir.xz = reflectDir.xz * vec2(0.5, reflectDir.y > 0.0 ? 0.25 : -0.25);
reflectDir.xz += vec2(0.5, reflectDir.y > 0.0 ? 0.25 : 0.75);
envMapSample4f = textureLod(u_environmentMap, reflectDir.xz, 0.0);
}
envMapSample4f.a += min(lightmapCoords2f.g * 2.0, 1.0) * (1.0 - envMapSample4f.a);
@ -292,7 +290,7 @@ void main() {
float fogFade = 0.0;
if(u_fogParameters4f.x > 0.0) {
float atmos = COMPARE_LT_C_C(u_fogParameters4f.x, 4.0, 0.0, 4.0);
float atmos = u_fogParameters4f.x >= 4.0 ? 4.0 : 0.0;
float type = u_fogParameters4f.x - atmos;
fogFade = mix(u_fogColorDark4f.a, u_fogColorLight4f.a, lightmapCoords2f.g);

View File

@ -108,7 +108,7 @@ vec2(0.675, 0.682));
tmpVec2 = clamp(tmpVec2, vec2(0.001), vec2(0.999));\
tmpVec2.y += lod;\
tmpVec2.y *= SUN_SHADOW_MAP_FRAC;\
accum += textureLod(tex, vec3(tmpVec2, vec3Pos.z + 0.0001), 0.0);
accum += CMPLOD0_D3D_WORKAROUND(tex, vec3(tmpVec2, vec3Pos.z + 0.0001));
#endif
#endif
@ -122,8 +122,6 @@ uniform sampler2D u_normalMap;
uniform vec4 u_waterWindOffset4f;
uniform vec3 u_wavingBlockOffset3f;
#EAGLER INCLUDE (4) "eagler:glsl/deferred/lib/branchless_comparison.glsl"
#define WATER_ROUGHNESS 0.05
#define WATER_F0 0.5
@ -305,7 +303,7 @@ void main() {
for(;;) {
shadowTexPos4f = u_sunShadowMatrixLOD04f * shadowWorldPos4f;
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy * vec2(1.0, SUN_SHADOW_MAP_FRAC), shadowTexPos4f.z + 0.0001), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy * vec2(1.0, SUN_SHADOW_MAP_FRAC), shadowTexPos4f.z + 0.0001));
#ifdef COMPILE_SUN_SHADOW_SMOOTH
SMOOTH_SHADOW_POISSON_SAMPLE(0, u_sunShadowDepthTexture, 0.0, shadowTexPos4f.xyz, shadowSample, tmpVec2)
SMOOTH_SHADOW_POISSON_SAMPLE(1, u_sunShadowDepthTexture, 0.0, shadowTexPos4f.xyz, shadowSample, tmpVec2)
@ -324,7 +322,7 @@ void main() {
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowTexPos4f.y += 1.0;
shadowTexPos4f.y *= SUN_SHADOW_MAP_FRAC;
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015));
break;
}
#endif
@ -334,7 +332,7 @@ void main() {
if(shadowTexPos4f.xyz == clamp(shadowTexPos4f.xyz, vec3(0.005), vec3(0.995))) {
shadowTexPos4f.y += 2.0;
shadowTexPos4f.y *= SUN_SHADOW_MAP_FRAC;
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowTexPos4f.xy, shadowTexPos4f.z + 0.00015));
}
#endif
break;
@ -352,8 +350,8 @@ void main() {
lightmapCoords2f *= lightmapCoords2f;
float e = 0.0;
e += COMPARE_GT_0_ANY(envMapSample4f.g, 0.0);
e += COMPARE_LT_0_ANY(lightmapCoords2f.y, 0.5);
e += envMapSample4f.g <= 0.0 ? 0.0 : 1.0;
e += lightmapCoords2f.y > 0.5 ? 0.0 : 1.0;
//e += abs(normalVector3f.y) > 0.1 ? 0.0 : 1.0;
if(e == 0.0) {
vec3 reflectDir = reflect(worldDirection4f.xyz, normalVector3f);
@ -366,8 +364,8 @@ void main() {
vec4 sample2 = textureLod(u_environmentMap, reflectDir.xz * vec2(0.5, -0.25) + vec2(0.5, 0.75), 0.0);
envMapSample4f = vec4(mix(sample1.rgb, sample2.rgb, smoothstep(0.0, 1.0, reflectDir.y * -12.5 + 0.5)).rgb, min(sample1.a, sample2.a));
}else {
reflectDir.xz *= vec2(0.5, COMPARE_GT_C_C(reflectDir.y, 0.0, 0.25, -0.25));
reflectDir.xz += vec2(0.5, COMPARE_GT_C_C(reflectDir.y, 0.0, 0.25, 0.75));
reflectDir.xz *= vec2(0.5, reflectDir.y > 0.0 ? 0.25 : -0.25);
reflectDir.xz += vec2(0.5, reflectDir.y > 0.0 ? 0.25 : 0.75);
envMapSample4f = textureLod(u_environmentMap, reflectDir.xz, 0.0);
}
envMapSample4f.rgb *= (lightmapCoords2f.y * 2.0 - 1.0);

View File

@ -84,8 +84,6 @@ uniform vec4 u_nearFarPlane4f;
uniform vec4 u_pixelAlignment4f;
#EAGLER INCLUDE (4) "eagler:glsl/deferred/lib/branchless_comparison.glsl"
#define reprojDepthLimit 0.25
#define GET_LINEAR_DEPTH_FROM_VALUE(depthSample) (u_nearFarPlane4f.z / (u_nearFarPlane4f.y + u_nearFarPlane4f.x + (depthSample * 2.0 - 1.0) * u_nearFarPlane4f.w))
@ -137,9 +135,9 @@ void main() {
#ifdef COMPILE_REPROJECT_SSR
vec4 materials = textureLod(u_gbufferMaterialTexture, v_position2f2, 0.0);
float f = COMPARE_LT_0_ANY(materials.g, 0.06);
f += COMPARE_LT_0_ANY(materials.r, 0.5);
f += COMPARE_GT_0_ANY(materials.a, 0.5);
float f = materials.g < 0.06 ? 1.0 : 0.0;
f += materials.r < 0.5 ? 1.0 : 0.0;
f += materials.a > 0.5 ? 1.0 : 0.0;
if(f > 0.0) {
return;
}

View File

@ -1,7 +1,7 @@
{
"name": "§eHigh Performance PBR",
"desc": "Pack made from scratch specifically for this client, designed to give what I call the best balance between quality and performance possible in a browser but obviously that's just my opinion",
"vers": "1.4.1",
"vers": "1.4.2",
"author": "lax1dude",
"api_vers": 1,
"features": [

View File

@ -67,7 +67,7 @@ vec2(0.675, 0.682));
tmpVec2 = clamp(tmpVec2, vec2(0.001), vec2(0.999));\
tmpVec2.y += lod;\
tmpVec2.y *= SUN_SHADOW_MAP_FRAC;\
accum += textureLod(tex, vec3(tmpVec2, vec3Pos.z + 0.0001), 0.0);
accum += CMPLOD0_D3D_WORKAROUND(tex, vec3(tmpVec2, vec3Pos.z + 0.0001));
#endif
uniform vec3 u_sunDirection3f;
@ -106,7 +106,7 @@ void main() {
for(;;) {
shadowSpacePosition = u_sunShadowMatrixLOD04f * worldSpacePosition;
if(shadowSpacePosition.xyz == clamp(shadowSpacePosition.xyz, vec3(0.005), vec3(0.995))) {
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowSpacePosition.xy * vec2(1.0, SUN_SHADOW_MAP_FRAC), shadowSpacePosition.z + 0.0001), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowSpacePosition.xy * vec2(1.0, SUN_SHADOW_MAP_FRAC), shadowSpacePosition.z + 0.0001));
#ifdef COMPILE_SUN_SHADOW_SMOOTH
SMOOTH_SHADOW_POISSON_SAMPLE(0, u_sunShadowDepthTexture, 0.0, shadowSpacePosition.xyz, shadowSample, tmpVec2)
SMOOTH_SHADOW_POISSON_SAMPLE(1, u_sunShadowDepthTexture, 0.0, shadowSpacePosition.xyz, shadowSample, tmpVec2)
@ -128,7 +128,7 @@ void main() {
if(shadowSpacePosition.xyz == clamp(shadowSpacePosition.xyz, vec3(0.005), vec3(0.995))) {
shadowSpacePosition.y += 1.0;
shadowSpacePosition.y *= SUN_SHADOW_MAP_FRAC;
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowSpacePosition.xy, shadowSpacePosition.z + 0.00015), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowSpacePosition.xy, shadowSpacePosition.z + 0.00015));
break;
}
#endif
@ -138,7 +138,7 @@ void main() {
if(shadowSpacePosition.xyz == clamp(shadowSpacePosition.xyz, vec3(0.005), vec3(0.995))) {
shadowSpacePosition.y += 2.0;
shadowSpacePosition.y *= SUN_SHADOW_MAP_FRAC;
shadowSample = textureLod(u_sunShadowDepthTexture, vec3(shadowSpacePosition.xy, shadowSpacePosition.z + 0.00015), 0.0);
shadowSample = CMPLOD0_D3D_WORKAROUND(u_sunShadowDepthTexture, vec3(shadowSpacePosition.xy, shadowSpacePosition.z + 0.00015));
break;
}
#endif

View File

@ -44,8 +44,6 @@ uniform sampler2D u_gbufferDepthTexture;
#define SKY_BRIGHTNESS 5.0
#EAGLER INCLUDE (4) "eagler:glsl/deferred/lib/branchless_comparison.glsl"
void main() {
gl_FragDepth = 0.0;
vec3 viewDir = normalize(v_position3f);
@ -55,7 +53,7 @@ void main() {
float f = max(dot(viewDir, u_sunDirection3f) - 0.995, 0.0) * 100.0;
float intensity = min(f * 2.0, 1.0);
intensity *= intensity * intensity * intensity * textureLod(u_sunOcclusion, vec2(0.5, 0.5), 0.0).r * 2.0;
intensity *= 1.0 - COMPARE_GT_0_1(textureLod(u_gbufferDepthTexture, (v_positionClip3f.xy / v_positionClip3f.z) * 0.5 + 0.5, 0.0).r, 0.0);
intensity *= step(textureLod(u_gbufferDepthTexture, (v_positionClip3f.xy / v_positionClip3f.z) * 0.5 + 0.5, 0.0).r, 0.0);
output4f = vec4(v_color3f * SKY_BRIGHTNESS + intensity * u_sunColor3f, 0.0);
#endif
#ifdef COMPILE_CLOUDS

View File

@ -33,8 +33,6 @@ uniform mat4 u_inverseProjectionMatrix4f;
uniform mat2 u_randomizerDataMatrix2f;
#EAGLER INCLUDE (4) "eagler:glsl/deferred/lib/branchless_comparison.glsl"
const vec3 ssaoKernel[8] = vec3[](
vec3(0.599,0.721,0.350),vec3(0.114,0.791,0.601),
vec3(0.067,0.995,0.069),vec3(0.511,-0.510,0.692),
@ -50,8 +48,8 @@ vec3(0.716,-0.439,0.543),vec3(-0.400,0.733,0.550));
tmpVec4_2.zw = matProjInv2f * vec4(tmpVec4_2.xy, textureLod(u_gbufferDepthTexture, tmpVec4_2.xy * 0.5 + 0.5, 0.0).r * 2.0 - 1.0, 1.0);\
tmpVec4_2.z /= tmpVec4_2.w;\
tmpVec4_2.x = smoothstep(0.0, 1.0, radius * 0.5 / abs(pos.z - tmpVec4_2.z));\
divisor += COMPARE_GT_0_1(tmpVec4_2.x, 0.0);\
occlusion += COMPARE_GT_0_1(tmpVec4_2.z, tmpVec4_1.z) * tmpVec4_2.x;
divisor += tmpVec4_2.x > 0.0 ? 1.0 : 0.0;\
occlusion += (tmpVec4_2.z >= tmpVec4_1.z ? 1.0 : 0.0) * tmpVec4_2.x;
void main() {
vec3 originalClipSpacePos = vec3(v_position2f, textureLod(u_gbufferDepthTexture, v_position2f, 0.0).r);

View File

@ -48,8 +48,6 @@ uniform mat4 u_sunShadowMatrixLOD24f;
#define SUN_SHADOW_MAP_FRAC 0.3333333
#endif
#EAGLER INCLUDE (4) "eagler:glsl/deferred/lib/branchless_comparison.glsl"
const vec2 POISSON_DISK[4] = vec2[](
vec2(0.998, -0.0438),
vec2(-0.345, -0.933),
@ -96,7 +94,7 @@ void main() {
return;
}
float material1f = textureLod(u_gbufferMaterialTexture, v_position2f, 0.0).a;
material1f = 2.0 * material1f - COMPARE_GT_0_1(material1f, 0.5);
material1f = 2.0 * material1f - step(0.5, material1f);
if(material1f < 0.05) {
return;
}

View File

@ -47,21 +47,17 @@ void main() {
discard;
}
vec4 light;
vec4 dlight;
float blockLight = v_lightmap2f.x;
float diffuse = 0.0;
float len;
if(u_dynamicLightCount1i > 0) {
vec4 worldPosition4f = u_inverseViewMatrix4f * v_position4f;
worldPosition4f.xyz /= worldPosition4f.w;
int safeLightCount = u_dynamicLightCount1i > 12 ? 0 : u_dynamicLightCount1i;
for(int i = 0; i < safeLightCount; ++i) {
light = u_dynamicLightArray[i];
light.xyz = light.xyz - worldPosition4f.xyz;
len = length(light.xyz);
diffuse += max(light.w - len, 0.0);
dlight = u_dynamicLightArray[i];
dlight.xyz = dlight.xyz - worldPosition4f.xyz;
blockLight = max((dlight.w - length(dlight.xyz)) * 0.066667, blockLight);
}
blockLight = min(blockLight + diffuse * 0.066667, 1.0);
}
color *= texture(u_lightmapTexture, vec2(blockLight, v_lightmap2f.y));

View File

@ -146,25 +146,21 @@ void main() {
#endif
#ifdef COMPILE_ENABLE_LIGHTMAP
float diffuse = 0.0;
#ifdef COMPILE_LIGHTMAP_ATTRIB
float blockLight = v_lightmap2f.x;
#else
float blockLight = u_textureCoords02.x;
#endif
float len;
vec4 light;
vec4 dlight;
if(u_dynamicLightCount1i > 0) {
vec4 worldPosition4f = u_inverseViewMatrix4f * v_position4f;
worldPosition4f.xyz /= worldPosition4f.w;
int safeLightCount = u_dynamicLightCount1i > 12 ? 0 : u_dynamicLightCount1i;
for(int i = 0; i < safeLightCount; ++i) {
light = u_dynamicLightArray[i];
light.xyz = light.xyz - worldPosition4f.xyz;
len = length(light.xyz);
diffuse += max(light.w - len, 0.0);
dlight = u_dynamicLightArray[i];
dlight.xyz = dlight.xyz - worldPosition4f.xyz;
blockLight = max((dlight.w - length(dlight.xyz)) * 0.066667, blockLight);
}
blockLight = min(blockLight + diffuse * 0.066667, 1.0);
}
#ifdef COMPILE_LIGHTMAP_ATTRIB
color *= texture(u_samplerLightmap, vec2(blockLight, v_lightmap2f.y));
@ -190,12 +186,8 @@ void main() {
#endif
#ifdef COMPILE_ENABLE_MC_LIGHTING
#ifndef COMPILE_ENABLE_LIGHTMAP
vec4 light;
float diffuse = 0.0;
#else
diffuse = 0.0;
#endif
for(int i = 0; i < u_lightsEnabled1i; ++i) {
light = u_lightsDirections4fv[i];
diffuse += max(dot(light.xyz, normal), 0.0) * light.w;

Binary file not shown.

View File

@ -40,6 +40,7 @@ dependencies {
}
tasks.withType<Jar> {
entryCompression = ZipEntryCompression.STORED
// TeaVM will fail if anything from platform-api is in the JAR
fileTree("src/platform-api/java").visit {
if (!isDirectory) {

View File

@ -5,7 +5,7 @@ import org.teavm.gradle.api.WasmDebugInfoLevel
plugins {
id("java")
id("org.teavm") version "0.12.0-EAGLER-R2"
id("org.teavm") version "0.12.1-EAGLER-R3"
id("com.resentclient.oss.eaglercraft.build") version "0.0.0"
}
@ -36,7 +36,7 @@ repositories {
dependencies {
teavm(teavm.libs.jso)
teavm(teavm.libs.jsoApis)
compileOnly("org.teavm:teavm-core:0.12.0-EAGLER-R2") // workaround for a few hacks
compileOnly("org.teavm:teavm-core:0.12.1-EAGLER-R3") // workaround for a few hacks
implementation(rootProject)
implementation(libs.jorbis)
implementation(libs.bundles.common)

View File

@ -1,8 +1,8 @@
client-version-integer=51
client-version-integer=52
client-package-name=net.lax1dude.eaglercraft.v1_8.client
client-origin-name=EaglercraftX
client-origin-version=u51
client-origin-version=u52
client-origin-vendor=lax1dude
client-fork-name=EaglercraftX
client-fork-version=u51
client-fork-version=u52
client-fork-vendor=lax1dude

View File

@ -27,6 +27,8 @@ 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.dom.html.HTMLAudioElement;
import org.teavm.jso.dom.html.HTMLSourceElement;
import org.teavm.jso.typedarrays.ArrayBuffer;
import org.teavm.jso.typedarrays.Int8Array;
import org.teavm.jso.webaudio.AudioBuffer;
@ -43,6 +45,7 @@ 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.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;
@ -244,6 +247,24 @@ public class PlatformAudio {
PlatformInput.clearEventBuffers();
if(((TeaVMClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isKeepAliveHackTeaVM()) {
byte[] silenceFile = PlatformAssets.getResourceBytes("/assets/eagler/silence_loop.wav");
if (silenceFile != null) {
HTMLAudioElement audio = (HTMLAudioElement) PlatformRuntime.doc.createElement("audio");
audio.getClassList().add("_eaglercraftX_keepalive_hack");
audio.setAttribute("style", "display:none;");
audio.setAutoplay(true);
audio.setLoop(true);
HTMLSourceElement source = (HTMLSourceElement) PlatformRuntime.doc.createElement("source");
source.setType("audio/wav");
source.setSrc(TeaVMBlobURLManager.registerNewURLByte(silenceFile, "audio/wav").toExternalForm());
audio.appendChild(source);
audio.addEventListener("seeked", (e) -> {
// NOP, wakes up the browser's event loop
});
PlatformRuntime.parent.appendChild(audio);
}
}
}
@JSBody(params = { "ctx" }, script = "var tmpBuf = ctx.createBuffer(2, 16, 16000); return (typeof tmpBuf.copyToChannel === \"function\");")

View File

@ -58,6 +58,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.teavm.InputEvent;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.LegacyKeycodeTranslator;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.OffsetTouch;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.SortedTouchEvent;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMClientConfigAdapter;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.WebGLBackBuffer;
public class PlatformInput {
@ -241,14 +242,10 @@ public class PlatformInput {
static boolean vsync = true;
static boolean vsyncSupport = false;
static boolean finish = true;
private static long vsyncWaiting = -1l;
private static AsyncCallback<Void> vsyncAsyncCallback = null;
private static int vsyncTimeout = -1;
// hack to fix occasional freeze on iOS
private static int vsyncSaveLockInterval = -1;
@JSFunctor
private static interface UnloadCallback extends JSObject {
void call();
@ -723,8 +720,6 @@ public class PlatformInput {
}catch(Throwable t) {
}
vsyncWaiting = -1l;
vsyncAsyncCallback = null;
vsyncTimeout = -1;
vsyncSupport = false;
@ -735,39 +730,7 @@ public class PlatformInput {
PlatformRuntime.logger.error("VSync is not supported on this browser!");
}
if(vsyncSupport) {
if(vsyncSaveLockInterval != -1) {
try {
Window.clearInterval(vsyncSaveLockInterval);
}catch(Throwable t) {
}
vsyncSaveLockInterval = -1;
}
// fix for iOS freezing randomly...?
vsyncSaveLockInterval = Window.setInterval(() -> {
if(vsyncWaiting != -1l) {
long steadyTime = PlatformRuntime.steadyTimeMillis();
if(steadyTime - vsyncWaiting > 1000) {
PlatformRuntime.logger.error("VSync lockup detected! Attempting to recover...");
vsyncWaiting = -1l;
if(vsyncTimeout != -1) {
try {
Window.clearTimeout(vsyncTimeout);
}catch(Throwable t) {
}
vsyncTimeout = -1;
}
if(vsyncAsyncCallback != null) {
AsyncCallback<Void> cb = vsyncAsyncCallback;
vsyncAsyncCallback = null;
cb.complete(null);
}else {
PlatformRuntime.logger.error("Async callback is null!");
}
}
}
}, 1000);
}
finish = ((TeaVMClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isFinishOnSwapTeaVM();
try {
gamepadSupported = gamepadSupported();
@ -968,6 +931,9 @@ public class PlatformInput {
syncTimer = 0.0;
asyncRequestAnimationFrame();
}else {
if(finish) {
PlatformOpenGL.ctx.finish();
}
if(fpsLimit <= 0 || fpsLimit > 1000) {
syncTimer = 0.0;
PlatformRuntime.swapDelayTeaVM();
@ -1010,38 +976,27 @@ public class PlatformInput {
private static native void asyncRequestAnimationFrame();
private static void asyncRequestAnimationFrame(AsyncCallback<Void> cb) {
if(vsyncWaiting != -1l) {
if(vsyncTimeout != -1) {
cb.error(new IllegalStateException("Already waiting for vsync!"));
return;
}
vsyncWaiting = PlatformRuntime.steadyTimeMillis();
vsyncAsyncCallback = cb;
final boolean[] hasTimedOut = new boolean[] { false };
final int[] timeout = new int[] { -1 };
Window.requestAnimationFrame((d) -> {
if(!hasTimedOut[0]) {
hasTimedOut[0] = true;
if(vsyncWaiting != -1l) {
vsyncWaiting = -1l;
if(vsyncTimeout != -1 && vsyncTimeout == timeout[0]) {
try {
Window.clearTimeout(vsyncTimeout);
}catch(Throwable t) {
}
vsyncTimeout = -1;
}
vsyncAsyncCallback = null;
cb.complete(null);
if(vsyncTimeout != -1) {
Window.clearTimeout(vsyncTimeout);
vsyncTimeout = -1;
}
cb.complete(null);
}
});
vsyncTimeout = timeout[0] = Window.setTimeout(() -> {
if(!hasTimedOut[0]) {
hasTimedOut[0] = true;
if(vsyncWaiting != -1l) {
if(vsyncTimeout != -1) {
vsyncTimeout = -1;
vsyncWaiting = -1l;
vsyncAsyncCallback = null;
cb.complete(null);
}
}
@ -1578,13 +1533,6 @@ public class PlatformInput {
Window.clearTimeout(mouseUngrabTimeout);
mouseUngrabTimeout = -1;
}
if(vsyncSaveLockInterval != -1) {
try {
Window.clearInterval(vsyncSaveLockInterval);
}catch(Throwable t) {
}
vsyncSaveLockInterval = -1;
}
if(touchKeyboardField != null) {
touchKeyboardField.blur();
if(parent != null) {

View File

@ -32,6 +32,7 @@ import org.teavm.jso.browser.Window;
import org.teavm.jso.core.JSArrayReader;
import org.teavm.jso.core.JSError;
import org.teavm.jso.dom.css.CSSStyleDeclaration;
import org.teavm.jso.dom.html.HTMLButtonElement;
import org.teavm.jso.dom.html.HTMLCanvasElement;
import org.teavm.jso.dom.html.HTMLDocument;
import org.teavm.jso.dom.html.HTMLElement;
@ -292,6 +293,46 @@ public class ClientMain {
}
}
private static HTMLElement createToolButtons(HTMLDocument doc) {
HTMLButtonElement buttonResetSettings = (HTMLButtonElement) doc.createElement("button");
buttonResetSettings.setAttribute("style", "margin-left:10px;");
buttonResetSettings.setInnerText("Reset Settings");
buttonResetSettings.addEventListener("click", (evt) -> {
boolean y = false;
if (Window.confirm("Do you want to reset client settings?")) {
PlatformApplication.setLocalStorage("g", null);
PlatformApplication.setLocalStorage("p", null);
y = true;
}
if (Window.confirm("Do you want to reset servers and relays?")) {
PlatformApplication.setLocalStorage("r", null);
PlatformApplication.setLocalStorage("s", null);
y = true;
}
if (y) {
Window.alert("Settings reset.");
}
});
HTMLButtonElement buttonOpenConsole = (HTMLButtonElement) doc.createElement("button");
buttonOpenConsole.setAttribute("style", "margin-left:10px;");
buttonOpenConsole.setInnerText("Open Debug Console");
buttonOpenConsole.addEventListener("click", (evt) -> {
DebugConsoleWindow.showDebugConsole();
});
HTMLElement div1 = doc.createElement("div");
div1.setAttribute("style", "position:absolute;bottom:5px;right:0px;");
div1.appendChild(buttonResetSettings);
div1.appendChild(buttonOpenConsole);
HTMLElement div2 = doc.createElement("div");
div2.setAttribute("style", "position:relative;");
div2.appendChild(div1);
HTMLElement div3 = doc.createElement("div");
div3.getClassList().add("_eaglercraftX_crash_tools_element");
div3.setAttribute("style", "z-index:101;position:absolute;top:135px;left:10%;right:10%;height:0px;");
div3.appendChild(div2);
return div3;
}
public static void showCrashScreen(String message, Throwable t) {
try {
showCrashScreen(message + "\n\n" + EagRuntime.getStackTrace(t));
@ -410,6 +451,7 @@ public class ClientMain {
div.getClassList().add("_eaglercraftX_crash_element");
el.appendChild(img);
el.appendChild(div);
el.appendChild(createToolButtons(doc));
div.appendChild(doc.createTextNode(strFinal));
PlatformRuntime.removeEventHandlers();
@ -581,6 +623,7 @@ public class ClientMain {
div.getClassList().add("_eaglercraftX_incompatible_element");
el.appendChild(img);
el.appendChild(div);
el.appendChild(createToolButtons(doc));
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&ensp;:(</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=\"_eaglercraftX_crashReason\"></span><br /></p>"
@ -664,14 +707,17 @@ public class ClientMain {
div.getClassList().add("_eaglercraftX_context_lost_element");
el.appendChild(img);
el.appendChild(div);
el.appendChild(createToolButtons(doc));
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> + WebGL context lost!</h2>"
+ "<div style=\"margin-left:40px;\">"
+ "<p style=\"font-size:1.2em;\">Your browser has forcibly released all of the resources "
+ "allocated by the game's 3D rendering context. EaglercraftX cannot continue, please refresh "
+ "the page to restart the game.</p>"
+ "the page to restart the game, sorry for the inconvenience.</p>"
+ "<p style=\"font-size:1.2em;\">This is not a bug, it is usually caused by the browser "
+ "deciding it no longer has sufficient resources to continue rendering this page. If it "
+ "happens again, try closing your other browser tabs and windows.</p>"
+ "<p style=\"font-size:1.2em;\">If you're playing with vsync disabled, try enabling vsync "
+ "to allow the browser to control the GPU usage more precisely.</p>"
+ "<p style=\"overflow-wrap:break-word;white-space:pre-wrap;font:0.75em monospace;margin-top:1.5em;\" id=\"_eaglercraftX_contextLostTrace\"></p>"
+ "</div>");

View File

@ -16,6 +16,7 @@
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
@ -27,13 +28,15 @@ import com.jcraft.jzlib.CRC32;
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.InflaterInputStream;
import net.lax1dude.eaglercraft.v1_8.IOUtils;
public class EPKLoader {
public static final void loadEPK(ArrayBuffer epkFile, Map<String, byte[]> loadedFiles) throws IOException {
public static void loadEPK(ArrayBuffer epkFile, Map<String, byte[]> loadedFiles) throws IOException {
loadEPK(epkFile, "", loadedFiles);
}
public static final void loadEPK(ArrayBuffer epkFile, String path, Map<String, byte[]> loadedFiles) throws IOException {
public static void loadEPK(ArrayBuffer epkFile, String path, Map<String, byte[]> loadedFiles) throws IOException {
int byteLength = epkFile.getByteLength();
int l = byteLength - 16;
if(l < 1) {
@ -43,7 +46,7 @@ public class EPKLoader {
ArrayBufferInputStream is = new ArrayBufferInputStream(epkFile, 0, byteLength - 8);
byte[] header = new byte[8];
is.read(header);
IOUtils.readFully(is, header);
String type = readASCII(header);
if(!"EAGPKG$$".equals(type)) {
@ -65,13 +68,13 @@ public class EPKLoader {
throw new IOException("Unknown or invalid EPK version: " + vers);
}
is.skip(is.read()); // skip filename
is.skip(loadShort(is)); // skip comment
is.skip(8); // skip millis date
IOUtils.skipFully(is, loadByte(is)); // skip filename
IOUtils.skipFully(is, loadShort(is)); // skip comment
IOUtils.skipFully(is, 8); // skip millis date
int numFiles = loadInt(is);
char compressionType = (char)is.read();
char compressionType = (char)loadByte(is);
InputStream zis;
switch(compressionType) {
@ -112,11 +115,11 @@ public class EPKLoader {
if(i == 0) {
if(blockType == blockHead) {
byte[] readType = new byte[len];
zis.read(readType);
IOUtils.readFully(zis, readType);
if(!"file-type".equals(name) || !"epk/resources".equals(readASCII(readType))) {
throw new IOException("EPK is not of file-type 'epk/resources'!");
}
if(zis.read() != '>') {
if(loadByte(zis) != '>') {
throw new IOException("Object '" + name + "' is incomplete");
}
continue;
@ -133,7 +136,7 @@ public class EPKLoader {
int expectedCRC = loadInt(zis);
byte[] load = new byte[len - 5];
zis.read(load);
IOUtils.readFully(zis, load);
if(len > 5) {
crc32.reset();
@ -143,16 +146,16 @@ public class EPKLoader {
}
}
if(zis.read() != ':') {
if(loadByte(zis) != ':') {
throw new IOException("File '" + name + "' is incomplete");
}
loadedFiles.put(path + name, load);
}else {
zis.skip(len);
IOUtils.skipFully(zis, len);
}
if(zis.read() != '>') {
if(loadByte(zis) != '>') {
throw new IOException("Object '" + name + "' is incomplete");
}
}
@ -164,15 +167,23 @@ public class EPKLoader {
zis.close();
}
private static final int loadShort(InputStream is) throws IOException {
return (is.read() << 8) | is.read();
private static int loadByte(InputStream is) throws IOException {
int i = is.read();
if (i < 0) {
throw new EOFException();
}
return i;
}
private static final int loadInt(InputStream is) throws IOException {
return (is.read() << 24) | (is.read() << 16) | (is.read() << 8) | is.read();
private static int loadShort(InputStream is) throws IOException {
return (loadByte(is) << 8) | loadByte(is);
}
private static final String readASCII(byte[] bytesIn) throws IOException {
private static int loadInt(InputStream is) throws IOException {
return (loadByte(is) << 24) | (loadByte(is) << 16) | (loadByte(is) << 8) | loadByte(is);
}
private static String readASCII(byte[] bytesIn) throws IOException {
char[] charIn = new char[bytesIn.length];
for(int i = 0; i < bytesIn.length; ++i) {
charIn[i] = (char)((int)bytesIn[i] & 0xFF);
@ -180,11 +191,11 @@ public class EPKLoader {
return new String(charIn);
}
private static final String readASCII(InputStream bytesIn) throws IOException {
int len = bytesIn.read();
private static String readASCII(InputStream bytesIn) throws IOException {
int len = loadByte(bytesIn);
char[] charIn = new char[len];
for(int i = 0; i < len; ++i) {
charIn[i] = (char)(bytesIn.read() & 0xFF);
charIn[i] = (char)loadByte(bytesIn);
}
return new String(charIn);
}

View File

@ -67,9 +67,6 @@ public class PCMToWAVLoader {
if (val < -32768) {
val = -32768;
}
if (val < 0) {
val |= 32768;
}
bufferOut.putShort((short)val);
}
}

View File

@ -90,6 +90,8 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter, IBootMenu
private boolean ramdiskMode = false;
private boolean singleThreadMode = false;
private boolean enableEPKVersionCheck = true;
private boolean keepAliveHack = true;
private boolean finishOnSwap = true;
public void loadNative(JSObject jsObject) {
integratedServerOpts = new JSONObject();
@ -139,6 +141,8 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter, IBootMenu
ramdiskMode = eaglercraftXOpts.getRamdiskMode(false);
singleThreadMode = eaglercraftXOpts.getSingleThreadMode(false);
enableEPKVersionCheck = eaglercraftXOpts.getEnableEPKVersionCheck(true);
keepAliveHack = eaglercraftXOpts.getKeepAliveHack(true);
finishOnSwap = eaglercraftXOpts.getFinishOnSwap(true);
JSEaglercraftXOptsHooks hooksObj = eaglercraftXOpts.getHooks();
if(hooksObj != null) {
hooks.loadHooks(hooksObj);
@ -270,6 +274,8 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter, IBootMenu
ramdiskMode = eaglercraftOpts.optBoolean("ramdiskMode", false);
singleThreadMode = eaglercraftOpts.optBoolean("singleThreadMode", false);
enableEPKVersionCheck = eaglercraftOpts.optBoolean("enableEPKVersionCheck", true);
keepAliveHack = eaglercraftOpts.optBoolean("keepAliveHack", true);
finishOnSwap = eaglercraftOpts.optBoolean("finishOnSwap", true);
defaultServers.clear();
JSONArray serversArray = eaglercraftOpts.optJSONArray("servers");
if(serversArray != null) {
@ -556,6 +562,14 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter, IBootMenu
return false;
}
public boolean isKeepAliveHackTeaVM() {
return keepAliveHack;
}
public boolean isFinishOnSwapTeaVM() {
return finishOnSwap;
}
@Override
public IClientConfigAdapterHooks getHooks() {
return hooks;
@ -608,6 +622,8 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter, IBootMenu
jsonObject.put("ramdiskMode", ramdiskMode);
jsonObject.put("singleThreadMode", singleThreadMode);
jsonObject.put("enableEPKVersionCheck", enableEPKVersionCheck);
jsonObject.put("keepAliveHack", keepAliveHack);
jsonObject.put("finishOnSwap", finishOnSwap);
JSONArray serversArr = new JSONArray();
for(int i = 0, l = defaultServers.size(); i < l; ++i) {
DefaultServer srv = defaultServers.get(i);

View File

@ -149,10 +149,14 @@ public class TeaVMClientConfigAdapterHooks implements IClientConfigAdapterHooks
}
public void loadHooks(JSEaglercraftXOptsHooks hooks) {
saveHook = (LocalStorageSaveHook)hooks.getLocalStorageSavedHook();
loadHook = (LocalStorageLoadHook)hooks.getLocalStorageLoadedHook();
crashHook = (CrashReportHook)hooks.getCrashReportHook();
screenChangedHook = (ScreenChangeHook)hooks.getScreenChangedHook();
JSObject obj = hooks.getLocalStorageSavedHook();
saveHook = obj != null ? (LocalStorageSaveHook) obj : null;
obj = hooks.getLocalStorageLoadedHook();
loadHook = obj != null ? (LocalStorageLoadHook) obj : null;
obj = hooks.getCrashReportHook();
crashHook = obj != null ? (CrashReportHook) obj : null;
obj = hooks.getScreenChangedHook();
screenChangedHook = obj != null ? (ScreenChangeHook) obj : null;
}
}

View File

@ -178,4 +178,10 @@ public abstract class JSEaglercraftXOptsRoot implements JSObject {
@JSBody(params = { "def" }, script = "return (typeof this.enableEPKVersionCheck === \"boolean\") ? this.enableEPKVersionCheck : def;")
public native boolean getEnableEPKVersionCheck(boolean deobfStackTraces);
@JSBody(params = { "def" }, script = "return (typeof this.keepAliveHack === \"boolean\") ? this.keepAliveHack : def;")
public native boolean getKeepAliveHack(boolean keepAliveHack);
@JSBody(params = { "def" }, script = "return (typeof this.finishOnSwap === \"boolean\") ? this.finishOnSwap : def;")
public native boolean getFinishOnSwap(boolean finishOnSwap);
}

View File

@ -24,6 +24,7 @@ import java.util.Date;
import org.teavm.interop.Import;
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.Storage;
@ -43,6 +44,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.buffer.MemoryStack;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.ClientMain;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.TeaVMUtils;
public class PlatformApplication {
@ -174,7 +176,23 @@ public class PlatformApplication {
}
}
public static final void displayFileChooser(String mime, String ext) {
public static void setResetSettingsCallbackWASM() {
setResetSettingsCallbackWASM0().call(ClientMain::resetSettings);
}
@JSFunctor
private static interface JSWASMResetSettingsCallback extends JSObject {
void callback();
}
private static interface JSWASMResetSettingsCallbackInterface extends JSObject {
void call(JSWASMResetSettingsCallback callback);
}
@Import(module = "platformApplication", name = "setResetSettingsCallback")
private static native JSWASMResetSettingsCallbackInterface setResetSettingsCallbackWASM0();
public static void displayFileChooser(String mime, String ext) {
displayFileChooser0(BetterJSStringConverter.stringToJS(mime), BetterJSStringConverter.stringToJS(ext));
}

View File

@ -94,11 +94,26 @@ public class PlatformAudio {
logger.error("OGG file support detected as false! Using embedded JOrbis OGG decoder");
}
}
if(((WASMGCClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isKeepAliveHackTeaVM()) {
byte[] silenceFile = PlatformAssets.getResourceBytes("/assets/eagler/silence_loop.wav");
if (silenceFile != null) {
MemoryStack.push();
try {
initKeepAliveHack(WASMGCDirectArrayConverter.byteArrayToStackU8Array(silenceFile));
}finally {
MemoryStack.pop();
}
}
}
}
@Import(module = "platformAudio", name = "getContext")
private static native AudioContext getContext();
@Import(module = "platformAudio", name = "initKeepAliveHack")
private static native void initKeepAliveHack(Uint8Array array);
protected static class BrowserAudioResource implements IAudioResource {
protected AudioBuffer buffer;
@ -356,11 +371,8 @@ public class PlatformAudio {
return audioctx != null;
}
@JSBody(params = { "node" }, script = "node.distanceModel = \"linear\";")
static native void setDistanceModelLinearFast(PannerNode node) ;
@JSBody(params = { "node" }, script = "node.panningModel = \"HRTF\";")
static native void setPanningModelHRTFFast(PannerNode node) ;
@Import(module = "platformAudio", name = "setupPanner")
static native void setupPanner(PannerNode node, float maxDist, float x, float y, float z);
public static IAudioHandle beginPlayback(IAudioResource track, float x, float y, float z,
float volume, float pitch, boolean repeat) {
@ -373,17 +385,10 @@ public class PlatformAudio {
src.setLoop(repeat);
PannerNode panner = audioctx.createPanner();
panner.setPosition(x, y, z);
float v1 = volume * 16.0f;
if(v1 < 16.0f) v1 = 16.0f;
panner.setMaxDistance(v1);
panner.setRolloffFactor(1.0f);
setDistanceModelLinearFast(panner);
setPanningModelHRTFFast(panner);
panner.setConeInnerAngle(360.0f);
panner.setConeOuterAngle(0.0f);
panner.setConeOuterGain(0.0f);
panner.setOrientation(0.0f, 1.0f, 0.0f);
setupPanner(panner, v1, x, y, z);
GainNode gain = audioctx.createGain();
float v2 = volume;

View File

@ -113,6 +113,7 @@ public class PlatformInput {
static boolean vsync = true;
static boolean vsyncSupport = false;
static boolean finish = true;
private static Map<String, LegacyKeycodeTranslator.LegacyKeycode> keyCodeTranslatorMap = null;
@ -125,6 +126,7 @@ public class PlatformInput {
fullscreenSupported = supportsFullscreen0();
vsyncSupport = isVSyncSupported0();
WASMGCClientConfigAdapter conf = (WASMGCClientConfigAdapter)PlatformRuntime.getClientConfigAdapter();
finish = conf.isFinishOnSwapTeaVM();
useVisualViewport = conf.isUseVisualViewportTeaVM() && isVisualViewport0();
lastWasResizedWindowWidth = -2;
lastWasResizedWindowHeight = -2;
@ -656,7 +658,7 @@ public class PlatformInput {
PlatformScreenRecord.captureFrameHook();
}
updatePlatformAndSleep(fpsLimit, vsync && vsyncSupport);// && vsync
updatePlatformAndSleep(fpsLimit, vsync && vsyncSupport, finish);
PlatformRuntime.pollJSEventsAfterSleep();
}
@ -664,7 +666,7 @@ public class PlatformInput {
private static native void updateCanvasSize(int width, int height);
@Import(module = "platformInput", name = "updatePlatformAndSleep")
private static native void updatePlatformAndSleep(int fpsLimit, boolean vsync);
private static native void updatePlatformAndSleep(int fpsLimit, boolean vsync, boolean finish);
public static boolean isVSyncSupported() {
return vsyncSupport;

View File

@ -79,6 +79,7 @@ public class PlatformRuntime {
canvas = getCanvasElement();
printMemoryStackAddrWASMGC();
PlatformApplication.setMCServerWindowGlobal(null);
PlatformApplication.setResetSettingsCallbackWASM();
PlatformOpenGL.initContext();
PlatformInput.initContext(win, parent, canvas);

View File

@ -215,16 +215,8 @@ public class PlatformVoiceClient {
audioNode.disconnect(gain);
}
panner = PlatformAudio.audioctx.createPanner();
panner.setRolloffFactor(1f);
PlatformAudio.setDistanceModelLinearFast(panner);
PlatformAudio.setPanningModelHRTFFast(panner);
panner.setConeInnerAngle(360f);
panner.setConeOuterAngle(0f);
panner.setConeOuterGain(0f);
panner.setOrientation(0f, 1f, 0f);
panner.setPosition(0, 0, 0);
float vol = VoiceClientController.getVoiceListenVolume();
panner.setMaxDistance(vol * 2 * VoiceClientController.getVoiceProximity() + 0.1f);
PlatformAudio.setupPanner(panner, vol * 2 * VoiceClientController.getVoiceProximity() + 0.1f, 0, 0, 0);
gain = PlatformAudio.audioctx.createGain();
gain.getGain().setValue(vol);
audioNode.connect(gain);

View File

@ -30,22 +30,26 @@ public class BetterJSStringConverter {
@Unmanaged
public static JSString stringToJS(String input) {
if (input == null) return null;
return (JSString) JS.wrap(input);
}
@Unmanaged
@SuppressWarnings("unchecked")
public static JSArray<JSString> stringArrayToJS(String[] input) {
if (input == null) return null;
return (JSArray<JSString>) JS.wrap(input);
}
@Unmanaged
public static String stringFromJS(JSString input) {
if (input == null) return null;
return JS.unwrapString(input);
}
@Unmanaged
public static String[] stringArrayFromJS(JSArray<JSString> input) {
if (input == null) return null;
return JS.unwrapStringArray(input);
}

View File

@ -24,6 +24,7 @@ import org.teavm.jso.browser.Window;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.ContextLostError;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts.JSEaglercraftXOptsRoot;
import net.minecraft.client.main.Main;
@ -98,6 +99,25 @@ public class ClientMain {
}
}
/**
* Defined here to match the JS runtime
*/
public static void resetSettings() {
boolean y = false;
if (Window.confirm("Do you want to reset client settings?")) {
PlatformApplication.setLocalStorage("g", null);
PlatformApplication.setLocalStorage("p", null);
y = true;
}
if (Window.confirm("Do you want to reset servers and relays?")) {
PlatformApplication.setLocalStorage("r", null);
PlatformApplication.setLocalStorage("s", null);
y = true;
}
if (y) {
Window.alert("Settings reset.");
}
}
@Import(module = "platformRuntime", name = "getEaglercraftXOpts")
private static native JSObject getEaglerXOpts();

View File

@ -16,6 +16,7 @@
package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
@ -24,16 +25,17 @@ import com.jcraft.jzlib.CRC32;
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.InflaterInputStream;
import net.lax1dude.eaglercraft.v1_8.IOUtils;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.EaglerBufferInputStream;
public class EPKLoader {
public static final void loadEPK(ByteBuffer epkFile, Map<String, byte[]> loadedFiles) throws IOException {
public static void loadEPK(ByteBuffer epkFile, Map<String, byte[]> loadedFiles) throws IOException {
loadEPK(epkFile, "", loadedFiles);
}
public static final void loadEPK(ByteBuffer epkFile, String path, Map<String, byte[]> loadedFiles) throws IOException {
public static void loadEPK(ByteBuffer epkFile, String path, Map<String, byte[]> loadedFiles) throws IOException {
int byteLength = epkFile.remaining();
int l = byteLength - 16;
if(l < 1) {
@ -43,7 +45,7 @@ public class EPKLoader {
EaglerBufferInputStream is = new EaglerBufferInputStream(epkFile);
byte[] header = new byte[8];
is.read(header);
IOUtils.readFully(is, header);
String type = readASCII(header);
if(!"EAGPKG$$".equals(type)) {
@ -65,13 +67,13 @@ public class EPKLoader {
throw new IOException("Unknown or invalid EPK version: " + vers);
}
is.skip(is.read()); // skip filename
is.skip(loadShort(is)); // skip comment
is.skip(8); // skip millis date
IOUtils.skipFully(is, loadByte(is)); // skip filename
IOUtils.skipFully(is, loadShort(is)); // skip comment
IOUtils.skipFully(is, 8); // skip millis date
int numFiles = loadInt(is);
char compressionType = (char)is.read();
char compressionType = (char)loadByte(is);
InputStream zis;
switch(compressionType) {
@ -112,11 +114,11 @@ public class EPKLoader {
if(i == 0) {
if(blockType == blockHead) {
byte[] readType = new byte[len];
zis.read(readType);
IOUtils.readFully(zis, readType);
if(!"file-type".equals(name) || !"epk/resources".equals(readASCII(readType))) {
throw new IOException("EPK is not of file-type 'epk/resources'!");
}
if(zis.read() != '>') {
if(loadByte(zis) != '>') {
throw new IOException("Object '" + name + "' is incomplete");
}
continue;
@ -133,7 +135,7 @@ public class EPKLoader {
int expectedCRC = loadInt(zis);
byte[] load = new byte[len - 5];
zis.read(load);
IOUtils.readFully(zis, load);
if(len > 5) {
crc32.reset();
@ -143,16 +145,16 @@ public class EPKLoader {
}
}
if(zis.read() != ':') {
if(loadByte(zis) != ':') {
throw new IOException("File '" + name + "' is incomplete");
}
loadedFiles.put(path + name, load);
}else {
zis.skip(len);
IOUtils.skipFully(zis, len);
}
if(zis.read() != '>') {
if(loadByte(zis) != '>') {
throw new IOException("Object '" + name + "' is incomplete");
}
}
@ -164,15 +166,23 @@ public class EPKLoader {
zis.close();
}
private static final int loadShort(InputStream is) throws IOException {
return (is.read() << 8) | is.read();
private static int loadByte(InputStream is) throws IOException {
int i = is.read();
if (i < 0) {
throw new EOFException();
}
return i;
}
private static final int loadInt(InputStream is) throws IOException {
return (is.read() << 24) | (is.read() << 16) | (is.read() << 8) | is.read();
private static int loadShort(InputStream is) throws IOException {
return (loadByte(is) << 8) | loadByte(is);
}
private static final String readASCII(byte[] bytesIn) throws IOException {
private static int loadInt(InputStream is) throws IOException {
return (loadByte(is) << 24) | (loadByte(is) << 16) | (loadByte(is) << 8) | loadByte(is);
}
private static String readASCII(byte[] bytesIn) throws IOException {
char[] charIn = new char[bytesIn.length];
for(int i = 0; i < bytesIn.length; ++i) {
charIn[i] = (char)((int)bytesIn[i] & 0xFF);
@ -180,11 +190,11 @@ public class EPKLoader {
return new String(charIn);
}
private static final String readASCII(InputStream bytesIn) throws IOException {
int len = bytesIn.read();
private static String readASCII(InputStream bytesIn) throws IOException {
int len = loadByte(bytesIn);
char[] charIn = new char[len];
for(int i = 0; i < len; ++i) {
charIn[i] = (char)(bytesIn.read() & 0xFF);
charIn[i] = (char)loadByte(bytesIn);
}
return new String(charIn);
}

View File

@ -82,6 +82,8 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
private boolean ramdiskMode = false;
private boolean singleThreadMode = false;
private boolean enforceVSync = true;
private boolean keepAliveHack = true;
private boolean finishOnSwap = true;
public void loadNative(JSObject jsObject) {
JSEaglercraftXOptsRoot eaglercraftXOpts = (JSEaglercraftXOptsRoot)jsObject;
@ -124,6 +126,8 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
ramdiskMode = eaglercraftXOpts.getRamdiskMode(false);
singleThreadMode = eaglercraftXOpts.getSingleThreadMode(false);
enforceVSync = eaglercraftXOpts.getEnforceVSync(true);
keepAliveHack = eaglercraftXOpts.getKeepAliveHack(true);
finishOnSwap = eaglercraftXOpts.getFinishOnSwap(true);
JSEaglercraftXOptsHooks hooksObj = eaglercraftXOpts.getHooks();
if(hooksObj != null) {
hooks.loadHooks(hooksObj);
@ -400,6 +404,14 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
return enforceVSync;
}
public boolean isKeepAliveHackTeaVM() {
return keepAliveHack;
}
public boolean isFinishOnSwapTeaVM() {
return finishOnSwap;
}
@Override
public IClientConfigAdapterHooks getHooks() {
return hooks;
@ -446,6 +458,8 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
jsonObject.put("ramdiskMode", ramdiskMode);
jsonObject.put("singleThreadMode", singleThreadMode);
jsonObject.put("enforceVSync", enforceVSync);
jsonObject.put("keepAliveHack", keepAliveHack);
jsonObject.put("finishOnSwap", finishOnSwap);
JSONArray serversArr = new JSONArray();
for(int i = 0, l = defaultServers.size(); i < l; ++i) {
DefaultServer srv = defaultServers.get(i);

View File

@ -114,10 +114,14 @@ public class WASMGCClientConfigAdapterHooks implements IClientConfigAdapterHooks
}
public void loadHooks(JSEaglercraftXOptsHooks hooks) {
saveHook = (LocalStorageSaveHook)hooks.getLocalStorageSavedHook();
loadHook = (LocalStorageLoadHook)hooks.getLocalStorageLoadedHook();
crashHook = (CrashReportHook)hooks.getCrashReportHook();
screenChangedHook = (ScreenChangeHook)hooks.getScreenChangedHook();
JSObject obj = hooks.getLocalStorageSavedHook();
saveHook = obj != null ? (LocalStorageSaveHook) obj : null;
obj = hooks.getLocalStorageLoadedHook();
loadHook = obj != null ? (LocalStorageLoadHook) obj : null;
obj = hooks.getCrashReportHook();
crashHook = obj != null ? (CrashReportHook) obj : null;
obj = hooks.getScreenChangedHook();
screenChangedHook = obj != null ? (ScreenChangeHook) obj : null;
}
}

View File

@ -172,4 +172,10 @@ public interface JSEaglercraftXOptsRoot extends JSObject {
@JSBody(params = { "def" }, script = "return (typeof this.enableEPKVersionCheck === \"boolean\") ? this.enableEPKVersionCheck : def;")
boolean getEnableEPKVersionCheck(boolean deobfStackTraces);
@JSBody(params = { "def" }, script = "return (typeof this.keepAliveHack === \"boolean\") ? this.keepAliveHack : def;")
boolean getKeepAliveHack(boolean keepAliveHack);
@JSBody(params = { "def" }, script = "return (typeof this.finishOnSwap === \"boolean\") ? this.finishOnSwap : def;")
boolean getFinishOnSwap(boolean finishOnSwap);
}

View File

@ -100,6 +100,12 @@ var isCrashed = false;
const crashReportStrings = [];
/** @type {function()|null} */
var removeEventHandlers = null;
/** @type {function()|null} */
var keepAliveCallback = null;
/** @type {function()|null} */
var showDebugConsole = null;
/** @type {function()|null} */
var resetSettings = null;
const runtimeOpts = {
localStorageNamespace: "_eaglercraftX",
@ -530,6 +536,44 @@ function deobfuscateStack(stack) {
return stackFrames;
}
/**
* @return {HTMLElement}
*/
function createToolButtons() {
const buttonResetSettings = /** @type {HTMLButtonElement} */ (document.createElement("button"));
buttonResetSettings.setAttribute("style", "margin-left:10px;");
buttonResetSettings.innerText = "Reset Settings";
buttonResetSettings.addEventListener("click", function(/** Event */ evt) {
if (resetSettings) {
resetSettings();
} else {
window.alert("Local storage has not been initialized yet");
}
});
const buttonOpenConsole = /** @type {HTMLButtonElement} */ (document.createElement("button"));
buttonOpenConsole.setAttribute("style", "margin-left:10px;");
buttonOpenConsole.innerText = "Open Debug Console";
buttonOpenConsole.addEventListener("click", function(/** Event */ evt) {
if (showDebugConsole) {
showDebugConsole();
} else {
window.alert("Debug console has not been initialized yet");
}
});
const div1 = /** @type {HTMLElement} */ (document.createElement("div"));
div1.setAttribute("style", "position:absolute;bottom:5px;right:0px;");
div1.appendChild(buttonResetSettings);
div1.appendChild(buttonOpenConsole);
const div2 = /** @type {HTMLElement} */ (document.createElement("div"));
div2.setAttribute("style", "position:relative;");
div2.appendChild(div1);
const div3 = /** @type {HTMLElement} */ (document.createElement("div"));
div3.classList.add("_eaglercraftX_crash_tools_element");
div3.setAttribute("style", "z-index:101;position:absolute;top:135px;left:10%;right:10%;height:0px;");
div3.appendChild(div2);
return div3;
}
function displayUncaughtCrashReport(error) {
const stack = error ? deobfuscateStack(error.stack) : null;
const crashContent = "Native Browser Exception\n" +
@ -657,6 +701,7 @@ function displayCrashReport(crashReport, enablePrint) {
div.classList.add("_eaglercraftX_crash_element");
parentEl.appendChild(img);
parentEl.appendChild(div);
parentEl.appendChild(createToolButtons());
div.appendChild(document.createTextNode(strFinal));
if(removeEventHandlers) removeEventHandlers();
@ -709,6 +754,7 @@ function showIncompatibleScreen(msg) {
div.classList.add("_eaglercraftX_incompatible_element");
parentEl.appendChild(img);
parentEl.appendChild(div);
parentEl.appendChild(createToolButtons());
div.innerHTML = "<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&ensp;:(</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=\"_eaglercraftX_crashReason\"></span><br /></p>"
@ -785,14 +831,17 @@ function showContextLostScreen(msg) {
div.classList.add("_eaglercraftX_context_lost_element");
parentEl.appendChild(img);
parentEl.appendChild(div);
parentEl.appendChild(createToolButtons());
div.innerHTML = "<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> + WebGL context lost!</h2>"
+ "<div style=\"margin-left:40px;\">"
+ "<p style=\"font-size:1.2em;\">Your browser has forcibly released all of the resources "
+ "allocated by the game's 3D rendering context. EaglercraftX cannot continue, please refresh "
+ "the page to restart the game.</p>"
+ "the page to restart the game, sorry for the inconvenience.</p>"
+ "<p style=\"font-size:1.2em;\">This is not a bug, it is usually caused by the browser "
+ "deciding it no longer has sufficient resources to continue rendering this page. If it "
+ "happens again, try closing your other browser tabs and windows.</p>"
+ "<p style=\"font-size:1.2em;\">If you're playing with vsync disabled, try enabling vsync "
+ "to allow the browser to control the GPU usage more precisely.</p>"
+ "<p style=\"overflow-wrap:break-word;white-space:pre-wrap;font:0.75em monospace;margin-top:1.5em;\" id=\"_eaglercraftX_contextLostTrace\"></p>"
+ "</div>";

View File

@ -328,7 +328,7 @@ function initializePlatfApplication(applicationImports) {
/** @type {HTMLElement} */
var loggerMessageContainer = null;
/** @type {string} */
const loggerLocalStorageKey = runtimeOpts.localStorageNamespace + "showDebugConsole";
const loggerLocalStorageKey = runtimeOpts.localStorageNamespace + ".showDebugConsole";
/** @type {string} */
const loggerWinUnloadEvent = runtimeOpts.fixDebugConsoleUnloadListener ? "beforeunload" : "unload";
@ -365,7 +365,7 @@ function initializePlatfApplication(applicationImports) {
showDebugConsole0();
}
function showDebugConsole() {
showDebugConsole = function() {
debugConsoleLocalStorageSet(true);
showDebugConsole0();
}
@ -480,6 +480,17 @@ function initializePlatfApplication(applicationImports) {
return faviconURL;
};
/**
* @return {Object}
*/
applicationImports["setResetSettingsCallback"] = function() {
return {
"call": function(/** function() */ cb) {
resetSettings = cb;
}
};
};
}
/**
@ -499,4 +510,5 @@ function initializeNoPlatfApplication(applicationImports) {
setUnsupportedFunc(applicationImports, platfApplicationName, "addLogMessage");
setUnsupportedFunc(applicationImports, platfApplicationName, "isShowingDebugConsole");
setUnsupportedFunc(applicationImports, platfApplicationName, "getFaviconURL");
setUnsupportedFunc(applicationImports, platfApplicationName, "setResetSettingsCallback");
}

View File

@ -25,6 +25,47 @@ function setCurrentAudioContext(audioContext, audioImports) {
return audioContext;
};
/**
* @param {Uint8Array} fileData
*/
audioImports["initKeepAliveHack"] = function(fileData) {
const copiedData = new Uint8Array(fileData.length);
copiedData.set(fileData, 0);
const copiedDataURI = URL.createObjectURL(new Blob([copiedData], {type: "audio/wav"}));
const audioElement = /** @type {HTMLAudioElement} */ (document.createElement("audio"));
audioElement.classList.add("_eaglercraftX_keepalive_hack");
audioElement.setAttribute("style", "display:none;");
audioElement.autoplay = true;
audioElement.loop = true;
const sourceElement = /** @type {HTMLSourceElement} */ (document.createElement("source"));
sourceElement.type = "audio/wav";
sourceElement.src = copiedDataURI;
audioElement.appendChild(sourceElement);
audioElement.addEventListener("seeked", function() {
// NOP, wakes up the browser's event loop
});
parentElement.appendChild(audioElement);
};
/**
* @param {PannerNode} node
* @param {number} maxDist
* @param {number} x
* @param {number} y
* @param {number} z
*/
audioImports["setupPanner"] = function(node, maxDist, x, y, z) {
node.maxDistance = maxDist;
node.rolloffFactor = 1.0;
node.panningModel = "HRTF";
node.distanceModel = "linear";
node.coneInnerAngle = 360.0;
node.coneOuterAngle = 0.0;
node.coneOuterGain = 0.0;
node.setOrientation(0.0, 1.0, 0.0);
node.setPosition(x, y, z);
};
/**
* @param {AudioBufferSourceNode} sourceNode
* @param {Object} isEnded
@ -72,6 +113,8 @@ function setNoAudioContext(audioImports) {
audioImports["getContext"] = function() {
return null;
};
setUnsupportedFunc(audioImports, platfAudioName, "setupPanner");
setUnsupportedFunc(audioImports, platfAudioName, "initKeepAliveHack");
setUnsupportedFunc(audioImports, platfAudioName, "registerIsEndedHandler");
setUnsupportedFunc(audioImports, platfAudioName, "releaseIsEndedHandler");
setUnsupportedFunc(audioImports, platfAudioName, "decodeAudioBrowser");

View File

@ -617,15 +617,19 @@ async function initPlatformInput(inputImports) {
/**
* @param {number} fpsLimit
* @param {number} vsync
* @param {number} finish
* @return {Promise}
*/
function updatePlatformAndSleepImpl(fpsLimit, vsync) {
function updatePlatformAndSleepImpl(fpsLimit, vsync, finish) {
reportWindowSize();
if((typeof document.visibilityState !== "string") || (document.visibilityState === "visible")) {
if(vsyncSupport && vsync) {
manualSyncTimer = 0;
return asyncRequestAnimationFrame();
}else {
if(finish) {
webglContext.finish();
}
if(fpsLimit <= 0) {
manualSyncTimer = 0;
return swapDelayImpl();